diff --git a/.vscode/page.code-snippets b/.vscode/page.code-snippets index c6188878..4668b0b5 100644 --- a/.vscode/page.code-snippets +++ b/.vscode/page.code-snippets @@ -30,21 +30,21 @@ "body": [ "import 'package:flutter/material.dart';", "import 'package:flutter_hooks/flutter_hooks.dart';", + "import 'package:themes/themes.dart';", "", - "import '../../../../commons/hooks/hooks.dart';", - "import '../../../style/style.dart';", + "import '../../../gen/strings.g.dart';", "", "class ${TM_FILENAME_BASE/(.*)/${1:/pascalcase}${2}/} extends HookWidget implements PreferredSizeWidget {", " const ${TM_FILENAME_BASE/(.*)/${1:/pascalcase}${2}/}({super.key});", "", " @override", - " Size get preferredSize => appbarSize;", + " Size get preferredSize => appBarDefaultSize;", "", " @override", " Widget build(BuildContext context) {", - " final l10n = useL10n();", + " final t = Translations.of(context);", " return AppBar(", - " title: Text(l10n.${TM_FILENAME_BASE/(.*)/${1:/camelcase}${2}/}Title),", + " title: Text(t.${TM_FILENAME_BASE/[\\._]/./g}.title),", " );", " }", "}", @@ -67,7 +67,7 @@ "", " @override", " Widget build(BuildContext context, WidgetRef ref) {", - " final l10n = useL10n();", + " final t = Translations.of(context);", " return $1;", " }", "}", diff --git a/melos.yaml b/melos.yaml index 64cf5f0a..bb907dd7 100644 --- a/melos.yaml +++ b/melos.yaml @@ -8,9 +8,6 @@ packages: command: bootstrap: runPubGetInParallel: true - hooks: - post: | - melos exec --flutter --dir-exists=lib/l10n -- "flutter gen-l10n" clean: hooks: post: | @@ -75,10 +72,11 @@ scripts: flutter: true dirExists: [lib, test] + # ファイル除外機能に関するissue https://github.com/dart-lang/dart_style/issues/864 format:ci: run: | melos exec -- \ - dart format --set-exit-if-changed lib test + dart format --set-exit-if-changed $(find . -name "*.dart" -not \( -name "*.*freezed.dart" -o -name "*.*g.dart" -o -name "*.gen.dart" -o -wholename "./.dart_tool/*" \) ) description: Run format. packageFilters: flutter: true diff --git a/packages/flutter_app/README.md b/packages/flutter_app/README.md index ce565419..7a9ba03b 100644 --- a/packages/flutter_app/README.md +++ b/packages/flutter_app/README.md @@ -34,8 +34,27 @@ Required only `--release` mode. ## How to use ### Localizations +JSONファイルを作成 +```json +{ + "hello": "Hello $name", + "save": "Save", + "login": { + "success": "Logged in successfully", + "fail": "Logged in failed" + } +} +``` +Dartファイルを生成 +```shell +dart run slang +``` + +生成されたDartファイルをimportして使用 ```dart -final l10n = L10n.of(context); +import '../../../gen/strings.g.dart'; + +final t = Translations.of(context); ``` ## FlutterFire Configure diff --git a/packages/flutter_app/analysis_options.yaml b/packages/flutter_app/analysis_options.yaml index f48362f5..f3ebe017 100644 --- a/packages/flutter_app/analysis_options.yaml +++ b/packages/flutter_app/analysis_options.yaml @@ -3,6 +3,5 @@ analyzer: plugins: - custom_lint exclude: - - "**/l10n/*.dart" - "**/gen/*.dart" - "**/firebase_options_*.dart" diff --git a/packages/flutter_app/l10n.yaml b/packages/flutter_app/l10n.yaml deleted file mode 100644 index eff7e600..00000000 --- a/packages/flutter_app/l10n.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# https://docs.flutter.dev/development/accessibility-and-localization/internationalization -arb-dir: lib/l10n -template-arb-file: app_en.arb -output-localization-file: app_localizations.dart diff --git a/packages/flutter_app/lib/features/theme_selector/theme_mode_ext.dart b/packages/flutter_app/lib/features/theme_selector/theme_mode_ext.dart index 7c1fc242..8638d1a2 100644 --- a/packages/flutter_app/lib/features/theme_selector/theme_mode_ext.dart +++ b/packages/flutter_app/lib/features/theme_selector/theme_mode_ext.dart @@ -1,26 +1,27 @@ import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +import '../../gen/strings.g.dart'; extension ThemeModeExt on ThemeMode { - String title(AppLocalizations l10n) { + String title(Translations t) { switch (this) { case ThemeMode.system: - return l10n.themeModeTitleSystem; + return t.theme.mode.title.system; case ThemeMode.light: - return l10n.themeModeTitleLight; + return t.theme.mode.title.light; case ThemeMode.dark: - return l10n.themeModeTitleDark; + return t.theme.mode.title.dark; } } - String subtitle(AppLocalizations l10n) { + String subtitle(Translations t) { switch (this) { case ThemeMode.system: - return l10n.themeModeSubtitleSystem; + return t.theme.mode.subtitle.system; case ThemeMode.light: - return l10n.themeModeSubtitleLight; + return t.theme.mode.subtitle.light; case ThemeMode.dark: - return l10n.themeModeSubtitleDark; + return t.theme.mode.subtitle.dark; } } diff --git a/packages/flutter_app/lib/flutter_app.dart b/packages/flutter_app/lib/flutter_app.dart index 95dc9415..83a78255 100644 --- a/packages/flutter_app/lib/flutter_app.dart +++ b/packages/flutter_app/lib/flutter_app.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:themes/themes.dart'; import 'features/theme_selector/theme_selector.dart'; +import 'gen/strings.g.dart'; import 'router/router.dart'; -import 'util/localizer/localizer.dart'; import 'util/providers/scaffold_messenger_key_provider.dart'; class FlutterApp extends ConsumerWidget { @@ -17,12 +17,12 @@ class FlutterApp extends ConsumerWidget { return MaterialApp.router( routerConfig: router, - onGenerateTitle: (context) => L10n.of(context).title, + onGenerateTitle: (context) => Translations.of(context).title, theme: appLightThemeData, darkTheme: appDarkThemeData, themeMode: ref.watch(themeSelectorProvider), - localizationsDelegates: AppLocalizations.localizationsDelegates, - supportedLocales: AppLocalizations.supportedLocales, + localizationsDelegates: GlobalMaterialLocalizations.delegates, + supportedLocales: AppLocaleUtils.supportedLocales, scaffoldMessengerKey: ref.watch(scaffoldMessengerKeyProvider), ); } diff --git a/packages/flutter_app/lib/gen/strings.g.dart b/packages/flutter_app/lib/gen/strings.g.dart new file mode 100644 index 00000000..ef4e1241 --- /dev/null +++ b/packages/flutter_app/lib/gen/strings.g.dart @@ -0,0 +1,940 @@ +/// Generated file. Do not edit. +/// +/// Original: lib/i18n +/// To regenerate, run: `dart run slang` +/// +/// Locales: 2 +/// Strings: 52 (26 per locale) +/// +/// Built on 2023-12-14 at 08:17 UTC + +// coverage:ignore-file +// ignore_for_file: type=lint + +import 'package:flutter/widgets.dart'; +import 'package:slang/builder/model/node.dart'; +import 'package:slang_flutter/slang_flutter.dart'; +export 'package:slang_flutter/slang_flutter.dart'; + +const AppLocale _baseLocale = AppLocale.ja; + +/// Supported locales, see extension methods below. +/// +/// Usage: +/// - LocaleSettings.setLocale(AppLocale.ja) // set locale +/// - Locale locale = AppLocale.ja.flutterLocale // get flutter locale from enum +/// - if (LocaleSettings.currentLocale == AppLocale.ja) // locale check +enum AppLocale with BaseAppLocale { + ja(languageCode: 'ja', build: Translations.build), + en(languageCode: 'en', build: _StringsEn.build); + + const AppLocale({required this.languageCode, this.scriptCode, this.countryCode, required this.build}); // ignore: unused_element + + @override final String languageCode; + @override final String? scriptCode; + @override final String? countryCode; + @override final TranslationBuilder build; + + /// Gets current instance managed by [LocaleSettings]. + Translations get translations => LocaleSettings.instance.translationMap[this]!; +} + +/// Method A: Simple +/// +/// No rebuild after locale change. +/// Translation happens during initialization of the widget (call of t). +/// Configurable via 'translate_var'. +/// +/// Usage: +/// String a = t.someKey.anotherKey; +/// String b = t['someKey.anotherKey']; // Only for edge cases! +Translations get t => LocaleSettings.instance.currentTranslations; + +/// Method B: Advanced +/// +/// All widgets using this method will trigger a rebuild when locale changes. +/// Use this if you have e.g. a settings page where the user can select the locale during runtime. +/// +/// Step 1: +/// wrap your App with +/// TranslationProvider( +/// child: MyApp() +/// ); +/// +/// Step 2: +/// final t = Translations.of(context); // Get t variable. +/// String a = t.someKey.anotherKey; // Use t variable. +/// String b = t['someKey.anotherKey']; // Only for edge cases! +class TranslationProvider extends BaseTranslationProvider { + TranslationProvider({required super.child}) : super(settings: LocaleSettings.instance); + + static InheritedLocaleData of(BuildContext context) => InheritedLocaleData.of(context); +} + +/// Method B shorthand via [BuildContext] extension method. +/// Configurable via 'translate_var'. +/// +/// Usage (e.g. in a widget's build method): +/// context.t.someKey.anotherKey +extension BuildContextTranslationsExtension on BuildContext { + Translations get t => TranslationProvider.of(this).translations; +} + +/// Manages all translation instances and the current locale +class LocaleSettings extends BaseFlutterLocaleSettings { + LocaleSettings._() : super(utils: AppLocaleUtils.instance); + + static final instance = LocaleSettings._(); + + // static aliases (checkout base methods for documentation) + static AppLocale get currentLocale => instance.currentLocale; + static Stream getLocaleStream() => instance.getLocaleStream(); + static AppLocale setLocale(AppLocale locale, {bool? listenToDeviceLocale = false}) => instance.setLocale(locale, listenToDeviceLocale: listenToDeviceLocale); + static AppLocale setLocaleRaw(String rawLocale, {bool? listenToDeviceLocale = false}) => instance.setLocaleRaw(rawLocale, listenToDeviceLocale: listenToDeviceLocale); + static AppLocale useDeviceLocale() => instance.useDeviceLocale(); + @Deprecated('Use [AppLocaleUtils.supportedLocales]') static List get supportedLocales => instance.supportedLocales; + @Deprecated('Use [AppLocaleUtils.supportedLocalesRaw]') static List get supportedLocalesRaw => instance.supportedLocalesRaw; + static void setPluralResolver({String? language, AppLocale? locale, PluralResolver? cardinalResolver, PluralResolver? ordinalResolver}) => instance.setPluralResolver( + language: language, + locale: locale, + cardinalResolver: cardinalResolver, + ordinalResolver: ordinalResolver, + ); +} + +/// Provides utility functions without any side effects. +class AppLocaleUtils extends BaseAppLocaleUtils { + AppLocaleUtils._() : super(baseLocale: _baseLocale, locales: AppLocale.values); + + static final instance = AppLocaleUtils._(); + + // static aliases (checkout base methods for documentation) + static AppLocale parse(String rawLocale) => instance.parse(rawLocale); + static AppLocale parseLocaleParts({required String languageCode, String? scriptCode, String? countryCode}) => instance.parseLocaleParts(languageCode: languageCode, scriptCode: scriptCode, countryCode: countryCode); + static AppLocale findDeviceLocale() => instance.findDeviceLocale(); + static List get supportedLocales => instance.supportedLocales; + static List get supportedLocalesRaw => instance.supportedLocalesRaw; +} + +// translations + +// Path: +class Translations implements BaseTranslations { + /// Returns the current translations of the given [context]. + /// + /// Usage: + /// final t = Translations.of(context); + static Translations of(BuildContext context) => InheritedLocaleData.of(context).translations; + + /// You can call this constructor and build your own translation instance of this locale. + /// Constructing via the enum [AppLocale.build] is preferred. + Translations.build({Map? overrides, PluralResolver? cardinalResolver, PluralResolver? ordinalResolver}) + : assert(overrides == null, 'Set "translation_overrides: true" in order to enable this feature.'), + $meta = TranslationMetadata( + locale: AppLocale.ja, + overrides: overrides ?? {}, + cardinalResolver: cardinalResolver, + ordinalResolver: ordinalResolver, + ) { + $meta.setFlatMapFunction(_flatMapFunction); + } + + /// Metadata for the translations of . + @override final TranslationMetadata $meta; + + /// Access flat map + dynamic operator[](String key) => $meta.getTranslation(key); + + late final Translations _root = this; // ignore: unused_field + + // Translations + String get title => 'Flutter App Template'; + late final _StringsAccountPageJa accountPage = _StringsAccountPageJa._(_root); + late final _StringsAppInfoJa appInfo = _StringsAppInfoJa._(_root); + late final _StringsAuthorJa author = _StringsAuthorJa._(_root); + late final _StringsButtonJa button = _StringsButtonJa._(_root); + late final _StringsHomePageJa homePage = _StringsHomePageJa._(_root); + late final _StringsNotFoundPageJa notFoundPage = _StringsNotFoundPageJa._(_root); + late final _StringsPinkieMewPageJa pinkieMewPage = _StringsPinkieMewPageJa._(_root); + late final _StringsSettingsPageJa settingsPage = _StringsSettingsPageJa._(_root); + late final _StringsThemeJa theme = _StringsThemeJa._(_root); + late final _StringsTopLevelTabJa topLevelTab = _StringsTopLevelTabJa._(_root); +} + +// Path: accountPage +class _StringsAccountPageJa { + _StringsAccountPageJa._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + late final _StringsAccountPageAppBarJa appBar = _StringsAccountPageAppBarJa._(_root); +} + +// Path: appInfo +class _StringsAppInfoJa { + _StringsAppInfoJa._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + late final _StringsAppInfoAppBarJa appBar = _StringsAppInfoAppBarJa._(_root); +} + +// Path: author +class _StringsAuthorJa { + _StringsAuthorJa._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + String get name => 'Altive株式会社'; +} + +// Path: button +class _StringsButtonJa { + _StringsButtonJa._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + String get cancel => 'キャンセル'; + late final _StringsButtonSignJa sign = _StringsButtonSignJa._(_root); +} + +// Path: homePage +class _StringsHomePageJa { + _StringsHomePageJa._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + late final _StringsHomePageAppBarJa appBar = _StringsHomePageAppBarJa._(_root); + late final _StringsHomePageListJa list = _StringsHomePageListJa._(_root); +} + +// Path: notFoundPage +class _StringsNotFoundPageJa { + _StringsNotFoundPageJa._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + String get title => '404 Not Found'; + late final _StringsNotFoundPageHeaderJa header = _StringsNotFoundPageHeaderJa._(_root); + String get description => 'is not found.'; + late final _StringsNotFoundPageBackButtonJa backButton = _StringsNotFoundPageBackButtonJa._(_root); +} + +// Path: pinkieMewPage +class _StringsPinkieMewPageJa { + _StringsPinkieMewPageJa._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + String get title => 'Pinkie and Mew'; +} + +// Path: settingsPage +class _StringsSettingsPageJa { + _StringsSettingsPageJa._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + late final _StringsSettingsPageAppBarJa appBar = _StringsSettingsPageAppBarJa._(_root); + late final _StringsSettingsPageListJa list = _StringsSettingsPageListJa._(_root); +} + +// Path: theme +class _StringsThemeJa { + _StringsThemeJa._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + late final _StringsThemeModeJa mode = _StringsThemeModeJa._(_root); + late final _StringsThemeSelectionJa selection = _StringsThemeSelectionJa._(_root); +} + +// Path: topLevelTab +class _StringsTopLevelTabJa { + _StringsTopLevelTabJa._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + late final _StringsTopLevelTabHomeJa home = _StringsTopLevelTabHomeJa._(_root); + late final _StringsTopLevelTabRiverpodJa riverpod = _StringsTopLevelTabRiverpodJa._(_root); + late final _StringsTopLevelTabSettingsJa settings = _StringsTopLevelTabSettingsJa._(_root); +} + +// Path: accountPage.appBar +class _StringsAccountPageAppBarJa { + _StringsAccountPageAppBarJa._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + String get title => 'アカウント'; +} + +// Path: appInfo.appBar +class _StringsAppInfoAppBarJa { + _StringsAppInfoAppBarJa._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + String get title => 'アプリ情報'; +} + +// Path: button.sign +class _StringsButtonSignJa { + _StringsButtonSignJa._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + String get out => 'サインアウト'; +} + +// Path: homePage.appBar +class _StringsHomePageAppBarJa { + _StringsHomePageAppBarJa._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + String get title => 'Home'; +} + +// Path: homePage.list +class _StringsHomePageListJa { + _StringsHomePageListJa._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + late final _StringsHomePageListAppInfoJa appInfo = _StringsHomePageListAppInfoJa._(_root); +} + +// Path: notFoundPage.header +class _StringsNotFoundPageHeaderJa { + _StringsNotFoundPageHeaderJa._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + String get label => 'ごめんなさい🙏'; +} + +// Path: notFoundPage.backButton +class _StringsNotFoundPageBackButtonJa { + _StringsNotFoundPageBackButtonJa._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + String get label => 'is not found.'; +} + +// Path: settingsPage.appBar +class _StringsSettingsPageAppBarJa { + _StringsSettingsPageAppBarJa._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + String get title => '設定'; +} + +// Path: settingsPage.list +class _StringsSettingsPageListJa { + _StringsSettingsPageListJa._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + late final _StringsSettingsPageListThemeSelectorJa themeSelector = _StringsSettingsPageListThemeSelectorJa._(_root); + late final _StringsSettingsPageListAccountJa account = _StringsSettingsPageListAccountJa._(_root); +} + +// Path: theme.mode +class _StringsThemeModeJa { + _StringsThemeModeJa._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + late final _StringsThemeModeTitleJa title = _StringsThemeModeTitleJa._(_root); + late final _StringsThemeModeSubtitleJa subtitle = _StringsThemeModeSubtitleJa._(_root); +} + +// Path: theme.selection +class _StringsThemeSelectionJa { + _StringsThemeSelectionJa._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + late final _StringsThemeSelectionPageJa page = _StringsThemeSelectionPageJa._(_root); +} + +// Path: topLevelTab.home +class _StringsTopLevelTabHomeJa { + _StringsTopLevelTabHomeJa._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + String get label => 'Home'; +} + +// Path: topLevelTab.riverpod +class _StringsTopLevelTabRiverpodJa { + _StringsTopLevelTabRiverpodJa._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + late final _StringsTopLevelTabRiverpodExampleJa example = _StringsTopLevelTabRiverpodExampleJa._(_root); +} + +// Path: topLevelTab.settings +class _StringsTopLevelTabSettingsJa { + _StringsTopLevelTabSettingsJa._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + String get label => 'Settings'; +} + +// Path: homePage.list.appInfo +class _StringsHomePageListAppInfoJa { + _StringsHomePageListAppInfoJa._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + String get label => 'アプリ情報'; +} + +// Path: settingsPage.list.themeSelector +class _StringsSettingsPageListThemeSelectorJa { + _StringsSettingsPageListThemeSelectorJa._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + String get label => 'テーマ選択'; +} + +// Path: settingsPage.list.account +class _StringsSettingsPageListAccountJa { + _StringsSettingsPageListAccountJa._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + String get label => 'アカウント'; +} + +// Path: theme.mode.title +class _StringsThemeModeTitleJa { + _StringsThemeModeTitleJa._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + String get system => 'System'; + String get light => 'Light theme'; + String get dark => 'Dark theme'; +} + +// Path: theme.mode.subtitle +class _StringsThemeModeSubtitleJa { + _StringsThemeModeSubtitleJa._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + String get system => 'システム設定に従う'; + String get light => '明るいテーマ'; + String get dark => '暗いテーマ'; +} + +// Path: theme.selection.page +class _StringsThemeSelectionPageJa { + _StringsThemeSelectionPageJa._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + late final _StringsThemeSelectionPageAppBarJa appBar = _StringsThemeSelectionPageAppBarJa._(_root); +} + +// Path: topLevelTab.riverpod.example +class _StringsTopLevelTabRiverpodExampleJa { + _StringsTopLevelTabRiverpodExampleJa._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + String get label => 'Riverpod'; +} + +// Path: theme.selection.page.appBar +class _StringsThemeSelectionPageAppBarJa { + _StringsThemeSelectionPageAppBarJa._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + String get title => 'テーマ選択'; +} + +// Path: +class _StringsEn implements Translations { + /// You can call this constructor and build your own translation instance of this locale. + /// Constructing via the enum [AppLocale.build] is preferred. + _StringsEn.build({Map? overrides, PluralResolver? cardinalResolver, PluralResolver? ordinalResolver}) + : assert(overrides == null, 'Set "translation_overrides: true" in order to enable this feature.'), + $meta = TranslationMetadata( + locale: AppLocale.en, + overrides: overrides ?? {}, + cardinalResolver: cardinalResolver, + ordinalResolver: ordinalResolver, + ) { + $meta.setFlatMapFunction(_flatMapFunction); + } + + /// Metadata for the translations of . + @override final TranslationMetadata $meta; + + /// Access flat map + @override dynamic operator[](String key) => $meta.getTranslation(key); + + @override late final _StringsEn _root = this; // ignore: unused_field + + // Translations + @override String get title => 'Flutter App Template'; + @override late final _StringsAccountPageEn accountPage = _StringsAccountPageEn._(_root); + @override late final _StringsAppInfoEn appInfo = _StringsAppInfoEn._(_root); + @override late final _StringsAuthorEn author = _StringsAuthorEn._(_root); + @override late final _StringsButtonEn button = _StringsButtonEn._(_root); + @override late final _StringsHomePageEn homePage = _StringsHomePageEn._(_root); + @override late final _StringsNotFoundPageEn notFoundPage = _StringsNotFoundPageEn._(_root); + @override late final _StringsPinkieMewPageEn pinkieMewPage = _StringsPinkieMewPageEn._(_root); + @override late final _StringsSettingsPageEn settingsPage = _StringsSettingsPageEn._(_root); + @override late final _StringsThemeEn theme = _StringsThemeEn._(_root); + @override late final _StringsTopLevelTabEn topLevelTab = _StringsTopLevelTabEn._(_root); +} + +// Path: accountPage +class _StringsAccountPageEn implements _StringsAccountPageJa { + _StringsAccountPageEn._(this._root); + + @override final _StringsEn _root; // ignore: unused_field + + // Translations + @override late final _StringsAccountPageAppBarEn appBar = _StringsAccountPageAppBarEn._(_root); +} + +// Path: appInfo +class _StringsAppInfoEn implements _StringsAppInfoJa { + _StringsAppInfoEn._(this._root); + + @override final _StringsEn _root; // ignore: unused_field + + // Translations + @override late final _StringsAppInfoAppBarEn appBar = _StringsAppInfoAppBarEn._(_root); +} + +// Path: author +class _StringsAuthorEn implements _StringsAuthorJa { + _StringsAuthorEn._(this._root); + + @override final _StringsEn _root; // ignore: unused_field + + // Translations + @override String get name => 'Altive inc'; +} + +// Path: button +class _StringsButtonEn implements _StringsButtonJa { + _StringsButtonEn._(this._root); + + @override final _StringsEn _root; // ignore: unused_field + + // Translations + @override String get cancel => 'Cancel'; + @override late final _StringsButtonSignEn sign = _StringsButtonSignEn._(_root); +} + +// Path: homePage +class _StringsHomePageEn implements _StringsHomePageJa { + _StringsHomePageEn._(this._root); + + @override final _StringsEn _root; // ignore: unused_field + + // Translations + @override late final _StringsHomePageAppBarEn appBar = _StringsHomePageAppBarEn._(_root); + @override late final _StringsHomePageListEn list = _StringsHomePageListEn._(_root); +} + +// Path: notFoundPage +class _StringsNotFoundPageEn implements _StringsNotFoundPageJa { + _StringsNotFoundPageEn._(this._root); + + @override final _StringsEn _root; // ignore: unused_field + + // Translations + @override String get title => '404 Not Found'; + @override late final _StringsNotFoundPageHeaderEn header = _StringsNotFoundPageHeaderEn._(_root); + @override String get description => 'is not found.'; + @override late final _StringsNotFoundPageBackButtonEn backButton = _StringsNotFoundPageBackButtonEn._(_root); +} + +// Path: pinkieMewPage +class _StringsPinkieMewPageEn implements _StringsPinkieMewPageJa { + _StringsPinkieMewPageEn._(this._root); + + @override final _StringsEn _root; // ignore: unused_field + + // Translations + @override String get title => 'Pinkie and Mew'; +} + +// Path: settingsPage +class _StringsSettingsPageEn implements _StringsSettingsPageJa { + _StringsSettingsPageEn._(this._root); + + @override final _StringsEn _root; // ignore: unused_field + + // Translations + @override late final _StringsSettingsPageAppBarEn appBar = _StringsSettingsPageAppBarEn._(_root); + @override late final _StringsSettingsPageListEn list = _StringsSettingsPageListEn._(_root); +} + +// Path: theme +class _StringsThemeEn implements _StringsThemeJa { + _StringsThemeEn._(this._root); + + @override final _StringsEn _root; // ignore: unused_field + + // Translations + @override late final _StringsThemeModeEn mode = _StringsThemeModeEn._(_root); + @override late final _StringsThemeSelectionEn selection = _StringsThemeSelectionEn._(_root); +} + +// Path: topLevelTab +class _StringsTopLevelTabEn implements _StringsTopLevelTabJa { + _StringsTopLevelTabEn._(this._root); + + @override final _StringsEn _root; // ignore: unused_field + + // Translations + @override late final _StringsTopLevelTabHomeEn home = _StringsTopLevelTabHomeEn._(_root); + @override late final _StringsTopLevelTabRiverpodEn riverpod = _StringsTopLevelTabRiverpodEn._(_root); + @override late final _StringsTopLevelTabSettingsEn settings = _StringsTopLevelTabSettingsEn._(_root); +} + +// Path: accountPage.appBar +class _StringsAccountPageAppBarEn implements _StringsAccountPageAppBarJa { + _StringsAccountPageAppBarEn._(this._root); + + @override final _StringsEn _root; // ignore: unused_field + + // Translations + @override String get title => 'Account'; +} + +// Path: appInfo.appBar +class _StringsAppInfoAppBarEn implements _StringsAppInfoAppBarJa { + _StringsAppInfoAppBarEn._(this._root); + + @override final _StringsEn _root; // ignore: unused_field + + // Translations + @override String get title => 'App information'; +} + +// Path: button.sign +class _StringsButtonSignEn implements _StringsButtonSignJa { + _StringsButtonSignEn._(this._root); + + @override final _StringsEn _root; // ignore: unused_field + + // Translations + @override String get out => 'Sign out'; +} + +// Path: homePage.appBar +class _StringsHomePageAppBarEn implements _StringsHomePageAppBarJa { + _StringsHomePageAppBarEn._(this._root); + + @override final _StringsEn _root; // ignore: unused_field + + // Translations + @override String get title => 'Home'; +} + +// Path: homePage.list +class _StringsHomePageListEn implements _StringsHomePageListJa { + _StringsHomePageListEn._(this._root); + + @override final _StringsEn _root; // ignore: unused_field + + // Translations + @override late final _StringsHomePageListAppInfoEn appInfo = _StringsHomePageListAppInfoEn._(_root); +} + +// Path: notFoundPage.header +class _StringsNotFoundPageHeaderEn implements _StringsNotFoundPageHeaderJa { + _StringsNotFoundPageHeaderEn._(this._root); + + @override final _StringsEn _root; // ignore: unused_field + + // Translations + @override String get label => 'Sorry...'; +} + +// Path: notFoundPage.backButton +class _StringsNotFoundPageBackButtonEn implements _StringsNotFoundPageBackButtonJa { + _StringsNotFoundPageBackButtonEn._(this._root); + + @override final _StringsEn _root; // ignore: unused_field + + // Translations + @override String get label => 'is not found.'; +} + +// Path: settingsPage.appBar +class _StringsSettingsPageAppBarEn implements _StringsSettingsPageAppBarJa { + _StringsSettingsPageAppBarEn._(this._root); + + @override final _StringsEn _root; // ignore: unused_field + + // Translations + @override String get title => 'Settings'; +} + +// Path: settingsPage.list +class _StringsSettingsPageListEn implements _StringsSettingsPageListJa { + _StringsSettingsPageListEn._(this._root); + + @override final _StringsEn _root; // ignore: unused_field + + // Translations + @override late final _StringsSettingsPageListThemeSelectorEn themeSelector = _StringsSettingsPageListThemeSelectorEn._(_root); + @override late final _StringsSettingsPageListAccountEn account = _StringsSettingsPageListAccountEn._(_root); +} + +// Path: theme.mode +class _StringsThemeModeEn implements _StringsThemeModeJa { + _StringsThemeModeEn._(this._root); + + @override final _StringsEn _root; // ignore: unused_field + + // Translations + @override late final _StringsThemeModeTitleEn title = _StringsThemeModeTitleEn._(_root); + @override late final _StringsThemeModeSubtitleEn subtitle = _StringsThemeModeSubtitleEn._(_root); +} + +// Path: theme.selection +class _StringsThemeSelectionEn implements _StringsThemeSelectionJa { + _StringsThemeSelectionEn._(this._root); + + @override final _StringsEn _root; // ignore: unused_field + + // Translations + @override late final _StringsThemeSelectionPageEn page = _StringsThemeSelectionPageEn._(_root); +} + +// Path: topLevelTab.home +class _StringsTopLevelTabHomeEn implements _StringsTopLevelTabHomeJa { + _StringsTopLevelTabHomeEn._(this._root); + + @override final _StringsEn _root; // ignore: unused_field + + // Translations + @override String get label => 'Home'; +} + +// Path: topLevelTab.riverpod +class _StringsTopLevelTabRiverpodEn implements _StringsTopLevelTabRiverpodJa { + _StringsTopLevelTabRiverpodEn._(this._root); + + @override final _StringsEn _root; // ignore: unused_field + + // Translations + @override late final _StringsTopLevelTabRiverpodExampleEn example = _StringsTopLevelTabRiverpodExampleEn._(_root); +} + +// Path: topLevelTab.settings +class _StringsTopLevelTabSettingsEn implements _StringsTopLevelTabSettingsJa { + _StringsTopLevelTabSettingsEn._(this._root); + + @override final _StringsEn _root; // ignore: unused_field + + // Translations + @override String get label => 'Settings'; +} + +// Path: homePage.list.appInfo +class _StringsHomePageListAppInfoEn implements _StringsHomePageListAppInfoJa { + _StringsHomePageListAppInfoEn._(this._root); + + @override final _StringsEn _root; // ignore: unused_field + + // Translations + @override String get label => 'App Info'; +} + +// Path: settingsPage.list.themeSelector +class _StringsSettingsPageListThemeSelectorEn implements _StringsSettingsPageListThemeSelectorJa { + _StringsSettingsPageListThemeSelectorEn._(this._root); + + @override final _StringsEn _root; // ignore: unused_field + + // Translations + @override String get label => 'Theme Selector'; +} + +// Path: settingsPage.list.account +class _StringsSettingsPageListAccountEn implements _StringsSettingsPageListAccountJa { + _StringsSettingsPageListAccountEn._(this._root); + + @override final _StringsEn _root; // ignore: unused_field + + // Translations + @override String get label => 'Account'; +} + +// Path: theme.mode.title +class _StringsThemeModeTitleEn implements _StringsThemeModeTitleJa { + _StringsThemeModeTitleEn._(this._root); + + @override final _StringsEn _root; // ignore: unused_field + + // Translations + @override String get system => 'System'; + @override String get light => 'Light theme'; + @override String get dark => 'Dark theme'; +} + +// Path: theme.mode.subtitle +class _StringsThemeModeSubtitleEn implements _StringsThemeModeSubtitleJa { + _StringsThemeModeSubtitleEn._(this._root); + + @override final _StringsEn _root; // ignore: unused_field + + // Translations + @override String get system => 'Follows the system settings of the device'; + @override String get light => 'Light'; + @override String get dark => 'Dark'; +} + +// Path: theme.selection.page +class _StringsThemeSelectionPageEn implements _StringsThemeSelectionPageJa { + _StringsThemeSelectionPageEn._(this._root); + + @override final _StringsEn _root; // ignore: unused_field + + // Translations + @override late final _StringsThemeSelectionPageAppBarEn appBar = _StringsThemeSelectionPageAppBarEn._(_root); +} + +// Path: topLevelTab.riverpod.example +class _StringsTopLevelTabRiverpodExampleEn implements _StringsTopLevelTabRiverpodExampleJa { + _StringsTopLevelTabRiverpodExampleEn._(this._root); + + @override final _StringsEn _root; // ignore: unused_field + + // Translations + @override String get label => 'Riverpod'; +} + +// Path: theme.selection.page.appBar +class _StringsThemeSelectionPageAppBarEn implements _StringsThemeSelectionPageAppBarJa { + _StringsThemeSelectionPageAppBarEn._(this._root); + + @override final _StringsEn _root; // ignore: unused_field + + // Translations + @override String get title => 'Theme Selector'; +} + +/// Flat map(s) containing all translations. +/// Only for edge cases! For simple maps, use the map function of this library. + +extension on Translations { + dynamic _flatMapFunction(String path) { + switch (path) { + case 'title': return 'Flutter App Template'; + case 'accountPage.appBar.title': return 'アカウント'; + case 'appInfo.appBar.title': return 'アプリ情報'; + case 'author.name': return 'Altive株式会社'; + case 'button.cancel': return 'キャンセル'; + case 'button.sign.out': return 'サインアウト'; + case 'homePage.appBar.title': return 'Home'; + case 'homePage.list.appInfo.label': return 'アプリ情報'; + case 'notFoundPage.title': return '404 Not Found'; + case 'notFoundPage.header.label': return 'ごめんなさい🙏'; + case 'notFoundPage.description': return 'is not found.'; + case 'notFoundPage.backButton.label': return 'is not found.'; + case 'pinkieMewPage.title': return 'Pinkie and Mew'; + case 'settingsPage.appBar.title': return '設定'; + case 'settingsPage.list.themeSelector.label': return 'テーマ選択'; + case 'settingsPage.list.account.label': return 'アカウント'; + case 'theme.mode.title.system': return 'System'; + case 'theme.mode.title.light': return 'Light theme'; + case 'theme.mode.title.dark': return 'Dark theme'; + case 'theme.mode.subtitle.system': return 'システム設定に従う'; + case 'theme.mode.subtitle.light': return '明るいテーマ'; + case 'theme.mode.subtitle.dark': return '暗いテーマ'; + case 'theme.selection.page.appBar.title': return 'テーマ選択'; + case 'topLevelTab.home.label': return 'Home'; + case 'topLevelTab.riverpod.example.label': return 'Riverpod'; + case 'topLevelTab.settings.label': return 'Settings'; + default: return null; + } + } +} + +extension on _StringsEn { + dynamic _flatMapFunction(String path) { + switch (path) { + case 'title': return 'Flutter App Template'; + case 'accountPage.appBar.title': return 'Account'; + case 'appInfo.appBar.title': return 'App information'; + case 'author.name': return 'Altive inc'; + case 'button.cancel': return 'Cancel'; + case 'button.sign.out': return 'Sign out'; + case 'homePage.appBar.title': return 'Home'; + case 'homePage.list.appInfo.label': return 'App Info'; + case 'notFoundPage.title': return '404 Not Found'; + case 'notFoundPage.header.label': return 'Sorry...'; + case 'notFoundPage.description': return 'is not found.'; + case 'notFoundPage.backButton.label': return 'is not found.'; + case 'pinkieMewPage.title': return 'Pinkie and Mew'; + case 'settingsPage.appBar.title': return 'Settings'; + case 'settingsPage.list.themeSelector.label': return 'Theme Selector'; + case 'settingsPage.list.account.label': return 'Account'; + case 'theme.mode.title.system': return 'System'; + case 'theme.mode.title.light': return 'Light theme'; + case 'theme.mode.title.dark': return 'Dark theme'; + case 'theme.mode.subtitle.system': return 'Follows the system settings of the device'; + case 'theme.mode.subtitle.light': return 'Light'; + case 'theme.mode.subtitle.dark': return 'Dark'; + case 'theme.selection.page.appBar.title': return 'Theme Selector'; + case 'topLevelTab.home.label': return 'Home'; + case 'topLevelTab.riverpod.example.label': return 'Riverpod'; + case 'topLevelTab.settings.label': return 'Settings'; + default: return null; + } + } +} diff --git a/packages/flutter_app/lib/i18n/app_en.json b/packages/flutter_app/lib/i18n/app_en.json new file mode 100644 index 00000000..be007029 --- /dev/null +++ b/packages/flutter_app/lib/i18n/app_en.json @@ -0,0 +1,93 @@ +{ + "@@locale": "en", + "title": "Flutter App Template", + "accountPage": { + "appBar": { + "title": "Account" + } + }, + "appInfo": { + "appBar": { + "title": "App information" + } + }, + "author": { + "name": "Altive inc" + }, + "button": { + "cancel": "Cancel", + "sign": { + "out": "Sign out" + } + }, + "homePage": { + "appBar": { + "title": "Home" + }, + "list": { + "appInfo": { + "label": "App Info" + } + } + }, + "notFoundPage": { + "title": "404 Not Found", + "header": { + "label": "Sorry..." + }, + "description": "is not found.", + "backButton": { + "label": "is not found." + } + }, + "pinkieMewPage": { + "title": "Pinkie and Mew" + }, + "settingsPage": { + "appBar": { + "title": "Settings" + }, + "list": { + "themeSelector": { + "label": "Theme Selector" + }, + "account": { + "label": "Account" + } + } + }, + "theme": { + "mode": { + "title": { + "system": "System", + "light": "Light theme", + "dark": "Dark theme" + }, + "subtitle": { + "system": "Follows the system settings of the device", + "light": "Light", + "dark": "Dark" + } + }, + "selection": { + "page": { + "appBar": { + "title": "Theme Selector" + } + } + } + }, + "topLevelTab": { + "home": { + "label": "Home" + }, + "riverpod": { + "example": { + "label": "Riverpod" + } + }, + "settings": { + "label": "Settings" + } + } +} \ No newline at end of file diff --git a/packages/flutter_app/lib/i18n/app_ja.json b/packages/flutter_app/lib/i18n/app_ja.json new file mode 100644 index 00000000..1c769726 --- /dev/null +++ b/packages/flutter_app/lib/i18n/app_ja.json @@ -0,0 +1,93 @@ +{ + "@@locale": "ja", + "title": "Flutter App Template", + "accountPage": { + "appBar": { + "title": "アカウント" + } + }, + "appInfo": { + "appBar": { + "title": "アプリ情報" + } + }, + "author": { + "name": "Altive株式会社" + }, + "button": { + "cancel": "キャンセル", + "sign": { + "out": "サインアウト" + } + }, + "homePage": { + "appBar": { + "title": "Home" + }, + "list": { + "appInfo": { + "label": "アプリ情報" + } + } + }, + "notFoundPage": { + "title": "404 Not Found", + "header": { + "label": "ごめんなさい🙏" + }, + "description": "is not found.", + "backButton": { + "label": "is not found." + } + }, + "pinkieMewPage": { + "title": "Pinkie and Mew" + }, + "settingsPage": { + "appBar": { + "title": "設定" + }, + "list": { + "themeSelector": { + "label": "テーマ選択" + }, + "account": { + "label": "アカウント" + } + } + }, + "theme": { + "mode": { + "title": { + "system": "System", + "light": "Light theme", + "dark": "Dark theme" + }, + "subtitle": { + "system": "システム設定に従う", + "light": "明るいテーマ", + "dark": "暗いテーマ" + } + }, + "selection": { + "page": { + "appBar": { + "title": "テーマ選択" + } + } + } + }, + "topLevelTab": { + "home": { + "label": "Home" + }, + "riverpod": { + "example": { + "label": "Riverpod" + } + }, + "settings": { + "label": "Settings" + } + } +} \ No newline at end of file diff --git a/packages/flutter_app/lib/l10n/app_en.arb b/packages/flutter_app/lib/l10n/app_en.arb deleted file mode 100644 index e78c5655..00000000 --- a/packages/flutter_app/lib/l10n/app_en.arb +++ /dev/null @@ -1,57 +0,0 @@ -{ - "@@locale": "en", - "title": "Flutter App Template", - "accountPageAppBarTitle": "Account", - "appInfoAppBarTitle": "App information", - "authorName": "Altive inc", - "buttonAdd": "Add", - "buttonCancel": "Cancel", - "buttonDelete": "Delete", - "buttonDone": "Done", - "buttonOK": "OK", - "buttonSignOut": "Sign out", - "buttonUnlink": "Unlink", - "buttonUpdate": "Update", - "buttonRemovePhoto": "Remove photo", - "buttonTakeNewPhoto": "Take new photo", - "buttonSelectNewPhoto": "Select new Photo", - "homePageAppBarTitle": "Home", - "homePageListAppInfoLabel": "App Info", - "homePageListRiverpodExampleLabel": "Riverpod Examples", - "homePageListTo404Label": "To 404", - "notFoundPageTitle": "404 Not Found", - "notFoundPageHeaderLabel": "Sorry...", - "notFoundPageDescription": "is not found.", - "notFoundPageReturnButtonLabel": "is not found.", - "pinkieMewPageTitle": "Pinkie and Mew", - "settingsPageAppBarTitle": "Settings", - "settingsPageListThemeSelectorLabel": "Theme Selector", - "settingsPageListAccountLabel": "Account", - "signInMethodAnonymous": "Anonymous", - "signInMethodApple": "Apple", - "signInMethodGoogle": "Google", - "signInPageAnonymousButton": "Continue with Guest", - "signInPageAppleButton": "Continue with Apple", - "signInPageGoogleButton": "Continue with Google", - "themeModeTitleSystem": "System", - "themeModeTitleLight": "Light theme", - "themeModeTitleDark": "Dark theme", - "themeModeSubtitleSystem": "Follows the system settings of the device", - "themeModeSubtitleLight": "Light", - "themeModeSubtitleDark": "Dark", - "themeSelectionPageAppBarTitle": "Theme Selector", - "titleChangePhoto": "Change photo", - "topLevelTabHomeLabel": "Home", - "topLevelTabRiverpodExampleLabel": "Riverpod", - "topLevelTabSettingsLabel": "Settings", - "hello": "Hello {userName}!", - "@hello": { - "description": "A message with a single parameter.", - "placeholders": { - "userName": { - "type": "String", - "example": "Bob" - } - } - } -} \ No newline at end of file diff --git a/packages/flutter_app/lib/l10n/app_ja.arb b/packages/flutter_app/lib/l10n/app_ja.arb deleted file mode 100644 index d9c22bc0..00000000 --- a/packages/flutter_app/lib/l10n/app_ja.arb +++ /dev/null @@ -1,48 +0,0 @@ -{ - "@@locale": "ja", - "title": "Flutter App Template", - "accountPageAppBarTitle": "アカウント", - "appInfoAppBarTitle": "アプリ情報", - "authorName": "Altive株式会社", - "buttonAdd": "追加", - "buttonCancel": "キャンセル", - "buttonDelete": "削除", - "buttonDone": "完了", - "buttonOK": "OK", - "buttonSignOut": "サインアウト", - "buttonUnlink": "連携解除", - "buttonUpdate": "更新", - "buttonRemovePhoto": "写真を削除", - "buttonTakeNewPhoto": "カメラで撮影", - "buttonSelectNewPhoto": "写真を選ぶ", - "homePageAppBarTitle": "Home", - "homePageListAppInfoLabel": "アプリ情報", - "homePageListRiverpodExampleLabel": "Riverpod Examples", - "homePageListTo404Label": "To 404", - "notFoundPageTitle": "404 Not Found", - "notFoundPageHeaderLabel": "ごめんなさい🙏", - "notFoundPageDescription": "is not found.", - "notFoundPageReturnButtonLabel": "is not found.", - "pinkieMewPageTitle": "Pinkie and Mew", - "settingsPageAppBarTitle": "設定", - "settingsPageListThemeSelectorLabel": "テーマ選択", - "settingsPageListAccountLabel": "アカウント", - "signInMethodAnonymous": "匿名", - "signInMethodApple": "Apple", - "signInMethodGoogle": "Google", - "signInPageAnonymousButton": "ゲストではじめる", - "signInPageAppleButton": "Appleではじめる", - "signInPageGoogleButton": "Googleではじめる", - "themeModeTitleSystem": "System", - "themeModeTitleLight": "Light theme", - "themeModeTitleDark": "Dark theme", - "themeModeSubtitleSystem": "システム設定に従う", - "themeModeSubtitleLight": "明るいテーマ", - "themeModeSubtitleDark": "暗いテーマ", - "themeSelectionPageAppBarTitle": "テーマ選択", - "titleChangePhoto": "写真を変更", - "topLevelTabHomeLabel": "Home", - "topLevelTabRiverpodExampleLabel": "Riverpod", - "topLevelTabSettingsLabel": "Setting", - "hello": "こんにちは、{userName}!" -} \ No newline at end of file diff --git a/packages/flutter_app/lib/main.dart b/packages/flutter_app/lib/main.dart index 86e964b1..a314b4cf 100644 --- a/packages/flutter_app/lib/main.dart +++ b/packages/flutter_app/lib/main.dart @@ -11,6 +11,7 @@ import 'package:shared_preferences/shared_preferences.dart'; import 'environment/environment.dart'; import 'features/user_device/user_device.dart'; import 'flutter_app.dart'; +import 'gen/strings.g.dart'; import 'package_adaptor/analysis_logger/analysis_logger_provider.dart'; import 'util/logger/provider_logger.dart'; import 'util/providers/providers.dart'; @@ -45,7 +46,9 @@ Future main() async { analysisLoggerProvider.overrideWithValue(analysisLogger), ], observers: [ProviderLogger()], - child: const FlutterApp(), + child: TranslationProvider( + child: const FlutterApp(), + ), ), ); } diff --git a/packages/flutter_app/lib/pages/account/components/account_page_app_bar.dart b/packages/flutter_app/lib/pages/account/components/account_page_app_bar.dart index 66676ce1..257310e6 100644 --- a/packages/flutter_app/lib/pages/account/components/account_page_app_bar.dart +++ b/packages/flutter_app/lib/pages/account/components/account_page_app_bar.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:themes/themes.dart'; -import '../../../util/localizer/localizer.dart'; +import '../../../gen/strings.g.dart'; class AccountPageAppBar extends StatelessWidget implements PreferredSizeWidget { const AccountPageAppBar({super.key}); @@ -11,9 +11,9 @@ class AccountPageAppBar extends StatelessWidget implements PreferredSizeWidget { @override Widget build(BuildContext context) { - final l10n = L10n.of(context); + final t = Translations.of(context); return AppBar( - title: Text(l10n.accountPageAppBarTitle), + title: Text(t.accountPage.appBar.title), ); } } diff --git a/packages/flutter_app/lib/pages/account/components/account_page_body.dart b/packages/flutter_app/lib/pages/account/components/account_page_body.dart index 84b5248b..ba186d7e 100644 --- a/packages/flutter_app/lib/pages/account/components/account_page_body.dart +++ b/packages/flutter_app/lib/pages/account/components/account_page_body.dart @@ -3,14 +3,14 @@ import 'package:awaitable_button/awaitable_button.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import '../../../util/localizer/localizer.dart'; +import '../../../gen/strings.g.dart'; class AccountPageBody extends HookConsumerWidget { const AccountPageBody({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { - final l10n = L10n.of(context); + final t = Translations.of(context); final authenticator = ref.watch(authenticatorProvider); Future signOut() async { final isBool = await showDialog( @@ -22,11 +22,11 @@ class AccountPageBody extends HookConsumerWidget { actions: [ TextButton( onPressed: () => Navigator.pop(context, false), - child: Text(l10n.buttonCancel), + child: Text(t.button.cancel), ), TextButton( onPressed: () => Navigator.pop(context, true), - child: Text(l10n.buttonSignOut), + child: Text(t.button.sign.out), ), ], ); @@ -47,7 +47,7 @@ class AccountPageBody extends HookConsumerWidget { ), AwaitableTextButton( onPressed: signOut, - child: Text(l10n.buttonSignOut), + child: Text(t.button.sign.out), ), ], ); diff --git a/packages/flutter_app/lib/pages/app_info_page/components/app_info_app_bar.dart b/packages/flutter_app/lib/pages/app_info_page/components/app_info_app_bar.dart index cf9b96ba..6d88895c 100644 --- a/packages/flutter_app/lib/pages/app_info_page/components/app_info_app_bar.dart +++ b/packages/flutter_app/lib/pages/app_info_page/components/app_info_app_bar.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:themes/themes.dart'; -import '../../../util/localizer/localizer.dart'; +import '../../../gen/strings.g.dart'; class AppInfoAppBar extends StatelessWidget implements PreferredSizeWidget { const AppInfoAppBar({super.key}); @@ -11,9 +11,9 @@ class AppInfoAppBar extends StatelessWidget implements PreferredSizeWidget { @override Widget build(BuildContext context) { - final l10n = L10n.of(context); + final t = Translations.of(context); return AppBar( - title: Text(l10n.appInfoAppBarTitle), + title: Text(t.appInfo.appBar.title), ); } } diff --git a/packages/flutter_app/lib/pages/app_info_page/components/app_info_body.dart b/packages/flutter_app/lib/pages/app_info_page/components/app_info_body.dart index 6d37ccff..2c7904d8 100644 --- a/packages/flutter_app/lib/pages/app_info_page/components/app_info_body.dart +++ b/packages/flutter_app/lib/pages/app_info_page/components/app_info_body.dart @@ -3,7 +3,7 @@ import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import '../../../environment/environment.dart'; -import '../../../util/localizer/localizer.dart'; +import '../../../gen/strings.g.dart'; import '../../../util/providers/package_info_provider.dart'; import '../../../widgets/widgets.dart'; @@ -12,14 +12,14 @@ class AppInfoBody extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final l10n = L10n.of(context); + final t = Translations.of(context); final flavor = ref.watch(flavorProvider); final packageInfo = ref.watch(packageInfoProvider); return ListView( children: [ - Text(l10n.title), + Text(t.title), const Gap(8), - Text('Author is ${l10n.authorName}.'), + Text('Author is ${t.author.name}.'), const Gap(8), const Text('Developed by Ryunosuke Muramatsu.'), const Divider(), diff --git a/packages/flutter_app/lib/pages/home_page/components/home_page_app_bar.dart b/packages/flutter_app/lib/pages/home_page/components/home_page_app_bar.dart index cfae3d21..0cd2f56e 100644 --- a/packages/flutter_app/lib/pages/home_page/components/home_page_app_bar.dart +++ b/packages/flutter_app/lib/pages/home_page/components/home_page_app_bar.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:themes/themes.dart'; -import '../../../util/localizer/localizer.dart'; +import '../../../gen/strings.g.dart'; class HomePageAppBar extends StatelessWidget implements PreferredSizeWidget { const HomePageAppBar({super.key}); @@ -11,9 +11,9 @@ class HomePageAppBar extends StatelessWidget implements PreferredSizeWidget { @override Widget build(BuildContext context) { - final l10n = L10n.of(context); + final t = Translations.of(context); return AppBar( - title: Text(l10n.homePageAppBarTitle), + title: Text(t.homePage.appBar.title), ); } } diff --git a/packages/flutter_app/lib/pages/home_page/components/home_page_body.dart b/packages/flutter_app/lib/pages/home_page/components/home_page_body.dart index 54fd9c25..122475c2 100644 --- a/packages/flutter_app/lib/pages/home_page/components/home_page_body.dart +++ b/packages/flutter_app/lib/pages/home_page/components/home_page_body.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; +import '../../../gen/strings.g.dart'; import '../../../router/router.dart'; -import '../../../util/localizer/localizer.dart'; import 'home_list_card.dart'; class HomePageBody extends HookWidget { @@ -12,13 +12,13 @@ class HomePageBody extends HookWidget { @override Widget build(BuildContext context) { - final l10n = L10n.of(context); + final t = Translations.of(context); return ListView( padding: const EdgeInsets.all(16), children: [ HomeListCard( onTap: () => const AppInfoRouteData().go(context), - label: l10n.homePageListAppInfoLabel, + label: t.homePage.list.appInfo.label, ), HomeListCard( onTap: () => const RiverpodExampleRouteData().go(context), diff --git a/packages/flutter_app/lib/pages/not_found_page/components/not_found_app_bar.dart b/packages/flutter_app/lib/pages/not_found_page/components/not_found_app_bar.dart index 01bf9ff4..8b5b60a5 100644 --- a/packages/flutter_app/lib/pages/not_found_page/components/not_found_app_bar.dart +++ b/packages/flutter_app/lib/pages/not_found_page/components/not_found_app_bar.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:themes/themes.dart'; -import '../../../util/localizer/localizer.dart'; +import '../../../gen/strings.g.dart'; class NotFoundAppBar extends StatelessWidget implements PreferredSizeWidget { const NotFoundAppBar({super.key}); @@ -11,9 +11,9 @@ class NotFoundAppBar extends StatelessWidget implements PreferredSizeWidget { @override Widget build(BuildContext context) { - final l10n = L10n.of(context); + final t = Translations.of(context); return AppBar( - title: Text(l10n.notFoundPageTitle), + title: Text(t.notFoundPage.title), ); } } diff --git a/packages/flutter_app/lib/pages/not_found_page/components/not_found_page_body.dart b/packages/flutter_app/lib/pages/not_found_page/components/not_found_page_body.dart index d7ca3dce..a990ae48 100644 --- a/packages/flutter_app/lib/pages/not_found_page/components/not_found_page_body.dart +++ b/packages/flutter_app/lib/pages/not_found_page/components/not_found_page_body.dart @@ -2,8 +2,8 @@ import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import '../../../gen/strings.g.dart'; import '../../../router/router.dart'; -import '../../../util/localizer/localizer.dart'; import '../../../widgets/widgets.dart'; class NotFoundPageBody extends HookConsumerWidget { @@ -16,19 +16,19 @@ class NotFoundPageBody extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final l10n = L10n.of(context); + final t = Translations.of(context); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(l10n.notFoundPageHeaderLabel), + Text(t.notFoundPage.header.label), const Gap(32), DisplaySmallText(path), const Gap(32), - Text(l10n.notFoundPageDescription), + Text(t.notFoundPage.description), Center( child: ElevatedButton( onPressed: () => const HomeRouteData().go(context), - child: Text(l10n.notFoundPageReturnButtonLabel), + child: Text(t.notFoundPage.backButton.label), ), ), ], diff --git a/packages/flutter_app/lib/pages/pinkie_mew/components/pinkie_mew_page_app_bar.dart b/packages/flutter_app/lib/pages/pinkie_mew/components/pinkie_mew_page_app_bar.dart index da52aaf5..f64983cf 100644 --- a/packages/flutter_app/lib/pages/pinkie_mew/components/pinkie_mew_page_app_bar.dart +++ b/packages/flutter_app/lib/pages/pinkie_mew/components/pinkie_mew_page_app_bar.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:themes/themes.dart'; -import '../../../util/localizer/localizer.dart'; +import '../../../gen/strings.g.dart'; class PinkieMewPageAppBar extends StatelessWidget implements PreferredSizeWidget { @@ -12,9 +12,9 @@ class PinkieMewPageAppBar extends StatelessWidget @override Widget build(BuildContext context) { - final l10n = L10n.of(context); + final t = Translations.of(context); return AppBar( - title: Text(l10n.pinkieMewPageTitle), + title: Text(t.pinkieMewPage.title), ); } } diff --git a/packages/flutter_app/lib/pages/settings_page/components/settings_page_app_bar.dart b/packages/flutter_app/lib/pages/settings_page/components/settings_page_app_bar.dart index 33536057..dab50082 100644 --- a/packages/flutter_app/lib/pages/settings_page/components/settings_page_app_bar.dart +++ b/packages/flutter_app/lib/pages/settings_page/components/settings_page_app_bar.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:themes/themes.dart'; -import '../../../util/localizer/localizer.dart'; +import '../../../gen/strings.g.dart'; class SettingsPageAppBar extends StatelessWidget implements PreferredSizeWidget { @@ -12,9 +12,9 @@ class SettingsPageAppBar extends StatelessWidget @override Widget build(BuildContext context) { - final l10n = L10n.of(context); + final t = Translations.of(context); return AppBar( - title: Text(l10n.settingsPageAppBarTitle), + title: Text(t.settingsPage.appBar.title), ); } } diff --git a/packages/flutter_app/lib/pages/settings_page/components/settings_page_body.dart b/packages/flutter_app/lib/pages/settings_page/components/settings_page_body.dart index f399eb8c..98b4b75c 100644 --- a/packages/flutter_app/lib/pages/settings_page/components/settings_page_body.dart +++ b/packages/flutter_app/lib/pages/settings_page/components/settings_page_body.dart @@ -2,15 +2,15 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import '../../../features/user_device/user_device.dart'; +import '../../../gen/strings.g.dart'; import '../../../router/router.dart'; -import '../../../util/localizer/localizer.dart'; class SettingsPageBody extends HookConsumerWidget { const SettingsPageBody({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { - final l10n = L10n.of(context); + final t = Translations.of(context); final userDevice = ref.watch(userDeviceProvider); return ListView( padding: const EdgeInsets.all(16), @@ -18,13 +18,13 @@ class SettingsPageBody extends HookConsumerWidget { Card( child: ListTile( onTap: () => const ThemeSelectionRouteData().go(context), - title: Text(l10n.settingsPageListThemeSelectorLabel), + title: Text(t.settingsPage.list.themeSelector.label), ), ), Card( child: ListTile( onTap: () => const AccountRouteData().go(context), - title: Text(l10n.settingsPageListAccountLabel), + title: Text(t.settingsPage.list.account.label), ), ), ListTile( diff --git a/packages/flutter_app/lib/pages/theme_selection_page/components/theme_selection_page_app_bar.dart b/packages/flutter_app/lib/pages/theme_selection_page/components/theme_selection_page_app_bar.dart index 50fa9377..d7aca49a 100644 --- a/packages/flutter_app/lib/pages/theme_selection_page/components/theme_selection_page_app_bar.dart +++ b/packages/flutter_app/lib/pages/theme_selection_page/components/theme_selection_page_app_bar.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:themes/themes.dart'; -import '../../../util/localizer/localizer.dart'; +import '../../../gen/strings.g.dart'; import '../../../widgets/widgets.dart'; class ThemeSelectionPageAppBar extends StatelessWidget @@ -14,10 +14,10 @@ class ThemeSelectionPageAppBar extends StatelessWidget @override Widget build(BuildContext context) { - final l10n = L10n.of(context); + final t = Translations.of(context); final themeDescription = context.themeDescription; return AppBar( - title: Text(l10n.themeSelectionPageAppBarTitle), + title: Text(t.theme.selection.page.appBar.title), actions: [ themeDescription.icon, Align( diff --git a/packages/flutter_app/lib/pages/theme_selection_page/components/theme_selection_page_body.dart b/packages/flutter_app/lib/pages/theme_selection_page/components/theme_selection_page_body.dart index 1d91b454..384ed60f 100644 --- a/packages/flutter_app/lib/pages/theme_selection_page/components/theme_selection_page_body.dart +++ b/packages/flutter_app/lib/pages/theme_selection_page/components/theme_selection_page_body.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import '../../../features/theme_selector/theme_selector.dart'; -import '../../../util/localizer/localizer.dart'; +import '../../../gen/strings.g.dart'; class ThemeSelectionPageBody extends HookConsumerWidget { const ThemeSelectionPageBody({super.key}); @@ -11,7 +11,7 @@ class ThemeSelectionPageBody extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final themeSelector = ref.watch(themeSelectorProvider.notifier); final currentThemeMode = ref.watch(themeSelectorProvider); - final l10n = L10n.of(context); + final t = Translations.of(context); return ListView.builder( padding: const EdgeInsets.symmetric(vertical: 16), itemCount: ThemeMode.values.length, @@ -21,8 +21,8 @@ class ThemeSelectionPageBody extends HookConsumerWidget { value: themeMode, groupValue: currentThemeMode, onChanged: (newTheme) async => themeSelector.changeAndSave(newTheme!), - title: Text(themeMode.title(l10n)), - subtitle: Text(themeMode.subtitle(l10n)), + title: Text(themeMode.title(t)), + subtitle: Text(themeMode.subtitle(t)), secondary: Icon(themeMode.iconData), ); }, diff --git a/packages/flutter_app/lib/pages/top_level_tab/top_level_tab.dart b/packages/flutter_app/lib/pages/top_level_tab/top_level_tab.dart index 3fc6f66c..00fe6ca4 100644 --- a/packages/flutter_app/lib/pages/top_level_tab/top_level_tab.dart +++ b/packages/flutter_app/lib/pages/top_level_tab/top_level_tab.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import '../../gen/strings.g.dart'; import '../home_page/home_page.dart'; import '../riverpod_example_page/riverpod_example_page.dart'; import '../settings_page/settings_page.dart'; @@ -30,14 +30,14 @@ enum TopLevelTab { } extension TopLevelTabExt on TopLevelTab { - String labelText(AppLocalizations l10n) { + String labelText(Translations t) { switch (this) { case TopLevelTab.home: - return l10n.topLevelTabHomeLabel; + return t.topLevelTab.home.label; case TopLevelTab.riverpod: - return l10n.topLevelTabRiverpodExampleLabel; + return t.topLevelTab.riverpod.example.label; case TopLevelTab.settings: - return l10n.topLevelTabSettingsLabel; + return t.topLevelTab.settings.label; } } } diff --git a/packages/flutter_app/lib/util/localizer/localizer.dart b/packages/flutter_app/lib/util/localizer/localizer.dart deleted file mode 100644 index 552767e5..00000000 --- a/packages/flutter_app/lib/util/localizer/localizer.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:flutter/widgets.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; - -extension L10n on AppLocalizations { - /// `L10n.of(context)` - /// - /// `AppLocalizations.of(context)` の以下2つの問題を解決するための実装 - /// 1. IDEでサジェストされない(自動でインポートされない) - /// 2. 取得できるAppLocalizationsがNullable - /// この代替メソッドでは両問題を解決している - static AppLocalizations of(BuildContext context) => - AppLocalizations.of(context)!; -} - -extension BuildContextL10n on BuildContext { - /// `context.l10n` - AppLocalizations get l10n => L10n.of(this); -} diff --git a/packages/flutter_app/pubspec.lock b/packages/flutter_app/pubspec.lock index 64f1c52a..948f9a12 100644 --- a/packages/flutter_app/pubspec.lock +++ b/packages/flutter_app/pubspec.lock @@ -302,6 +302,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.3" + csv: + dependency: transitive + description: + name: csv + sha256: "63ed2871dd6471193dffc52c0e6c76fb86269c00244d244297abbb355c84a86e" + url: "https://pub.dev" + source: hosted + version: "5.1.1" cupertino_icons: dependency: "direct main" description: @@ -930,6 +938,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.7" + json2yaml: + dependency: transitive + description: + name: json2yaml + sha256: da94630fbc56079426fdd167ae58373286f603371075b69bf46d848d63ba3e51 + url: "https://pub.dev" + source: hosted + version: "3.0.1" json_annotation: dependency: "direct main" description: @@ -1318,6 +1334,30 @@ packages: description: flutter source: sdk version: "0.0.99" + slang: + dependency: "direct main" + description: + name: slang + sha256: "3231d9b11163acb96e7611c5da54404e93d1e0d0eb8f31126ce86a1a43a1c7d4" + url: "https://pub.dev" + source: hosted + version: "3.26.2" + slang_build_runner: + dependency: "direct dev" + description: + name: slang_build_runner + sha256: "3c1efe9cc32af091859962fc67d68aedf8e649ac758348f3b2ecf4942b2eac4a" + url: "https://pub.dev" + source: hosted + version: "3.26.2" + slang_flutter: + dependency: "direct main" + description: + name: slang_flutter + sha256: "88e40b238307abdc191c9c26cdfb7c1b6d5075f461e8fbcf89f6f2a3f92cd5d7" + url: "https://pub.dev" + source: hosted + version: "3.26.2" sort_key_generator: dependency: "direct main" description: diff --git a/packages/flutter_app/pubspec.yaml b/packages/flutter_app/pubspec.yaml index 0d56c0a5..ed44e5f1 100644 --- a/packages/flutter_app/pubspec.yaml +++ b/packages/flutter_app/pubspec.yaml @@ -47,6 +47,8 @@ dependencies: riverpod_annotation: ^2.1.5 shared_preferences: ^2.2.2 simple_logger: ^1.9.0+1 + slang: ^3.26.2 + slang_flutter: ^3.26.2 sort_key_generator: ^0.2.0+1 themes: path: ../themes/ @@ -69,6 +71,7 @@ dev_dependencies: mockito: ^5.2.0 riverpod_generator: ^2.3.2 riverpod_lint: ^2.1.0 + slang_build_runner: ^3.26.2 flutter_gen: enabled: true diff --git a/packages/flutter_app/slang.yaml b/packages/flutter_app/slang.yaml new file mode 100644 index 00000000..c5ac732c --- /dev/null +++ b/packages/flutter_app/slang.yaml @@ -0,0 +1,4 @@ +base_locale: ja +input_directory: lib/i18n +input_file_pattern: .json +output_directory: lib/gen diff --git a/packages/util/analysis_options.yaml b/packages/util/analysis_options.yaml index ff442717..1ef0ad32 100644 --- a/packages/util/analysis_options.yaml +++ b/packages/util/analysis_options.yaml @@ -1,6 +1,5 @@ include: package:altive_lints/altive_lints.yaml analyzer: exclude: - - "**/l10n/*.dart" - "**/gen/*.dart" - "lib/environment/src/firebase_options*.dart"