fimo 0.2.4 → 0.2.5-experimental.1782327181771

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.
Files changed (62) hide show
  1. package/README.md +2 -2
  2. package/assets/agent-templates/content-translator/GOAL.md +8 -11
  3. package/assets/agent-templates/content-translator/capabilities.yaml +1 -3
  4. package/assets/agent-templates/content-translator/scripts/translate-entries.ts +10 -7
  5. package/assets/content-templates/hooks-template.eta +73 -23
  6. package/assets/skills/fimo/SKILL.md +3 -3
  7. package/assets/skills/fimo/references/forms.md +1 -1
  8. package/assets/skills/fimo/references/setup-plain-vite.md +1 -1
  9. package/assets/skills/fimo/references/setup-react-router.md +1 -1
  10. package/assets/skills/fimo/references/translations.md +42 -14
  11. package/assets/skills/fimo/references/ui.md +4 -4
  12. package/assets/skills/fimo-cli/SKILL.md +3 -3
  13. package/assets/skills/fimo-cli/references/content.md +1 -1
  14. package/assets/skills/fimo-cli/references/forms.md +1 -1
  15. package/assets/skills/fimo-cli/references/translations.md +45 -19
  16. package/dist/build/vite/plugins/fimo-config.d.ts.map +1 -1
  17. package/dist/build/vite/plugins/fimo-config.js +16 -0
  18. package/dist/build/vite/plugins/fimo-config.test.d.ts +2 -0
  19. package/dist/build/vite/plugins/fimo-config.test.d.ts.map +1 -0
  20. package/dist/build/vite/plugins/fimo-config.test.js +46 -0
  21. package/dist/build/vite/plugins/translations.d.ts +7 -6
  22. package/dist/build/vite/plugins/translations.d.ts.map +1 -1
  23. package/dist/build/vite/plugins/translations.js +366 -33
  24. package/dist/build/vite/plugins/translations.test.d.ts +2 -0
  25. package/dist/build/vite/plugins/translations.test.d.ts.map +1 -0
  26. package/dist/build/vite/plugins/translations.test.js +177 -0
  27. package/dist/cli/bundle.json +2 -2
  28. package/dist/cli/index.js +1305 -1078
  29. package/dist/runtime/app/FimoScripts.d.ts.map +1 -1
  30. package/dist/runtime/app/FimoScripts.js +35 -1
  31. package/dist/runtime/app/prefetch.d.ts.map +1 -1
  32. package/dist/runtime/app/prefetch.js +6 -1
  33. package/dist/runtime/paths/get-fimo-paths.d.ts.map +1 -1
  34. package/dist/runtime/paths/get-fimo-paths.js +9 -4
  35. package/dist/runtime/primitives/components/Text.d.ts +1 -1
  36. package/dist/runtime/primitives/components/Text.js +1 -1
  37. package/dist/runtime/primitives/lib/query.d.ts +5 -0
  38. package/dist/runtime/primitives/lib/query.d.ts.map +1 -1
  39. package/dist/runtime/primitives/lib/template.d.ts +1 -1
  40. package/dist/runtime/primitives/lib/template.js +1 -1
  41. package/dist/runtime/primitives/translations.d.ts +9 -3
  42. package/dist/runtime/primitives/translations.d.ts.map +1 -1
  43. package/dist/runtime/primitives/translations.js +32 -5
  44. package/dist/runtime/seo/htmlProps.d.ts +1 -1
  45. package/dist/runtime/seo/htmlProps.js +2 -2
  46. package/dist/runtime/shared/fimo-config.server.d.ts.map +1 -1
  47. package/dist/runtime/shared/fimo-config.server.js +1 -0
  48. package/dist/runtime/shared/fimo-config.types.d.ts +7 -0
  49. package/dist/runtime/shared/fimo-config.types.d.ts.map +1 -1
  50. package/dist/scripts/extract-translations.d.ts +8 -8
  51. package/dist/scripts/extract-translations.js +22 -57
  52. package/dist/scripts/lint-translation-keys.js +24 -5
  53. package/dist/scripts/lint-translation-keys.test.d.ts +1 -0
  54. package/dist/scripts/lint-translation-keys.test.js +16 -0
  55. package/package.json +1 -1
  56. package/release.json +2 -2
  57. package/templates/react-router/fimo-config.json +4 -0
  58. package/templates/react-router/package.json +1 -1
  59. package/assets/agent-templates/content-translator/scripts/write-locale-files.ts +0 -66
  60. package/dist/scripts/inject-translations.d.ts +0 -6
  61. package/dist/scripts/inject-translations.js +0 -168
  62. package/templates/react-router/translations/en.json +0 -1
