国际化(i18n)和本地化(l10n)这两个词经常被错误地互换使用。
本地化是将你的应用程序提供给其他地区的过程。它包括定义用户语言、地区和任何特定变体的参数,例如en-US(美式英语)。
听起来很简单,但你怎么做到的?是否为每种语言创建代码副本?或者在选择要显示的文本的地方添加if语句?不,这就是国际化的救星。
国际化是您设计代码以使其易于本地化的方式。所以,是的,你可以有一个国际化的应用程序,只支持一个地区,因为它有能力最终支持多个地区没有戏剧性的工程努力。事实上,这是值得鼓励的。国际化,即使你的应用程序计划中没有成为通晓多种语言的人。它将使您的代码更干净,更经得起未来的考验。
接下来,您将看到如何使用Flutter来国际化应用程序。
Flutter中的国际化
为应用程序的每个可翻译文本创建一个包含动态字符串属性或函数的类。例如,formPageAppBarTitle属性可能返回'Death by Caffeine Calculator'或'Calculadora de Morte por Cafeína',具体取决于区域设置。
创建一个中介类,负责在应用程序初始化或区域设置更改时设置在上一步中创建的类的实例。
向MaterialApp中的Localizations小部件提供这个中介类的实例。
用新的动态版本替换硬编码文本。例如,Text('Death by Caffeine Calculator')变成Text(本地化<classfromFirstStepContainingValuesthatVaryPerLocale>(上下文,classfromFirstStepContainingValuesthatVaryPerLocale).formPageAppBarTitle)。
尽管第一步中的类包含的值varyperLocale是一个有意义的名称,但肯定的是,它相当长,而且您要输入很多。从现在起,你就叫它S。
维护翻译
剩下的问题是:S应该如何存储所有受支持语言的字符串?
这就是您所说的实现细节,因此,这取决于您。但这里有一些想法:
使用Map<String,Map<String,String>>,其中第一个Map的键是语言,第二个Map的键是文本的ID,例如formPageAppBarTitle。
维护和读取类似JSON的文件,每个区域设置一个。
发挥你的创造力。
第二种方法看起来是一个很好的选择,您将在本教程中使用它。您将依靠Intl包来帮助您从文件中读取值。
将内容与逻辑分开,可以保持代码整洁,甚至可以让非编码人员更改代码。
即使有国际的帮助,你还有很多工作要做。每次输入新语言时更改委托,每次需要添加新字符串时更改代理的过程(这种情况经常发生)既繁琐又自动化。
如你所知,消除繁琐和机器人的任务是一个开发人员的超级能力。这就是为什么Localizely的人为androidstudio和visualstudio代码创建了一个令人兴奋的flatter Intl插件来为您处理这些任务。
每当您添加新的语言或字符串时,插件都会为您生成一个新的LocalizationsDelegate和S。
注意:不要混淆Intl包和flatterintlide插件。后者处理生成使用前者的类。在本教程中,您将使用这两种方法。
安装Flutter Intl
在macOS上按Command-,(逗号),在Linux或Windows上按Control-Alt-s,打开Android Studio的首选项。
选择左侧面板(1)上的插件和上部选项卡栏(2)上的市场。在搜索栏(3)中键入intl,然后单击Install以获得Localizely的flatter intl结果(4)。
插件安装好之后重启IDE,
生成类
打开 pubspec.yaml 并替换#TODO:在此处添加intl和flatterŠu本地化。使用:
intl: 0.16.1
flutter_localizations:
sdk: flutter
您刚刚向项目中添加了两个包:
上面的命令为您的出版规范. 将main_locale:en_US添加到其下面,并在相同的缩进级别enabled:true中。
flutter_intl:
main_locale: en_US
enabled: true
在屏幕顶部的flUtter命令栏中单击Pub get。
返回IDE菜单栏上的Tools▸flutter Intl,但这次选择Remove Locale。选择en并单击OK。
在这里,您将默认区域设置从en更改为en\u US,正确地识别了细微差别。这将允许您稍后基于国家代码执行自定义逻辑。
插件在lib中创建了两个文件夹,包括一些文件:
generated:保存生成的类。你不需要碰它们。
l10n:your.arbs的主页,JSON语法文件将保存您的翻译。美国英语的那个已经在那里了。
既然您已经设置了flatter Intl,现在是时候配置您的应用程序来使用它了。
配置应用程序
转到lib/main.dart,并将这两个导入添加到其他导入的下面:
import 'package:buzzkill/generated/l10n.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
现在,在MaterialApp中,remove//TODO:指定localizationsDelegates和supportedLocales。加上这个:
localizationsDelegates: [
// 1
S.delegate,
// 2
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: S.delegate.supportedLocales,
以下是您所做的:
lib/l10n文件夹现在包含一个新文件-intl\u pt_BR.arb公司-你的应用程序的Android版本已经可以支持en-US和pt-BR了。
只有Android版本?不是iOS?是的!这是因为一个特定的iOS配置文件需要您特别小心。好消息是您不需要Xcode来完成它。
在项目结构中,打开ios/Runner/信息列表并替换<!--TODO:指定支持的区域设置。-->使用:
<key>CFBundleLocalizations</key>
<array>
<string>en_US</string>
<string>pt_BR</string>
</array>
停止应用程序的上一次执行,然后重新生成并运行它,以确保没有引入任何错误。不要期望任何视觉变化。
提取字符串
替换lib/l10n/intl_en_US.arb
{
"@@locale": "en_US",
"formPageAppBarTitle": "Death by Caffeine Calculator",
"firstSuggestedDrinkName": "Drip Coffee (Cup)",
"secondSuggestedDrinkName": "Espresso (Shot)",
"thirdSuggestedDrinkName": "Latte (Mug)",
"formPageWeightInputLabel": "Body Weight",
"formPageWeightInputSuffix": "pounds",
"formPageRadioListLabel": "Choose a drink",
"formPageActionButtonTitle": "CALCULATE",
"formPageCustomDrinkRadioTitle": "Other",
"formPageCustomDrinkServingSizeInputLabel": "Serving Size",
"formPageCustomDrinkServingSizeInputSuffix": "fl. oz",
"formPageCustomDrinkCaffeineAmountInputLabel": "Caffeine",
"formPageCustomDrinkCaffeineAmountInputSuffix": "mg",
"resultsPageAppBarTitle": "Dosages",
"resultsPageLethalDosageTitle": "Lethal Dosage",
"resultsPageFirstDisclaimer": "*Based on {servingSize} fl. oz serving.",
"resultsPageLethalDosageMessage": "{quantity, plural, one{One serving.} other{{formattedNumber} servings in your system at one time.}}",
"resultsPageSafeDosageTitle": "Daily Safe Maximum",
"resultsPageSafeDosageMessage": "{quantity, plural, one{One serving per day.} other{{formattedNumber} servings per day.}}",
"resultsPageSecondDisclaimer": "*Applies to age 18 and over. This calculator does not replace professional medical advice."
}
这些是buzzkill中每个可见文本行的en-US条目。特别注意:
添加巴西葡萄牙语翻译
这次,在 lib/l10n/intl_pt_BR.arb,将所有内容替换为:
{
"@@locale": "pt_BR",
"formPageAppBarTitle": "Calculadora de Morte por Cafeína",
"firstSuggestedDrinkName": "Café Coado (Xícara)",
"secondSuggestedDrinkName": "Espresso (Shot)",
"thirdSuggestedDrinkName": "Latte (Caneca)",
"formPageWeightInputLabel": "Peso Corporal",
"formPageWeightInputSuffix": "libras",
"formPageRadioListLabel": "Escolha uma bebida",
"formPageActionButtonTitle": "CALCULAR",
"formPageCustomDrinkRadioTitle": "Outra",
"formPageCustomDrinkServingSizeInputLabel": "Tamanho",
"formPageCustomDrinkServingSizeInputSuffix": "fl. oz",
"formPageCustomDrinkCaffeineAmountInputLabel": "Cafeína",
"formPageCustomDrinkCaffeineAmountInputSuffix": "mg",
"resultsPageAppBarTitle": "Dosagens",
"resultsPageLethalDosageTitle": "Dose Letal",
"resultsPageLethalDosageMessage": "{quantity, plural, one{Uma porção.} other{{formattedNumber} porções no seu sistema de uma vez.}}",
"resultsPageSafeDosageTitle": "Limite Seguro Diário",
"resultsPageSafeDosageMessage": "{quantity, plural, one{Uma porção por dia.} other{{formattedNumber} porções por dia.}}",
"resultsPageFirstDisclaimer": "*Baseado em uma porção de {servingSize} fl. oz.",
"resultsPageSecondDisclaimer": "*Se aplica a pessoas com 18 anos ou mais. Essa calculadora não substitui conselhos médicos profissionais."
}
这里没有什么新鲜事。这些是相同lib/l10n/intl\en的葡萄牙语翻译_美国arb条目。
删除硬编码值
你的旅程从lib/pages/form_page.dart,巴斯·基尔的家。打开文件,在imports块的顶部添加以下行:
import 'package:buzzkill/generated/l10n.dart';
用户界面向用户提供三种建议的含咖啡因饮料。在代码中,您可以通过_drinkSuggestions控制它们。删除当前的_drinkSuggestions定义并添加以下内容:
// 1
List<Drink> _drinkSuggestions;
// 2
Locale _userLocale;
@override
void didChangeDependencies() {
// 3
final newLocale = Localizations.localeOf(context);
if (newLocale != _userLocale) {
_userLocale = newLocale;
// 4
_weightTextController.clear();
_servingSizeTextController.clear();
_caffeineTextController.clear();
_selectedDrinkSuggestion = null;
_drinkSuggestions = [
Drink(
// 5
name: S.of(context).firstSuggestedDrinkName,
caffeineAmount: 145,
servingSize: 8,
),
Drink(
name: S.of(context).secondSuggestedDrinkName,
caffeineAmount: 77,
servingSize: 1.5,
),
Drink(
name: S.of(context).thirdSuggestedDrinkName,
caffeineAmount: 154,
servingSize: 16,
),
];
}
super.didChangeDependencies();
}