Defines internationalization labels for multiple locales. The en locale is required as the default locale. Labels can be either static strings or dynamic functions that take props.
Signature
function defineI18nLabels<
const L extends Exclude<string, "en">,
const Def extends Record<string, LabelValue>
>(labels: I18nLabels<Def, L>): {
useT: () => TFunction;
t: <K extends keyof Def>(key: K, ...args: TFunctionArgs<K>) => LocalizedString;
}
Parameters
Object with locale keys containing label definitions. The en locale is required.Each locale object contains:
- Static string labels:
key: "Label text"
- Dynamic function labels:
key: (props: T) => string
All locales must have the same label keys as the en locale. For dynamic labels (functions), the props type must match across all locales.
Return Type
React hook that returns a translator function for use in components.The translator function has two forms:
t(key) - For static labels
t(key, props) - For dynamic labels with props
t.dynamic(key, fallback) - For runtime-constructed keys with fallback
t
<K extends keyof Def>(key: K, ...args: TFunctionArgs<K>) => LocalizedString
Function to get a LocalizedString for use in module/resource meta.title.Note: When using dynamic labels with props in meta.title, the props are bound at definition time (when calling labels.t), not at render time.
Examples
Basic Labels Definition
import { defineI18nLabels } from "@tailor-platform/app-shell";
export const labels = defineI18nLabels({
en: {
welcome: "Welcome to our app",
hello: "Hello",
goodbye: "Goodbye",
},
ja: {
welcome: "アプリへようこそ",
hello: "こんにちは",
goodbye: "さようなら",
},
});
// Export useT hook for use in components
export const useT = labels.useT;
Dynamic Labels with Props
import { defineI18nLabels } from "@tailor-platform/app-shell";
export const labels = defineI18nLabels({
en: {
greeting: (props: { name: string }) => `Hello, ${props.name}!`,
itemCount: (props: { count: number }) => `${props.count} items`,
welcome: (props: { name: string; role: string }) =>
`Welcome, ${props.name} (${props.role})`,
},
ja: {
greeting: (props: { name: string }) => `こんにちは、${props.name}さん!`,
itemCount: (props: { count: number }) => `${props.count}個のアイテム`,
welcome: (props: { name: string; role: string }) =>
`ようこそ、${props.name}さん (${props.role})`,
},
});
export const useT = labels.useT;
Using in Components
import { useT } from "./i18n-labels";
const MyComponent = ({ userName, itemCount }: { userName: string; itemCount: number }) => {
const t = useT();
return (
<div>
{/* Static label */}
<h1>{t("welcome")}</h1>
{/* Dynamic labels with type-safe props */}
<p>{t("greeting", { name: userName })}</p>
<p>{t("itemCount", { count: itemCount })}</p>
</div>
);
};
Dynamic Label Resolution
Use t.dynamic() for runtime-constructed keys:
import { useT } from "./i18n-labels";
// First define labels with possible keys:
const labels = defineI18nLabels({
en: {
"employees.STAFF": "Staff",
"employees.MANAGER": "Manager",
"employees.ADMIN": "Admin",
},
ja: {
"employees.STAFF": "スタッフ",
"employees.MANAGER": "マネージャー",
"employees.ADMIN": "管理者",
},
});
const EmployeeType = ({ type }: { type: string }) => {
const t = useT();
return (
<div>
{/* Dynamic key resolution with fallback */}
<p>Type: {t.dynamic(`employees.${type}`, "Unknown")}</p>
</div>
);
};
Using in Module/Resource Definitions
import { defineModule, defineResource } from "@tailor-platform/app-shell";
import { labels } from "./i18n-labels";
const dashboardModule = defineModule({
path: "dashboard",
meta: {
// Static label for meta.title
title: labels.t("dashboardTitle"),
},
component: DashboardPage,
resources: [
defineResource({
path: "settings",
meta: {
// Labels are resolved based on current locale
title: labels.t("settingsTitle"),
},
component: SettingsPage,
})
],
});
Multiple Locales
import { defineI18nLabels } from "@tailor-platform/app-shell";
export const labels = defineI18nLabels({
en: {
save: "Save",
cancel: "Cancel",
delete: "Delete",
},
ja: {
save: "保存",
cancel: "キャンセル",
delete: "削除",
},
fr: {
save: "Enregistrer",
cancel: "Annuler",
delete: "Supprimer",
},
de: {
save: "Speichern",
cancel: "Abbrechen",
delete: "Löschen",
},
});
export const useT = labels.useT;
Complete Example
// i18n-labels.ts
import { defineI18nLabels } from "@tailor-platform/app-shell";
export const labels = defineI18nLabels({
en: {
// Static labels
appTitle: "Product Management",
orders: "Orders",
products: "Products",
// Dynamic labels
welcomeMessage: (props: { name: string }) => `Welcome, ${props.name}!`,
orderCount: (props: { count: number }) =>
props.count === 1 ? "1 order" : `${props.count} orders`,
},
ja: {
appTitle: "製品管理",
orders: "注文",
products: "製品",
welcomeMessage: (props: { name: string }) => `ようこそ、${props.name}さん!`,
orderCount: (props: { count: number }) => `${props.count}件の注文`,
},
});
export const useT = labels.useT;
// app.tsx
import { AppShell, defineModule } from "@tailor-platform/app-shell";
import { labels, useT } from "./i18n-labels";
const modules = [
defineModule({
path: "orders",
meta: {
title: labels.t("orders"),
},
component: ({ title }) => {
const t = useT();
return (
<div>
<h1>{title}</h1>
<p>{t("welcomeMessage", { name: "John" })}</p>
<p>{t("orderCount", { count: 5 })}</p>
</div>
);
},
resources: [],
}),
];
const App = () => (
<AppShell modules={modules} locale="en">
<SidebarLayout />
</AppShell>
);
TypeScript Types
// Label value can be either static or dynamic
type LabelValue = string | ((props: any) => string);
// I18n labels structure
type I18nLabels<
Def extends Record<string, LabelValue>,
L extends string
> = {
en: Def;
} & { [K in L]: LabelDefinition<Def> } & DynamicLocales<Def>;
// Ensures other locales match the base definition
type LabelDefinition<Def extends Record<string, LabelValue>> = {
[K in keyof Def]: Def[K] extends (props: infer P) => string
? (props: P) => string
: string;
};
// Translator function type
type TFunction = (<K extends keyof Def>(
key: K,
...args: TFunctionArgs<K>
) => string) & {
dynamic: (key: string, fallback: string) => string;
};
Notes
- The
en locale is required and serves as the fallback
- All locales must have the same label keys as the
en locale
- For dynamic labels (functions), the props type must match across all locales
- The browser’s language preference is automatically detected to select the appropriate locale
- Falls back to
en if the detected locale is not defined
- When using dynamic labels with props in
meta.title, the props are bound at definition time, not at render time