i18n

Plugin NocoBase quản lý các file đa ngôn ngữ thông qua thư mục src/locale/. Sau khi viết xong file dịch, bạn có thể lấy văn bản đã dịch bằng this.t() trong Plugin, bằng hook useT() trong component, và bằng tExpr() trong định nghĩa FlowModel.

File dịch

Trong src/locale/ của plugin, hãy tạo các file JSON theo từng ngôn ngữ. Key là văn bản gốc tiếng Anh, value là bản dịch của ngôn ngữ tương ứng:

my-plugin/
└── src/
    └── locale/
        ├── zh-CN.json
        └── en-US.json
// src/locale/zh-CN.json
{
  "Hello": "你好",
  "Save": "保存",
  "Your name is {{name}}": "你的名字是 {{name}}",
  "{{count}} February items": "{{count}} 条记录"
}
// src/locale/en-US.json
{
  "Hello": "Hello",
  "Save": "Save",
  "Your name is {{name}}": "Your name is {{name}}",
  "{{count}} February items": "{{count}} items"
}

Một số lưu ý:

  • key dùng văn bản gốc tiếng Anh, để khi thiếu bản dịch vẫn có thể fallback về tiếng Anh
  • biến dùng dấu ngoặc nhọn kép {{name}}, đồng nhất với cú pháp của i18next
  • Khi thêm file ngôn ngữ lần đầu, bạn cần khởi động lại ứng dụng để có hiệu lực; sau đó nếu sửa nội dung thì hot reload là đủ
  • NocoBase sẽ tự động dùng tên package của plugin làm namespace của bản dịch, nên các bản dịch của các plugin khác nhau sẽ không xung đột

Sử dụng trong Plugin: this.t()

Trong class Plugin, this.t() sẽ tự động inject tên package của plugin hiện tại làm namespace, bạn không cần truyền ns thủ công:

// src/client-v2/plugin.tsx
import { Plugin, Application } from '@nocobase/client-v2';

export class MyPlugin extends Plugin<any, Application> {
  async load() {
    // Tự động dùng tên package của plugin hiện tại làm ns
    console.log(this.t('Hello')); // -> "你好"

    // Khi đăng ký trang cài đặt, dùng this.t() để dịch tiêu đề
    this.pluginSettingsManager.addMenuItem({
      key: 'my-settings',
      title: this.t('My Settings'),
      icon: 'SettingOutlined',
    });
    this.pluginSettingsManager.addPageTabItem({
      menuKey: 'my-settings',
      key: 'index',
      title: this.t('My Settings'),
      componentLoader: () => import('./pages/MySettingsPage'),
    });
  }
}

Sử dụng trong Component: useT()

Trong React component, bạn không thể dùng trực tiếp this.t(). Plugin scaffold sẽ tự động sinh ra một file locale.ts, trong đó cung cấp hook useT():

// src/client-v2/locale.ts (scaffold tự sinh)
import { tExpr as _tExpr, useFlowEngine } from '@nocobase/flow-engine';
// @ts-ignore
import pkg from './../../package.json';

export function useT() {
  const engine = useFlowEngine();
  return (str: string) => engine.context.t(str, { ns: [pkg.name, 'client'] });
}

export function tExpr(key: string) {
  return _tExpr(key, { ns: [pkg.name, 'client'] });
}

Trong component bạn dùng như sau:

// src/client-v2/pages/MySettingsPage.tsx
import React from 'react';
import { Button } from 'antd';
import { useT } from '../locale';

export default function MySettingsPage() {
  const t = useT();

  return (
    <div>
      <h2>{t('Hello')}</h2>
      <p>{t('Your name is {{name}}', { name: 'NocoBase' })}</p>
      <Button>{t('Save')}</Button>
    </div>
  );
}

Hàm t được trả về bởi useT() đã bind sẵn namespace của plugin, bạn chỉ cần truyền key trực tiếp.

Sử dụng trong FlowModel: tExpr()

FlowModel.define()registerFlow() được thực thi khi module được load — lúc đó i18n chưa được khởi tạo nên không thể gọi trực tiếp t(). Trong tình huống này hãy dùng tExpr() — nó sinh ra một chuỗi biểu thức dịch trễ, và sẽ được resolve khi runtime:

// src/client-v2/models/MyBlockModel.tsx
import { BlockModel } from '@nocobase/client-v2';
import { tExpr } from '../locale';

export class MyBlockModel extends BlockModel {
  renderComponent() {
    return <div>My custom block</div>;
  }
}

// tExpr sinh ra chuỗi kiểu '{{t("My block")}}', dịch khi runtime
MyBlockModel.define({
  label: tExpr('My block'),
});

MyBlockModel.registerFlow({
  key: 'flow1',
  title: tExpr('My Block Settings'),
  on: 'beforeRender',
  steps: {
    editTitle: {
      title: tExpr('Edit Title'),
      uiSchema: {
        title: {
          type: 'string',
          title: tExpr('Title'),
          'x-decorator': 'FormItem',
          'x-component': 'Input',
        },
      },
      handler(ctx, params) {
        ctx.model.props.title = params.title;
      },
    },
  },
});

Nói đơn giản: this.t()useT() dùng để dịch khi runtime, còn tExpr() dùng để dịch trễ khi định nghĩa tĩnh.

Mẹo

tExpr có hai nguồn: locale.ts được plugin tự sinh và @nocobase/flow-engine. Khác biệt là tExpr trong locale.ts đã bind sẵn tên package của plugin làm namespace, còn tExpr import trực tiếp từ @nocobase/flow-engine thì không có namespace bind sẵn. Trong code plugin, luôn dùng tExpr được export từ locale.ts, để bản dịch có thể khớp đúng với file ngôn ngữ của chính plugin.

Bảng tra cứu nhanh ba cách dùng

Tình huốngCách dùngNguồn
Trong load() của Pluginthis.t('key')Có sẵn trong class Plugin cơ sở
Trong React componentconst t = useT(); t('key')locale.ts
Định nghĩa tĩnh của FlowModeltExpr('key')locale.ts

Liên kết liên quan