@@ -1,66 +0,0 @@
1
- /**
2
- * Tool: write-locale-files
3
- *
4
- * For locale-keyed JSON files in `src/locales/<locale>.json`, fill in
5
- * missing keys from the default-locale file using the configured
6
- * translation provider. Run after translate-entries when projects mix
7
- * CMS-driven copy with hardcoded i18n strings.
8
- */
9
-
10
- import { promises as fs } from 'node:fs';
11
- import path from 'node:path';
12
-
13
- interface Catalog {
14
- [key: string]: string;
15
- }
16
-
17
- async function readCatalog(file: string): Promise<Catalog> {
18
- try {
19
- const text = await fs.readFile(file, 'utf8');
20
- return JSON.parse(text) as Catalog;
21
- } catch {
22
- return {};
23
- }
24
- }
25
-
26
- async function writeCatalog(file: string, catalog: Catalog): Promise<void> {
27
- await fs.writeFile(file, JSON.stringify(catalog, null, 2) + '\n', 'utf8');
28
- }
29
-
30
- async function translate(text: string, _to: string): Promise<string> {
31
- // Stub — provider-specific call.
32
- return text;
33
- }
34
-
35
- async function main(): Promise<void> {
36
- const defaultLocale = process.env.FIMO_DEFAULT_LOCALE ?? 'en';
37
- const targetLocales = (process.env.FIMO_TARGET_LOCALES ?? '').split(',').filter(Boolean);
38
- const localesDir = process.env.FIMO_LOCALES_DIR ?? 'src/locales';
39
-
40
- const defaultFile = path.join(localesDir, `${defaultLocale}.json`);
41
- const defaultCatalog = await readCatalog(defaultFile);
42
-
43
- for (const target of targetLocales) {
44
- const targetFile = path.join(localesDir, `${target}.json`);
45
- const targetCatalog = await readCatalog(targetFile);
46
- let dirty = false;
47
-
48
- for (const [key, sourceValue] of Object.entries(defaultCatalog)) {
49
- if (targetCatalog[key]) {
50
- continue;
51
- }
52
- targetCatalog[key] = await translate(sourceValue, target);
53
- dirty = true;
54
- }
55
-
56
- if (dirty) {
57
- await writeCatalog(targetFile, targetCatalog);
58
- console.log(`Wrote ${targetFile}`);
59
- }
60
- }
61
- }
62
-
63
- main().catch((err) => {
64
- console.error(err);
65
- process.exit(1);
66
- });
@@ -1,6 +0,0 @@
1
- export interface InjectTranslationsResult {
2
- updated: number;
3
- }
4
- export declare function injectTranslations(options?: {
5
- cwd?: string;
6
- }): InjectTranslationsResult;
@@ -1,168 +0,0 @@
1
- /**
2
- * Inject translation values from translations/en.json back into code.
3
- *
4
- * Reads `translations/en.json` (treated as authoritative input — typically
5
- * written by the backend after a dashboard editor edits an English string)
6
- * and rewrites the second argument of every matching `t('key', 'default')`
7
- * call in `src/` to match. Idempotent.
8
- *
9
- * Exposed as `fimo scripts inject-translations` (hidden namespace). The
10
- * backend's bidirectional-sync flow will eventually invoke this command in
11
- * the sandbox instead of the legacy `node .fimo/i18n/scripts/...` path.
12
- */
13
- import { existsSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs';
14
- import { join } from 'node:path';
15
- import { parseSync } from 'oxc-parser';
16
- function findReactFiles(dir, files = []) {
17
- if (!existsSync(dir)) {
18
- return files;
19
- }
20
- const entries = readdirSync(dir);
21
- for (const entry of entries) {
22
- const fullPath = join(dir, entry);
23
- const stat = statSync(fullPath);
24
- if (stat.isDirectory()) {
25
- if (entry === 'ui' || entry === 'node_modules') {
26
- continue;
27
- }
28
- findReactFiles(fullPath, files);
29
- }
30
- else if (/\.(ts|tsx|js|jsx)$/.test(entry)) {
31
- files.push(fullPath);
32
- }
33
- }
34
- return files;
35
- }
36
- function findTCalls(filePath, code) {
37
- const locations = [];
38
- try {
39
- const result = parseSync(filePath, code);
40
- const ast = result.program;
41
- function visit(node) {
42
- if (!node || typeof node !== 'object') {
43
- return;
44
- }
45
- if (node.type === 'CallExpression') {
46
- const callee = node.callee;
47
- if (callee?.type === 'Identifier' && callee?.name === 't') {
48
- const args = node.arguments || [];
49
- const firstArg = args[0];
50
- if (firstArg?.type === 'Literal' && typeof firstArg.value === 'string') {
51
- const key = firstArg.value;
52
- const secondArg = args[1];
53
- if (secondArg?.type === 'Literal' && typeof secondArg.value === 'string') {
54
- locations.push({
55
- file: filePath,
56
- key,
57
- currentDefault: secondArg.value,
58
- secondArgStart: secondArg.start,
59
- secondArgEnd: secondArg.end,
60
- hasSecondArg: true,
61
- firstArgEnd: firstArg.end,
62
- });
63
- }
64
- else if (!secondArg) {
65
- locations.push({
66
- file: filePath,
67
- key,
68
- currentDefault: '',
69
- secondArgStart: 0,
70
- secondArgEnd: 0,
71
- hasSecondArg: false,
72
- firstArgEnd: firstArg.end,
73
- });
74
- }
75
- }
76
- }
77
- }
78
- for (const k of Object.keys(node)) {
79
- const child = node[k];
80
- if (Array.isArray(child)) {
81
- for (const item of child) {
82
- visit(item);
83
- }
84
- }
85
- else if (typeof child === 'object' && child !== null) {
86
- visit(child);
87
- }
88
- }
89
- }
90
- visit(ast);
91
- }
92
- catch (error) {
93
- console.error(`Failed to parse ${filePath}:`, error);
94
- }
95
- return locations;
96
- }
97
- function escapeString(str) {
98
- return str
99
- .replace(/\\/g, '\\\\')
100
- .replace(/'/g, "\\'")
101
- .replace(/\n/g, '\\n')
102
- .replace(/\r/g, '\\r')
103
- .replace(/\t/g, '\\t');
104
- }
105
- export function injectTranslations(options = {}) {
106
- const cwd = options.cwd ?? process.cwd();
107
- const translationsFile = join(cwd, 'translations', 'en.json');
108
- if (!existsSync(translationsFile)) {
109
- return { updated: 0 };
110
- }
111
- let translations;
112
- try {
113
- translations = JSON.parse(readFileSync(translationsFile, 'utf-8'));
114
- }
115
- catch {
116
- throw new Error(`Failed to parse ${translationsFile}`);
117
- }
118
- const srcDir = join(cwd, 'src');
119
- const files = findReactFiles(srcDir);
120
- const fileContents = new Map();
121
- const fileLocations = new Map();
122
- for (const file of files) {
123
- const code = readFileSync(file, 'utf-8');
124
- fileContents.set(file, code);
125
- const locations = findTCalls(file, code);
126
- if (locations.length > 0) {
127
- fileLocations.set(file, locations);
128
- }
129
- }
130
- let totalUpdates = 0;
131
- for (const [file, locations] of fileLocations) {
132
- let code = fileContents.get(file);
133
- let offset = 0;
134
- let fileModified = false;
135
- const sortedLocations = [...locations].sort((a, b) => a.secondArgStart - b.secondArgStart);
136
- for (const loc of sortedLocations) {
137
- const expectedValue = translations[loc.key];
138
- if (expectedValue === undefined) {
139
- continue;
140
- }
141
- if (loc.hasSecondArg) {
142
- if (loc.currentDefault !== expectedValue) {
143
- const newLiteral = `'${escapeString(expectedValue)}'`;
144
- const before = code.slice(0, loc.secondArgStart + offset);
145
- const after = code.slice(loc.secondArgEnd + offset);
146
- code = before + newLiteral + after;
147
- offset += newLiteral.length - (loc.secondArgEnd - loc.secondArgStart);
148
- fileModified = true;
149
- totalUpdates++;
150
- }
151
- }
152
- else {
153
- if (expectedValue !== '') {
154
- const newArg = `, '${escapeString(expectedValue)}'`;
155
- const insertPos = loc.firstArgEnd + offset;
156
- code = code.slice(0, insertPos) + newArg + code.slice(insertPos);
157
- offset += newArg.length;
158
- fileModified = true;
159
- totalUpdates++;
160
- }
161
- }
162
- }
163
- if (fileModified) {
164
- writeFileSync(file, code, 'utf-8');
165
- }
166
- }
167
- return { updated: totalUpdates };
168
- }