135 lines
3.0 KiB
Markdown
135 lines
3.0 KiB
Markdown
|
# 国际化
|
|||
|
|
|||
|
NocoBase 国际化基于 [i18next](https://npmjs.com/package/i18next) 实现。
|
|||
|
|
|||
|
## 如何注册多语言包?
|
|||
|
|
|||
|
```ts
|
|||
|
export class MyPlugin extends Plugin {
|
|||
|
load() {
|
|||
|
this.app.i18n.addResources('zh-CN', 'test', {
|
|||
|
Hello: '你好',
|
|||
|
World: '世界',
|
|||
|
});
|
|||
|
this.app.i18n.addResources('en-US', 'test', {
|
|||
|
Hello: 'Hello',
|
|||
|
World: 'World',
|
|||
|
});
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
## 两个 i18n 实例
|
|||
|
|
|||
|
### app.i18n
|
|||
|
|
|||
|
全局 i18n 实例,一般用于 CLI 中国。
|
|||
|
|
|||
|
```ts
|
|||
|
app.i18n.t('World') // “世界”或“World”
|
|||
|
```
|
|||
|
|
|||
|
### ctx.i18n
|
|||
|
|
|||
|
全局 i18n 的 cloneInstance,每个请求的 context 完全独立,通常用于根据客户端语言响应多语言信息。
|
|||
|
|
|||
|
```ts
|
|||
|
app.use(async (ctx, next) => {
|
|||
|
ctx.body = `${ctx.i18n.t('Hello')} ${ctx.i18n.t('World')}`;
|
|||
|
await next();
|
|||
|
});
|
|||
|
```
|
|||
|
|
|||
|
客户端请求参数可以放在 query string 里
|
|||
|
|
|||
|
```bash
|
|||
|
GET /?locale=en-US HTTP/1.1
|
|||
|
Host: localhost:13000
|
|||
|
```
|
|||
|
|
|||
|
或放在 request headers 里
|
|||
|
|
|||
|
```bash
|
|||
|
GET / HTTP/1.1
|
|||
|
Host: localhost:13000
|
|||
|
X-Locale: en-US
|
|||
|
```
|
|||
|
|
|||
|
## 建议配置
|
|||
|
|
|||
|
以英文文案为 key,翻译为 value,这样的好处,即使多语言缺失,也会以英文显示,不会造成阅读障碍,如:
|
|||
|
|
|||
|
```ts
|
|||
|
i18n.addResources('zh-CN', 'your-namespace', {
|
|||
|
'Show dialog': '显示对话框',
|
|||
|
'Hide dialog': '隐藏对话框'
|
|||
|
});
|
|||
|
```
|
|||
|
|
|||
|
为了更方便管理多语言文件,推荐在插件中创建一个 `locale` 文件夹,并把对应语言文件都放置在其中方便管理:
|
|||
|
|
|||
|
```bash
|
|||
|
|- /my-plugin
|
|||
|
|- /src
|
|||
|
|- /server
|
|||
|
|- locale # 多语言文件夹
|
|||
|
|- zh-CN.ts
|
|||
|
|- en-US.ts
|
|||
|
```
|
|||
|
|
|||
|
## 示例
|
|||
|
|
|||
|
### 服务端错误提示
|
|||
|
|
|||
|
例如用户在店铺对某个商品下单时,如果商品的库存不够,或者未上架,那么下单接口被调用时,应该返回相应的错误。
|
|||
|
|
|||
|
```ts
|
|||
|
const namespace = 'shop';
|
|||
|
|
|||
|
export default class ShopPlugin extends Plugin {
|
|||
|
async load() {
|
|||
|
this.app.i18n.addResources('zh-CN', namespace, {
|
|||
|
'No such product': '商品不存在',
|
|||
|
'Product not on sale': '商品已下架',
|
|||
|
'Out of stock': '库存不足',
|
|||
|
});
|
|||
|
|
|||
|
this.app.resource({
|
|||
|
name: 'orders',
|
|||
|
actions: {
|
|||
|
async create(ctx, next) {
|
|||
|
const productRepo = ctx.db.getRepository('products');
|
|||
|
const product = await productRepo.findOne({
|
|||
|
filterByTk: ctx.action.params.values.productId
|
|||
|
});
|
|||
|
|
|||
|
if (!product) {
|
|||
|
return ctx.throw(404, ctx.t('No such product'));
|
|||
|
}
|
|||
|
|
|||
|
if (!product.enabled) {
|
|||
|
return ctx.throw(400, ctx.t('Product not on sale'));
|
|||
|
}
|
|||
|
|
|||
|
if (!product.inventory) {
|
|||
|
return ctx.throw(400, ctx.t('Out of stock'));
|
|||
|
}
|
|||
|
|
|||
|
const orderRepo = ctx.db.getRepository('orders');
|
|||
|
ctx.body = await orderRepo.create({
|
|||
|
values: {
|
|||
|
productId: product.id,
|
|||
|
quantity: 1,
|
|||
|
totalPrice: product.price,
|
|||
|
userId: ctx.state.currentUser.id
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
next();
|
|||
|
}
|
|||
|
}
|
|||
|
});
|
|||
|
}
|
|||
|
}
|
|||
|
```
|