Skip to content

i18n ​

thl-ui ships a small, dependency-free i18n engine in @thl-ui/core with built-in messages for English (en), Tiếng Việt (vi), 日本語 (ja) and 简体中文 (zh). Every built-in string (pagination, table, date picker, validation, upload, layout, menu, page templates…) is translated.

Live: switch locale

Translating ​

vue
<script setup lang="ts">
import { useI18n } from '@thl-ui/vue';
const { t, n, d, locale, setLocale } = useI18n();
</script>
<template>
  <p>{{ t('pagination.total', { count: 3 }) }}</p>
  <p>{{ n(1234.5, { style: 'currency', currency: 'USD' }) }} · {{ d(new Date()) }}</p>
  <button @click="setLocale('ja')">日本語</button>
  <!-- or $t('common.save') anywhere -->
</template>
tsx
import { useT, useLocale } from '@thl-ui/react';
const t = useT();
const [locale, setLocale] = useLocale();
<p>{t('pagination.total', { count: 3 })}</p>
<p>{t.n(1234.5, { style: 'currency', currency: 'USD' })} · {t.d(new Date())}</p>
<button onClick={() => setLocale('ja')}>日本語</button>
ts
import { ThlI18nService, ThlTranslatePipe } from '@thl-ui/angular';
readonly i18n = inject(ThlI18nService);
// template:
//   {{ 'pagination.total' | thlT: { count: 3 } }}
//   {{ i18n.n(1234.5, { style: 'currency', currency: 'USD' }) }}
//   <button (click)="i18n.setLocale('ja')">日本語</button>

Interpolation & plurals ​

Messages use {name} placeholders. Plurals are objects keyed by Intl.PluralRules categories (zero is optional and used when count === 0). count is formatted with the locale's number format.

ts
pagination: {
  showing: 'Showing {from} to {to} of {total} entries',
  total: { one: '{count} item', other: '{count} items' },
}
ts
t('pagination.total', { count: 1 })     // "1 item"
t('pagination.total', { count: 1200 })  // "1,200 items"  (vi: "1.200 mục")

Adding / overriding messages ​

Pass messages in config — they are deep-merged with the built-ins:

ts
createThl({
  locale: 'en',
  messages: {
    vi: { common: { save: 'Lưu lại' }, app: { welcome: 'Xin chào, {name}!' } },
    en: { app: { welcome: 'Welcome, {name}!' } },
  },
});

At runtime: thl.i18n.mergeMessages('vi', { app: { bye: 'Tạm biệt' } }) — components re-render automatically.

Adding a new locale (lazy) ​

ts
createThl({
  locales: ['en', 'ja', 'vi', 'zh', 'ko'],
  loadLocale: (l) => import(`./locales/${l}.ts`),   // returns Messages or { default: Messages }
});
thl.setLocale('ko'); // loads, merges, then switches

Copy @thl-ui/locales/src/en.ts as a template. Type ThlMessages lets you check completeness:

ts
import type { ThlMessages } from '@thl-ui/locales';
const ko: ThlMessages = { /* TypeScript reports missing keys */ };

Right-to-left locales (ar, he, fa, ur) set dir="rtl" on <html>; styles use logical properties so layouts mirror automatically.

Number & date formatting ​

n() and d() wrap Intl.NumberFormat / Intl.DateTimeFormat using the mapped Intl locale (vi → vi-VN, ja → ja-JP, zh → zh-CN, en → en-US). Date pickers, calendars, tables and charts use the same formatter, so switching locale changes month names, week starts and separators everywhere.

Message namespaces ​

NamespaceUsed by
common.*ok, cancel, save, search, loading, empty…
pagination.*, table.*Pagination, DataTable
select.*, datepicker.*, upload.*form controls
validation.*rules.* error messages
modal.*, toast.*, steps.*, session.*feedback components
layout.*, menu.*AppLayout, Topbar, SettingsPanel, side menu
pages.*page templates (login, inbox, invoice, pricing, errors…)