You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter Web如何实现URL与当前语言状态保持同步

问题根因

两个异常的核心原因是路由状态和语言状态完全脱节,没有做双向同步:

  • 切换语言时仅更新了Provider中的语言状态,没有触发路由跳转,浏览器地址栏自然不会同步更新
  • 应用启动时默认写死了英语为初始语言,完全没有解析当前访问URL中的语言前缀,也没有在路由匹配时同步语言状态,导致直接输入阿拉伯语路径时仍然加载英语资源
  • 原代码用全局变量urlLang存语言状态的写法不可靠,build流程触发时机和路由更新时机不匹配,会出现状态不同步问题
修复方案
  1. 调整LocaleProvider,支持传入初始语言值,不再硬编码默认英语
  2. 应用启动时先解析当前浏览器访问路径,从路径前缀中提取语言参数,作为Provider的初始语言值
  3. 切换语言的逻辑中,更新Provider状态的同时,调用路由替换方法跳转到对应语言前缀的页面,同步更新浏览器地址栏
  4. 路由生成逻辑中,每次匹配到路径时先解析路径中的语言前缀,同步更新Provider的语言状态,保证直接输入URL访问时能加载对应语言资源
  5. 移除不可靠的全局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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 22:51:26