Skip to content

Commit

Permalink
fix(ui_localizations): fix loading Traditional Chinese (#128)
Browse files Browse the repository at this point in the history
* Uses the way how _WidgetsLocalizationsDelegate choose an appropriate translation.

* melos run format

* Add trailing comma

* Add test cases to verify that Traditional Chinese loads correctly and the localization overrides work properly.

* Add test cases to verify that Traditional Chinese loads correctly and the localization overrides work properly.

* Add widget tests for the `firebase_ui_localizations` package.

* Code refactoring and applies `melos run format`.

* Convert to block body.

* Execute `melos run format`

* formatting and naming

---------

Co-authored-by: Andrei Lesnitsky <a@lesnitsky.dev>
  • Loading branch information
LaemonT and lesnitsky authored Oct 11, 2023
1 parent 73d5f00 commit 5e09eab
Show file tree
Hide file tree
Showing 3 changed files with 296 additions and 59 deletions.
145 changes: 107 additions & 38 deletions packages/firebase_ui_localizations/lib/src/all_languages.dart
Original file line number Diff line number Diff line change
@@ -1,52 +1,121 @@
// Copyright 2023, the Chromium project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import "./default_localizations.dart";
import 'dart:ui';

import "./default_localizations.dart";
import 'lang/ar.dart';
import 'lang/de.dart';
import 'lang/en.dart';
import 'lang/es.dart';
import 'lang/es_419.dart';
import 'lang/ko.dart';
import 'lang/fr.dart';
import 'lang/he.dart';
import 'lang/hi.dart';
import 'lang/hu.dart';
import 'lang/id.dart';
import 'lang/pt.dart';
import 'lang/he.dart';
import 'lang/de.dart';
import 'lang/it.dart';
import 'lang/zh.dart';
import 'lang/zh_tw.dart';
import 'lang/uk.dart';
import 'lang/th.dart';
import 'lang/ar.dart';
import 'lang/tr.dart';
import 'lang/ja.dart';
import 'lang/ko.dart';
import 'lang/nl.dart';
import 'lang/pl.dart';
import 'lang/en.dart';
import 'lang/ja.dart';
import 'lang/hi.dart';
import 'lang/pt.dart';
import 'lang/ru.dart';
import 'lang/fr.dart';
import 'lang/th.dart';
import 'lang/tr.dart';
import 'lang/uk.dart';
import 'lang/zh.dart';
import 'lang/zh_tw.dart';

final localizations = <String, FirebaseUILocalizationLabels>{
'es': const EsLocalizations(),
'es_419': const Es419Localizations(),
'ko': const KoLocalizations(),
'hu': const HuLocalizations(),
'id': const IdLocalizations(),
'pt': const PtLocalizations(),
'he': const HeLocalizations(),
'de': const DeLocalizations(),
'it': const ItLocalizations(),
'zh': const ZhLocalizations(),
'zh_tw': const ZhTWLocalizations(),
'uk': const UkLocalizations(),
'th': const ThLocalizations(),
'ar': const ArLocalizations(),
'tr': const TrLocalizations(),
'nl': const NlLocalizations(),
'pl': const PlLocalizations(),
'en': const EnLocalizations(),
'ja': const JaLocalizations(),
'hi': const HiLocalizations(),
'ru': const RuLocalizations(),
'fr': const FrLocalizations(),
final Set<String> kSupportedLanguages = {
'ar', // Arabic
'de', // German
'en', // English
'es', // Spanish Castilian
'fr', // French
'he', // Hebrew
'hi', // Hindi
'hu', // Hungarian
'id', // Indonesian
'it', // Italian
'ja', // Japanese
'ko', // Korean
'nl', // Dutch Flemish
'pl', // Polish
'pt', // Portuguese
'ru', // Russian
'th', // Thai
'tr', // Turkish
'uk', // Ukrainian
'zh', // Chinese
};

FirebaseUILocalizationLabels getFirebaseUITranslation(
Locale useLocale, [
Locale? defaultLocale,
]) {
final Locale locale;
if (kSupportedLanguages.contains(useLocale.languageCode)) {
locale = useLocale;
} else {
locale = defaultLocale ?? useLocale;
}

switch (locale.languageCode) {
case 'ar':
return const ArLocalizations();
case 'de':
return const DeLocalizations();
case 'en':
return const EnLocalizations();
case 'es':
switch (locale.countryCode) {
case '419':
return const Es419Localizations();
}
return const EsLocalizations();
case 'fr':
return const FrLocalizations();
case 'he':
return const HeLocalizations();
case 'hi':
return const HiLocalizations();
case 'hu':
return const HuLocalizations();
case 'id':
return const IdLocalizations();
case 'it':
return const ItLocalizations();
case 'ja':
return const JaLocalizations();
case 'ko':
return const KoLocalizations();
case 'nl':
return const NlLocalizations();
case 'pl':
return const PlLocalizations();
case 'pt':
return const PtLocalizations();
case 'ru':
return const RuLocalizations();
case 'th':
return const ThLocalizations();
case 'tr':
return const TrLocalizations();
case 'uk':
return const UkLocalizations();
case 'zh':
switch (locale.scriptCode) {
case 'Hant':
return const ZhTWLocalizations();
}
switch (locale.countryCode) {
case 'HK':
case 'TW':
return const ZhTWLocalizations();
}
return const ZhLocalizations();
}

throw ('getTranslationLabels() called for unsupported locale "$locale"');
}
30 changes: 9 additions & 21 deletions packages/firebase_ui_localizations/lib/src/l10n.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ class FirebaseUILocalizations<T extends FirebaseUILocalizationLabels> {
return l;
}

final defaultLocalizations = localizations[kDefaultLocale.languageCode]!;
return FirebaseUILocalizations(kDefaultLocale, defaultLocalizations);
final defaultTranslation = getFirebaseUITranslation(kDefaultLocale);
return FirebaseUILocalizations(kDefaultLocale, defaultTranslation);
}

/// Returns localization labels.
Expand Down Expand Up @@ -74,32 +74,20 @@ class FirebaseUILocalizationDelegate<T extends FirebaseUILocalizationLabels>
]);

