nocobase/docs/zh-CN/development/guide/i18n.md
Junyi b9ce35d621
Doc: dev i18n (#858)
* docs: add guide index table and i18n

* docs: add dev i18n sample
2022-09-29 21:04:58 +08:00

5.4 KiB

国际化

基础概念

多语言国际化支持根据服务端和客户端分为两大部分,各自有相应的实现。

语言配置均为普通的 JSON 对象键值对,如果没有翻译文件(或省略编写)的话会直接输出键名的字符串。

服务端

服务端的多语言国际化基于 npm 包 i18next 实现,在服务端应用初始化时会创建一个 i18next 实例,同时也会将此实例注入到请求上下文(context)中,以供在各处方便的使用。

在创建服务端 Application 实例时可以传入配置对应的初始化参数:

import { Application } from '@nocobase/server';

const app = new Application({
  i18n: {
    defaultNS: 'test',
    resources: {
      'en-US': {
        test: {
          hello: 'Hello',
        },
      },
      'zh-CN': {
        test: {
          hello: '你好',
        }
      }
    }
  }
})

或者在插件中对已存在的 app 实例添加语言数据至对应命名空间:

app.i18n.addResources('zh-CN', 'test', {
  Hello: '你好',
  World: '世界',
});

app.i18n.addResources('en-US', 'test', {
  Hello: 'Hello',
  World: 'World',
});

基于应用:

app.i18n.t('World') // “世界”或“World”

基于请求:

app.resource({
  name: 'test',
  actions: {
    async get(ctx, next) {
      ctx.body = `${ctx.t('Hello')} ${ctx.t('World')}`;
      await next();
    }
  }
});

通常服务端的多语言处理主要用于错误信息的输出。

客户端

客户端的多语言国际化基于 npm 包 react-i18next 实现,在应用顶层提供了 <I18nextProvider> 组件的包装,可以在任意位置直接使用相关的方法。

在组件中调用翻译函数:

import React from 'react';
import { useTranslation } from 'react-i18next';

export default function MyComponent() {
  const { t } = useTranslation();

  return (
    <div>
      <p>{t('World')}</p>
    </div>
  );
}

其中 compile() 方法是专门针对 SchemaComponent 中纯 JSON 配置提供的编译方法,执行后也可以得到对应的翻译结果。

在 Schema 组件中使用:

import React from 'react';
import { useTranslation } from 'react-i18next';
import { SchemaComponent } from '@nocobase/client';

export default function MySchemaComponent() {
  const { t } = useTranslation();
  return (
    <SchemaComponent
      schema={{
        type: 'string',
        'x-component': 'Input',
        'x-component-props': {
          value: '{{t("Hello")}}'
        },
      }}
      scope={{
        t
      }}
    />
  );
}

在 SchemaComponent 组件中使用时,可以在 schema 配置里直接使用 JSON 的可编译字符串模板表达要翻译的语言片段键名。

示例

服务端错误提示

例如用户在店铺对某个商品下单时,如果商品的库存不够,或者未上架,那么下单接口被调用时,应该返回相应的错误。

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.filterByTk
          });

          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();
        }
      }
    });
  }
}

客户端组件多语言

例如订单状态的组件,根据不同值有不同的文本显示:

import React from 'react';
import { Select } from 'antd';
import i18next from 'i18next';
import { I18nextProvider, initReactI18next, useTranslation } from 'react-i18next';

const i18n = i18next.createInstance();
i18n.use(initReactI18next).init({
  lng: 'zh-CN',
  defaultNS: 'client',
  resources: {
    'zh-CN': {
      client: {
        Pending: '已下单',
        Paid: '已支付',
        Delivered: '已发货',
        Received: '已签收'
      }
    }
  }
});

const ORDER_STATUS_LIST = [
  { value: -1, label: 'Canceled (untranslated)' },
  { value: 0, label: 'Pending' },
  { value: 1, label: 'Paid' },
  { value: 2, label: 'Delivered' },
  { value: 3, label: 'Received' },
]

function OrderStatusSelect() {
  const { t } = useTranslation();

  return (
    <Select style={{ minWidth: '8em' }}>
      {ORDER_STATUS_LIST.map(item => (
        <Select.Option value={item.value}>{t(item.label)}</Select.Option>
      ))}
    </Select>
  );
}

export default function () {
  return (
    <I18nextProvider i18n={i18n}>
      <OrderStatusSelect />
    </I18nextProvider>
  );
}