fimo 0.2.4 → 0.2.5-experimental.1782379858530
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/README.md +2 -2
- package/assets/agent-templates/content-translator/GOAL.md +8 -11
- package/assets/agent-templates/content-translator/capabilities.yaml +1 -3
- package/assets/agent-templates/content-translator/scripts/translate-entries.ts +10 -7
- package/assets/content-templates/hooks-template.eta +89 -28
- package/assets/skills/fimo/SKILL.md +10 -8
- package/assets/skills/fimo/assets/react-router-locales/navigation.tsx +13 -0
- package/assets/skills/fimo/assets/react-router-locales/routes.ts +11 -0
- package/assets/skills/fimo/references/content.md +36 -2
- package/assets/skills/fimo/references/forms.md +1 -1
- package/assets/skills/fimo/references/labels.md +106 -0
- package/assets/skills/fimo/references/locales.md +138 -0
- package/assets/skills/fimo/references/setup-plain-vite.md +1 -1
- package/assets/skills/fimo/references/setup-react-router.md +9 -3
- package/assets/skills/fimo/references/ui.md +16 -15
- package/assets/skills/fimo-cli/SKILL.md +6 -5
- package/assets/skills/fimo-cli/references/content.md +30 -3
- package/assets/skills/fimo-cli/references/create.md +1 -1
- package/assets/skills/fimo-cli/references/forms.md +2 -2
- package/assets/skills/fimo-cli/references/labels.md +71 -0
- package/assets/skills/fimo-cli/references/locales.md +104 -0
- package/assets/skills/fimo-migration/scripts/build-canonical-manifest.mjs +14 -11
- package/assets/skills/fimo-migration/scripts/write-migration-report.mjs +19 -8
- package/dist/build/vite/plugins/fimo-config.d.ts.map +1 -1
- package/dist/build/vite/plugins/fimo-config.js +16 -0
- package/dist/build/vite/plugins/fimo-config.test.d.ts +2 -0
- package/dist/build/vite/plugins/fimo-config.test.d.ts.map +1 -0
- package/dist/build/vite/plugins/fimo-config.test.js +46 -0
- package/dist/build/vite/plugins/translations.d.ts +8 -6
- package/dist/build/vite/plugins/translations.d.ts.map +1 -1
- package/dist/build/vite/plugins/translations.js +406 -33
- package/dist/build/vite/plugins/translations.test.d.ts +2 -0
- package/dist/build/vite/plugins/translations.test.d.ts.map +1 -0
- package/dist/build/vite/plugins/translations.test.js +197 -0
- package/dist/cli/bundle.json +2 -2
- package/dist/cli/index.js +1449 -1095
- package/dist/runtime/app/FimoProviders.d.ts +4 -2
- package/dist/runtime/app/FimoProviders.d.ts.map +1 -1
- package/dist/runtime/app/FimoProviders.js +30 -5
- package/dist/runtime/app/FimoScripts.d.ts.map +1 -1
- package/dist/runtime/app/FimoScripts.js +35 -1
- package/dist/runtime/app/index.d.ts +5 -1
- package/dist/runtime/app/index.d.ts.map +1 -1
- package/dist/runtime/app/index.js +2 -0
- package/dist/runtime/app/prefetch.d.ts +1 -0
- package/dist/runtime/app/prefetch.d.ts.map +1 -1
- package/dist/runtime/app/prefetch.js +12 -3
- package/dist/runtime/i18n/FimoLocaleContext.d.ts +9 -0
- package/dist/runtime/i18n/FimoLocaleContext.d.ts.map +1 -0
- package/dist/runtime/i18n/FimoLocaleContext.js +17 -0
- package/dist/runtime/i18n/locale.d.ts +23 -0
- package/dist/runtime/i18n/locale.d.ts.map +1 -0
- package/dist/runtime/i18n/locale.js +112 -0
- package/dist/runtime/i18n/locale.test.d.ts +2 -0
- package/dist/runtime/i18n/locale.test.d.ts.map +1 -0
- package/dist/runtime/i18n/locale.test.js +58 -0
- package/dist/runtime/index.d.ts +4 -0
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js +2 -0
- package/dist/runtime/paths/get-fimo-paths.d.ts +3 -0
- package/dist/runtime/paths/get-fimo-paths.d.ts.map +1 -1
- package/dist/runtime/paths/get-fimo-paths.js +51 -34
- package/dist/runtime/paths/types.d.ts +2 -0
- package/dist/runtime/paths/types.d.ts.map +1 -1
- package/dist/runtime/primitives/components/Text.d.ts +1 -1
- package/dist/runtime/primitives/components/Text.js +1 -1
- package/dist/runtime/primitives/index.d.ts +1 -1
- package/dist/runtime/primitives/index.d.ts.map +1 -1
- package/dist/runtime/primitives/index.js +2 -2
- package/dist/runtime/primitives/lib/query.d.ts +15 -4
- package/dist/runtime/primitives/lib/query.d.ts.map +1 -1
- package/dist/runtime/primitives/lib/template.d.ts +1 -1
- package/dist/runtime/primitives/lib/template.js +1 -1
- package/dist/runtime/primitives/translations.d.ts +19 -5
- package/dist/runtime/primitives/translations.d.ts.map +1 -1
- package/dist/runtime/primitives/translations.js +48 -11
- package/dist/runtime/react-router/index.d.ts +8 -1
- package/dist/runtime/react-router/index.d.ts.map +1 -1
- package/dist/runtime/react-router/index.js +4 -0
- package/dist/runtime/react-router/navigation-utils.d.ts +8 -0
- package/dist/runtime/react-router/navigation-utils.d.ts.map +1 -0
- package/dist/runtime/react-router/navigation-utils.js +36 -0
- package/dist/runtime/react-router/navigation.d.ts +12 -0
- package/dist/runtime/react-router/navigation.d.ts.map +1 -0
- package/dist/runtime/react-router/navigation.js +28 -0
- package/dist/runtime/react-router/navigation.test.d.ts +2 -0
- package/dist/runtime/react-router/navigation.test.d.ts.map +1 -0
- package/dist/runtime/react-router/navigation.test.js +35 -0
- package/dist/runtime/react-router/routes.d.ts +15 -0
- package/dist/runtime/react-router/routes.d.ts.map +1 -0
- package/dist/runtime/react-router/routes.js +71 -0
- package/dist/runtime/react-router/routes.test.d.ts +2 -0
- package/dist/runtime/react-router/routes.test.d.ts.map +1 -0
- package/dist/runtime/react-router/routes.test.js +67 -0
- package/dist/runtime/seo/htmlProps.d.ts +5 -2
- package/dist/runtime/seo/htmlProps.d.ts.map +1 -1
- package/dist/runtime/seo/htmlProps.js +6 -5
- package/dist/runtime/seo/index.d.ts +1 -0
- package/dist/runtime/seo/index.d.ts.map +1 -1
- package/dist/runtime/seo/index.js +3 -2
- package/dist/runtime/shared/fimo-config.server.d.ts.map +1 -1
- package/dist/runtime/shared/fimo-config.server.js +1 -0
- package/dist/runtime/shared/fimo-config.types.d.ts +23 -0
- package/dist/runtime/shared/fimo-config.types.d.ts.map +1 -1
- package/dist/scripts/extract-translations.d.ts +8 -8
- package/dist/scripts/extract-translations.js +22 -57
- package/dist/scripts/lib/parse-routes-file.js +4 -3
- package/dist/scripts/lint-translation-keys.js +24 -5
- package/dist/scripts/lint-translation-keys.test.d.ts +1 -0
- package/dist/scripts/lint-translation-keys.test.js +16 -0
- package/dist/scripts/validate-route-metadata.js +104 -0
- package/dist/scripts/validate-route-metadata.test.d.ts +1 -0
- package/dist/scripts/validate-route-metadata.test.js +116 -0
- package/package.json +5 -1
- package/release.json +2 -2
- package/scripts/lib/postinstall-onboarding.mjs +3 -1
- package/scripts/postinstall.mjs +2 -4
- package/scripts/publish-npm.mjs +3 -1
- package/templates/react-router/fimo-config.json +9 -0
- package/templates/react-router/package.json +1 -1
- package/templates/react-router/src/index.css +11 -5
- package/templates/react-router/src/pages/legal/LegalPage.tsx +5 -5
- package/templates/react-router/src/routes.ts +4 -1
- package/assets/agent-templates/content-translator/scripts/write-locale-files.ts +0 -66
- package/assets/skills/fimo/references/translations.md +0 -75
- package/assets/skills/fimo-cli/references/translations.md +0 -43
- package/dist/scripts/inject-translations.d.ts +0 -6
- package/dist/scripts/inject-translations.js +0 -168
- package/templates/react-router/translations/en.json +0 -1
package/README.md
CHANGED
|
@@ -50,13 +50,13 @@ npm install
|
|
|
50
50
|
npm run dev
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
Manage content, schemas, media, and
|
|
53
|
+
Manage content, schemas, media, and labels from the CLI:
|
|
54
54
|
|
|
55
55
|
```bash
|
|
56
56
|
fimo schemas push # sync your content models
|
|
57
57
|
fimo entries list # browse content
|
|
58
58
|
fimo assets upload <file> # add media (or `fimo assets generate` with AI)
|
|
59
|
-
fimo
|
|
59
|
+
fimo labels list # manage labels and localized copy
|
|
60
60
|
```
|
|
61
61
|
|
|
62
62
|
## Deploying
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
# Content translator
|
|
2
2
|
|
|
3
|
-
Translate CMS entries from the project's default
|
|
4
|
-
non-default locale declared in `fimo
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
review.
|
|
3
|
+
Translate CMS entries and DB-backed labels from the project's default
|
|
4
|
+
locale into every non-default locale declared in `fimo-config.json`.
|
|
5
|
+
Writes go through Fimo's content and label APIs. Do not create locale
|
|
6
|
+
JSON files or put translated values in source code.
|
|
8
7
|
|
|
9
8
|
## Inputs
|
|
10
9
|
|
|
11
10
|
- The list of CMS entries that have a non-empty default-locale field
|
|
12
11
|
but an empty translation for any non-default locale.
|
|
13
|
-
- The configured target locales (read from `fimo
|
|
12
|
+
- The configured target locales (read from `fimo-config.json`).
|
|
14
13
|
|
|
15
14
|
## What to do
|
|
16
15
|
|
|
@@ -19,16 +18,14 @@ For each entry:
|
|
|
19
18
|
1. Read the default-locale field via `cms:read`.
|
|
20
19
|
2. Translate to every missing locale (via `net:fetch` to your translation
|
|
21
20
|
provider of choice).
|
|
22
|
-
3. Write the translation back to the entry via `cms:write`.
|
|
23
|
-
4.
|
|
21
|
+
3. Write the translation back to the matching locale entry via `cms:write`.
|
|
22
|
+
4. For static UI labels, update values with the i18n/labels API rather
|
|
23
|
+
than editing files.
|
|
24
24
|
|
|
25
25
|
## What NOT to do
|
|
26
26
|
|
|
27
27
|
- Don't translate fields that the entry's schema marks `translate: false`.
|
|
28
28
|
- Don't overwrite an existing translation, even if it looks lower quality.
|
|
29
|
-
- Don't auto-merge to main — the lifecycle engine will propose a PR
|
|
30
|
-
when `agents.auto_merge.enabled` is true and the diff classifies as
|
|
31
|
-
`low_risk` or `trivial`.
|
|
32
29
|
|
|
33
30
|
## v1.x note
|
|
34
31
|
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
# Reads CMS
|
|
1
|
+
# Reads CMS and writes DB-backed localized content / labels. Net is for the translation provider.
|
|
2
2
|
- cms:read
|
|
3
3
|
- cms:write
|
|
4
4
|
- i18n:read
|
|
5
5
|
- i18n:write
|
|
6
|
-
- files:read
|
|
7
|
-
- files:write
|
|
8
6
|
- net:fetch
|
|
9
7
|
- git:branch
|
|
10
8
|
- git:commit
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Tool: translate-entries
|
|
3
3
|
*
|
|
4
|
-
* Walk every CMS entry, find
|
|
5
|
-
* call the configured translation provider, and write back
|
|
6
|
-
* Fimo API. Skips fields marked `translate: false` in the schema.
|
|
4
|
+
* Walk every default-locale CMS entry, find missing non-default-locale
|
|
5
|
+
* document rows, call the configured translation provider, and write back
|
|
6
|
+
* through the Fimo API. Skips fields marked `translate: false` in the schema.
|
|
7
7
|
*
|
|
8
8
|
* Provider is configured via env (FIMO_AGENT_TRANSLATE_PROVIDER) — the
|
|
9
9
|
* runtime threads project secrets through when the v1.x secrets API
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
|
|
13
13
|
interface Entry {
|
|
14
14
|
id: string;
|
|
15
|
+
documentId: string;
|
|
16
|
+
locale: string;
|
|
15
17
|
type: string;
|
|
16
18
|
data: Record<string, unknown>;
|
|
17
19
|
}
|
|
@@ -24,8 +26,8 @@ interface TranslateOptions {
|
|
|
24
26
|
|
|
25
27
|
async function listMissing(_opts: TranslateOptions): Promise<Entry[]> {
|
|
26
28
|
// Stub — wired to the Fimo API by the runtime. Returns entries with
|
|
27
|
-
// at least one
|
|
28
|
-
//
|
|
29
|
+
// at least one missing target-locale document row while the default-locale
|
|
30
|
+
// row is populated.
|
|
29
31
|
return [];
|
|
30
32
|
}
|
|
31
33
|
|
|
@@ -35,7 +37,8 @@ async function translate(text: string, _to: string): Promise<string> {
|
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
async function writeBack(_entry: Entry, _locale: string, _value: string): Promise<void> {
|
|
38
|
-
// Stub —
|
|
40
|
+
// Stub — create/update the target locale entry via
|
|
41
|
+
// `/api/management/projects/:id/envs/:env/tenant/entries/:type`.
|
|
39
42
|
}
|
|
40
43
|
|
|
41
44
|
async function main(): Promise<void> {
|
|
@@ -50,7 +53,7 @@ async function main(): Promise<void> {
|
|
|
50
53
|
const entries = await listMissing({ fromLocale, toLocales, api });
|
|
51
54
|
for (const entry of entries) {
|
|
52
55
|
for (const locale of toLocales) {
|
|
53
|
-
const sourceValue =
|
|
56
|
+
const sourceValue = typeof entry.data.title === 'string' ? entry.data.title : undefined;
|
|
54
57
|
if (!sourceValue) {
|
|
55
58
|
continue;
|
|
56
59
|
}
|
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
%>
|
|
10
10
|
// AUTO-GENERATED ⚠️ – do not edit by hand
|
|
11
11
|
import { useSuspenseQuery, useQuery, useMutation, useQueryClient, QueryKey } from "@tanstack/react-query";
|
|
12
|
-
import { FimoString, FimoDate, FimoMedia, FimoBoolean, FimoRichText, type Fields, type Projected, type Query, type Sort, type Where } from "fimo/ui";
|
|
12
|
+
import { FimoString, FimoDate, FimoMedia, FimoBoolean, FimoRichText, useLocale, type Fields, type Projected, type Query, type Sort, type Where } from "fimo/ui";
|
|
13
|
+
// @ts-ignore - virtual module provided by Fimo's Vite plugin
|
|
14
|
+
import fimoConfig from "virtual:fimo-config";
|
|
13
15
|
|
|
14
16
|
/*───────────────────────────────────────────────────────────────*/
|
|
15
17
|
/* ──────────── 📄 Type definitions ──────────── */
|
|
@@ -18,12 +20,19 @@ import { FimoString, FimoDate, FimoMedia, FimoBoolean, FimoRichText, type Fields
|
|
|
18
20
|
/**
|
|
19
21
|
* <%= model.pascal %> entity interface
|
|
20
22
|
*/
|
|
23
|
+
export interface <%= model.pascal %>FimoMeta {
|
|
24
|
+
contentType: string;
|
|
25
|
+
translationProgress: { state: "queued" | "translating"; fields?: string[] } | null;
|
|
26
|
+
}
|
|
27
|
+
|
|
21
28
|
export interface <%= model.pascal %> {
|
|
22
29
|
id: string;
|
|
30
|
+
documentId: string;
|
|
23
31
|
slug: FimoString;
|
|
24
|
-
|
|
32
|
+
locale: string;
|
|
25
33
|
createdAt: string;
|
|
26
34
|
updatedAt: string;
|
|
35
|
+
__fimo: <%= model.pascal %>FimoMeta;
|
|
27
36
|
<%
|
|
28
37
|
// Generate interface properties based on fields
|
|
29
38
|
if (model.fields) {
|
|
@@ -82,13 +91,19 @@ export type <%= model.pascal %>Result<TFields extends <%= model.pascal %>Fields
|
|
|
82
91
|
function wrapWithSource(entry: Record<string, any>): <%= model.pascal %> {
|
|
83
92
|
const id = entry.id;
|
|
84
93
|
const sourcePrefix = `<%= model.pascal %>.${id}`;
|
|
94
|
+
const entryFimo = entry.__fimo && typeof entry.__fimo === "object" ? entry.__fimo : {};
|
|
85
95
|
|
|
86
96
|
return {
|
|
87
97
|
id,
|
|
98
|
+
documentId: entry.documentId,
|
|
88
99
|
slug: entry.slug != null ? new FimoString(entry.slug, `${sourcePrefix}.slug`) : entry.slug,
|
|
89
|
-
|
|
100
|
+
locale: entry.locale,
|
|
90
101
|
createdAt: entry.createdAt,
|
|
91
102
|
updatedAt: entry.updatedAt,
|
|
103
|
+
__fimo: {
|
|
104
|
+
contentType: typeof entryFimo.contentType === "string" ? entryFimo.contentType : "<%= model.uid %>",
|
|
105
|
+
translationProgress: entryFimo.translationProgress && typeof entryFimo.translationProgress === "object" ? entryFimo.translationProgress as <%= model.pascal %>FimoMeta["translationProgress"] : null,
|
|
106
|
+
},
|
|
92
107
|
<%
|
|
93
108
|
if (model.fields) {
|
|
94
109
|
Object.entries(model.fields).forEach(([key, field]) => {
|
|
@@ -137,6 +152,14 @@ if (model.fields) {
|
|
|
137
152
|
// @ts-ignore - meta.env is a vite feature
|
|
138
153
|
const base = import.meta.env.VITE_API_URL;
|
|
139
154
|
|
|
155
|
+
function defaultLocale(): string {
|
|
156
|
+
return (fimoConfig as { i18n?: { defaultLocale?: string } }).i18n?.defaultLocale ?? "en";
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function useQueryLocale(locale?: string): string {
|
|
160
|
+
return locale ?? useLocale().locale ?? defaultLocale();
|
|
161
|
+
}
|
|
162
|
+
|
|
140
163
|
function fimoHeaders(extra?: Record<string, string>): Record<string, string> {
|
|
141
164
|
const h: Record<string, string> = { ...extra };
|
|
142
165
|
// @ts-ignore
|
|
@@ -145,23 +168,47 @@ function fimoHeaders(extra?: Record<string, string>): Record<string, string> {
|
|
|
145
168
|
return h;
|
|
146
169
|
}
|
|
147
170
|
|
|
148
|
-
|
|
149
|
-
const
|
|
171
|
+
function withLocale(url: string, locale?: string): string {
|
|
172
|
+
const u = new URL(url, "http://fimo.local");
|
|
173
|
+
u.searchParams.set("locale", locale ?? defaultLocale());
|
|
174
|
+
return `${u.pathname}${u.search}`;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function splitWritePayload(payload: Partial<<%= model.pascal %>>): Record<string, unknown> {
|
|
178
|
+
const {
|
|
179
|
+
id,
|
|
180
|
+
documentId,
|
|
181
|
+
locale,
|
|
182
|
+
createdAt,
|
|
183
|
+
updatedAt,
|
|
184
|
+
...data
|
|
185
|
+
} = payload as Record<string, unknown>;
|
|
186
|
+
delete data.__fimo;
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
data,
|
|
190
|
+
...(typeof locale === "string" && locale.length > 0 ? { locale } : {}),
|
|
191
|
+
...(typeof documentId === "string" && documentId.length > 0 ? { documentId } : {}),
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export async function getById(id: string, options: { locale?: string } = {}): Promise<<%= model.pascal %>> {
|
|
196
|
+
const res = await fetch(`${base}${withLocale(`/entries/<%= model.uid %>/${id}`, options.locale)}`, { headers: fimoHeaders() });
|
|
150
197
|
if (!res.ok) throw new Error(await res.text());
|
|
151
198
|
const data = await res.json();
|
|
152
199
|
return wrapWithSource(data.data);
|
|
153
200
|
}
|
|
154
201
|
|
|
155
|
-
export async function getBySlug(slug: string): Promise<<%= model.pascal %> | null> {
|
|
156
|
-
const res = await fetch(`${base}
|
|
202
|
+
export async function getBySlug(slug: string, options: { locale?: string } = {}): Promise<<%= model.pascal %> | null> {
|
|
203
|
+
const res = await fetch(`${base}${withLocale(`/entries/<%= model.uid %>/slug/${encodeURIComponent(slug)}`, options.locale)}`, { headers: fimoHeaders() });
|
|
157
204
|
if (res.status === 404) return null;
|
|
158
205
|
if (!res.ok) throw new Error(await res.text());
|
|
159
206
|
const data = await res.json();
|
|
160
207
|
return wrapWithSource(data.data);
|
|
161
208
|
}
|
|
162
209
|
|
|
163
|
-
export async function getByField(fieldName: string, value: string): Promise<<%= model.pascal %> | null> {
|
|
164
|
-
const res = await fetch(`${base}
|
|
210
|
+
export async function getByField(fieldName: string, value: string, options: { locale?: string } = {}): Promise<<%= model.pascal %> | null> {
|
|
211
|
+
const res = await fetch(`${base}${withLocale(`/entries/<%= model.uid %>/field/${encodeURIComponent(fieldName)}/${encodeURIComponent(value)}`, options.locale)}`, { headers: fimoHeaders() });
|
|
165
212
|
if (res.status === 404) return null;
|
|
166
213
|
if (!res.ok) throw new Error(await res.text());
|
|
167
214
|
const data = await res.json();
|
|
@@ -189,6 +236,10 @@ function toQueryString<TFields extends <%= model.pascal %>Fields | undefined = u
|
|
|
189
236
|
query.set("where", JSON.stringify(where));
|
|
190
237
|
}
|
|
191
238
|
|
|
239
|
+
if (!query.has("locale")) {
|
|
240
|
+
query.set("locale", defaultLocale());
|
|
241
|
+
}
|
|
242
|
+
|
|
192
243
|
return query.toString();
|
|
193
244
|
}
|
|
194
245
|
|
|
@@ -204,7 +255,7 @@ export async function create(payload: Partial<<%= model.pascal %>>): Promise<<%=
|
|
|
204
255
|
const res = await fetch(`${base}/entries/<%= model.uid %>`, {
|
|
205
256
|
method: "POST",
|
|
206
257
|
headers: fimoHeaders({ "Content-Type": "application/json" }),
|
|
207
|
-
body: JSON.stringify(
|
|
258
|
+
body: JSON.stringify(splitWritePayload(payload))
|
|
208
259
|
});
|
|
209
260
|
if (!res.ok) throw new Error(await res.text());
|
|
210
261
|
const data = await res.json();
|
|
@@ -215,7 +266,7 @@ export async function update(id: string, payload: Partial<<%= model.pascal %>>):
|
|
|
215
266
|
const res = await fetch(`${base}/entries/<%= model.uid %>/${id}`, {
|
|
216
267
|
method: "PUT",
|
|
217
268
|
headers: fimoHeaders({ "Content-Type": "application/json" }),
|
|
218
|
-
body: JSON.stringify(
|
|
269
|
+
body: JSON.stringify(splitWritePayload(payload))
|
|
219
270
|
});
|
|
220
271
|
if (!res.ok) throw new Error(await res.text());
|
|
221
272
|
const data = await res.json();
|
|
@@ -233,42 +284,52 @@ export async function remove(id: string): Promise<void> {
|
|
|
233
284
|
|
|
234
285
|
const qk = {
|
|
235
286
|
root : ["<%= model.pascal %>"] as const,
|
|
236
|
-
byId : (id: string) => ["<%= model.pascal %>", id] as const,
|
|
237
|
-
bySlug : (slug: string) => ["<%= model.pascal %>", "slug", slug] as const,
|
|
238
|
-
byField : (field: string, value: string) => ["<%= model.pascal %>", field, value] as const,
|
|
287
|
+
byId : (id: string, locale?: string) => ["<%= model.pascal %>", id, locale ?? defaultLocale()] as const,
|
|
288
|
+
bySlug : (slug: string, locale?: string) => ["<%= model.pascal %>", "slug", slug, locale ?? defaultLocale()] as const,
|
|
289
|
+
byField : (field: string, value: string, locale?: string) => ["<%= model.pascal %>", field, value, locale ?? defaultLocale()] as const,
|
|
239
290
|
list : (params: <%= model.pascal %>Query) => ["<%= model.pascal %>", params] as const
|
|
240
291
|
};
|
|
241
292
|
|
|
242
|
-
export function useGetById(id: string) {
|
|
243
|
-
|
|
293
|
+
export function useGetById(id: string, options: { locale?: string } = {}) {
|
|
294
|
+
const locale = useQueryLocale(options.locale);
|
|
295
|
+
return useQuery<<%= model.pascal %>, Error>({ queryKey: qk.byId(id, locale), queryFn: () => getById(id, { ...options, locale }) });
|
|
244
296
|
}
|
|
245
297
|
|
|
246
|
-
export function useGetBySlug(slug: string) {
|
|
247
|
-
|
|
298
|
+
export function useGetBySlug(slug: string, options: { locale?: string } = {}) {
|
|
299
|
+
const locale = useQueryLocale(options.locale);
|
|
300
|
+
return useQuery<<%= model.pascal %> | null, Error>({ queryKey: qk.bySlug(slug, locale), queryFn: () => getBySlug(slug, { ...options, locale }), enabled: !!slug });
|
|
248
301
|
}
|
|
249
302
|
|
|
250
|
-
export function useGetByField(fieldName: string, value: string) {
|
|
251
|
-
|
|
303
|
+
export function useGetByField(fieldName: string, value: string, options: { locale?: string } = {}) {
|
|
304
|
+
const locale = useQueryLocale(options.locale);
|
|
305
|
+
return useQuery<<%= model.pascal %> | null, Error>({ queryKey: qk.byField(fieldName, value, locale), queryFn: () => getByField(fieldName, value, { ...options, locale }), enabled: !!value });
|
|
252
306
|
}
|
|
253
307
|
|
|
254
308
|
export function useGet<TFields extends <%= model.pascal %>Fields | undefined = undefined>(params: <%= model.pascal %>Query<TFields> = {} as <%= model.pascal %>Query<TFields>) {
|
|
255
|
-
|
|
309
|
+
const locale = useQueryLocale(params.locale);
|
|
310
|
+
const localizedParams = { ...params, locale };
|
|
311
|
+
return useQuery<<%= model.pascal %>Result<TFields>[], Error>({ queryKey: qk.list(localizedParams), queryFn: () => get(localizedParams) });
|
|
256
312
|
}
|
|
257
313
|
|
|
258
|
-
export function useSuspenseGetById(id: string) {
|
|
259
|
-
|
|
314
|
+
export function useSuspenseGetById(id: string, options: { locale?: string } = {}) {
|
|
315
|
+
const locale = useQueryLocale(options.locale);
|
|
316
|
+
return useSuspenseQuery<<%= model.pascal %>, Error>({ queryKey: qk.byId(id, locale), queryFn: () => getById(id, { ...options, locale }) });
|
|
260
317
|
}
|
|
261
318
|
|
|
262
|
-
export function useSuspenseGetBySlug(slug: string) {
|
|
263
|
-
|
|
319
|
+
export function useSuspenseGetBySlug(slug: string, options: { locale?: string } = {}) {
|
|
320
|
+
const locale = useQueryLocale(options.locale);
|
|
321
|
+
return useSuspenseQuery<<%= model.pascal %> | null, Error>({ queryKey: qk.bySlug(slug, locale), queryFn: () => getBySlug(slug, { ...options, locale }) });
|
|
264
322
|
}
|
|
265
323
|
|
|
266
|
-
export function useSuspenseGetByField(fieldName: string, value: string) {
|
|
267
|
-
|
|
324
|
+
export function useSuspenseGetByField(fieldName: string, value: string, options: { locale?: string } = {}) {
|
|
325
|
+
const locale = useQueryLocale(options.locale);
|
|
326
|
+
return useSuspenseQuery<<%= model.pascal %> | null, Error>({ queryKey: qk.byField(fieldName, value, locale), queryFn: () => getByField(fieldName, value, { ...options, locale }) });
|
|
268
327
|
}
|
|
269
328
|
|
|
270
329
|
export function useSuspenseGet<TFields extends <%= model.pascal %>Fields | undefined = undefined>(params: <%= model.pascal %>Query<TFields> = {} as <%= model.pascal %>Query<TFields>) {
|
|
271
|
-
|
|
330
|
+
const locale = useQueryLocale(params.locale);
|
|
331
|
+
const localizedParams = { ...params, locale };
|
|
332
|
+
return useSuspenseQuery<<%= model.pascal %>Result<TFields>[], Error>({ queryKey: qk.list(localizedParams), queryFn: () => get(localizedParams) });
|
|
272
333
|
}
|
|
273
334
|
|
|
274
335
|
export function useCreate() {
|
|
@@ -3,7 +3,7 @@ name: fimo
|
|
|
3
3
|
description: >-
|
|
4
4
|
How to write code in a Fimo project — the npm package surface. Load when
|
|
5
5
|
writing JSX that renders schema content, defining schemas or forms (JSON),
|
|
6
|
-
using `t()` for
|
|
6
|
+
using `t()` for labels, plugging media into `fimo/ui` components,
|
|
7
7
|
or applying the project's code conventions. CLI commands and workflows
|
|
8
8
|
(push, deploy, sync, etc.) live in the `fimo-cli` skill.
|
|
9
9
|
---
|
|
@@ -14,14 +14,14 @@ You are writing code in a Fimo project — a content + media + forms + analytics
|
|
|
14
14
|
|
|
15
15
|
This skill is version-locked to this project's installed `fimo` package — the shapes here match what your code can actually use.
|
|
16
16
|
|
|
17
|
-
For CLI commands and workflows (creating a project, pushing schemas/forms, deploying,
|
|
17
|
+
For CLI commands and workflows (creating a project, pushing schemas/forms, deploying, managing labels, etc.), the **`fimo-cli`** skill is the source of truth. If `fimo-cli` isn't loaded for your AI tool, run `fimo skills install` to register it.
|
|
18
18
|
|
|
19
19
|
## Critical: NEVER hardcode user-facing things
|
|
20
20
|
|
|
21
21
|
This is the load-bearing rule for every code change. **Fimo's whole point is that content editors edit content in the dashboard, not in code.** Anything hardcoded breaks that.
|
|
22
22
|
|
|
23
23
|
- **Content** (posts, products, pages, lists, anything structured) → declare a schema, render with the generated `useGet()` hook + `fimo/ui` components. **Never** write `const posts = [...]` in JSX.
|
|
24
|
-
- **Strings** (labels, CTAs, headings, descriptions, any visible text) → wrap in `t('key'
|
|
24
|
+
- **Strings** (labels, CTAs, headings, descriptions, any visible text) → wrap in `t('key')` and create the default-locale value with `fimo labels set`. Use `--locale <locale>` only when intentionally writing a non-default locale. **Never** write `<h1>Welcome</h1>` directly — write `<Text value={t('home.title')} as="h1" />`.
|
|
25
25
|
- **Media** (images, videos, files) → use `<Image>` / `<Video>` from `fimo/ui` for schema media, `<StaticImage>` for hard-coded URLs. **Never** use `<img src={...}>`.
|
|
26
26
|
- **Forms** → use the generated Zod schema + `submitX` helper from `@/forms/<name>`. Never hand-roll fetch or validation.
|
|
27
27
|
|
|
@@ -29,15 +29,16 @@ If you find yourself about to hardcode anything user-facing, **stop and use the
|
|
|
29
29
|
|
|
30
30
|
## When starting code work, load these references up front
|
|
31
31
|
|
|
32
|
-
These define the package surface for the load-bearing primitives. **Read all
|
|
32
|
+
These define the package surface for the load-bearing primitives. **Read all six before generating significant code:**
|
|
33
33
|
|
|
34
34
|
- `references/content.md` — schema JSON shape, field types, Tiptap nodes, generated React Query hooks
|
|
35
35
|
- `references/forms.md` — form JSON spec, generated Zod + `submitX` helper
|
|
36
36
|
- `references/assets.md` — `FimoMedia` shape, how to render assets
|
|
37
|
-
- `references/
|
|
37
|
+
- `references/labels.md` — `t()` helper, `useLabels()`, wrap rules
|
|
38
|
+
- `references/locales.md` — locale routing helpers, `FimoLink`, custom-framework contract
|
|
38
39
|
- `references/ui.md` — `fimo/ui` components + JSX code conventions + app code rules
|
|
39
40
|
|
|
40
|
-
Workflow / CLI-side concerns (pushing schemas, generating images,
|
|
41
|
+
Workflow / CLI-side concerns (pushing schemas, generating images, managing labels, deploying) live in the **`fimo-cli`** skill's matching references.
|
|
41
42
|
|
|
42
43
|
## Decision Tree (specific tasks)
|
|
43
44
|
|
|
@@ -47,7 +48,8 @@ Workflow / CLI-side concerns (pushing schemas, generating images, syncing transl
|
|
|
47
48
|
- Writing schema JSON / picking field types / Tiptap richtext → `references/content.md`
|
|
48
49
|
- Writing form JSON / generated form client / form code → `references/forms.md`
|
|
49
50
|
- Plugging media into a component / `FimoMedia` shape → `references/assets.md`
|
|
50
|
-
- ANY user-facing string in code → `references/
|
|
51
|
+
- ANY user-facing string in code → `references/labels.md`
|
|
52
|
+
- Locale-aware routes, links, or framework adapters → `references/locales.md`
|
|
51
53
|
- ANY JSX rendering schema content or `t()` output → `references/ui.md`
|
|
52
54
|
- Major visual / redesign / hero section / brand → `references/design.md`
|
|
53
55
|
|
|
@@ -55,7 +57,7 @@ Workflow / CLI-side concerns (pushing schemas, generating images, syncing transl
|
|
|
55
57
|
|
|
56
58
|
- **Hardcoding content in JSX** — `<h1>My Blog</h1>` + an array of posts in code. Use schemas + entries + `t()`.
|
|
57
59
|
- **Raw HTML for schema content** — `<img src={post.coverImage.url}>` instead of `<Image value={post.coverImage}>`. The `fimo/ui` components carry metadata for inline editing in the admin.
|
|
58
|
-
- **Raw `<a href>` for internal navigation** — causes full page reloads. Use `<
|
|
60
|
+
- **Raw `<a href>` for internal navigation** — causes full page reloads. Use `<FimoLink>` for locale-aware internal links or React Router `<Link>` when intentionally bypassing locale helpers. Reserve `<a>` for external links.
|
|
59
61
|
- **Pulling stock photos from external URLs** — editors can't manage them. Use `fimo generate` (CLI).
|
|
60
62
|
- **Editing generated `.ts` files** under `src/schemas/` or `src/forms/` — regenerated on every build.
|
|
61
63
|
- **Writing raw HTML in `richtext` fields** — must be Tiptap JSONContent.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { FimoLink } from 'fimo/react-router';
|
|
2
|
+
|
|
3
|
+
export function Navigation() {
|
|
4
|
+
return (
|
|
5
|
+
<nav>
|
|
6
|
+
<FimoLink to="/">Home</FimoLink>
|
|
7
|
+
<FimoLink to="/pricing">Pricing</FimoLink>
|
|
8
|
+
<FimoLink to="/pricing" locale="es">
|
|
9
|
+
Pricing in Spanish
|
|
10
|
+
</FimoLink>
|
|
11
|
+
</nav>
|
|
12
|
+
);
|
|
13
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { RouteConfig } from '@react-router/dev/routes';
|
|
2
|
+
import { index, route } from '@react-router/dev/routes';
|
|
3
|
+
import { fimoRoutes } from 'fimo/react-router/routes';
|
|
4
|
+
|
|
5
|
+
const routes = [
|
|
6
|
+
index('./pages/Index.tsx', { id: 'home' }),
|
|
7
|
+
route('pricing', './pages/Pricing.tsx', { id: 'pricing' }),
|
|
8
|
+
route('blog/:slug', './pages/BlogPost.tsx', { id: 'blog-post' }),
|
|
9
|
+
] satisfies RouteConfig;
|
|
10
|
+
|
|
11
|
+
export default fimoRoutes(routes) satisfies RouteConfig;
|
|
@@ -114,10 +114,15 @@ After pre-build (or `fimo deploy`), `src/schemas/{Uid}.ts` exports typed helpers
|
|
|
114
114
|
```ts
|
|
115
115
|
interface BlogPost {
|
|
116
116
|
id: string;
|
|
117
|
+
documentId: string;
|
|
117
118
|
slug: FimoString;
|
|
118
|
-
|
|
119
|
+
locale: string;
|
|
119
120
|
createdAt: string;
|
|
120
121
|
updatedAt: string;
|
|
122
|
+
__fimo: {
|
|
123
|
+
contentType: string;
|
|
124
|
+
translationProgress: { state: 'queued' | 'translating'; fields?: string[] } | null;
|
|
125
|
+
};
|
|
121
126
|
title: FimoString;
|
|
122
127
|
body: FimoRichText;
|
|
123
128
|
coverImage: FimoMedia;
|
|
@@ -127,6 +132,34 @@ interface BlogPost {
|
|
|
127
132
|
}
|
|
128
133
|
```
|
|
129
134
|
|
|
135
|
+
Content fields live at the top level. Fimo-owned metadata that is not part of the user schema lives under `__fimo`, so a schema can safely define fields named `contentType`, `sourceLocale`, `translationStatus`, or `translationMeta`.
|
|
136
|
+
|
|
137
|
+
## Schema i18n
|
|
138
|
+
|
|
139
|
+
Content auto-translation uses `fimo-config.json#i18n.autoTranslateContent` as the global switch. Use schema-level `i18n.autoTranslate` for translatable content types and field-level `i18n.autoTranslate: false` for fields that must be copied exactly.
|
|
140
|
+
|
|
141
|
+
```json
|
|
142
|
+
{
|
|
143
|
+
"uid": "BlogPost",
|
|
144
|
+
"name": "Blog Post",
|
|
145
|
+
"isSingleton": false,
|
|
146
|
+
"i18n": {
|
|
147
|
+
"autoTranslate": true,
|
|
148
|
+
"translatePrompt": "Use friendly editorial Spanish and keep product names unchanged."
|
|
149
|
+
},
|
|
150
|
+
"fields": {
|
|
151
|
+
"title": { "type": "string" },
|
|
152
|
+
"summary": { "type": "string" },
|
|
153
|
+
"sku": {
|
|
154
|
+
"type": "string",
|
|
155
|
+
"i18n": { "autoTranslate": false }
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Use schema-level `translatePrompt` for wording rules that apply to the whole content type. Use field-level `i18n.autoTranslate: false` for fields that should be copied exactly.
|
|
162
|
+
|
|
130
163
|
**Generated API:**
|
|
131
164
|
|
|
132
165
|
```ts
|
|
@@ -170,6 +203,7 @@ BlogPost.useGet({
|
|
|
170
203
|
views: { $gte: 100 },
|
|
171
204
|
title: { $contains: 'React' },
|
|
172
205
|
coverImage: { $null: false },
|
|
206
|
+
__fimo: { contentType: { $eq: 'BlogPost' } },
|
|
173
207
|
},
|
|
174
208
|
sort: ['-publishedAt', 'title'],
|
|
175
209
|
limit: 12,
|
|
@@ -210,7 +244,7 @@ Rules of thumb:
|
|
|
210
244
|
- **Pass the whole primitive to the matching `fimo/ui` component** — `<Text value={post.title} />`, `<Image value={post.cover} />`, `<RichText value={post.body} />`, `<Boolean value={p.inStock} />`. Don't unwrap first; the component reads the source metadata so the field stays inline-editable in the dashboard.
|
|
211
245
|
- **`FimoString extends String`**, so `typeof post.title === 'object'` and `post.title === 'Hi'` is `false`. It stringifies fine inside JSX and template literals; call `.toString()` only when something genuinely needs a primitive `string`.
|
|
212
246
|
- **Never hand-construct** a `FimoString` / `FimoMedia` / etc. — that bypasses source tracking. For a literal string use `t()`; for a literal image use `<StaticImage>`.
|
|
213
|
-
- `t()` also returns a `FimoString` (see `references/
|
|
247
|
+
- `t()` also returns a `FimoString` (see `references/labels.md`).
|
|
214
248
|
|
|
215
249
|
`FimoMedia` and `FimoBoolean` shapes:
|
|
216
250
|
|
|
@@ -99,7 +99,7 @@ export function ContactForm() {
|
|
|
99
99
|
|
|
100
100
|
## Labels and copy
|
|
101
101
|
|
|
102
|
-
Wrap every visible string in `t('key'
|
|
102
|
+
Wrap every visible string in `t('key')` and create the DB value with `fimo labels set` — see `references/labels.md`. This includes form labels, placeholders, button text, success/error messages.
|
|
103
103
|
|
|
104
104
|
## Forms vs. schemas
|
|
105
105
|
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Labels — package surface
|
|
2
|
+
|
|
3
|
+
> For the CLI workflow (`fimo validate`, `fimo labels set`, bulk updates), see **`fimo-cli/references/labels.md`**.
|
|
4
|
+
> For locale-aware routing and links (`fimoRoutes`, `FimoLink`, custom framework contract), see **`references/locales.md`**.
|
|
5
|
+
|
|
6
|
+
This file covers the **package surface**: the `t()` helper, the `useLabels()` hook, and the code-side rules for wrapping strings.
|
|
7
|
+
|
|
8
|
+
## The rule
|
|
9
|
+
|
|
10
|
+
Every piece of **user-facing static text** in the app (page titles, button labels, nav items, form placeholders, empty-state copy, alt text, meta tags) MUST be wrapped in `t('key')`.
|
|
11
|
+
|
|
12
|
+
Raw string literals in JSX are a bug — they won't show up in the Fimo admin as editable content and won't be translatable. **Hard-coded JSX text (`<h1>Welcome</h1>`) skips the entire i18n + inline-editing pipeline and becomes invisible to the editor.**
|
|
13
|
+
|
|
14
|
+
## Setup (already wired)
|
|
15
|
+
|
|
16
|
+
Labels are wired automatically by the `fimo/vite` build plugin. `useLabels()` reads DB-backed label values for the active locale — there's no provider to add or remove. The app entry is `src/root.tsx`, which wraps the app in `<FimoProviders>` from `fimo/react-router`. Pages and components call `useLabels()`.
|
|
17
|
+
|
|
18
|
+
`useTranslations()` is kept as a legacy alias for older projects. Use `useLabels()` in new code.
|
|
19
|
+
|
|
20
|
+
In development, the generated app reloads when DB label values change. In production, the build embeds the DB-backed label bundle.
|
|
21
|
+
|
|
22
|
+
## Locale behavior
|
|
23
|
+
|
|
24
|
+
The default label locale is `i18n.defaultLocale` in `fimo-config.json`, falling back to `en` when it is not set. `fimo validate`, the generated app runtime, and `fimo labels set` without `--locale` all use that default locale.
|
|
25
|
+
|
|
26
|
+
For non-default locales, create explicit DB values with `fimo labels set --locale <locale>` or `fimo labels set-many --locale <locale>`. Keep the code shape the same for every locale: `t('hero.title')` declares the key, and the DB decides which locale value is shown.
|
|
27
|
+
|
|
28
|
+
`fimo-config.json` owns the locale list:
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"i18n": {
|
|
33
|
+
"defaultLocale": "en",
|
|
34
|
+
"locales": ["en"],
|
|
35
|
+
"autoTranslateLabels": false,
|
|
36
|
+
"autoTranslateContent": true
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
- `locales` is the enabled locale list and must include `defaultLocale`. Do not add non-default locales unless the user asks for multiple languages, localized routes, translations, or a specific locale.
|
|
42
|
+
- `autoTranslateLabels` is explicit opt-in. Runtime default is `false`; when the user asks for locales and does not opt out of automatic label translation, set it to `true`.
|
|
43
|
+
- `autoTranslateContent` is the global content auto-translation switch. Set it to `false` to disable content auto-translation globally.
|
|
44
|
+
- Content auto-translation can be narrowed or customized per schema and per field in `src/schemas/*.json`; see `references/content.md`.
|
|
45
|
+
- Public locale routing is project/framework code. In React Router projects, prefer `fimoRoutes()` plus `FimoLink`; for custom frameworks, pass the active locale to `FimoProviders`. See `references/locales.md`.
|
|
46
|
+
|
|
47
|
+
## Usage
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
import { useLabels, Text } from 'fimo/ui';
|
|
51
|
+
|
|
52
|
+
export function Hero() {
|
|
53
|
+
const { t } = useLabels();
|
|
54
|
+
return (
|
|
55
|
+
<section>
|
|
56
|
+
<h1>
|
|
57
|
+
<Text value={t('hero.title')} />
|
|
58
|
+
</h1>
|
|
59
|
+
<p>
|
|
60
|
+
<Text value={t('hero.subtitle')} />
|
|
61
|
+
</p>
|
|
62
|
+
<input placeholder={String(t('hero.emailPlaceholder'))} />
|
|
63
|
+
<img alt={String(t('hero.imageAlt'))} src="..." />
|
|
64
|
+
</section>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
- `t(key)` returns a `FimoString` (wrapped for source tracking). Render it via `<Text value={...} />` inside JSX.
|
|
70
|
+
- For HTML attributes (`placeholder`, `alt`, `title`, `aria-label`) cast with `String(t(...))` — the attribute can't accept a `FimoString` object directly.
|
|
71
|
+
- If a key is missing from the DB, it renders empty. Run `fimo validate` and add missing values with `fimo labels set`.
|
|
72
|
+
|
|
73
|
+
## Hard rules (enforced by the pre-build linter)
|
|
74
|
+
|
|
75
|
+
**The first argument to `t()` MUST be a string literal.** Dynamic keys break source tracking and fail the lint step.
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
// ❌ BAD — variable key
|
|
79
|
+
t(item.key);
|
|
80
|
+
|
|
81
|
+
// ❌ BAD — template with expression
|
|
82
|
+
t(`nav.${page}`);
|
|
83
|
+
|
|
84
|
+
// ✅ GOOD — put the t() call where the literal lives
|
|
85
|
+
const items = [{ label: t('nav.home') }, { label: t('nav.about') }];
|
|
86
|
+
items.map((item) => <li>{item.label}</li>);
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Key naming
|
|
90
|
+
|
|
91
|
+
- Dot-namespaced, lowercase, kebab or camelCase leaves: `hero.title`, `nav.signIn`, `footer.copyright`, `errors.required`.
|
|
92
|
+
- Namespace by **page/section/component**, not by feature (`contact.form.submitLabel`, not `forms.contactSubmit`).
|
|
93
|
+
- Values live in the DB. Do not put user-visible fallback copy in code.
|
|
94
|
+
|
|
95
|
+
## What NOT to translate
|
|
96
|
+
|
|
97
|
+
- Dynamic content from entries or forms — that's already stored in the DB (content via schemas, submissions via forms).
|
|
98
|
+
- Content-type field values rendered via `fimo/ui` primitives (`<Text>`, `<RichText>`, …) — those carry their own admin-editing metadata.
|
|
99
|
+
- Purely decorative / non-text (icons, logos).
|
|
100
|
+
- Developer-only strings (`console.log`, error messages thrown for debugging).
|
|
101
|
+
|
|
102
|
+
## Rule of thumb
|
|
103
|
+
|
|
104
|
+
If a human reader sees the text AND the project owner might want to reword it in the admin, it goes through `t()`. When in doubt: wrap it.
|
|
105
|
+
|
|
106
|
+
After adding or changing `t()` calls, run `fimo validate`, then fill missing values with `fimo labels set` or `fimo labels set-many`. See `fimo-cli/references/labels.md`.
|