-
-
Notifications
You must be signed in to change notification settings - Fork 140
feat(i18n): merge translation branch #68
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 6 commits
1796d30
c62877c
c022673
e5bf325
d17812d
72d6ce6
64a58ee
5472c49
81791f7
228b62f
a76d90e
c7a96ea
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,9 @@ | ||
| import 'package:flutter/material.dart'; | ||
| import 'package:flutter_localizations/flutter_localizations.dart'; | ||
| import 'package:google_fonts/google_fonts.dart'; | ||
| import 'package:provider/provider.dart'; | ||
| import 'l10n/app_localizations.dart'; | ||
| import 'providers/locale_provider.dart'; | ||
| import 'providers/setup_provider.dart'; | ||
| import 'providers/gateway_provider.dart'; | ||
| import 'providers/node_provider.dart'; | ||
|
|
@@ -41,6 +44,7 @@ class OpenClawApp extends StatelessWidget { | |
| Widget build(BuildContext context) { | ||
| return MultiProvider( | ||
| providers: [ | ||
| ChangeNotifierProvider(create: (_) => LocaleProvider()..load()), | ||
| ChangeNotifierProvider(create: (_) => SetupProvider()), | ||
| ChangeNotifierProvider(create: (_) => GatewayProvider()), | ||
| ChangeNotifierProxyProvider<GatewayProvider, NodeProvider>( | ||
|
|
@@ -51,13 +55,53 @@ class OpenClawApp extends StatelessWidget { | |
| }, | ||
| ), | ||
| ], | ||
| child: MaterialApp( | ||
| title: 'OpenClaw', | ||
| debugShowCheckedModeBanner: false, | ||
| theme: _buildLightTheme(), | ||
| darkTheme: _buildDarkTheme(), | ||
| themeMode: ThemeMode.system, | ||
| home: const SplashScreen(), | ||
| child: Consumer<LocaleProvider>( | ||
| builder: (context, localeProvider, _) => MaterialApp( | ||
| debugShowCheckedModeBanner: false, | ||
| onGenerateTitle: (context) => context.l10n.t('appName'), | ||
| locale: localeProvider.locale, | ||
| localeListResolutionCallback: (deviceLocales, supportedLocales) { | ||
| if (localeProvider.locale != null) { | ||
| return localeProvider.locale; | ||
| } | ||
|
|
||
| for (final deviceLocale in deviceLocales ?? const <Locale>[]) { | ||
| for (final supportedLocale in supportedLocales) { | ||
| if (supportedLocale.languageCode == deviceLocale.languageCode && | ||
| supportedLocale.scriptCode == deviceLocale.scriptCode && | ||
| supportedLocale.countryCode == deviceLocale.countryCode) { | ||
| return supportedLocale; | ||
| } | ||
| } | ||
|
|
||
| for (final supportedLocale in supportedLocales) { | ||
| if (supportedLocale.languageCode == deviceLocale.languageCode && | ||
| supportedLocale.scriptCode == deviceLocale.scriptCode) { | ||
| return supportedLocale; | ||
| } | ||
| } | ||
|
|
||
| for (final supportedLocale in supportedLocales) { | ||
| if (supportedLocale.languageCode == deviceLocale.languageCode) { | ||
| return supportedLocale; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return supportedLocales.first; | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| }, | ||
|
Comment on lines
+63
to
+105
|
||
| supportedLocales: AppLocalizations.supportedLocales, | ||
| localizationsDelegates: const [ | ||
| AppLocalizations.delegate, | ||
| GlobalMaterialLocalizations.delegate, | ||
| GlobalWidgetsLocalizations.delegate, | ||
| GlobalCupertinoLocalizations.delegate, | ||
| ], | ||
| theme: _buildLightTheme(), | ||
| darkTheme: _buildDarkTheme(), | ||
| themeMode: ThemeMode.system, | ||
| home: const SplashScreen(), | ||
| ), | ||
| ), | ||
| ); | ||
| } | ||
|
|
@@ -95,15 +139,7 @@ class OpenClawApp extends StatelessWidget { | |
| color: Colors.white, | ||
| ), | ||
| ), | ||
| cardTheme: CardTheme( | ||
| elevation: 0, | ||
| color: AppColors.darkSurface, | ||
| shape: RoundedRectangleBorder( | ||
| borderRadius: BorderRadius.circular(12), | ||
| side: const BorderSide(color: AppColors.darkBorder), | ||
| ), | ||
| margin: const EdgeInsets.symmetric(vertical: 4), | ||
| ), | ||
| cardColor: AppColors.darkSurface, | ||
cubic-dev-ai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| filledButtonTheme: FilledButtonThemeData( | ||
| style: FilledButton.styleFrom( | ||
| backgroundColor: AppColors.accent, | ||
|
|
@@ -165,13 +201,7 @@ class OpenClawApp extends StatelessWidget { | |
| color: AppColors.darkBorder, | ||
| space: 1, | ||
| ), | ||
| dialogTheme: DialogTheme( | ||
| backgroundColor: AppColors.darkSurface, | ||
| shape: RoundedRectangleBorder( | ||
| borderRadius: BorderRadius.circular(16), | ||
| side: const BorderSide(color: AppColors.darkBorder), | ||
| ), | ||
| ), | ||
| dialogBackgroundColor: AppColors.darkSurface, | ||
cubic-dev-ai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| snackBarTheme: SnackBarThemeData( | ||
| backgroundColor: AppColors.darkSurfaceAlt, | ||
| contentTextStyle: GoogleFonts.inter(color: Colors.white), | ||
|
|
@@ -226,15 +256,7 @@ class OpenClawApp extends StatelessWidget { | |
| color: const Color(0xFF0A0A0A), | ||
| ), | ||
| ), | ||
| cardTheme: CardTheme( | ||
| elevation: 0, | ||
| color: AppColors.lightBg, | ||
| shape: RoundedRectangleBorder( | ||
| borderRadius: BorderRadius.circular(12), | ||
| side: const BorderSide(color: AppColors.lightBorder), | ||
| ), | ||
| margin: const EdgeInsets.symmetric(vertical: 4), | ||
| ), | ||
| cardColor: AppColors.lightBg, | ||
| filledButtonTheme: FilledButtonThemeData( | ||
| style: FilledButton.styleFrom( | ||
| backgroundColor: AppColors.accent, | ||
|
|
@@ -296,13 +318,7 @@ class OpenClawApp extends StatelessWidget { | |
| color: AppColors.lightBorder, | ||
| space: 1, | ||
| ), | ||
| dialogTheme: DialogTheme( | ||
| backgroundColor: AppColors.lightBg, | ||
| shape: RoundedRectangleBorder( | ||
| borderRadius: BorderRadius.circular(16), | ||
| side: const BorderSide(color: AppColors.lightBorder), | ||
| ), | ||
| ), | ||
| dialogBackgroundColor: AppColors.lightBg, | ||
| snackBarTheme: SnackBarThemeData( | ||
| backgroundColor: const Color(0xFF0A0A0A), | ||
| contentTextStyle: GoogleFonts.inter(color: Colors.white), | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,97 @@ | ||
| import 'package:flutter/material.dart'; | ||
|
|
||
| import 'app_strings_en.dart'; | ||
| import 'app_strings_ja.dart'; | ||
| import 'app_strings_zh_hans.dart'; | ||
| import 'app_strings_zh_hant.dart'; | ||
|
|
||
| class AppLocalizations { | ||
| AppLocalizations(this.locale); | ||
|
|
||
| final Locale locale; | ||
|
|
||
| static const supportedLocales = [ | ||
| Locale('en'), | ||
| Locale('zh'), | ||
| Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant'), | ||
| Locale('ja'), | ||
| ]; | ||
|
|
||
| static const LocalizationsDelegate<AppLocalizations> delegate = | ||
| _AppLocalizationsDelegate(); | ||
|
|
||
| static AppLocalizations of(BuildContext context) { | ||
| final localizations = Localizations.of<AppLocalizations>( | ||
| context, | ||
| AppLocalizations, | ||
| ); | ||
| assert(localizations != null, 'AppLocalizations not found in context'); | ||
| return localizations!; | ||
| } | ||
|
|
||
| String t(String key, [Map<String, Object?> params = const {}]) { | ||
| final localeKey = _localeToKey(locale); | ||
| final localized = _localizedValues[localeKey] ?? | ||
| _localizedValues[locale.languageCode] ?? | ||
| _localizedValues['en']!; | ||
| final fallback = _localizedValues['en']!; | ||
|
|
||
| var value = localized[key] ?? fallback[key] ?? key; | ||
| for (final entry in params.entries) { | ||
| value = value.replaceAll('{${entry.key}}', '${entry.value ?? ''}'); | ||
| } | ||
| return value; | ||
| } | ||
|
|
||
| static bool isLocaleSupported(Locale locale) { | ||
| final localeKey = _localeToKey(locale); | ||
| if (_localizedValues.containsKey(localeKey)) { | ||
| return true; | ||
| } | ||
| return _localizedValues.containsKey(locale.languageCode); | ||
| } | ||
|
|
||
| static String _localeToKey(Locale locale) { | ||
| final scriptCode = locale.scriptCode?.toLowerCase(); | ||
| final countryCode = locale.countryCode?.toUpperCase(); | ||
|
|
||
| if (locale.languageCode == 'zh') { | ||
| if (scriptCode == 'hant') { | ||
| return 'zh-Hant'; | ||
| } | ||
|
|
||
| if (countryCode == 'TW' || countryCode == 'HK' || countryCode == 'MO') { | ||
| return 'zh-Hant'; | ||
| } | ||
| } | ||
|
|
||
| return locale.languageCode; | ||
| } | ||
|
|
||
| static final Map<String, Map<String, String>> _localizedValues = { | ||
| 'en': appStringsEn, | ||
| 'zh': appStringsZhHans, | ||
| 'zh-Hant': appStringsZhHant, | ||
| 'ja': appStringsJa, | ||
| }; | ||
| } | ||
|
|
||
| class _AppLocalizationsDelegate | ||
| extends LocalizationsDelegate<AppLocalizations> { | ||
| const _AppLocalizationsDelegate(); | ||
|
|
||
| @override | ||
| bool isSupported(Locale locale) => AppLocalizations.isLocaleSupported(locale); | ||
|
|
||
| @override | ||
| Future<AppLocalizations> load(Locale locale) async { | ||
| return AppLocalizations(locale); | ||
| } | ||
|
|
||
| @override | ||
| bool shouldReload(_AppLocalizationsDelegate old) => false; | ||
| } | ||
|
|
||
| extension AppLocalizationsContextExtension on BuildContext { | ||
| AppLocalizations get l10n => AppLocalizations.of(this); | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.