From da79298df3958dc57e3edee94a1c0fd9fd802a91 Mon Sep 17 00:00:00 2001 From: Marcelo Glasberg <13332110+marcglasberg@users.noreply.github.com> Date: Fri, 13 Dec 2024 21:38:14 -0300 Subject: [PATCH] Preload translations. --- README.md | 1 - example/assets/translations/en-US.json | 7 + example/assets/translations/es-ES.json | 7 + example/assets/translations/pt-BR.json | 7 + .../language_settings_page.i18n.dart | 2 - example/lib/1_translation_example/main.dart | 14 +- .../lib/1_translation_example/main.i18n.dart | 2 - .../1_translation_example/my_screen.i18n.dart | 2 - .../1_translation_example/my_widget.i18n.dart | 2 - .../main.dart | 24 ++-- .../my.i18n.dart | 2 - .../main.dart | 24 ++-- .../my.i18n.dart | 2 - .../lib/4_const_translation_example/main.dart | 24 ++-- .../4_const_translation_example/my.i18n.dart | 2 - example/lib/5_interpolation_example/main.dart | 24 ++-- example/lib/6_load_example/i18nScreen.jpg | Bin 0 -> 45028 bytes .../language_settings_page.dart | 125 ++++++++++++++++++ .../language_settings_page.i18n.dart | 21 +++ example/lib/6_load_example/main.dart | 85 ++++++++++++ example/lib/6_load_example/main.i18n.dart | 14 ++ example/lib/6_load_example/my_screen.dart | 88 ++++++++++++ example/lib/6_load_example/my_widget.dart | 22 +++ example/pubspec.yaml | 2 + lib/src/extensions.dart | 37 +++++- lib/src/i18n_widget.dart | 29 ++++ lib/src/json_loader.dart | 23 ++++ lib/src/loader.dart | 96 ++++++++++++++ pubspec.yaml | 2 +- test/i18n_extension_test.dart | 2 - test/other_translations_test.dart | 2 - 31 files changed, 626 insertions(+), 68 deletions(-) create mode 100644 example/assets/translations/en-US.json create mode 100644 example/assets/translations/es-ES.json create mode 100644 example/assets/translations/pt-BR.json create mode 100644 example/lib/6_load_example/i18nScreen.jpg create mode 100644 example/lib/6_load_example/language_settings_page.dart create mode 100644 example/lib/6_load_example/language_settings_page.i18n.dart create mode 100644 example/lib/6_load_example/main.dart create mode 100644 example/lib/6_load_example/main.i18n.dart create mode 100644 example/lib/6_load_example/my_screen.dart create mode 100644 example/lib/6_load_example/my_widget.dart create mode 100644 lib/src/json_loader.dart create mode 100644 lib/src/loader.dart diff --git a/README.md b/README.md index 9a4107f..1714361 100644 --- a/README.md +++ b/README.md @@ -1449,7 +1449,6 @@ class MyI18n { extension Localization on String { String get i18n => localize(this, MyI18n.translations); String plural(value) => localizePlural(value, this, MyI18n.translations); - String fill(List params) => localizeFill(this, params); } ``` diff --git a/example/assets/translations/en-US.json b/example/assets/translations/en-US.json new file mode 100644 index 0000000..7d3dab5 --- /dev/null +++ b/example/assets/translations/en-US.json @@ -0,0 +1,7 @@ +{ + "Hello, welcome to this internationalization demo.": "Hello, welcome to this internationalization demo.", + "i18n Demo": "i18n Demo", + "Increment": "Increment", + "Change Language": "Change Language", + "You clicked the button %d times:": "You clicked the button %d times:" +} diff --git a/example/assets/translations/es-ES.json b/example/assets/translations/es-ES.json new file mode 100644 index 0000000..66ee559 --- /dev/null +++ b/example/assets/translations/es-ES.json @@ -0,0 +1,7 @@ +{ + "Hello, welcome to this internationalization demo.": "Hola, bienvenido a esta demostración de internacionalización.", + "i18n Demo": "Demostración i18n", + "Increment": "Incrementar", + "Change Language": "Cambiar idioma", + "You clicked the button %d times:": "Hiciste clic en el botón %d veces:" +} diff --git a/example/assets/translations/pt-BR.json b/example/assets/translations/pt-BR.json new file mode 100644 index 0000000..7d11528 --- /dev/null +++ b/example/assets/translations/pt-BR.json @@ -0,0 +1,7 @@ +{ + "Hello, welcome to this internationalization demo.": "Olá, bem-vindo a esta demonstração de internacionalização.", + "i18n Demo": "Demonstração i18n", + "Increment": "Incrementar", + "Change Language": "Mude Idioma", + "You clicked the button %d times:": "Você clicou o botão %d vezes:" +} diff --git a/example/lib/1_translation_example/language_settings_page.i18n.dart b/example/lib/1_translation_example/language_settings_page.i18n.dart index c988e6c..f1a4739 100644 --- a/example/lib/1_translation_example/language_settings_page.i18n.dart +++ b/example/lib/1_translation_example/language_settings_page.i18n.dart @@ -13,8 +13,6 @@ extension Localization on String { String get i18n => localize(this, _t); - String fill(List params) => localizeFill(this, params); - String plural(value) => localizePlural(value, this, _t); String version(Object modifier) => localizeVersion(modifier, this, _t); diff --git a/example/lib/1_translation_example/main.dart b/example/lib/1_translation_example/main.dart index f1eed6c..4cd29c6 100644 --- a/example/lib/1_translation_example/main.dart +++ b/example/lib/1_translation_example/main.dart @@ -31,7 +31,7 @@ import 'my_screen.dart'; /// void main() async { WidgetsFlutterBinding.ensureInitialized(); - + runApp( I18n( initialLocale: await I18n.loadLocale(), @@ -45,12 +45,8 @@ class AppCore extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( - /// The initial locale for this app's [Localizations] widget is based on this - /// value. If the 'locale' is null then the system's locale value is used. - /// The value of [Localizations.locale] will equal this locale if it matches one - /// of the [supportedLocales]. Otherwise it will be the first [supportedLocales]. - // locale: I18n.of(context).locale, locale: I18n.locale, + debugShowCheckedModeBanner: false, // localizationsDelegates: [ GlobalMaterialLocalizations.delegate, @@ -62,10 +58,7 @@ class AppCore extends StatelessWidget { const Locale('pt', 'BR'), const Locale('es', 'ES'), ], - home: Container( - // - child: MyHomePage(), - ), + home: MyHomePage(), theme: ThemeData( elevatedButtonTheme: ElevatedButtonThemeData( style: ElevatedButton.styleFrom( @@ -74,7 +67,6 @@ class AppCore extends StatelessWidget { ), ), ), - debugShowCheckedModeBanner: false, ); } } diff --git a/example/lib/1_translation_example/main.i18n.dart b/example/lib/1_translation_example/main.i18n.dart index 236acd8..c692460 100644 --- a/example/lib/1_translation_example/main.i18n.dart +++ b/example/lib/1_translation_example/main.i18n.dart @@ -13,8 +13,6 @@ extension Localization on String { String get i18n => localize(this, _t); - String fill(List params) => localizeFill(this, params); - String plural(value) => localizePlural(value, this, _t); String version(Object modifier) => localizeVersion(modifier, this, _t); diff --git a/example/lib/1_translation_example/my_screen.i18n.dart b/example/lib/1_translation_example/my_screen.i18n.dart index e4fa69c..4adf2c0 100644 --- a/example/lib/1_translation_example/my_screen.i18n.dart +++ b/example/lib/1_translation_example/my_screen.i18n.dart @@ -38,8 +38,6 @@ extension Localization on String { String get i18n => localize(this, _t); - String fill(List params) => localizeFill(this, params); - String plural(value) => localizePlural(value, this, _t); String version(Object modifier) => localizeVersion(modifier, this, _t); diff --git a/example/lib/1_translation_example/my_widget.i18n.dart b/example/lib/1_translation_example/my_widget.i18n.dart index fb78026..85d549e 100644 --- a/example/lib/1_translation_example/my_widget.i18n.dart +++ b/example/lib/1_translation_example/my_widget.i18n.dart @@ -13,8 +13,6 @@ extension Localization on String { String get i18n => localize(this, _t); - String fill(List params) => localizeFill(this, params); - String plural(value) => localizePlural(value, this, _t); String version(Object modifier) => localizeVersion(modifier, this, _t); diff --git a/example/lib/2_identifier_translation_example/main.dart b/example/lib/2_identifier_translation_example/main.dart index cfc9f71..8d5af1c 100644 --- a/example/lib/2_identifier_translation_example/main.dart +++ b/example/lib/2_identifier_translation_example/main.dart @@ -23,12 +23,24 @@ import 'my_screen.dart'; /// [appbarTitle], [greetings], [increment], [changeLanguage], /// and [youClickedThisNumberOfTimes]. /// -void main() => runApp(MyApp()); +void main() async { + WidgetsFlutterBinding.ensureInitialized(); -class MyApp extends StatelessWidget { + runApp( + I18n( + initialLocale: await I18n.loadLocale(), + autoSaveLocale: true, + child: AppCore(), + ), + ); +} + +class AppCore extends StatelessWidget { @override Widget build(BuildContext context) => MaterialApp( + locale: I18n.locale, debugShowCheckedModeBanner: false, + // localizationsDelegates: [ GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, @@ -38,13 +50,7 @@ class MyApp extends StatelessWidget { const Locale('en', "US"), const Locale('pt', "BR"), ], - home: I18n( - // - // Keep it commented out to use the current system locale. - // initialLocale: 'es-ES'.asLocale, - // - child: MyHomePage(), - ), + home: MyHomePage(), ); } diff --git a/example/lib/2_identifier_translation_example/my.i18n.dart b/example/lib/2_identifier_translation_example/my.i18n.dart index 584c894..9c21793 100644 --- a/example/lib/2_identifier_translation_example/my.i18n.dart +++ b/example/lib/2_identifier_translation_example/my.i18n.dart @@ -57,7 +57,5 @@ extension MyLocalization on Object { String get i18n => localize(this, _t); - String fill(List params) => localizeFill(this, params); - String plural(value) => localizePlural(value, this, _t); } diff --git a/example/lib/3_scoped_identifier_translation_example/main.dart b/example/lib/3_scoped_identifier_translation_example/main.dart index aee780e..8db751e 100644 --- a/example/lib/3_scoped_identifier_translation_example/main.dart +++ b/example/lib/3_scoped_identifier_translation_example/main.dart @@ -26,12 +26,24 @@ import 'my_screen.dart'; /// Text(MyScope.greetings.i18n); /// MaterialButton(child: Text(OtherScope.increment.i18n)); /// -void main() => runApp(MyApp()); +void main() async { + WidgetsFlutterBinding.ensureInitialized(); -class MyApp extends StatelessWidget { + runApp( + I18n( + initialLocale: await I18n.loadLocale(), + autoSaveLocale: true, + child: AppCore(), + ), + ); +} + +class AppCore extends StatelessWidget { @override Widget build(BuildContext context) => MaterialApp( + locale: I18n.locale, debugShowCheckedModeBanner: false, + // localizationsDelegates: [ GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, @@ -41,13 +53,7 @@ class MyApp extends StatelessWidget { const Locale('en', "US"), const Locale('pt', "BR"), ], - home: I18n( - // - // Keep it commented out to use the current system locale. - // initialLocale: 'es-ES'.asLocale, - // - child: MyHomePage(), - ), + home: MyHomePage(), ); } diff --git a/example/lib/3_scoped_identifier_translation_example/my.i18n.dart b/example/lib/3_scoped_identifier_translation_example/my.i18n.dart index 0fc7162..770f2fa 100644 --- a/example/lib/3_scoped_identifier_translation_example/my.i18n.dart +++ b/example/lib/3_scoped_identifier_translation_example/my.i18n.dart @@ -8,8 +8,6 @@ extension MyLocalization on Object { String get i18n => localize(this, _t); - String fill(List params) => localizeFill(this, params); - String plural(value) => localizePlural(value, this, _t); } diff --git a/example/lib/4_const_translation_example/main.dart b/example/lib/4_const_translation_example/main.dart index a396480..4b957eb 100644 --- a/example/lib/4_const_translation_example/main.dart +++ b/example/lib/4_const_translation_example/main.dart @@ -9,12 +9,24 @@ import 'my_screen.dart'; /// This example demonstrates how to use identifiers as translation keys, defined as /// constants, using a the [ConstTranslations] constructor: -void main() => runApp(MyApp()); +void main() async { + WidgetsFlutterBinding.ensureInitialized(); -class MyApp extends StatelessWidget { + runApp( + I18n( + initialLocale: await I18n.loadLocale(), + autoSaveLocale: true, + child: AppCore(), + ), + ); +} + +class AppCore extends StatelessWidget { @override Widget build(BuildContext context) => MaterialApp( + locale: I18n.locale, debugShowCheckedModeBanner: false, + // localizationsDelegates: [ GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, @@ -24,13 +36,7 @@ class MyApp extends StatelessWidget { const Locale('en', "US"), const Locale('pt', "BR"), ], - home: I18n( - // - // Keep it commented out to use the current system locale. - // initialLocale: 'es-ES'.asLocale, - // - child: MyHomePage(), - ), + home: MyHomePage(), ); } diff --git a/example/lib/4_const_translation_example/my.i18n.dart b/example/lib/4_const_translation_example/my.i18n.dart index 313be4a..4361990 100644 --- a/example/lib/4_const_translation_example/my.i18n.dart +++ b/example/lib/4_const_translation_example/my.i18n.dart @@ -58,7 +58,5 @@ extension Localization on String { String get i18n => localize(this, _t); - String fill(List params) => localizeFill(this, params); - String plural(value) => localizePlural(value, this, _t); } diff --git a/example/lib/5_interpolation_example/main.dart b/example/lib/5_interpolation_example/main.dart index 8cca2ef..8d61503 100644 --- a/example/lib/5_interpolation_example/main.dart +++ b/example/lib/5_interpolation_example/main.dart @@ -22,12 +22,24 @@ import 'my_screen.dart'; /// /// [appbarTitle], [greetings1], and [changeLanguage]. /// -void main() => runApp(MyApp()); +void main() async { + WidgetsFlutterBinding.ensureInitialized(); -class MyApp extends StatelessWidget { + runApp( + I18n( + initialLocale: await I18n.loadLocale(), + autoSaveLocale: true, + child: AppCore(), + ), + ); +} + +class AppCore extends StatelessWidget { @override Widget build(BuildContext context) => MaterialApp( + locale: I18n.locale, debugShowCheckedModeBanner: false, + // localizationsDelegates: [ GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, @@ -37,13 +49,7 @@ class MyApp extends StatelessWidget { const Locale('en', "US"), const Locale('pt', "BR"), ], - home: I18n( - // - // Keep it commented out to use the current system locale. - // initialLocale: 'es-ES'.asLocale, - // - child: MyHomePage(), - ), + home: MyHomePage(), ); } diff --git a/example/lib/6_load_example/i18nScreen.jpg b/example/lib/6_load_example/i18nScreen.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9dc275965dfc57be5753a1378ef5f7227dfdddb7 GIT binary patch literal 45028 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 literal 0 HcmV?d00001 diff --git a/example/lib/6_load_example/language_settings_page.dart b/example/lib/6_load_example/language_settings_page.dart new file mode 100644 index 0000000..5abed5d --- /dev/null +++ b/example/lib/6_load_example/language_settings_page.dart @@ -0,0 +1,125 @@ +import 'package:example/1_translation_example/language_settings_page.i18n.dart'; +import 'package:flutter/material.dart'; +import 'package:i18n_extension/i18n_extension.dart'; +import 'my_widget.dart'; + +const Widget space6 = SizedBox(width: 6, height: 6); +const Widget space16 = SizedBox(width: 16, height: 16); +final Widget divider = Container(width: double.infinity, height: 2, color: Colors.grey); +const commentStyle = TextStyle(color: Colors.grey, fontStyle: FontStyle.italic); + +class LanguageSettingsPage extends StatefulWidget { + @override + _LanguageSettingsPageState createState() => _LanguageSettingsPageState(); +} + +class _LanguageSettingsPageState extends State { + // + @override + Widget build(BuildContext ctx) { + return Scaffold( + appBar: AppBar( + backgroundColor: Colors.blue, + // + // 2) We can use the `const` keyword: + title: SelectableText('Language Settings Page'.i18n), + ), + body: Column( + children: [ + // + Transform.scale(scale: 0.75, child: MyWidget()), + // + Expanded( + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: _usingThemedOf(ctx), + ), + ), + ), + ], + ), + ); + } + + Column _usingThemedOf(BuildContext ctx) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton( + child: const Text( + "context.locale = Locale(en, US)", + style: const TextStyle(color: Colors.white, fontSize: 17), + ), + onPressed: () => context.locale = const Locale('en', 'US'), + ), + // + ElevatedButton( + child: const Text( + "context.locale = Locale(es, ES)", + style: const TextStyle(color: Colors.white, fontSize: 17), + ), + onPressed: () => context.locale = const Locale('es', 'ES'), + ), + // + ElevatedButton( + child: const Text( + "context.locale = Locale(pt, BR)", + style: const TextStyle(color: Colors.white, fontSize: 17), + ), + onPressed: () => context.locale = const Locale('pt', 'BR'), + ), + // + ElevatedButton( + child: const Text( + "context.locale = null", + style: const TextStyle(color: Colors.white, fontSize: 17), + ), + onPressed: () => context.locale = null, + ), + // + // + space16, + SelectableText( + 'context.locale == Locale(${context.locale.format(separator: ', ')})'), + SelectableText('I18n.locale == Locale(${I18n.locale.format(separator: ', ')})'), + const SelectableText('Recommended ways to get the current locale', + style: commentStyle), + space16, + SelectableText('I18n.languageTag == "${I18n.languageTag}"'), + const SelectableText('The current locale as a string', style: commentStyle), + space16, + SelectableText('I18n.languageOnly == "${I18n.languageOnly}"'), + const SelectableText('Language only, without country/region or script', + style: commentStyle), + space16, + // ignore: deprecated_member_use + SelectableText('I18n.localeStr == "${I18n.localeStr}"'), + const SelectableText('Deprecated way to get the locale as a string', + style: commentStyle), + space16, + divider, + space6, + // + space16, + SelectableText( + 'I18n.systemLocale == Locale(${I18n.systemLocale.format(separator: ', ')})'), + const SelectableText('The system locale read from the device', + style: commentStyle), + space16, + SelectableText( + 'I18n.forcedLocale == ${I18n.forcedLocale == null ? null : 'Locale(${I18n.forcedLocale!.format(separator: ', ')})'}'), + const SelectableText('The locale that overrides the system locale', + style: commentStyle), + const SelectableText('When null, the system locale will be used', + style: commentStyle), + space16, + SelectableText('Localizations.maybeLocaleOf(context) == ' + 'Locale(${Localizations.maybeLocaleOf(context)!.format(separator: ', ')})'), + const SelectableText('The locale as per the MaterialApp widget', + style: commentStyle), + space16, + ], + ); + } +} diff --git a/example/lib/6_load_example/language_settings_page.i18n.dart b/example/lib/6_load_example/language_settings_page.i18n.dart new file mode 100644 index 0000000..f1a4739 --- /dev/null +++ b/example/lib/6_load_example/language_settings_page.i18n.dart @@ -0,0 +1,21 @@ +// Developed by Marcelo Glasberg (2019) https://glasberg.dev and https://github.com/marcglasberg +// For more info, see: https://pub.dartlang.org/packages/i18n_extension +import 'package:i18n_extension/i18n_extension.dart'; + +extension Localization on String { + // + static final _t = Translations.byText("en-US") + + { + "en-US": "Language Settings Page", + "pt-BR": "Configuração de Idioma", + "es-ES": "Configuración de idioma", + }; + + String get i18n => localize(this, _t); + + String plural(value) => localizePlural(value, this, _t); + + String version(Object modifier) => localizeVersion(modifier, this, _t); + + Map allVersions() => localizeAllVersions(this, _t); +} diff --git a/example/lib/6_load_example/main.dart b/example/lib/6_load_example/main.dart new file mode 100644 index 0000000..c0cdec5 --- /dev/null +++ b/example/lib/6_load_example/main.dart @@ -0,0 +1,85 @@ +// Developed by Marcelo Glasberg (2019) https://glasberg.dev and https://github.com/marcglasberg +// For more info, see: https://pub.dartlang.org/packages/i18n_extension + +import 'package:example/1_translation_example/language_settings_page.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:i18n_extension/i18n_extension.dart'; + +import 'main.i18n.dart'; +import 'my_screen.dart'; + +/// This example demonstrates how to load translations from a file in the assets. +/// +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + + await MyTranslations.load(); + + runApp( + I18n( + initialLocale: await I18n.loadLocale(), + autoSaveLocale: true, + child: AppCore(), + ), + ); +} + +class AppCore extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp( + locale: I18n.locale, + debugShowCheckedModeBanner: false, + // + localizationsDelegates: [ + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + supportedLocales: [ + const Locale('en', 'US'), + const Locale('pt', 'BR'), + const Locale('es', 'ES'), + ], + home: MyHomePage(), + theme: ThemeData( + elevatedButtonTheme: ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + foregroundColor: Colors.white, + backgroundColor: Colors.blue, + ), + ), + ), + ); + } +} + +class MyHomePage extends StatefulWidget { + @override + _MyHomePageState createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text("i18n Demo".i18n), + backgroundColor: Colors.blue, + actions: [ + IconButton( + icon: const Icon(Icons.language), + onPressed: () { + Navigator.push( + context, + MaterialPageRoute(builder: (context) => LanguageSettingsPage()), + ); + }, + ), + ], + ), + body: MyScreen(), + ); + } +} diff --git a/example/lib/6_load_example/main.i18n.dart b/example/lib/6_load_example/main.i18n.dart new file mode 100644 index 0000000..3600984 --- /dev/null +++ b/example/lib/6_load_example/main.i18n.dart @@ -0,0 +1,14 @@ +// Developed by Marcelo Glasberg (2019) https://glasberg.dev and https://github.com/marcglasberg +// For more info, see: https://pub.dartlang.org/packages/i18n_extension +import 'package:i18n_extension/i18n_extension.dart'; + +extension MyTranslations on String { + // + static final _t = Translations.byFile('en-US', dir: 'assets/translations'); + + static Future load() => _t.load(); + + String get i18n => localize(this, _t); + + String plural(value) => localizePlural(value, this, _t); +} diff --git a/example/lib/6_load_example/my_screen.dart b/example/lib/6_load_example/my_screen.dart new file mode 100644 index 0000000..14f41c7 --- /dev/null +++ b/example/lib/6_load_example/my_screen.dart @@ -0,0 +1,88 @@ +// Developed by Marcelo Glasberg (2019) https://glasberg.dev and https://github.com/marcglasberg +// For more info, see: https://pub.dartlang.org/packages/i18n_extension +import 'package:example/1_translation_example/language_settings_page.dart'; +import 'package:flutter/material.dart'; +import 'package:i18n_extension/i18n_extension.dart'; + +import 'main.i18n.dart'; +import 'my_widget.dart'; + +class MyScreen extends StatefulWidget { + @override + _MyScreenState createState() => _MyScreenState(); +} + +class _MyScreenState extends State { + late int counter; + + @override + void initState() { + super.initState(); + counter = 0; + } + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Spacer(flex: 2), + MyWidget(), + const Spacer(), + Container( + height: 50, + alignment: Alignment.center, + child: Text( + "You clicked the button %d times:".plural(counter), + textAlign: TextAlign.center, + style: const TextStyle(fontSize: 17), + ), + ), + MaterialButton( + color: Colors.blue, + onPressed: _onIncrement, + child: Text( + "Increment".i18n, + style: const TextStyle(color: Colors.white, fontSize: 17), + ), + ), + const Spacer(), + // + MaterialButton( + color: Colors.blue, + onPressed: _onChangeLanguage, + child: Text( + "Change Language".i18n, + style: const TextStyle(color: Colors.white, fontSize: 17), + ), + ), + // + Text( + "Locale: ${I18n.languageTag}", + textAlign: TextAlign.center, + style: const TextStyle(fontSize: 13, color: Colors.grey), + ), + // + space16, + const Spacer(flex: 2), + ], + ), + ); + } + + /// English, them Spanish, then Portuguese, then English again. + void _onChangeLanguage() { + // + String next = (I18n.languageTag == "en-US") + ? 'es-ES' + : (I18n.languageTag == "es-ES") + ? 'pt-BR' + : 'en-US'; + + I18n.of(context).locale = next.asLocale; + } + + void _onIncrement() => setState(() => counter++); +} diff --git a/example/lib/6_load_example/my_widget.dart b/example/lib/6_load_example/my_widget.dart new file mode 100644 index 0000000..f89480a --- /dev/null +++ b/example/lib/6_load_example/my_widget.dart @@ -0,0 +1,22 @@ +// Developed by Marcelo Glasberg (2019) https://glasberg.dev and https://github.com/marcglasberg +// For more info, see: https://pub.dartlang.org/packages/i18n_extension +import 'package:flutter/material.dart'; + +import 'main.i18n.dart'; + +class MyWidget extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Container( + alignment: Alignment.center, + padding: const EdgeInsets.all(16.0), + height: 100, + color: Colors.grey[300], + child: Text( + "Hello, welcome to this internationalization demo.".i18n, + textAlign: TextAlign.center, + style: const TextStyle(fontSize: 20), + ), + ); + } +} diff --git a/example/pubspec.yaml b/example/pubspec.yaml index c83f411..d093397 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -20,3 +20,5 @@ dev_dependencies: flutter: uses-material-design: true + assets: + - assets/translations/ diff --git a/lib/src/extensions.dart b/lib/src/extensions.dart index e062f95..d1e207c 100644 --- a/lib/src/extensions.dart +++ b/lib/src/extensions.dart @@ -1,8 +1,43 @@ +import 'dart:io' as io; +import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'package:i18n_extension/i18n_extension.dart'; import 'package:i18n_extension_core/i18n_extension_core.dart' as core; -extension I18nMain on String { +// ignore: implementation_imports +import 'package:i18n_extension_core/src/translations_by_locale.dart' as tbl; + +import 'json_loader.dart'; + +extension I18nTranslationsExtension on Translations { + Future load() async { + _prepareLoadProcess(); + if (this is tbl.TranslationsByLocale) { + return (this as tbl.TranslationsByLocale).completer?.future; + } + } + + /// Load assets for translations created with `Translations.load(...)`. + static void _prepareLoadProcess() { + Translations.loadProcess = (tbl.TranslationsByLocale translations) async { + // + String? dir = translations.dir; + if (dir == null) return; + + if (kIsWeb) { + throw TranslationsException("Web loading from URL not yet implemented."); + } + // + else if (io.Platform.isAndroid || io.Platform.isIOS) { + Map> loadedTranslations = + await JsonLoader().fromAssetDir(dir); + translations.translationByLocale_ByTranslationKey.addAll(loadedTranslations); + } + }; + } +} + +extension I18nMainExtension on String { // /// The [args] function applies interpolations on this string with the given diff --git a/lib/src/i18n_widget.dart b/lib/src/i18n_widget.dart index caeec88..2de499a 100644 --- a/lib/src/i18n_widget.dart +++ b/lib/src/i18n_widget.dart @@ -1,11 +1,17 @@ // Developed by Marcelo Glasberg (2019) https://glasberg.dev and https://github.com/marcglasberg // For more info, see: https://pub.dartlang.org/packages/i18n_extension import 'dart:async'; +import 'dart:io' as io; import 'dart:ui'; import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:i18n_extension/i18n_extension.dart'; +import 'package:i18n_extension/src/json_loader.dart'; + +// ignore: implementation_imports +import 'package:i18n_extension_core/src/translations_by_locale.dart' as core; import 'package:intl/number_symbols.dart'; import 'package:intl/number_symbols_data.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -399,10 +405,31 @@ class I18n extends StatefulWidget { /// See also: [saveLocale], [deleteLocale]. /// static Future loadLocale() async { + _prepareLoadProcess(); + String? localeStr = await SharedPreferencesAsync().getString('locale'); return (localeStr == null) ? null : localeStr.asLocale; } + /// Load assets for translations created with `Translations.load(...)`. + static void _prepareLoadProcess() { + Translations.loadProcess = (core.TranslationsByLocale translations) async { + // + String? dir = translations.dir; + if (dir == null) return; + + if (kIsWeb) { + throw TranslationsException("Web loading from URL not yet implemented."); + } + // + else if (io.Platform.isAndroid || io.Platform.isIOS) { + Map> loadedTranslations = + await JsonLoader().fromAssetDir(dir); + translations.translationByLocale_ByTranslationKey.addAll(loadedTranslations); + } + }; + } + /// Deletes the locale previously saved with [saveLocale], from the shared /// preferences of the device. /// @@ -508,6 +535,8 @@ class _I18nState extends State with WidgetsBindingObserver { _locale = widget.initialLocale; _forceAndObserveLocale(); + I18n._prepareLoadProcess(); + if (autoSaveLocale) _loadAndSetLocale(context); // Add this widget as an observer to listen for system changes. diff --git a/lib/src/json_loader.dart b/lib/src/json_loader.dart new file mode 100644 index 0000000..7635415 --- /dev/null +++ b/lib/src/json_loader.dart @@ -0,0 +1,23 @@ +import 'dart:convert'; + +import 'loader.dart'; + +class JsonLoader extends Loader { + // + @override + String get extension => '.json'; + + /// load a file, like `es-ES.json`, containing something like: + /// + /// ```json + /// { + /// "Welcome to this demo.": "Bienvenido a esta demostración.", + /// "i18n Demo": "Demostración i18n", + /// "Increment": "Incrementar", + /// "Change Language": "Cambiar idioma", + /// "You clicked the button %d times:": "Hiciste clic en el botón %d veces:" + /// } + /// ``` + @override + Map decode(String source) => json.decode(source); +} diff --git a/lib/src/loader.dart b/lib/src/loader.dart new file mode 100644 index 0000000..b420c5e --- /dev/null +++ b/lib/src/loader.dart @@ -0,0 +1,96 @@ +import 'dart:collection'; +import 'dart:convert'; + +import 'package:flutter/services.dart'; +import 'package:i18n_extension/i18n_extension.dart'; + +abstract class Loader { + // + + /// For example, for file 'en-US.json', the extension is '.json'. + String get extension; + + /// Given [source], the text content of the asset file, + /// returns a JSON map of translations. + Map decode(String source); + + /// load a file, like `es-ES.json`, containing something like: + /// + /// ```json + /// { + /// "Welcome to this demo.": "Bienvenido a esta demostración.", + /// "i18n Demo": "Demostración i18n", + /// "Increment": "Incrementar", + /// "Change Language": "Cambiar idioma", + /// "You clicked the button %d times:": "Hiciste clic en el botón %d veces:" + /// } + /// ``` + /// + /// And add to the translations, with something like: + /// + /// ```dart + /// translations.translationByLocale_ByTranslationKey.addAll( + /// { + /// 'Hello, welcome to this demo.': { + /// 'en-US': 'Welcome to this demo.', + /// 'pt-BR': 'Bem-vindo a esta demonstração.', + /// 'es-ES': 'Bienvenido a esta demostración.', + /// }, + /// 'i18n Demo': { + /// 'en-US': 'i18n Demo', + /// 'pt-BR': 'Demonstração i18n', + /// 'es-ES': 'Demostración i18n', + /// }, + /// 'Increment': { + /// 'en-US': 'Increment', + /// 'pt-BR': 'Incrementar', + /// 'es-ES': 'Incrementar', + /// }, + /// 'Change Language': { + /// 'en-US': 'Change Language', + /// 'pt-BR': 'Mude Idioma', + /// 'es-ES': 'Cambiar idioma', + /// }, + /// "You clicked the button %d times:": { + /// "en-US": "You clicked the button %d times:", + /// "pt-BR": "Você clicou o botão %d vezes:", + /// "es-ES": "Hiciste clic en el botón %d veces:", + /// }, + /// }, + /// ); + Future>> fromAssetDir(String dir) async { + // + String manifestContent = await rootBundle.loadString("AssetManifest.json"); + Map manifestMap = decode(manifestContent); + + Map> translations = HashMap(); + + for (String path in manifestMap.keys) { + if (!path.startsWith(dir)) continue; + + var fileName = path.split("/").last; + + if (!fileName.endsWith(extension)) { + throw TranslationsException("Ignoring '$path' which is not a $extension file."); + } + + var languageTag = fileName.split(".")[0].asLanguageTag; + + var stringReadFromBundle = await rootBundle.loadString(path); + + var translationsInFile = + Map.from(json.decode(stringReadFromBundle)); + + for (MapEntry entry in translationsInFile.entries) { + String key = entry.key; + dynamic value = entry.value; + if (value is String) { + translations.putIfAbsent(key, () => HashMap())[languageTag] = value; + } else + throw TranslationsException("Value for key '$key' in '$path' is not a String."); + } + } + + return translations; + } +} diff --git a/pubspec.yaml b/pubspec.yaml index ce38c32..9f0ccb2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ environment: dependencies: intl: '>=0.17.0-0 <=0.20.1' - i18n_extension_core: ^3.0.0-dev.2 + i18n_extension_core: ^3.0.0-dev.6 shared_preferences: ^2.3.3 flutter: sdk: flutter diff --git a/test/i18n_extension_test.dart b/test/i18n_extension_test.dart index ddd4a93..54a2191 100644 --- a/test/i18n_extension_test.dart +++ b/test/i18n_extension_test.dart @@ -1157,8 +1157,6 @@ extension Localization on String { String get i18n => localize(this, _t); - String fill(List params) => localizeFill(this, params); - String? plural(value) => localizePlural(value, this, _t); String version(Object modifier) => localizeVersion(modifier, this, _t); diff --git a/test/other_translations_test.dart b/test/other_translations_test.dart index 43f6ac0..79e5b00 100644 --- a/test/other_translations_test.dart +++ b/test/other_translations_test.dart @@ -61,8 +61,6 @@ extension Localization on Object? { String get i18n => localize(this, _t); - String fill(List params) => localizeFill(this, params); - String? plural(value) => localizePlural(value, this, _t); String version(Object modifier) => localizeVersion(modifier, this, _t);