本地化
本地化使应用按玩家语言显示文本。翻译保存在本地化文件中,即包含一个或多个语言区域消息的 JSON 资源,各消息由键标识。文本元素或脚本请求某个键后,应用的 I18n 对象 app.i18n 返回当前语言的消息。
本地化文件
文件格式如下:
{
"header": {
"version": 1
},
"data": [
{
"info": {
"locale": "en-US"
},
"messages": {
"title": "Treasure Hunt",
"coins": ["You have {number} coin", "You have {number} coins"]
}
}
]
}
header.version必须为 1,否则文件会被拒绝,控制台记录错误。data为各语言区域保存条目,一个文件可包含一种或多种语言。- 消息可为字符串;根据数字变化的文本则使用字符串数组,分别对应语言的各复数形式。
编辑器设置的 LOCALIZATION 区域中,CREATE NEW ASSET 会创建此格式的文件。
复数形式
各语言的复数形式数量不同,数组按其采用的 Unicode 复数类别排列:zero、one、two、few、many、other。引擎内置以下语言规则:
| 形式 | 语言 |
|---|---|
| 一种:other | 中文、印度尼西亚语、日语、韩语、泰语、越南语 |
| 两种:one、other | 丹麦语、英语、芬兰语、德语、希腊语、意大利语、挪威语、西班牙语、瑞典语、土耳其语和乌尔都语中,one 对应 1;法语、印地语、波斯语和葡萄牙语中,one 对应 0 或 1 |
| 四种:one、few、many、other | 波兰语、俄语、乌克兰语 |
| 六种:zero、one、two、few、many、other | 阿拉伯语 |
其他语言使用英语规则。
加载文件
app.i18n 会在本地化文件加载后读取它,但不负责发起加载:
- 引擎
- 编辑器
- React
- Web Components
const english = new pc.Asset('en-US', 'json', { url: 'localization/en-US.json' });
const french = new pc.Asset('fr-FR', 'json', { url: 'localization/fr-FR.json' });
app.assets.add(english);
app.assets.add(french);
app.i18n.assets = [english, french];
app.assets.load(english);
app.assets.load(french);
创建应用时注册 pc.JsonHandler。若已有数据,可直接传给 app.i18n.addData()。
在编辑器设置 LOCALIZATION 的 Assets 中添加文件,保持 Preload 开启,确保应用启动时已加载。
useAsset 不加载 JSON,因此需自行获取文件并添加数据:
import { useEffect } from 'react';
import { useApp } from '@playcanvas/react/hooks';
export function Localization({ urls }) {
const app = useApp();
useEffect(() => {
let cancelled = false;
const files = [];
Promise.all(urls.map(url => fetch(url).then(response => response.json()))).then((data) => {
if (cancelled) return;
for (const file of data) {
app.i18n.addData(file);
files.push(file);
}
});
return () => {
cancelled = true;
files.forEach(file => app.i18n.removeData(file));
};
}, [app, urls.join()]);
return null;
}
在 <Application> 内渲染 <Localization urls={['localization/en-US.json', 'localization/fr-FR.json']} />。
<pc-asset id="en-US" type="json" src="localization/en-US.json"></pc-asset>
<pc-asset id="fr-FR" type="json" src="localization/fr-FR.json"></pc-asset>
<script type="module">
import { AssetElement, whenReady } from '@playcanvas/web-components';
const { app } = await whenReady('pc-app');
app.i18n.assets = [AssetElement.get('en-US'), AssetElement.get('fr-FR')];
</script>
在 <pc-app> 内声明 <pc-asset>。
选择语言区域
当前语言为 app.i18n.locale,默认 en-US,不会自动读取浏览器设置。包括编辑器发布的应用,也会以 en-US 启动,直到显式选择。可以根据玩家选择或浏览器语言设置:
app.i18n.locale = navigator.language;
每个语言区域不一定都有独立数据。例如,没有 fr-CA 时,会使用 fr-FR 或其他法语区域;完全没有法语数据时,回退到 en-US。app.i18n.findAvailableLocale('fr-CA') 返回实际将使用的区域。
切换语言会更新全部本地化文本元素。HTML 等其他内容可监听 change:
app.i18n.on('change', (locale, previous) => {
document.documentElement.lang = locale;
});
编辑器设置 EDITOR 下的 Locale 仅用于视口及启动应用的预览,不影响发布构建。
本地化文本元素
具有 key 的文本元素显示当前语言对应消息,并随语言切换。找不到消息时显示键本身。
- 引擎
- 编辑器
- React
- Web Components
const heading = new pc.Entity('heading');
heading.addComponent('element', {
type: pc.ELEMENTTYPE_TEXT,
fontAsset: font.id,
key: 'title',
anchor: [0.5, 1, 0.5, 1],
pivot: [0.5, 1]
});
screen.addChild(heading);
设置 text 会清除键,设置 key 会替换文本。
勾选 Localized,在替代 Text 的 Key 字段中输入键。
React 保留了 key prop,因此应直接设置引擎组件:
import { useEffect } from 'react';
import { Entity } from '@playcanvas/react';
import { Element } from '@playcanvas/react/components';
import { useParent } from '@playcanvas/react/hooks';
// Gives the text element of the entity it is placed in a localization key
function LocalizationKey({ value }) {
const entity = useParent();
useEffect(() => {
entity.element.key = value;
}, [entity, value]);
return null;
}
export function Heading({ font }) {
return (
<Entity name="heading">
<Element type="text" fontAsset={font} anchor={[0.5, 1, 0.5, 1]} pivot={[0.5, 1]} />
<LocalizationKey value="title" />
</Entity>
);
}
不要同时给 <Element> 传入 text,否则每次渲染重新应用它时,都会移除键。
<pc-element> 没有设置键的 attribute,应直接设置引擎组件:
<pc-entity name="heading">
<pc-element id="heading" type="text" font-asset="arial" anchor="0.5 1 0.5 1" pivot="0.5 1"></pc-element>
</pc-entity>
<script type="module">
import { whenReady } from '@playcanvas/web-components';
const heading = await whenReady('#heading');
heading.component.key = 'title';
</script>
复数消息键会显示第一种形式,并保留 {number},因此应通过脚本设置此类文本。
脚本中的字符串
getText 返回当前语言消息,缺失时返回键本身。getPluralText 按数字选择复数形式,但保留 {number},需要自行替换:
const title = app.i18n.getText('title');
const count = 3;
const coins = app.i18n.getPluralText('coins', count).replace('{number}', count);
// "You have 3 coins"
两者均可通过最后一个可选参数指定语言区域。
本地化字体
字体资源可以为不同语言区域指定替代字体,适合需要原字体未包含字符的语言。切换语言后,本地化文本元素改用对应字体,并在需要时加载。没有键的元素仍保留原字体。
- **编辑器:**选中字体,在检查器 LOCALIZATION 区域单击 Add Locale,再选择该语言使用的字体。
- **引擎、React 和 Web Components:**调用字体资源的
addLocalizedAssetId:
latinFont.addLocalizedAssetId('ja-JP', japaneseFont.id);
新语言字体加载期间,文本不会绘制。
数字与日期格式
不同语言区域采用不同数字、价格和日期格式,可用浏览器 Intl 对象及当前语言格式化:
const price = new Intl.NumberFormat(app.i18n.locale, { style: 'currency', currency: 'EUR' }).format(4.5);
const today = new Intl.DateTimeFormat(app.i18n.locale, { dateStyle: 'long' }).format(new Date());
语言说明
中文、日文和韩文
文本元素支持在中日韩字符之间无空格换行,并将闭合标点和小假名尽量保留在前一行。字符集较大,应参考选择字符,并使用本地化字体。
泰语
泰语词间没有空格,而文本元素仅在空格、制表符、连字符和零宽空格处换行。可让译者在词间加入零宽空格 U+200B,以支持换行。
从右到左的语言
文本元素从左到右布局。阿拉伯语、希伯来语等需要先重排字符,阿拉伯语还需处理连写。从右到左语言支持教程提供相应脚本。