---
title: "i18n"
description: "i18n cho Plugin NocoBase: cấu trúc thư mục locale, cách viết file dịch, cách dùng this.t / useT / tExpr trong các tình huống khác nhau."
keywords: "i18n,quốc tế hóa,locale,dịch,this.t,useT,tExpr,NocoBase"
---

# 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:

```bash
my-plugin/
└── src/
    └── locale/
        ├── zh-CN.json
        └── en-US.json
```

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

```json
// 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](https://www.i18next.com/translation-function/interpolation)
- **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:

```ts
// 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()`:

```ts
// 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:

```tsx
// 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()` và `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:

```tsx
// 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()` và `useT()` dùng để dịch khi runtime, còn `tExpr()` dùng để dịch trễ khi định nghĩa tĩnh.

:::tip 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ống | Cách dùng | Nguồn |
| --- | --- | --- |
| Trong `load()` của Plugin | `this.t('key')` | Có sẵn trong class Plugin cơ sở |
| Trong React component | `const t = useT(); t('key')` | `locale.ts` |
| Định nghĩa tĩnh của FlowModel | `tExpr('key')` | `locale.ts` |

## Liên kết liên quan

- [Context → Khả năng thường dùng → i18n](../ctx/common-capabilities#i18n-ctxt--ctxi18n) — Tham chiếu API đầy đủ của ctx.t, ctx.i18n
- [Danh sách ngôn ngữ](../../languages) — Danh sách đầy đủ mã ngôn ngữ NocoBase hỗ trợ
- [Phát triển Component](./index.md) — Sử dụng useFlowContext trong component
- [Tổng quan FlowEngine](../flow-engine/index.md) — Cách dùng tExpr trong FlowModel
