package org.schabi.newpipe.util; import android.annotation.SuppressLint; import android.content.Context; import android.content.SharedPreferences; import android.content.res.Configuration; import android.content.res.Resources; import android.icu.text.CompactDecimalFormat; import android.os.Build; import android.text.TextUtils; import android.util.DisplayMetrics; import androidx.annotation.NonNull; import androidx.annotation.PluralsRes; import androidx.annotation.StringRes; import androidx.preference.PreferenceManager; import org.ocpsoft.prettytime.PrettyTime; import org.ocpsoft.prettytime.units.Decade; import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.ListExtractor; import org.schabi.newpipe.extractor.localization.ContentCountry; import java.math.BigDecimal; import java.math.RoundingMode; import java.text.NumberFormat; import java.time.OffsetDateTime; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.time.format.FormatStyle; import java.util.Arrays; import java.util.List; import java.util.Locale; /* * Created by chschtsch on 12/29/15. * * Copyright (C) Gregory Arkhipov 2015 * Localization.java is part of NewPipe. * * NewPipe is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * NewPipe is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with NewPipe. If not, see . */ public final class Localization { public static final String DOT_SEPARATOR = " • "; private static PrettyTime prettyTime; private Localization() { } @NonNull public static String concatenateStrings(final String... strings) { return concatenateStrings(Arrays.asList(strings)); } @NonNull public static String concatenateStrings(final List strings) { if (strings.isEmpty()) { return ""; } final StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(strings.get(0)); for (int i = 1; i < strings.size(); i++) { final String string = strings.get(i); if (!TextUtils.isEmpty(string)) { stringBuilder.append(DOT_SEPARATOR).append(strings.get(i)); } } return stringBuilder.toString(); } public static org.schabi.newpipe.extractor.localization.Localization getPreferredLocalization( final Context context) { final String contentLanguage = PreferenceManager .getDefaultSharedPreferences(context) .getString(context.getString(R.string.content_language_key), context.getString(R.string.default_localization_key)); if (contentLanguage.equals(context.getString(R.string.default_localization_key))) { return org.schabi.newpipe.extractor.localization.Localization .fromLocale(Locale.getDefault()); } return org.schabi.newpipe.extractor.localization.Localization .fromLocalizationCode(contentLanguage); } public static ContentCountry getPreferredContentCountry(final Context context) { final String contentCountry = PreferenceManager.getDefaultSharedPreferences(context) .getString(context.getString(R.string.content_country_key), context.getString(R.string.default_localization_key)); if (contentCountry.equals(context.getString(R.string.default_localization_key))) { return new ContentCountry(Locale.getDefault().getCountry()); } return new ContentCountry(contentCountry); } public static Locale getPreferredLocale(final Context context) { final SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); final String languageCode = sp.getString(context.getString(R.string.content_language_key), context.getString(R.string.default_localization_key)); try { if (languageCode.length() == 2) { return new Locale(languageCode); } else if (languageCode.contains("_")) { final String country = languageCode.substring(languageCode.indexOf("_")); return new Locale(languageCode.substring(0, 2), country); } } catch (final Exception ignored) { } return Locale.getDefault(); } public static String localizeNumber(final Context context, final long number) { return localizeNumber(context, (double) number); } public static String localizeNumber(final Context context, final double number) { final NumberFormat nf = NumberFormat.getInstance(getAppLocale(context)); return nf.format(number); } public static String formatDate(final OffsetDateTime offsetDateTime, final Context context) { return DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM) .withLocale(getAppLocale(context)).format(offsetDateTime .atZoneSameInstant(ZoneId.systemDefault())); } @SuppressLint("StringFormatInvalid") public static String localizeUploadDate(final Context context, final OffsetDateTime offsetDateTime) { return context.getString(R.string.upload_date_text, formatDate(offsetDateTime, context)); } public static String localizeViewCount(final Context context, final long viewCount) { return getQuantity(context, R.plurals.views, R.string.no_views, viewCount, localizeNumber(context, viewCount)); } public static String localizeStreamCount(final Context context, final long streamCount) { switch ((int) streamCount) { case (int) ListExtractor.ITEM_COUNT_UNKNOWN: return ""; case (int) ListExtractor.ITEM_COUNT_INFINITE: return context.getResources().getString(R.string.infinite_videos); case (int) ListExtractor.ITEM_COUNT_MORE_THAN_100: return context.getResources().getString(R.string.more_than_100_videos); default: return getQuantity(context, R.plurals.videos, R.string.no_videos, streamCount, localizeNumber(context, streamCount)); } } public static String localizeStreamCountMini(final Context context, final long streamCount) { switch ((int) streamCount) { case (int) ListExtractor.ITEM_COUNT_UNKNOWN: return ""; case (int) ListExtractor.ITEM_COUNT_INFINITE: return context.getResources().getString(R.string.infinite_videos_mini); case (int) ListExtractor.ITEM_COUNT_MORE_THAN_100: return context.getResources().getString(R.string.more_than_100_videos_mini); default: return String.valueOf(streamCount); } } public static String localizeWatchingCount(final Context context, final long watchingCount) { return getQuantity(context, R.plurals.watching, R.string.no_one_watching, watchingCount, localizeNumber(context, watchingCount)); } public static String shortCount(final Context context, final long count) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { return CompactDecimalFormat.getInstance(getAppLocale(context), CompactDecimalFormat.CompactStyle.SHORT).format(count); } final double value = (double) count; if (count >= 1000000000) { return localizeNumber(context, round(value / 1000000000, 1)) + context.getString(R.string.short_billion); } else if (count >= 1000000) { return localizeNumber(context, round(value / 1000000, 1)) + context.getString(R.string.short_million); } else if (count >= 1000) { return localizeNumber(context, round(value / 1000, 1)) + context.getString(R.string.short_thousand); } else { return localizeNumber(context, value); } } public static String listeningCount(final Context context, final long listeningCount) { return getQuantity(context, R.plurals.listening, R.string.no_one_listening, listeningCount, shortCount(context, listeningCount)); } public static String shortWatchingCount(final Context context, final long watchingCount) { return getQuantity(context, R.plurals.watching, R.string.no_one_watching, watchingCount, shortCount(context, watchingCount)); } public static String shortViewCount(final Context context, final long viewCount) { return getQuantity(context, R.plurals.views, R.string.no_views, viewCount, shortCount(context, viewCount)); } public static String shortSubscriberCount(final Context context, final long subscriberCount) { return getQuantity(context, R.plurals.subscribers, R.string.no_subscribers, subscriberCount, shortCount(context, subscriberCount)); } public static String downloadCount(final Context context, final int downloadCount) { return getQuantity(context, R.plurals.download_finished_notification, 0, downloadCount, shortCount(context, downloadCount)); } public static String deletedDownloadCount(final Context context, final int deletedCount) { return getQuantity(context, R.plurals.deleted_downloads_toast, 0, deletedCount, shortCount(context, deletedCount)); } private static String getQuantity(final Context context, @PluralsRes final int pluralId, @StringRes final int zeroCaseStringId, final long count, final String formattedCount) { if (count == 0) { return context.getString(zeroCaseStringId); } // As we use the already formatted count // is not the responsibility of this method handle long numbers // (it probably will fall in the "other" category, // or some language have some specific rule... then we have to change it) final int safeCount = count > Integer.MAX_VALUE ? Integer.MAX_VALUE : count < Integer.MIN_VALUE ? Integer.MIN_VALUE : (int) count; return context.getResources().getQuantityString(pluralId, safeCount, formattedCount); } public static String getDurationString(final long duration) { final String output; final long days = duration / (24 * 60 * 60L); /* greater than a day */ final long hours = duration % (24 * 60 * 60L) / (60 * 60L); /* greater than an hour */ final long minutes = duration % (24 * 60 * 60L) % (60 * 60L) / 60L; final long seconds = duration % 60L; if (duration < 0) { output = "0:00"; } else if (days > 0) { //handle days output = String.format(Locale.US, "%d:%02d:%02d:%02d", days, hours, minutes, seconds); } else if (hours > 0) { output = String.format(Locale.US, "%d:%02d:%02d", hours, minutes, seconds); } else { output = String.format(Locale.US, "%d:%02d", minutes, seconds); } return output; } /** * Localize an amount of seconds into a human readable string. * *

The seconds will be converted to the closest whole time unit. *

For example, 60 seconds would give "1 minute", 119 would also give "1 minute". * * @param context used to get plurals resources. * @param durationInSecs an amount of seconds. * @return duration in a human readable string. */ @NonNull public static String localizeDuration(final Context context, final int durationInSecs) { if (durationInSecs < 0) { throw new IllegalArgumentException("duration can not be negative"); } final int days = (int) (durationInSecs / (24 * 60 * 60L)); final int hours = (int) (durationInSecs % (24 * 60 * 60L) / (60 * 60L)); final int minutes = (int) (durationInSecs % (24 * 60 * 60L) % (60 * 60L) / 60L); final int seconds = (int) (durationInSecs % (24 * 60 * 60L) % (60 * 60L) % 60L); final Resources resources = context.getResources(); if (days > 0) { return resources.getQuantityString(R.plurals.days, days, days); } else if (hours > 0) { return resources.getQuantityString(R.plurals.hours, hours, hours); } else if (minutes > 0) { return resources.getQuantityString(R.plurals.minutes, minutes, minutes); } else { return resources.getQuantityString(R.plurals.seconds, seconds, seconds); } } /*////////////////////////////////////////////////////////////////////////// // Pretty Time //////////////////////////////////////////////////////////////////////////*/ public static void initPrettyTime(final PrettyTime time) { prettyTime = time; // Do not use decades as YouTube doesn't either. prettyTime.removeUnit(Decade.class); } public static PrettyTime resolvePrettyTime(final Context context) { return new PrettyTime(getAppLocale(context)); } public static String relativeTime(final OffsetDateTime offsetDateTime) { return prettyTime.formatUnrounded(offsetDateTime); } private static void changeAppLanguage(final Locale loc, final Resources res) { final DisplayMetrics dm = res.getDisplayMetrics(); final Configuration conf = res.getConfiguration(); conf.setLocale(loc); res.updateConfiguration(conf, dm); } public static Locale getAppLocale(final Context context) { final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); String lang = prefs.getString(context.getString(R.string.app_language_key), "en"); final Locale loc; if (lang.equals(context.getString(R.string.default_localization_key))) { loc = Locale.getDefault(); } else if (lang.matches(".*-.*")) { //to differentiate different versions of the language //for example, pt (portuguese in Portugal) and pt-br (portuguese in Brazil) final String[] localisation = lang.split("-"); lang = localisation[0]; final String country = localisation[1]; loc = new Locale(lang, country); } else { loc = new Locale(lang); } return loc; } public static void assureCorrectAppLanguage(final Context c) { changeAppLanguage(getAppLocale(c), c.getResources()); } private static double round(final double value, final int places) { return new BigDecimal(value).setScale(places, RoundingMode.HALF_UP).doubleValue(); } /** * Workaround to match normalized captions like english to English or deutsch to Deutsch. * @param list the list to search into * @param toFind the string to look for * @return whether the string was found or not */ public static boolean containsCaseInsensitive(final List list, final String toFind) { for (final String i : list) { if (i.equalsIgnoreCase(toFind)) { return true; } } return false; } }