Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 68 additions & 39 deletions flutter_app/lib/app.dart
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';
Expand Down Expand Up @@ -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>(
Expand All @@ -51,13 +55,66 @@ 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>[]) {
if (deviceLocale.languageCode == 'zh' &&
deviceLocale.scriptCode == null) {
final country = deviceLocale.countryCode?.toUpperCase();
if (country == 'TW' || country == 'HK' || country == 'MO') {
for (final supportedLocale in supportedLocales) {
if (supportedLocale.languageCode == 'zh' &&
supportedLocale.scriptCode == 'Hant') {
return supportedLocale;
}
}
}
}

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;
},
supportedLocales: AppLocalizations.supportedLocales,
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
theme: _buildLightTheme(),
darkTheme: _buildDarkTheme(),
themeMode: ThemeMode.system,
home: const SplashScreen(),
),
),
);
}
Expand Down Expand Up @@ -95,15 +152,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,
filledButtonTheme: FilledButtonThemeData(
style: FilledButton.styleFrom(
backgroundColor: AppColors.accent,
Expand Down Expand Up @@ -165,13 +214,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,
snackBarTheme: SnackBarThemeData(
backgroundColor: AppColors.darkSurfaceAlt,
contentTextStyle: GoogleFonts.inter(color: Colors.white),
Expand Down Expand Up @@ -226,15 +269,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,
Expand Down Expand Up @@ -296,13 +331,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),
Expand Down
97 changes: 97 additions & 0 deletions flutter_app/lib/l10n/app_localizations.dart
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);
}
Loading
Loading