如果您想将您的Flutter应用程序部署给讲其他语言的用户,您需要对其进行本地化。而Flutter文档中已经有一个详细的国际化指南,深入地涵盖了这个主题。
为了使生活更轻松,我之前写了一份分步指南,解释了如何生成AppLocalizations 类,并使用BuildContext 扩展在我们的小部件内访问它。
但是,如果我们有一些业务逻辑是在小部件之外的,那么我们如何才能读取本地化的字符串?
本文展示了我是如何在自己的应用程序中使用Riverpod包解决本地化问题的。
本文是基于官方的
flutter_localization包,它支持超过78种语言。Flutter Gems网站在此列出了许多其他的本地化包。
[
赞助者
Code with Andrea对每个人都是免费的。帮助我保持这种方式,看看这个赞助商。

**面向Flutter开发者的开源后端服务器。**Appwrite是一个安全的、自我托管的解决方案,它为开发者提供了一套易于使用的REST API来管理他们的核心后台需求。你可以用Appwrite构建任何东西点击这里了解更多。
应用程序的本地化要求
在我们深入研究代码之前,让我们先弄清楚我们要做什么。
- 在我们的小部件之外访问本地化的字符串**(不**使用
BuildContext) - 确保任何监听器(提供者和小部件)在本地化发生变化时重新构建
为了满足这些要求,我们需要结合两件事。
- 一个
Provider<AppLocalizations>,我们可以在应用程序的任何地方使用它 - 一个
LocaleObserver类,我们可以用它来跟踪locale的变化。
下面是如何实现它们的。👇
1.创建AppLocalizations提供者
为了得到适合当前locale的AppLocalizations 对象,我们可以使用lookupAppLocalizations 方法,这个方法是在我们运行flutter gen-l10n 命令时产生的。
所以第一步是创建一个使用它的提供者。
import 'package:flutter_riverpod/flutter_riverpod.dart';
// lookupAppLocalizations is defined here 👇
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'dart:ui' as ui;
/// provider used to access the AppLocalizations object for the current locale
final appLocalizationsProvider = Provider<AppLocalizations>((ref) {
return lookupAppLocalizations(ui.window.locale);
});
但这还不够,因为我们的提供者不会在locale发生变化时重建并通知其监听器。
2.添加一个locale观察者
为了跟踪地区性变化,我们可以创建一个子类的 WidgetsBindingObserver的子类,并重写 didChangeLocales方法。
/// observer used to notify the caller when the locale changes
class _LocaleObserver extends WidgetsBindingObserver {
_LocaleObserver(this._didChangeLocales);
final void Function(List<Locale>? locales) _didChangeLocales;
@override
void didChangeLocales(List<Locale>? locales) {
_didChangeLocales(locales);
}
}
现在我们有了这个,我们可以更新我们的提供者代码。
/// provider used to access the AppLocalizations object for the current locale
final appLocalizationsProvider = Provider<AppLocalizations>((ref) {
// 1. initialize from the initial locale
ref.state = lookupAppLocalizations(ui.window.locale);
// 2. create an observer to update the state
final observer = _LocaleObserver((locales) {
ref.state = lookupAppLocalizations(ui.window.locale);
});
// 3. register the observer and dispose it when no longer needed
final binding = WidgetsBinding.instance;
binding.addObserver(observer);
ref.onDispose(() => binding.removeObserver(observer));
// 4. return the state
return ref.state;
});
这可以确保任何其他依赖appLocalizationsProvider 的提供者或小工具在locale发生变化时可以重建。
接下来,让我们看看如何使用我们的提供者。
3.使用AppLocalizations提供者
主要的想法是,我们可以使用一个ref 对象来读取 appLocalizationsProvider 。
// get the AppLocalizations object (read once)
final loc = ref.read(appLocalizationsProvider);
// read a property defined in the *.arb file
final error = loc.addToCartFailed;
例如,考虑这个CartService 类,它被用来更新一个购物车。
class CartService {
CartService(this.ref);
// declare ref as a property
final Ref ref;
Future<void> addItem(Item item) async {
try {
// fetch the cart
final cart = ref.read(cartRepositoryProvider).fetchCart();
// return a copy with the updated data
final updated = cart.addItem(item);
// set the cart with the updated data
await ref.read(cartRepositoryProvider).setCart(updated);
} catch (e) {
// get the localized error message
final errorMessage = ref.read(appLocalizationsProvider).addToCartFailed;
// throw it as an exception
throw Exception(errorMessage);
}
}
}
// the corresponding provider
final cartServiceProvider = Provider<CartService>((ref) {
return CartService(ref);
});
这个CartService 类需要一个Ref 参数,它被用来在catch块内调用ref.read(appLocalizationsProvider) - 从而使AppLocalizations 成为一个隐含的依赖关系。
在这种情况下,我们应该读取(而不是观察)
appLocalizationsProvider,因为我们需要在调用addItem方法时检索一次错误信息。
但是如果我们想让AppLocalizations 的依赖关系更加明确,我们可以这样做。
class CartService {
CartService({required this.cartRepository, required this.loc});
// declare dependencies as explicit properties
final CartRepository cartRepository;
final AppLocalizations loc;
Future<void> addItem(Item item) async {
try {
// fetch the cart
final cart = cartRepository.fetchCart();
// return a copy with the updated data
final updated = cart.addItem(item);
// set the cart with the updated data
await cartRepository.setCart(updated);
} catch (e) {
// get the localized error message
final errorMessage = loc.addToCartFailed;
// throw it as an exception
throw Exception(errorMessage);
}
}
}
// the corresponding provider
final cartServiceProvider = Provider<CartService>((ref) {
return CartService(
// pass the dependencies explicitly (using watch)
cartRepository: ref.watch(cartRepositoryProvider),
loc: ref.watch(appLocalizationsProvider),
);
});
在这种情况下,我们应该监视(而不是读取)
appLocalizationsProvider,以便在locale改变时重建cartServiceProvider返回的CartService。
无论如何,我们现在可以在我们的提供者中的任何地方访问AppLocalizations ,而不需要使用BuildContext 。
奖励:使用AppLocalizations进行错误处理
如果我们使用freezed,我们可以定义我们自己的特定领域的异常类。
@freezed
class AppException with _$AppException {
const factory AppException.permissionDenied() = PermissionDenied;
const factory AppException.paymentFailed() = PaymentFailed;
// add other error types here
}
而如果我们想把每个错误映射到一个本地化的错误信息(我们将在用户界面中显示),我们可以创建这个简单的扩展。
extension AppExceptionMessage on AppException {
String message(AppLocalizations loc) {
return when(
permissionDenied: () => loc.permissionDeniedMessage,
paymentFailed: () => loc.paymentFailedMessage,
// and so on...
);
}
}
然后,我们可以在一个StateNotifier<AsyncValue> 子类中设置一个带有本地化字符串的错误状态,我们的小部件可以监听到。
// inside a StateNotifier subclass
final exception = AppException.permissionDenied();
final loc = ref.read(appLocalizationsProvider);
state = AsyncError(exception.message(loc));
再一次,不需要BuildContext ,就可以在我们的小部件之外读取本地化的字符串。
总结
我们现在已经知道了如何在我们的应用程序中访问本地化的字符串。
- 在widgets内部→ 从
BuildContext获取AppLocalizations对象(如前文所述) - 在widgets之外→从
appLocalizationsProvider(这里解释过)获得AppLocalizations对象。
无论哪种方式,如果地区设置发生变化,我们的小部件和提供者都会重新建立。
关于如何在iOS和Android上测试locale变化的说明,请参阅GitHub上的这个例子项目。
如果你愿意,可以在你的应用程序中重新使用这些代码。
import 'package:flutter/widgets.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'dart:ui' as ui;
/// provider used to access the AppLocalizations object for the current locale
final appLocalizationsProvider = Provider<AppLocalizations>((ref) {
// 1. initialize from the initial locale
ref.state = lookupAppLocalizations(ui.window.locale);
// 2. create an observer to update the state
final observer = _LocaleObserver((locales) {
ref.state = lookupAppLocalizations(ui.window.locale);
});
// 3. register the observer and dispose it when no longer needed
final binding = WidgetsBinding.instance;
binding.addObserver(observer);
ref.onDispose(() => binding.removeObserver(observer));
// 4. return the state
return ref.state;
});
/// observer used to notify the caller when the locale changes
class _LocaleObserver extends WidgetsBindingObserver {
_LocaleObserver(this._didChangeLocales);
final void Function(List<Locale>? locales) _didChangeLocales;
@override
void didChangeLocales(List<Locale>? locales) {
_didChangeLocales(locales);
}
}
编码愉快!