@override
bool isSupported(Locale locale) {
return _forceSupportAllLocales ||
localizations.keys.contains(locale.languageCode);
}
bool isSupported(Locale locale) =>
_forceSupportAllLocales ||
kSupportedLanguages.contains(locale.languageCode);

@override
Future<FirebaseUILocalizations> load(Locale locale) {
late FirebaseUILocalizationLabels labels;

final key = locale.languageCode;
final fullKey = '${key}_${locale.countryCode.toString()}';

if (localizations.containsKey(fullKey)) {
labels = localizations[fullKey]!;
} else if (localizations.containsKey(key)) {
labels = localizations[key]!;
} else {
labels = localizations[kDefaultLocale.languageCode]!;
}
final translation = getFirebaseUITranslation(locale, kDefaultLocale);

final l = FirebaseUILocalizations(
final localizations = FirebaseUILocalizations(
locale,
overrides ?? labels,
overrides ?? translation,
);

return SynchronousFuture<FirebaseUILocalizations>(l);
return SynchronousFuture<FirebaseUILocalizations>(localizations);
}

@override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
// Copyright 2022, the Chromium project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'package:firebase_ui_localizations/firebase_ui_localizations.dart';
import 'package:firebase_ui_localizations/src/lang/zh_tw.dart';
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_test/flutter_test.dart';

const localeZh = Locale('zh');
const localeTW = Locale('zh', 'TW');

Future<void> main() async {
late FirebaseUILocalizationDelegate delegate;

group(
'FirebaseUILocalization loads the appropriate Chinese translation',
() {
localizeText(BuildContext context) {
final labels = FirebaseUILocalizations.labelsOf(context);
return labels.signInWithPhoneButtonText;
}

setUp(() async {
delegate = const FirebaseUILocalizationDelegate();
});

test(
'Loads the correct translation with the language tag "${localeZh.toLanguageTag()}"',
() async {
final localizations = await delegate.load(localeZh);
expect(localizations.labels.signInWithPhoneButtonText, '使用电话号码登录');
},
);

testWidgets(
'UI test for the "${localeZh.toLanguageTag()}" translation',
(tester) async {
await tester.pumpWidget(
TestMaterialApp(
locale: localeZh,
localizeText: localizeText,
),
);
expect(find.text('使用电话号码登录'), findsOneWidget);
},
);

test(
'Loads the correct translation with the language tag "${localeTW.toLanguageTag()}"',
() async {
final localizations = await delegate.load(localeTW);
expect(localizations.labels.signInWithPhoneButtonText, '使用電話號碼登入');
},
);

testWidgets(
'UI test for the "${localeTW.toLanguageTag()}" translation',
(tester) async {
await tester.pumpWidget(
TestMaterialApp(
locale: localeTW,
localizeText: localizeText,
),
);
expect(find.text('使用電話號碼登入'), findsOneWidget);
},
);
},
);

group(
'Localization override',
() {
localizeText(BuildContext context) {
return FirebaseUILocalizations.labelsOf(context).verifyEmailTitle;
}

test(
'Overrides the DefaultLocalizations',
() async {
final localizations = await const FirebaseUILocalizationDelegate(
DefaultLocalizationsOverrides(),
).load(localeTW);
expect(localizations.labels.verifyEmailTitle, 'Overwritten');
},
);

testWidgets(
'UI test for the default translation override',
(tester) async {
await tester.pumpWidget(
TestMaterialApp(
locale: localeZh,
localizationsOverride: const FirebaseUILocalizationDelegate(
DefaultLocalizationsOverrides(),
),
localizeText: localizeText,
),
);
expect(find.text('Overwritten'), findsOneWidget);
},
);

test(
'Overrides the DefaultLocalizations',
() async {
final localizations = await const FirebaseUILocalizationDelegate(
ZhTWLocalizationsOverrides(),
).load(localeTW);
expect(localizations.labels.verifyEmailTitle, '覆寫標題');
},
);

testWidgets(
'UI test for the "${localeTW.toLanguageTag()}" translation override',
(tester) async {
await tester.pumpWidget(
TestMaterialApp(
locale: localeTW,
localizationsOverride: const FirebaseUILocalizationDelegate(
ZhTWLocalizationsOverrides(),
),
localizeText: localizeText,
),
);
expect(find.text('覆寫標題'), findsOneWidget);
},
);
},
);
}

class DefaultLocalizationsOverrides extends DefaultLocalizations {
const DefaultLocalizationsOverrides();

@override
String get verifyEmailTitle => 'Overwritten';
}

class ZhTWLocalizationsOverrides extends ZhTWLocalizations {
const ZhTWLocalizationsOverrides();

@override
String get verifyEmailTitle => '覆寫標題';
}

class TestMaterialApp extends StatelessWidget {
final Locale locale;
final LocalizationsDelegate? localizationsOverride;
final String Function(BuildContext context) localizeText;

const TestMaterialApp({
super.key,
required this.locale,
this.localizationsOverride,
required this.localizeText,
});

@override
Widget build(BuildContext context) {
return MaterialApp(
supportedLocales: const [localeZh, localeTW],
locale: locale,
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
localizationsOverride == null
? FirebaseUILocalizations.delegate
: localizationsOverride!,
],
home: Builder(
builder: (context) => Text(
localizeText.call(context),
),
),
);
}
}

0 comments on commit 5e09eab

Please sign in to comment.