Flutter Web如何实现URL与当前语言状态保持同步
问题根因
两个异常的核心原因是路由状态和语言状态完全脱节,没有做双向同步:
- 切换语言时仅更新了Provider中的语言状态,没有触发路由跳转,浏览器地址栏自然不会同步更新
- 应用启动时默认写死了英语为初始语言,完全没有解析当前访问URL中的语言前缀,也没有在路由匹配时同步语言状态,导致直接输入阿拉伯语路径时仍然加载英语资源
- 原代码用全局变量
urlLang存语言状态的写法不可靠,build流程触发时机和路由更新时机不匹配,会出现状态不同步问题
修复方案
- 调整LocaleProvider,支持传入初始语言值,不再硬编码默认英语
- 应用启动时先解析当前浏览器访问路径,从路径前缀中提取语言参数,作为Provider的初始语言值
- 切换语言的逻辑中,更新Provider状态的同时,调用路由替换方法跳转到对应语言前缀的页面,同步更新浏览器地址栏
- 路由生成逻辑中,每次匹配到路径时先解析路径中的语言前缀,同步更新Provider的语言状态,保证直接输入URL访问时能加载对应语言资源
- 移除不可靠的全局
urlLang变量,页面中直接从Provider或AppLocalizations获取当前语言状态
修正后完整代码
import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:url_strategy/url_strategy.dart'; import 'package:provider/provider.dart'; class L10n { static final all = [ const Locale('en', ''), const Locale('ar', ''), ]; } Locale getSwitchToLanguage(Locale currentLocale) { if (currentLocale == const Locale('ar', '')) { return const Locale('en', ''); } return const Locale('ar', ''); } class LocaleProvider extends ChangeNotifier { Locale _locale; LocaleProvider(this._locale); Locale get locale => _locale; void setLocale(Locale locale) { if (!L10n.all.contains(locale)) return; _locale = locale; notifyListeners(); } void clearLocale() { _locale = const Locale('en', ''); notifyListeners(); } } void switchLanguage(BuildContext context) { final provider = Provider.of<LocaleProvider>(context, listen: false); final switchToLocale = getSwitchToLanguage(provider.locale); provider.setLocale(switchToLocale); // 切换语言时同步更新路由,刷新地址栏 Navigator.of(context).pushReplacementNamed('/${switchToLocale.languageCode}/home'); } void main() { setPathUrlStrategy(); runApp( const MyApp(), ); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); // 从初始访问路径中解析默认语言 Locale _getInitialLocale() { final initialPath = Uri.base.path; if (initialPath.startsWith('/ar/')) { return const Locale('ar', ''); } return const Locale('en', ''); } @override Widget build(BuildContext context) => ChangeNotifierProvider( create: (context) => LocaleProvider(_getInitialLocale()), builder: (context, child) { final provider = Provider.of<LocaleProvider>(context); return MaterialApp( localizationsDelegates: const [ AppLocalizations.delegate, GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, GlobalCupertinoLocalizations.delegate, ], supportedLocales: L10n.all, locale: provider.locale, debugShowCheckedModeBanner: false, initialRoute: '/${provider.locale.languageCode}/home', // 路由配置 onGenerateRoute: (settings) { final routeName = settings.name ?? ''; Locale targetLocale = const Locale('en', ''); Widget targetPage = const UnknownScreen(); if (routeName == '/ar/home' || routeName == '/en/home') { // 解析路径对应语言 if (routeName.startsWith('/ar/')) { targetLocale = const Locale('ar', ''); } targetPage = const HomePage(); // 帧渲染完成后同步语言状态,避免build阶段更新状态报错 WidgetsBinding.instance.addPostFrameCallback((_) { Provider.of<LocaleProvider>(context, listen: false).setLocale(targetLocale); }); return MaterialPageRoute( settings: settings, builder: (context) => targetPage); } return MaterialPageRoute<void>( settings: settings, builder: (BuildContext context) => targetPage, ); }, onUnknownRoute: (settings) { return MaterialPageRoute<void>( settings: settings, builder: (BuildContext context) => const UnknownScreen(), ); }, ); }, ); } class UnknownScreen extends StatelessWidget { const UnknownScreen({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("404 page"), ), ); } } class HomePage extends StatelessWidget { const HomePage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { final currentLang = Provider.of<LocaleProvider>(context).locale.languageCode; return Scaffold( appBar: AppBar( title: Text(AppLocalizations.of(context)!.appTitle), ), body: Center( child: Column(children: [ const SizedBox(height: 50), currentLang == 'ar' ? Text("This is the Arabic version : $currentLang") : Text("This is the English version : $currentLang"), const SizedBox(height: 100), ElevatedButton( child: Text( currentLang == 'en' ? "Switch to Arabic" : "Switch to English", ), onPressed: () { switchLanguage(context); }, ) ]), ), ); } }
配套的arb翻译文件、pubspec.yaml依赖配置无需修改,保持原有内容即可。修复后直接访问
/ar/home会自动加载阿拉伯语资源,点击切换语言按钮时地址栏会同步更新为对应语言的路径。
内容的提问来源于stack exchange,提问作者ajarad
相关产品推荐
相关产品推荐

