feat(i18n): add Hungarian and Traditional Chinese locales
Complete and revise every shipped locale against the current English source, preserve locale-specific plurals, and map script-specific Chinese locales through device, Intl, duration, and Plex boundaries. Co-authored-by: emgeje <mgj@mgj.hu> Co-authored-by: junyou1998 <junyou1998@gmail.com>
This commit is contained in:
co-authored by
emgeje
junyou1998
parent
0c6dab01b3
commit
ad7ef112fc
@@ -0,0 +1,65 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:plezy/i18n/app_locale_utils.dart';
|
||||
import 'package:plezy/i18n/strings.g.dart';
|
||||
|
||||
void main() {
|
||||
group('resolvePreferredAppLocale', () {
|
||||
test('uses explicit Chinese scripts before regions', () {
|
||||
expect(
|
||||
resolvePreferredAppLocale([
|
||||
const Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant', countryCode: 'TW'),
|
||||
]),
|
||||
AppLocale.zhHant,
|
||||
);
|
||||
expect(
|
||||
resolvePreferredAppLocale([
|
||||
const Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans', countryCode: 'TW'),
|
||||
]),
|
||||
AppLocale.zh,
|
||||
);
|
||||
});
|
||||
|
||||
test('maps region-only Chinese locales to the expected script', () {
|
||||
for (final region in const ['TW', 'HK', 'MO']) {
|
||||
expect(resolvePreferredAppLocale([Locale('zh', region)]), AppLocale.zhHant, reason: region);
|
||||
}
|
||||
for (final region in const ['CN', 'SG']) {
|
||||
expect(resolvePreferredAppLocale([Locale('zh', region)]), AppLocale.zh, reason: region);
|
||||
}
|
||||
});
|
||||
|
||||
test('preserves device preference order', () {
|
||||
expect(
|
||||
resolvePreferredAppLocale(const [
|
||||
Locale('fr', 'FR'),
|
||||
Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant'),
|
||||
]),
|
||||
AppLocale.fr,
|
||||
);
|
||||
expect(
|
||||
resolvePreferredAppLocale(const [Locale('xx'), Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant')]),
|
||||
AppLocale.zhHant,
|
||||
);
|
||||
expect(
|
||||
resolvePreferredAppLocale(const [Locale('zh'), Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant')]),
|
||||
AppLocale.zh,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('AppLocaleExternalFormats', () {
|
||||
test('preserves Traditional Chinese for downstream formatters and Plex', () {
|
||||
expect(AppLocale.zhHant.intlLocaleName, 'zh_TW');
|
||||
expect(AppLocale.zhHant.durationLocaleName, 'zh_Hant');
|
||||
expect(AppLocale.zhHant.plexLanguageCode, 'zh-TW');
|
||||
});
|
||||
|
||||
test('keeps existing locales on their language code', () {
|
||||
expect(AppLocale.hu.intlLocaleName, 'hu');
|
||||
expect(AppLocale.hu.durationLocaleName, 'hu');
|
||||
expect(AppLocale.hu.plexLanguageCode, 'hu');
|
||||
expect(AppLocale.zh.plexLanguageCode, 'zh');
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -2,72 +2,33 @@ import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:plezy/i18n/strings.g.dart';
|
||||
|
||||
void main() {
|
||||
test('empty overrides fall back in every affected locale', () async {
|
||||
test('completed locale entries override the English fallback', () async {
|
||||
final english = await AppLocale.en.build();
|
||||
final commonEnglishValues = <String>[
|
||||
final englishValues = <String>[
|
||||
english.videoControls.showPlaybackControls,
|
||||
english.videoControls.hidePlaybackControls,
|
||||
];
|
||||
final spotlightEnglishValues = <String>[
|
||||
english.settings.tvCornerSpotlightBackdrop,
|
||||
english.settings.tvCornerSpotlightBackdropDescription,
|
||||
];
|
||||
|
||||
expect([...commonEnglishValues, ...spotlightEnglishValues], everyElement(isNotEmpty));
|
||||
expect(englishValues, everyElement(isNotEmpty));
|
||||
|
||||
const commonFallbackLocales = <AppLocale>[
|
||||
AppLocale.bg,
|
||||
AppLocale.da,
|
||||
AppLocale.de,
|
||||
AppLocale.es,
|
||||
AppLocale.fr,
|
||||
AppLocale.it,
|
||||
AppLocale.ja,
|
||||
AppLocale.ko,
|
||||
AppLocale.nb,
|
||||
AppLocale.nl,
|
||||
AppLocale.pl,
|
||||
AppLocale.pt,
|
||||
AppLocale.ru,
|
||||
AppLocale.sv,
|
||||
AppLocale.zh,
|
||||
];
|
||||
const spotlightFallbackLocales = <AppLocale>[
|
||||
AppLocale.bg,
|
||||
AppLocale.da,
|
||||
AppLocale.es,
|
||||
AppLocale.fr,
|
||||
AppLocale.it,
|
||||
AppLocale.ja,
|
||||
AppLocale.ko,
|
||||
AppLocale.nb,
|
||||
AppLocale.nl,
|
||||
AppLocale.pl,
|
||||
AppLocale.pt,
|
||||
AppLocale.ru,
|
||||
AppLocale.sv,
|
||||
AppLocale.zh,
|
||||
];
|
||||
|
||||
for (final locale in commonFallbackLocales) {
|
||||
for (final locale in AppLocale.values.where((locale) => locale != AppLocale.en)) {
|
||||
final translations = await locale.build();
|
||||
expect(
|
||||
<String>[translations.videoControls.showPlaybackControls, translations.videoControls.hidePlaybackControls],
|
||||
commonEnglishValues,
|
||||
reason: '${locale.languageCode} must inherit the common English values',
|
||||
);
|
||||
}
|
||||
final localizedValues = <String>[
|
||||
translations.videoControls.showPlaybackControls,
|
||||
translations.videoControls.hidePlaybackControls,
|
||||
translations.settings.tvCornerSpotlightBackdrop,
|
||||
translations.settings.tvCornerSpotlightBackdropDescription,
|
||||
];
|
||||
|
||||
for (final locale in spotlightFallbackLocales) {
|
||||
final translations = await locale.build();
|
||||
expect(
|
||||
<String>[
|
||||
translations.settings.tvCornerSpotlightBackdrop,
|
||||
translations.settings.tvCornerSpotlightBackdropDescription,
|
||||
],
|
||||
spotlightEnglishValues,
|
||||
reason: '${locale.languageCode} must inherit the English spotlight values',
|
||||
);
|
||||
for (var index = 0; index < localizedValues.length; index++) {
|
||||
expect(
|
||||
localizedValues[index],
|
||||
isNot(englishValues[index]),
|
||||
reason: '${locale.name} must provide its own completed translation at index $index',
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:async';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:plezy/i18n/strings.g.dart';
|
||||
import 'package:plezy/models/audio_quality_preset.dart';
|
||||
import 'package:plezy/models/hotkey_model.dart';
|
||||
import 'package:plezy/services/base_shared_preferences_service.dart';
|
||||
@@ -164,6 +165,32 @@ void main() {
|
||||
});
|
||||
});
|
||||
|
||||
group('SettingsService app locale', () {
|
||||
test('persists script-specific locales by enum name', () async {
|
||||
var settings = await SettingsService.getInstance();
|
||||
|
||||
await settings.write(SettingsService.appLocale, AppLocale.zhHant);
|
||||
expect(settings.prefs.getString(SettingsService.appLocale.key), 'zhHant');
|
||||
|
||||
BaseSharedPreferencesService.resetForTesting();
|
||||
SettingsService.resetForTesting();
|
||||
settings = await SettingsService.getInstance();
|
||||
|
||||
expect(settings.read(SettingsService.appLocale), AppLocale.zhHant);
|
||||
});
|
||||
|
||||
test('continues to read legacy language-code values', () async {
|
||||
var settings = await SettingsService.getInstance();
|
||||
await settings.prefs.setString(SettingsService.appLocale.key, 'zh');
|
||||
|
||||
BaseSharedPreferencesService.resetForTesting();
|
||||
SettingsService.resetForTesting();
|
||||
settings = await SettingsService.getInstance();
|
||||
|
||||
expect(settings.read(SettingsService.appLocale), AppLocale.zh);
|
||||
});
|
||||
});
|
||||
|
||||
group('SettingsService platform gates', () {
|
||||
test('audio passthrough stays available on desktop and Apple TV', () {
|
||||
expect(PlatformDetector.supportsAudioPassthrough(), isTrue);
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:plezy/i18n/app_locale_utils.dart';
|
||||
import 'package:plezy/i18n/strings.g.dart';
|
||||
import 'package:plezy/utils/formatters.dart';
|
||||
|
||||
void main() {
|
||||
setUp(() => LocaleSettings.setLocaleSync(AppLocale.en));
|
||||
|
||||
group('padNumber', () {
|
||||
test('pads with leading zeros to given width', () {
|
||||
expect(padNumber(5, 3), '005');
|
||||
@@ -100,6 +105,26 @@ void main() {
|
||||
});
|
||||
});
|
||||
|
||||
group('Traditional Chinese formatting', () {
|
||||
test('uses script-specific compact-number symbols', () async {
|
||||
await LocaleSettings.setLocale(AppLocale.zh);
|
||||
expect(NumberFormat.compact(locale: LocaleSettings.currentLocale.intlLocaleName).format(10000), '1万');
|
||||
|
||||
await LocaleSettings.setLocale(AppLocale.zhHant);
|
||||
expect(NumberFormat.compact(locale: LocaleSettings.currentLocale.intlLocaleName).format(10000), '1萬');
|
||||
});
|
||||
|
||||
test('uses script-specific textual duration units', () async {
|
||||
const duration = Duration(hours: 1, minutes: 2);
|
||||
|
||||
await LocaleSettings.setLocale(AppLocale.zh);
|
||||
expect(formatDurationTextual(duration.inMilliseconds), '1小时 2分');
|
||||
|
||||
await LocaleSettings.setLocale(AppLocale.zhHant);
|
||||
expect(formatDurationTextual(duration.inMilliseconds), '1小時 2分鐘');
|
||||
});
|
||||
});
|
||||
|
||||
group('toBulletedString', () {
|
||||
test('joins with " · " separator', () {
|
||||
expect(toBulletedString(['a', 'b', 'c']), 'a · b · c');
|
||||
|
||||
@@ -92,7 +92,7 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final dialog = find.byType(AlertDialog);
|
||||
for (final label in ['Нет', 'Да', 'Масштабировать', 'Принудительно', 'Удалить стили']) {
|
||||
for (final label in ['Нет', 'Да', 'Масштаб', 'Принудительно', 'Удалить стили']) {
|
||||
expect(find.descendant(of: dialog, matching: find.text(label)), findsOneWidget);
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user