@tailor-platform/app-shell 1.17.0 → 1.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app-shell-core.css +1 -1
- package/dist/app-shell.d.ts +228 -103
- package/dist/app-shell.js +4236 -3585
- package/dist/{appshell-CahhZy7v.js → appshell-Y1AYCuVJ.js} +10 -10
- package/dist/semantic-contrast.test.ts +73 -0
- package/dist/style-entry.d.ts +102 -102
- package/dist/testing.d.ts +102 -102
- package/dist/testing.js +1 -1
- package/dist/theme-bridge.test.ts +30 -7
- package/dist/theme.bridge.css +127 -40
- package/dist/theme.css +4 -3
- package/dist/themes/_template.css +5 -6
- package/dist/themes/bloom.css +0 -2
- package/dist/themes/cream.css +0 -2
- package/dist/themes/default.css +112 -77
- package/dist/vite-plugin.d.ts +102 -102
- package/package.json +2 -2
- package/skills/app-shell-patterns/SKILL.md +41 -32
- package/skills/app-shell-patterns/references/api/define-i18n-labels.md +123 -91
- package/skills/app-shell-patterns/references/api/define-module.md +2 -4
- package/skills/app-shell-patterns/references/components/data-table.md +332 -25
- package/skills/app-shell-patterns/references/components/sidebar-group.md +6 -6
- package/skills/app-shell-patterns/references/components/text.md +198 -0
- package/skills/app-shell-patterns/references/concepts/styling-theming.md +30 -14
- package/skills/app-shell-patterns/references/migrations.md +22 -1
- package/skills/app-shell-patterns/references/patterns/i18n-labels.md +139 -0
- package/skills/app-shell-patterns/references/patterns/list-dense-scan.md +3 -2
|
@@ -1,143 +1,175 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: defineI18nLabels
|
|
3
|
-
description:
|
|
3
|
+
description: Type-safe locale-first labels for component text and localized navigation metadata
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
<!-- GENERATED by docs-kit from docs-src/hooks/define-i18n-labels.docs.outline.md — do not edit by hand. -->
|
|
7
7
|
|
|
8
8
|
# defineI18nLabels
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
Defines a label catalog and returns two resolvers: a React Hook for rendered text and a `LocalizedString` factory for AppShell configuration. No additional i18n package is required.
|
|
11
11
|
|
|
12
|
-
##
|
|
12
|
+
## Define a Catalog
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
Create the catalog once at module scope, not inside a component. The object is **locale-first**: `en` is the required base locale; each locale contains a flat map of label keys to strings or functions that return strings.
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
// i18n-labels.ts
|
|
18
|
+
import { defineI18nLabels } from "@tailor-platform/app-shell";
|
|
19
|
+
|
|
20
|
+
export const labels = defineI18nLabels({
|
|
21
|
+
en: {
|
|
22
|
+
title: "Requests",
|
|
23
|
+
save: "Save",
|
|
24
|
+
greeting: ({ name }: { name: string }) => `Hello, ${name}!`,
|
|
25
|
+
itemCount: ({ count }: { count: number }) => (count === 1 ? "1 item" : `${count} items`),
|
|
26
|
+
"status.PENDING": "Pending",
|
|
27
|
+
unknownStatus: "Unknown status",
|
|
28
|
+
},
|
|
29
|
+
ja: {
|
|
30
|
+
title: "申請一覧",
|
|
31
|
+
save: "保存",
|
|
32
|
+
greeting: ({ name }: { name: string }) => `こんにちは、${name}さん!`,
|
|
33
|
+
itemCount: ({ count }: { count: number }) => `${count}件`,
|
|
34
|
+
"status.PENDING": "承認待ち",
|
|
35
|
+
unknownStatus: "不明な状態",
|
|
36
|
+
},
|
|
37
|
+
});
|
|
19
38
|
|
|
20
|
-
|
|
39
|
+
export const useT = labels.useT;
|
|
40
|
+
```
|
|
21
41
|
|
|
22
|
-
|
|
42
|
+
`useT` above is exported by **your catalog module**, not directly by `@tailor-platform/app-shell`. The component examples below use this shared setup; import `labels` and `useT` from `./i18n-labels` in application files.
|
|
23
43
|
|
|
24
|
-
|
|
25
|
-
- **Required:** Yes
|
|
26
|
-
- **Description:** Object mapping label keys to locale-specific strings
|
|
44
|
+
Label keys are flat strings, not nested objects. A dotted key such as `"status.PENDING"` is an ordinary flat string key.
|
|
27
45
|
|
|
28
46
|
## Return Value
|
|
29
47
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
48
|
+
| Member | Returns | Use for |
|
|
49
|
+
| -------------------------- | -------------------------------------- | ---------------------------------------------------------------------------------------- |
|
|
50
|
+
| `labels.useT()` | A resolver `t` that returns a `string` | Text rendered by a React component, string-valued props, toasts, and validation messages |
|
|
51
|
+
| `t(key, props?)` | `string` | A known label key, with required props for function-valued labels |
|
|
52
|
+
| `t.dynamic(key, fallback)` | `string` | A runtime string key for a **static** label |
|
|
53
|
+
| `labels.t(key, props?)` | `LocalizedString` | `meta.title`, `meta.breadcrumbTitle`, and props explicitly accepting `LocalizedString` |
|
|
54
|
+
|
|
55
|
+
`LocalizedString` is `string | ((locale: string) => string)`. `labels.t()` creates a locale callback, **not rendered text**. Do not put its result directly into JSX children or pass it to a prop that accepts only `string`.
|
|
56
|
+
|
|
57
|
+
## In Components: `useT()`
|
|
58
|
+
|
|
59
|
+
Call the Hook at the top level of a component under `AppShell`. The returned resolver reads the current AppShell locale.
|
|
60
|
+
|
|
61
|
+
<example-preview name="in-components"></example-preview>
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
function InComponents() {
|
|
65
|
+
const t = useT();
|
|
66
|
+
|
|
67
|
+
return <Button>{t("save")}</Button>;
|
|
33
68
|
}
|
|
34
69
|
```
|
|
35
70
|
|
|
36
|
-
|
|
71
|
+
Use the same resolver for string props such as `placeholder`, `aria-label`, or `Layout.Header`'s `title`, and inside event handlers for localized notifications. Do not call a Hook in an event handler; capture `t` during render.
|
|
37
72
|
|
|
38
|
-
|
|
73
|
+
## Function-valued Labels: Typed Props
|
|
39
74
|
|
|
40
|
-
|
|
41
|
-
import { defineI18nLabels } from "@tailor-platform/app-shell";
|
|
75
|
+
Use a function for a whole sentence containing runtime values. Its props are inferred from the English definition and checked for the other declared locales. Keep pluralization and word order inside each locale's function rather than concatenating translated fragments.
|
|
42
76
|
|
|
43
|
-
|
|
44
|
-
dashboard: {
|
|
45
|
-
en: "Dashboard",
|
|
46
|
-
ja: "ダッシュボード",
|
|
47
|
-
},
|
|
48
|
-
products: {
|
|
49
|
-
en: "Products",
|
|
50
|
-
ja: "商品",
|
|
51
|
-
},
|
|
52
|
-
settings: {
|
|
53
|
-
en: "Settings",
|
|
54
|
-
ja: "設定",
|
|
55
|
-
},
|
|
56
|
-
});
|
|
77
|
+
<example-preview name="dynamic-labels"></example-preview>
|
|
57
78
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
})
|
|
79
|
+
```tsx
|
|
80
|
+
function DynamicLabels() {
|
|
81
|
+
const t = useT();
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<Layout>
|
|
85
|
+
<Layout.Header title={t("greeting", { name: "Alex" })} />
|
|
86
|
+
<Layout.Column>{t("itemCount", { count: 3 })}</Layout.Column>
|
|
87
|
+
</Layout>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
66
90
|
```
|
|
67
91
|
|
|
68
|
-
|
|
92
|
+
Both `t("greeting", { name })` and `labels.t("greeting", { name })` require the correct props. When using `labels.t()` for metadata, props are bound **when `labels.t()` is called**, not fetched at render time. For a breadcrumb depending on a URL segment, see [Routing & Navigation](../concepts/routing-navigation.md#dynamic-breadcrumb-titles).
|
|
69
93
|
|
|
70
|
-
|
|
71
|
-
const labels = defineI18nLabels({
|
|
72
|
-
welcome: {
|
|
73
|
-
en: "Welcome",
|
|
74
|
-
ja: "ようこそ",
|
|
75
|
-
es: "Bienvenido",
|
|
76
|
-
fr: "Bienvenue",
|
|
77
|
-
},
|
|
78
|
-
});
|
|
79
|
-
```
|
|
94
|
+
## Runtime Keys: `t.dynamic()`
|
|
80
95
|
|
|
81
|
-
|
|
96
|
+
`t.dynamic(key: string, fallback: string)` accepts a key constructed at runtime, for example from a server enum. Keep the enum stable and translate only its display label.
|
|
82
97
|
|
|
83
|
-
|
|
84
|
-
function WelcomeMessage() {
|
|
85
|
-
const labels = defineI18nLabels({
|
|
86
|
-
greeting: {
|
|
87
|
-
en: "Hello",
|
|
88
|
-
ja: "こんにちは",
|
|
89
|
-
},
|
|
90
|
-
});
|
|
98
|
+
<example-preview name="runtime-keys"></example-preview>
|
|
91
99
|
|
|
92
|
-
|
|
100
|
+
```tsx
|
|
101
|
+
function RuntimeKeys() {
|
|
102
|
+
const t = useT();
|
|
103
|
+
const status: string = "PENDING";
|
|
104
|
+
|
|
105
|
+
return <Badge variant="warning">{t.dynamic(`status.${status}`, t("unknownStatus"))}</Badge>;
|
|
93
106
|
}
|
|
94
107
|
```
|
|
95
108
|
|
|
96
|
-
|
|
109
|
+
Resolution order:
|
|
110
|
+
|
|
111
|
+
1. Look up the key in the current locale.
|
|
112
|
+
2. If absent, look it up in `en`.
|
|
113
|
+
3. If the resolved value is missing **or is a function**, return the supplied fallback unchanged.
|
|
97
114
|
|
|
98
|
-
|
|
99
|
-
import { SidebarGroup } from "@tailor-platform/app-shell";
|
|
115
|
+
The fallback is not translated automatically. Prefer another translated label, as above, instead of an English literal. `t.dynamic()` does not accept label props; use typed `t(key, props)` for function-valued labels. Prefer typed `t(key)` whenever the key is known at compile time.
|
|
100
116
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
117
|
+
## Navigation Metadata: `labels.t()`
|
|
118
|
+
|
|
119
|
+
Module and resource definitions are usually outside React components, so they cannot call Hooks. Pass the locale callback to AppShell, which resolves it using the active locale:
|
|
120
|
+
|
|
121
|
+
```tsx
|
|
122
|
+
import { defineModule } from "@tailor-platform/app-shell";
|
|
123
|
+
import { labels } from "./i18n-labels";
|
|
124
|
+
|
|
125
|
+
const requestModule = defineModule({
|
|
126
|
+
path: "requests",
|
|
127
|
+
meta: {
|
|
128
|
+
title: labels.t("title"),
|
|
129
|
+
breadcrumbTitle: labels.t("title"),
|
|
105
130
|
},
|
|
131
|
+
component: InComponents,
|
|
132
|
+
resources: [],
|
|
106
133
|
});
|
|
107
|
-
|
|
108
|
-
<SidebarGroup title={labels.t("admin")}>
|
|
109
|
-
<SidebarItem to="/admin/users" />
|
|
110
|
-
</SidebarGroup>
|
|
111
134
|
```
|
|
112
135
|
|
|
113
|
-
|
|
136
|
+
The same approach works for `defineResource().meta` and `SidebarGroup`, `SidebarSection`, and `SidebarItem` title overrides. For ordinary string-valued component props, use the Hook resolver instead.
|
|
114
137
|
|
|
115
|
-
|
|
138
|
+
## Setting Locale and Fallbacks
|
|
116
139
|
|
|
117
|
-
|
|
118
|
-
|
|
140
|
+
Choose the app's UI language explicitly when it is fixed:
|
|
141
|
+
|
|
142
|
+
```tsx
|
|
143
|
+
<AppShell locale="ja-JP" modules={[requestModule]}>
|
|
119
144
|
<SidebarLayout />
|
|
120
145
|
</AppShell>
|
|
121
146
|
```
|
|
122
147
|
|
|
123
|
-
|
|
148
|
+
- AppShell normalizes a BCP-47 tag such as `ja-JP` to `ja` for label lookup. Use language keys such as `ja` in the catalog, not `ja-JP`.
|
|
149
|
+
- If `locale` is omitted, AppShell uses the browser's preferred language. If browser locale information is unavailable or invalid, it uses `en`.
|
|
150
|
+
- If a catalog has no definition for the selected language, labels fall back to `en`. A missing localized key also falls back to its English value at runtime.
|
|
151
|
+
- Custom catalogs can define other languages such as `fr`; this does **not** translate AppShell's built-in UI. Its shared built-in catalog currently supplies English and Japanese, with English fallback for other languages.
|
|
152
|
+
- For regional date and number formatting, use the full tag from [useResolvedLocale](./use-resolved-locale.md); label translation does not format dates, numbers, or time zones automatically.
|
|
124
153
|
|
|
125
|
-
|
|
154
|
+
## TypeScript Contract
|
|
126
155
|
|
|
127
|
-
|
|
156
|
+
With an ordinary inferred definition such as the catalog above:
|
|
128
157
|
|
|
129
|
-
|
|
158
|
+
- `en` is required, even for an app whose selected UI language is Japanese.
|
|
159
|
+
- Other explicitly declared locales must supply all base keys and compatible function props. Translation omissions become compile errors.
|
|
160
|
+
- `t()` and `labels.t()` reject unknown keys, missing or wrongly typed function props, and props passed to static labels.
|
|
161
|
+
- `t.dynamic()` deliberately accepts any string key; unknown keys are handled by its required fallback instead of compile-time validation.
|
|
130
162
|
|
|
131
|
-
|
|
132
|
-
const labels = defineI18nLabels({
|
|
133
|
-
dashboard: { en: "Dashboard", ja: "ダッシュボード" },
|
|
134
|
-
products: { en: "Products", ja: "商品" },
|
|
135
|
-
});
|
|
163
|
+
These checks validate structure and call sites, not translation quality or language consistency.
|
|
136
164
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
165
|
+
## Exports
|
|
166
|
+
|
|
167
|
+
- `I18nLabels` — type
|
|
168
|
+
- `defineI18nLabels` — const
|
|
140
169
|
|
|
141
170
|
## Related
|
|
142
171
|
|
|
143
|
-
- [
|
|
172
|
+
- [i18n Labels Pattern](../patterns/i18n-labels.md) — UI language decisions, key naming, file splitting, and server-originated text
|
|
173
|
+
- [defineModule](./define-module.md) — localized navigation metadata
|
|
174
|
+
- [defineResource](./define-resource.md) — localized resource metadata
|
|
175
|
+
- [useResolvedLocale](./use-resolved-locale.md) — full locale for formatting
|
|
@@ -209,10 +209,8 @@ const productsModule = defineModule({
|
|
|
209
209
|
import { defineI18nLabels } from "@tailor-platform/app-shell";
|
|
210
210
|
|
|
211
211
|
const labels = defineI18nLabels({
|
|
212
|
-
dashboard:
|
|
213
|
-
|
|
214
|
-
ja: "ダッシュボード",
|
|
215
|
-
},
|
|
212
|
+
en: { dashboard: "Dashboard" },
|
|
213
|
+
ja: { dashboard: "ダッシュボード" },
|
|
216
214
|
});
|
|
217
215
|
|
|
218
216
|
const dashboardModule = defineModule({
|