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.
Translating
<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>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>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.
pagination: {
showing: 'Showing {from} to {to} of {total} entries',
total: { one: '{count} item', other: '{count} items' },
}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:
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)
createThl({
locales: ['en', 'ja', 'vi', 'zh', 'ko'],
loadLocale: (l) => import(`./locales/${l}.ts`), // returns Messages or { default: Messages }
});
thl.setLocale('ko'); // loads, merges, then switchesCopy @thl-ui/locales/src/en.ts as a template. Type ThlMessages lets you check completeness:
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
| Namespace | Used 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…) |