2017-05-08 13:28:33 +00:00
|
|
|
package org.schabi.newpipe.util;
|
2015-12-29 14:35:51 +00:00
|
|
|
|
2023-04-12 08:54:03 +00:00
|
|
|
import static org.schabi.newpipe.MainActivity.DEBUG;
|
|
|
|
|
2020-01-18 09:46:53 +00:00
|
|
|
import android.annotation.SuppressLint;
|
2015-12-29 14:35:51 +00:00
|
|
|
import android.content.Context;
|
|
|
|
import android.content.SharedPreferences;
|
2020-01-04 20:38:27 +00:00
|
|
|
import android.content.res.Configuration;
|
|
|
|
import android.content.res.Resources;
|
2020-10-18 02:00:32 +00:00
|
|
|
import android.icu.text.CompactDecimalFormat;
|
|
|
|
import android.os.Build;
|
2017-10-13 03:47:12 +00:00
|
|
|
import android.text.TextUtils;
|
2020-01-04 20:38:27 +00:00
|
|
|
import android.util.DisplayMetrics;
|
2021-01-15 16:11:04 +00:00
|
|
|
|
2020-01-09 08:40:05 +00:00
|
|
|
import androidx.annotation.NonNull;
|
2023-03-19 20:20:21 +00:00
|
|
|
import androidx.annotation.Nullable;
|
2020-01-09 08:40:05 +00:00
|
|
|
import androidx.annotation.PluralsRes;
|
|
|
|
import androidx.annotation.StringRes;
|
2022-07-18 03:30:11 +00:00
|
|
|
import androidx.core.math.MathUtils;
|
2020-10-18 02:00:32 +00:00
|
|
|
import androidx.preference.PreferenceManager;
|
2021-01-15 16:11:04 +00:00
|
|
|
|
|
|
|
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;
|
2023-04-12 08:54:03 +00:00
|
|
|
import org.schabi.newpipe.extractor.localization.DateWrapper;
|
2023-03-19 02:06:29 +00:00
|
|
|
import org.schabi.newpipe.extractor.stream.AudioStream;
|
2023-03-19 20:20:21 +00:00
|
|
|
import org.schabi.newpipe.extractor.stream.AudioTrackType;
|
2021-01-15 16:11:04 +00:00
|
|
|
|
2020-02-28 15:59:52 +00:00
|
|
|
import java.math.BigDecimal;
|
|
|
|
import java.math.RoundingMode;
|
2015-12-29 14:35:51 +00:00
|
|
|
import java.text.NumberFormat;
|
2020-10-18 06:16:55 +00:00
|
|
|
import java.time.OffsetDateTime;
|
|
|
|
import java.time.ZoneId;
|
|
|
|
import java.time.format.DateTimeFormatter;
|
|
|
|
import java.time.format.FormatStyle;
|
2018-01-28 06:14:38 +00:00
|
|
|
import java.util.Arrays;
|
|
|
|
import java.util.List;
|
2015-12-29 14:35:51 +00:00
|
|
|
import java.util.Locale;
|
2022-07-29 03:35:50 +00:00
|
|
|
import java.util.stream.Collectors;
|
2015-12-29 14:35:51 +00:00
|
|
|
|
2020-02-27 22:10:07 +00:00
|
|
|
|
2017-09-03 06:04:18 +00:00
|
|
|
/*
|
2015-12-29 14:35:51 +00:00
|
|
|
* Created by chschtsch on 12/29/15.
|
2016-01-05 20:41:55 +00:00
|
|
|
*
|
|
|
|
* 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 <http://www.gnu.org/licenses/>.
|
2015-12-29 14:35:51 +00:00
|
|
|
*/
|
|
|
|
|
2020-03-31 17:20:15 +00:00
|
|
|
public final class Localization {
|
2020-12-20 14:05:37 +00:00
|
|
|
public static final String DOT_SEPARATOR = " • ";
|
Multiple localization fixes
With the extractor PR, fixes title & description shown in the wrong language.
Fixed views / spectators counts possibly in the wrong language
Fixed live spectators not showing full count on detail page
Fixed LIVE shown on players, it shows translated instead
Fixed Videos string in search / three dots not available in Weblate
(because it was videos, but there already was a plural string named videos, in Weblate)
Subscriber count is always giving the short count.
We can't get exact number since this YouTube update: https://support.google.com/youtube/thread/6543166
But only short count (B, M, k), so showing full number, eg for 1.9M: 1,900,000, is wrong because the number could be 1,923,490 or 1,897,789…
Added a « sytem default » option to content language and country language selector.
It's the one selected by default (not en-GB anymore then), and correspond to the
language of the system / country of the system
By system I mean phone, tablet, TV…
Fixed russian showing - before time ago (eg 19hrs ago)
This is a workaround fix, I opened an issue on prettytime library repo.
Fixed russian plurals:
other was used instead of many for videos and subscribers
Fixed seek_duration english only
2020-02-14 17:19:35 +00:00
|
|
|
private static PrettyTime prettyTime;
|
2018-01-28 06:14:38 +00:00
|
|
|
|
2020-03-31 17:20:15 +00:00
|
|
|
private Localization() { }
|
2016-03-10 09:50:42 +00:00
|
|
|
|
2018-01-28 06:14:38 +00:00
|
|
|
@NonNull
|
|
|
|
public static String concatenateStrings(final String... strings) {
|
2022-07-29 03:35:50 +00:00
|
|
|
return concatenateStrings(DOT_SEPARATOR, Arrays.asList(strings));
|
2018-01-28 06:14:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@NonNull
|
2022-07-29 03:35:50 +00:00
|
|
|
public static String concatenateStrings(final String delimiter, final List<String> strings) {
|
|
|
|
return strings.stream()
|
|
|
|
.filter(string -> !TextUtils.isEmpty(string))
|
|
|
|
.collect(Collectors.joining(delimiter));
|
2018-01-28 06:14:38 +00:00
|
|
|
}
|
|
|
|
|
2020-03-31 17:20:15 +00:00
|
|
|
public static org.schabi.newpipe.extractor.localization.Localization getPreferredLocalization(
|
|
|
|
final Context context) {
|
|
|
|
return org.schabi.newpipe.extractor.localization.Localization
|
2022-08-22 02:50:16 +00:00
|
|
|
.fromLocale(getPreferredLocale(context));
|
2019-10-28 02:35:51 +00:00
|
|
|
}
|
2018-10-05 14:19:21 +00:00
|
|
|
|
2023-12-22 17:50:48 +00:00
|
|
|
public static ContentCountry getPreferredContentCountry(@NonNull final Context context) {
|
2020-03-31 17:20:15 +00:00
|
|
|
final String contentCountry = PreferenceManager.getDefaultSharedPreferences(context)
|
|
|
|
.getString(context.getString(R.string.content_country_key),
|
|
|
|
context.getString(R.string.default_localization_key));
|
2020-02-15 15:29:46 +00:00
|
|
|
if (contentCountry.equals(context.getString(R.string.default_localization_key))) {
|
Multiple localization fixes
With the extractor PR, fixes title & description shown in the wrong language.
Fixed views / spectators counts possibly in the wrong language
Fixed live spectators not showing full count on detail page
Fixed LIVE shown on players, it shows translated instead
Fixed Videos string in search / three dots not available in Weblate
(because it was videos, but there already was a plural string named videos, in Weblate)
Subscriber count is always giving the short count.
We can't get exact number since this YouTube update: https://support.google.com/youtube/thread/6543166
But only short count (B, M, k), so showing full number, eg for 1.9M: 1,900,000, is wrong because the number could be 1,923,490 or 1,897,789…
Added a « sytem default » option to content language and country language selector.
It's the one selected by default (not en-GB anymore then), and correspond to the
language of the system / country of the system
By system I mean phone, tablet, TV…
Fixed russian showing - before time ago (eg 19hrs ago)
This is a workaround fix, I opened an issue on prettytime library repo.
Fixed russian plurals:
other was used instead of many for videos and subscribers
Fixed seek_duration english only
2020-02-14 17:19:35 +00:00
|
|
|
return new ContentCountry(Locale.getDefault().getCountry());
|
|
|
|
}
|
2019-10-28 02:35:51 +00:00
|
|
|
return new ContentCountry(contentCountry);
|
2018-10-05 14:19:21 +00:00
|
|
|
}
|
|
|
|
|
2023-12-22 17:50:48 +00:00
|
|
|
public static Locale getPreferredLocale(@NonNull final Context context) {
|
2022-08-22 02:50:16 +00:00
|
|
|
return getLocaleFromPrefs(context, R.string.content_language_key);
|
|
|
|
}
|
2015-12-29 14:35:51 +00:00
|
|
|
|
2023-12-22 17:50:48 +00:00
|
|
|
public static Locale getAppLocale(@NonNull final Context context) {
|
2022-08-22 02:50:16 +00:00
|
|
|
return getLocaleFromPrefs(context, R.string.app_language_key);
|
2015-12-29 14:35:51 +00:00
|
|
|
}
|
|
|
|
|
2023-12-22 17:50:48 +00:00
|
|
|
public static String localizeNumber(@NonNull final Context context, final long number) {
|
2020-02-27 22:10:07 +00:00
|
|
|
return localizeNumber(context, (double) number);
|
|
|
|
}
|
|
|
|
|
2023-12-22 17:50:48 +00:00
|
|
|
public static String localizeNumber(@NonNull final Context context, final double number) {
|
2020-08-16 08:24:58 +00:00
|
|
|
final NumberFormat nf = NumberFormat.getInstance(getAppLocale(context));
|
2015-12-29 14:35:51 +00:00
|
|
|
return nf.format(number);
|
|
|
|
}
|
|
|
|
|
2023-12-22 17:50:48 +00:00
|
|
|
public static String formatDate(@NonNull final Context context,
|
|
|
|
@NonNull final OffsetDateTime offsetDateTime) {
|
2020-10-18 06:16:55 +00:00
|
|
|
return DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM)
|
|
|
|
.withLocale(getAppLocale(context)).format(offsetDateTime
|
|
|
|
.atZoneSameInstant(ZoneId.systemDefault()));
|
2015-12-29 14:35:51 +00:00
|
|
|
}
|
|
|
|
|
2020-01-18 09:46:53 +00:00
|
|
|
@SuppressLint("StringFormatInvalid")
|
2023-12-22 17:50:48 +00:00
|
|
|
public static String localizeUploadDate(@NonNull final Context context,
|
|
|
|
@NonNull final OffsetDateTime offsetDateTime) {
|
|
|
|
return context.getString(R.string.upload_date_text, formatDate(context, offsetDateTime));
|
2015-12-29 14:35:51 +00:00
|
|
|
}
|
2017-09-03 06:04:18 +00:00
|
|
|
|
2023-12-22 17:50:48 +00:00
|
|
|
public static String localizeViewCount(@NonNull final Context context, final long viewCount) {
|
2020-03-31 17:20:15 +00:00
|
|
|
return getQuantity(context, R.plurals.views, R.string.no_views, viewCount,
|
|
|
|
localizeNumber(context, viewCount));
|
2017-09-03 06:04:18 +00:00
|
|
|
}
|
|
|
|
|
2023-12-22 17:50:48 +00:00
|
|
|
public static String localizeStreamCount(@NonNull final Context context,
|
|
|
|
final long streamCount) {
|
2020-03-20 19:57:56 +00:00
|
|
|
switch ((int) streamCount) {
|
2020-03-20 21:01:56 +00:00
|
|
|
case (int) ListExtractor.ITEM_COUNT_UNKNOWN:
|
|
|
|
return "";
|
2020-03-20 19:57:56 +00:00
|
|
|
case (int) ListExtractor.ITEM_COUNT_INFINITE:
|
2020-03-20 20:43:17 +00:00
|
|
|
return context.getResources().getString(R.string.infinite_videos);
|
2020-03-20 21:01:56 +00:00
|
|
|
case (int) ListExtractor.ITEM_COUNT_MORE_THAN_100:
|
|
|
|
return context.getResources().getString(R.string.more_than_100_videos);
|
2020-03-20 19:57:56 +00:00
|
|
|
default:
|
|
|
|
return getQuantity(context, R.plurals.videos, R.string.no_videos, streamCount,
|
|
|
|
localizeNumber(context, streamCount));
|
|
|
|
}
|
2017-09-03 06:04:18 +00:00
|
|
|
}
|
|
|
|
|
2023-12-22 17:50:48 +00:00
|
|
|
public static String localizeStreamCountMini(@NonNull final Context context,
|
|
|
|
final long streamCount) {
|
2020-03-20 20:43:17 +00:00
|
|
|
switch ((int) streamCount) {
|
2020-03-20 21:01:56 +00:00
|
|
|
case (int) ListExtractor.ITEM_COUNT_UNKNOWN:
|
|
|
|
return "";
|
2020-03-20 20:43:17 +00:00
|
|
|
case (int) ListExtractor.ITEM_COUNT_INFINITE:
|
|
|
|
return context.getResources().getString(R.string.infinite_videos_mini);
|
2020-03-20 21:01:56 +00:00
|
|
|
case (int) ListExtractor.ITEM_COUNT_MORE_THAN_100:
|
|
|
|
return context.getResources().getString(R.string.more_than_100_videos_mini);
|
2020-03-20 20:43:17 +00:00
|
|
|
default:
|
|
|
|
return String.valueOf(streamCount);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-22 17:50:48 +00:00
|
|
|
public static String localizeWatchingCount(@NonNull final Context context,
|
|
|
|
final long watchingCount) {
|
2020-03-31 17:20:15 +00:00
|
|
|
return getQuantity(context, R.plurals.watching, R.string.no_one_watching, watchingCount,
|
|
|
|
localizeNumber(context, watchingCount));
|
Multiple localization fixes
With the extractor PR, fixes title & description shown in the wrong language.
Fixed views / spectators counts possibly in the wrong language
Fixed live spectators not showing full count on detail page
Fixed LIVE shown on players, it shows translated instead
Fixed Videos string in search / three dots not available in Weblate
(because it was videos, but there already was a plural string named videos, in Weblate)
Subscriber count is always giving the short count.
We can't get exact number since this YouTube update: https://support.google.com/youtube/thread/6543166
But only short count (B, M, k), so showing full number, eg for 1.9M: 1,900,000, is wrong because the number could be 1,923,490 or 1,897,789…
Added a « sytem default » option to content language and country language selector.
It's the one selected by default (not en-GB anymore then), and correspond to the
language of the system / country of the system
By system I mean phone, tablet, TV…
Fixed russian showing - before time ago (eg 19hrs ago)
This is a workaround fix, I opened an issue on prettytime library repo.
Fixed russian plurals:
other was used instead of many for videos and subscribers
Fixed seek_duration english only
2020-02-14 17:19:35 +00:00
|
|
|
}
|
|
|
|
|
2023-12-22 17:50:48 +00:00
|
|
|
public static String shortCount(@NonNull final Context context, final long count) {
|
2020-10-31 19:54:02 +00:00
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
2020-10-18 02:00:32 +00:00
|
|
|
return CompactDecimalFormat.getInstance(getAppLocale(context),
|
|
|
|
CompactDecimalFormat.CompactStyle.SHORT).format(count);
|
|
|
|
}
|
|
|
|
|
2020-08-16 08:24:58 +00:00
|
|
|
final double value = (double) count;
|
2017-09-03 06:04:18 +00:00
|
|
|
if (count >= 1000000000) {
|
2022-11-09 03:23:53 +00:00
|
|
|
return localizeNumber(context, round(value / 1000000000))
|
2020-03-31 17:20:15 +00:00
|
|
|
+ context.getString(R.string.short_billion);
|
2017-09-03 06:04:18 +00:00
|
|
|
} else if (count >= 1000000) {
|
2022-11-09 03:23:53 +00:00
|
|
|
return localizeNumber(context, round(value / 1000000))
|
2020-03-31 17:20:15 +00:00
|
|
|
+ context.getString(R.string.short_million);
|
2017-09-03 06:04:18 +00:00
|
|
|
} else if (count >= 1000) {
|
2022-11-09 03:23:53 +00:00
|
|
|
return localizeNumber(context, round(value / 1000))
|
2020-03-31 17:20:15 +00:00
|
|
|
+ context.getString(R.string.short_thousand);
|
2017-09-03 06:04:18 +00:00
|
|
|
} else {
|
2020-02-27 22:10:07 +00:00
|
|
|
return localizeNumber(context, value);
|
2017-09-03 06:04:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-22 17:50:48 +00:00
|
|
|
public static String listeningCount(@NonNull final Context context, final long listeningCount) {
|
2020-03-31 17:20:15 +00:00
|
|
|
return getQuantity(context, R.plurals.listening, R.string.no_one_listening, listeningCount,
|
|
|
|
shortCount(context, listeningCount));
|
2019-10-28 02:37:36 +00:00
|
|
|
}
|
|
|
|
|
2023-12-22 17:50:48 +00:00
|
|
|
public static String shortWatchingCount(@NonNull final Context context,
|
|
|
|
final long watchingCount) {
|
2020-03-31 17:20:15 +00:00
|
|
|
return getQuantity(context, R.plurals.watching, R.string.no_one_watching, watchingCount,
|
|
|
|
shortCount(context, watchingCount));
|
2019-10-28 02:37:36 +00:00
|
|
|
}
|
|
|
|
|
2023-12-22 17:50:48 +00:00
|
|
|
public static String shortViewCount(@NonNull final Context context, final long viewCount) {
|
2020-03-31 17:20:15 +00:00
|
|
|
return getQuantity(context, R.plurals.views, R.string.no_views, viewCount,
|
|
|
|
shortCount(context, viewCount));
|
2017-09-03 06:04:18 +00:00
|
|
|
}
|
|
|
|
|
2023-12-22 17:50:48 +00:00
|
|
|
public static String shortSubscriberCount(@NonNull final Context context,
|
|
|
|
final long subscriberCount) {
|
2020-03-31 17:20:15 +00:00
|
|
|
return getQuantity(context, R.plurals.subscribers, R.string.no_subscribers, subscriberCount,
|
|
|
|
shortCount(context, subscriberCount));
|
2017-09-03 06:04:18 +00:00
|
|
|
}
|
|
|
|
|
2023-12-22 17:50:48 +00:00
|
|
|
public static String downloadCount(@NonNull final Context context, final int downloadCount) {
|
2021-08-29 11:28:01 +00:00
|
|
|
return getQuantity(context, R.plurals.download_finished_notification, 0,
|
|
|
|
downloadCount, shortCount(context, downloadCount));
|
|
|
|
}
|
|
|
|
|
2023-12-22 17:50:48 +00:00
|
|
|
public static String deletedDownloadCount(@NonNull final Context context,
|
|
|
|
final int deletedCount) {
|
2021-08-29 11:28:01 +00:00
|
|
|
return getQuantity(context, R.plurals.deleted_downloads_toast, 0,
|
|
|
|
deletedCount, shortCount(context, deletedCount));
|
|
|
|
}
|
|
|
|
|
2023-12-22 17:50:48 +00:00
|
|
|
public static String replyCount(@NonNull final Context context, final int replyCount) {
|
2023-04-11 12:56:04 +00:00
|
|
|
return getQuantity(context, R.plurals.replies, 0, replyCount,
|
|
|
|
String.valueOf(replyCount));
|
|
|
|
}
|
|
|
|
|
2023-04-12 13:18:26 +00:00
|
|
|
/**
|
|
|
|
* @param context the Android context
|
|
|
|
* @param likeCount the like count, possibly negative if unknown
|
|
|
|
* @return if {@code likeCount} is smaller than {@code 0}, the string {@code "-"}, otherwise
|
|
|
|
* the result of calling {@link #shortCount(Context, long)} on the like count
|
|
|
|
*/
|
2023-12-22 17:50:48 +00:00
|
|
|
public static String likeCount(@NonNull final Context context, final int likeCount) {
|
2023-04-12 08:54:03 +00:00
|
|
|
if (likeCount < 0) {
|
|
|
|
return "-";
|
|
|
|
} else {
|
|
|
|
return shortCount(context, likeCount);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-10 21:26:20 +00:00
|
|
|
/**
|
|
|
|
* Get a readable text for a duration in the format {@code days:hours:minutes:seconds}.
|
|
|
|
* Prepended zeros are removed.
|
|
|
|
* @param duration the duration in seconds
|
|
|
|
* @return a formatted duration String or {@code 0:00} if the duration is zero.
|
|
|
|
*/
|
2020-03-31 17:20:15 +00:00
|
|
|
public static String getDurationString(final long duration) {
|
2024-04-06 05:58:05 +00:00
|
|
|
return getDurationString(duration, true, false);
|
2021-04-10 21:26:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get a readable text for a duration in the format {@code days:hours:minutes:seconds+}.
|
|
|
|
* Prepended zeros are removed. If the given duration is incomplete, a plus is appended to the
|
|
|
|
* duration string.
|
|
|
|
* @param duration the duration in seconds
|
|
|
|
* @param isDurationComplete whether the given duration is complete or whether info is missing
|
2024-04-06 05:58:05 +00:00
|
|
|
* @param showDurationPrefix whether the duration-prefix shall be shown
|
2021-04-10 21:26:20 +00:00
|
|
|
* @return a formatted duration String or {@code 0:00} if the duration is zero.
|
|
|
|
*/
|
2024-04-06 05:58:05 +00:00
|
|
|
public static String getDurationString(final long duration, final boolean isDurationComplete,
|
|
|
|
final boolean showDurationPrefix) {
|
2020-03-31 17:20:15 +00:00
|
|
|
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;
|
|
|
|
|
2017-09-03 06:04:18 +00:00
|
|
|
if (duration < 0) {
|
2020-03-31 17:20:15 +00:00
|
|
|
output = "0:00";
|
|
|
|
} else if (days > 0) {
|
|
|
|
//handle days
|
2017-09-03 06:04:18 +00:00
|
|
|
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);
|
|
|
|
}
|
2024-04-06 05:58:05 +00:00
|
|
|
final String durationPrefix = showDurationPrefix ? "⏱ " : "";
|
2021-04-10 21:26:20 +00:00
|
|
|
final String durationPostfix = isDurationComplete ? "" : "+";
|
2024-04-06 05:58:05 +00:00
|
|
|
return durationPrefix + output + durationPostfix;
|
2017-09-03 06:04:18 +00:00
|
|
|
}
|
2019-10-28 04:20:06 +00:00
|
|
|
|
2020-03-06 02:20:55 +00:00
|
|
|
/**
|
|
|
|
* Localize an amount of seconds into a human readable string.
|
|
|
|
*
|
|
|
|
* <p>The seconds will be converted to the closest whole time unit.
|
|
|
|
* <p>For example, 60 seconds would give "1 minute", 119 would also give "1 minute".
|
|
|
|
*
|
2020-03-31 17:20:15 +00:00
|
|
|
* @param context used to get plurals resources.
|
2020-03-06 02:20:55 +00:00
|
|
|
* @param durationInSecs an amount of seconds.
|
|
|
|
* @return duration in a human readable string.
|
|
|
|
*/
|
|
|
|
@NonNull
|
2023-12-22 17:50:48 +00:00
|
|
|
public static String localizeDuration(@NonNull final Context context,
|
|
|
|
final int durationInSecs) {
|
2020-03-06 02:20:55 +00:00
|
|
|
if (durationInSecs < 0) {
|
|
|
|
throw new IllegalArgumentException("duration can not be negative");
|
|
|
|
}
|
|
|
|
|
2020-03-31 17:20:15 +00:00
|
|
|
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);
|
2020-03-06 02:20:55 +00:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-19 02:06:29 +00:00
|
|
|
/**
|
|
|
|
* Get the localized name of an audio track.
|
|
|
|
*
|
2023-04-30 22:02:37 +00:00
|
|
|
* <p>Examples of results returned by this method:</p>
|
|
|
|
* <ul>
|
|
|
|
* <li>English (original)</li>
|
|
|
|
* <li>English (descriptive)</li>
|
|
|
|
* <li>Spanish (dubbed)</li>
|
|
|
|
* </ul>
|
|
|
|
*
|
|
|
|
* @param context the context used to get the app language
|
|
|
|
* @param track an {@link AudioStream} of the track
|
|
|
|
* @return the localized name of the audio track
|
2023-03-19 02:06:29 +00:00
|
|
|
*/
|
2023-12-22 17:50:48 +00:00
|
|
|
public static String audioTrackName(@NonNull final Context context, final AudioStream track) {
|
2023-03-19 20:20:21 +00:00
|
|
|
final String name;
|
2023-03-19 02:06:29 +00:00
|
|
|
if (track.getAudioLocale() != null) {
|
2023-03-19 20:20:21 +00:00
|
|
|
name = track.getAudioLocale().getDisplayLanguage(getAppLocale(context));
|
2023-03-19 02:06:29 +00:00
|
|
|
} else if (track.getAudioTrackName() != null) {
|
2023-03-19 20:20:21 +00:00
|
|
|
name = track.getAudioTrackName();
|
2023-03-19 02:06:29 +00:00
|
|
|
} else {
|
2023-03-19 20:20:21 +00:00
|
|
|
name = context.getString(R.string.unknown_audio_track);
|
2023-03-19 02:06:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (track.getAudioTrackType() != null) {
|
2023-03-19 20:20:21 +00:00
|
|
|
final String trackType = audioTrackType(context, track.getAudioTrackType());
|
|
|
|
if (trackType != null) {
|
|
|
|
return context.getString(R.string.audio_track_name, name, trackType);
|
2023-03-19 02:06:29 +00:00
|
|
|
}
|
|
|
|
}
|
2023-03-19 20:20:21 +00:00
|
|
|
return name;
|
|
|
|
}
|
2023-03-19 02:06:29 +00:00
|
|
|
|
2023-03-19 20:20:21 +00:00
|
|
|
@Nullable
|
2023-12-22 17:50:48 +00:00
|
|
|
private static String audioTrackType(@NonNull final Context context,
|
|
|
|
final AudioTrackType trackType) {
|
2023-03-19 20:20:21 +00:00
|
|
|
switch (trackType) {
|
|
|
|
case ORIGINAL:
|
|
|
|
return context.getString(R.string.audio_track_type_original);
|
|
|
|
case DUBBED:
|
|
|
|
return context.getString(R.string.audio_track_type_dubbed);
|
|
|
|
case DESCRIPTIVE:
|
|
|
|
return context.getString(R.string.audio_track_type_descriptive);
|
|
|
|
}
|
|
|
|
return null;
|
2023-03-19 02:06:29 +00:00
|
|
|
}
|
|
|
|
|
2019-10-28 04:20:06 +00:00
|
|
|
/*//////////////////////////////////////////////////////////////////////////
|
|
|
|
// Pretty Time
|
|
|
|
//////////////////////////////////////////////////////////////////////////*/
|
|
|
|
|
2023-12-22 17:50:48 +00:00
|
|
|
public static void initPrettyTime(@NonNull final PrettyTime time) {
|
2021-01-06 13:48:34 +00:00
|
|
|
prettyTime = time;
|
2019-10-28 04:20:06 +00:00
|
|
|
// Do not use decades as YouTube doesn't either.
|
|
|
|
prettyTime.removeUnit(Decade.class);
|
|
|
|
}
|
|
|
|
|
2023-12-22 17:50:48 +00:00
|
|
|
public static PrettyTime resolvePrettyTime(@NonNull final Context context) {
|
2021-01-06 13:48:34 +00:00
|
|
|
return new PrettyTime(getAppLocale(context));
|
|
|
|
}
|
|
|
|
|
2023-12-22 17:50:48 +00:00
|
|
|
public static String relativeTime(@NonNull final OffsetDateTime offsetDateTime) {
|
2020-12-20 04:23:05 +00:00
|
|
|
return prettyTime.formatUnrounded(offsetDateTime);
|
2019-10-28 04:20:06 +00:00
|
|
|
}
|
2020-01-04 20:38:27 +00:00
|
|
|
|
2023-04-12 13:18:26 +00:00
|
|
|
/**
|
|
|
|
* @param context the Android context; if {@code null} then even if in debug mode and the
|
|
|
|
* setting is enabled, {@code textual} will not be shown next to {@code parsed}
|
|
|
|
* @param parsed the textual date or time ago parsed by NewPipeExtractor, or {@code null} if
|
|
|
|
* the extractor could not parse it
|
|
|
|
* @param textual the original textual date or time ago string as provided by services
|
|
|
|
* @return {@link #relativeTime(OffsetDateTime)} is used if {@code parsed != null}, otherwise
|
|
|
|
* {@code textual} is returned. If in debug mode, {@code context != null},
|
|
|
|
* {@code parsed != null} and the relevant setting is enabled, {@code textual} will
|
|
|
|
* be appended to the returned string for debugging purposes.
|
|
|
|
*/
|
|
|
|
public static String relativeTimeOrTextual(@Nullable final Context context,
|
|
|
|
@Nullable final DateWrapper parsed,
|
|
|
|
final String textual) {
|
2023-04-12 08:54:03 +00:00
|
|
|
if (parsed == null) {
|
|
|
|
return textual;
|
|
|
|
} else if (DEBUG && context != null && PreferenceManager
|
|
|
|
.getDefaultSharedPreferences(context)
|
|
|
|
.getBoolean(context.getString(R.string.show_original_time_ago_key), false)) {
|
|
|
|
return relativeTime(parsed.offsetDateTime()) + " (" + textual + ")";
|
|
|
|
} else {
|
|
|
|
return relativeTime(parsed.offsetDateTime());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-09 03:23:53 +00:00
|
|
|
public static void assureCorrectAppLanguage(final Context c) {
|
|
|
|
final Resources res = c.getResources();
|
2020-08-16 08:24:58 +00:00
|
|
|
final DisplayMetrics dm = res.getDisplayMetrics();
|
|
|
|
final Configuration conf = res.getConfiguration();
|
2022-11-09 03:23:53 +00:00
|
|
|
conf.setLocale(getAppLocale(c));
|
2020-01-04 20:38:27 +00:00
|
|
|
res.updateConfiguration(conf, dm);
|
|
|
|
}
|
2020-01-09 08:40:05 +00:00
|
|
|
|
2023-12-22 17:50:48 +00:00
|
|
|
private static Locale getLocaleFromPrefs(@NonNull final Context context,
|
|
|
|
@StringRes final int prefKey) {
|
2022-11-09 03:23:53 +00:00
|
|
|
final SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
|
|
|
|
final String defaultKey = context.getString(R.string.default_localization_key);
|
|
|
|
final String languageCode = sp.getString(context.getString(prefKey), defaultKey);
|
|
|
|
|
|
|
|
if (languageCode.equals(defaultKey)) {
|
|
|
|
return Locale.getDefault();
|
|
|
|
} else {
|
|
|
|
return Locale.forLanguageTag(languageCode);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static double round(final double value) {
|
|
|
|
return new BigDecimal(value).setScale(1, RoundingMode.HALF_UP).doubleValue();
|
2020-01-28 19:48:42 +00:00
|
|
|
}
|
2020-02-28 15:59:52 +00:00
|
|
|
|
2023-12-22 17:50:48 +00:00
|
|
|
private static String getQuantity(@NonNull final Context context,
|
|
|
|
@PluralsRes final int pluralId,
|
|
|
|
@StringRes final int zeroCaseStringId,
|
|
|
|
final long count,
|
2022-11-09 03:23:53 +00:00
|
|
|
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 = (int) MathUtils.clamp(count, Integer.MIN_VALUE, Integer.MAX_VALUE);
|
|
|
|
return context.getResources().getQuantityString(pluralId, safeCount, formattedCount);
|
2020-02-28 15:59:52 +00:00
|
|
|
}
|
2015-12-29 14:35:51 +00:00
|
|
|
}
|