@xndrjs/i18n 0.4.0 → 0.5.0-alpha.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/README.md +156 -29
- package/dist/codegen/index.d.ts +85 -0
- package/dist/codegen/index.js +181 -0
- package/dist/codegen/index.js.map +1 -0
- package/dist/index.d.ts +34 -5
- package/dist/index.js +175 -74
- package/dist/index.js.map +1 -1
- package/dist/validation/index.d.ts +4 -0
- package/dist/validation/index.js.map +1 -1
- package/package.json +6 -1
- package/src/IcuTranslationProviderMulti.test.ts +47 -0
- package/src/IcuTranslationProviderMulti.ts +41 -61
- package/src/IcuTranslationProviderSingle.test.ts +67 -0
- package/src/IcuTranslationProviderSingle.ts +27 -51
- package/src/audit/audit-dictionaries.ts +4 -1
- package/src/audit/run-audit.test.ts +35 -1
- package/src/audit/run-audit.ts +15 -7
- package/src/codegen/codegen-config-schema.ts +68 -1
- package/src/codegen/config.test.ts +174 -39
- package/src/codegen/config.ts +14 -5
- package/src/codegen/constants.ts +8 -0
- package/src/codegen/delivery-artifacts.test.ts +142 -0
- package/src/codegen/delivery-artifacts.ts +124 -0
- package/src/codegen/emit/dictionary-file.test.ts +190 -0
- package/src/codegen/emit/dictionary-file.ts +163 -0
- package/src/codegen/emit/dictionary-schema-file.ts +5 -0
- package/src/codegen/emit/instance-file.ts +114 -28
- package/src/codegen/emit/namespace-loaders-file.test.ts +176 -0
- package/src/codegen/emit/namespace-loaders-file.ts +114 -6
- package/src/codegen/emit/types-file.test.ts +114 -0
- package/src/codegen/emit/types-file.ts +48 -11
- package/src/codegen/generate-i18n-types.test.ts +754 -16
- package/src/codegen/generate-i18n-types.ts +111 -47
- package/src/codegen/icu-analysis.ts +9 -2
- package/src/codegen/locale-fallback.ts +19 -10
- package/src/codegen/locale-policy.ts +4 -0
- package/src/codegen/paths.ts +9 -5
- package/src/codegen/project-locales-set-namespace.test.ts +5 -5
- package/src/codegen/read-dictionary.test.ts +474 -2
- package/src/codegen/read-dictionary.ts +164 -15
- package/src/codegen/write-file-if-changed.test.ts +42 -0
- package/src/codegen/write-file-if-changed.ts +20 -0
- package/src/codegen-config/build-config.ts +34 -0
- package/src/codegen-config/codegen-config.test.ts +32 -0
- package/src/codegen-config/index.ts +21 -0
- package/src/codegen-config/write-config.ts +8 -0
- package/src/deep-freeze.test.ts +13 -0
- package/src/deep-freeze.ts +5 -0
- package/src/format-core.ts +91 -0
- package/src/index.ts +8 -2
- package/src/project-locales.test.ts +129 -10
- package/src/project-locales.ts +90 -3
- package/src/setup/setup-i18n.test.ts +1 -1
- package/src/setup/setup-i18n.ts +6 -30
- package/src/types.ts +19 -4
- package/src/validation/normalize.ts +4 -0
- /package/src/{setup → codegen-config}/type-names.ts +0 -0
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
loadConfig,
|
|
5
|
+
resolveDeliveryOutputDir,
|
|
6
|
+
resolveLoadOnInit,
|
|
7
|
+
resolveNamespaces,
|
|
8
|
+
} from "./config.js";
|
|
4
9
|
import { formatDictionaryFile } from "./emit/dictionary-file.js";
|
|
5
10
|
import {
|
|
6
11
|
formatDictionarySchemaFile,
|
|
@@ -10,11 +15,18 @@ import { formatInstanceFile } from "./emit/instance-file.js";
|
|
|
10
15
|
import { formatNamespaceLoadersFile } from "./emit/namespace-loaders-file.js";
|
|
11
16
|
import { formatTypesFile } from "./emit/types-file.js";
|
|
12
17
|
import { analyzeDictionaries } from "./icu-analysis.js";
|
|
13
|
-
import {
|
|
18
|
+
import { getDeliveryArtifactsIssues } from "./delivery-artifacts.js";
|
|
19
|
+
import { collectRequestLocales, getCodegenLocaleFallbackIssues } from "./locale-fallback.js";
|
|
14
20
|
import { enrichLocaleFallback } from "./locale-policy.js";
|
|
15
|
-
import {
|
|
21
|
+
import { reportCodegenIssues, resolveImportExtension, toModuleBasename } from "./paths.js";
|
|
16
22
|
import { prepareDictionaryEntries } from "./read-dictionary.js";
|
|
23
|
+
import { writeFileIfChanged } from "./write-file-if-changed.js";
|
|
17
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Codegen CLI entry point. Pipeline:
|
|
27
|
+
* config → analyze (ICU + types input) → validate locale/delivery policy →
|
|
28
|
+
* prepare artifacts (YAML→JSON, split) → resolve lazy/eager → emit generated .ts files.
|
|
29
|
+
*/
|
|
18
30
|
function main() {
|
|
19
31
|
const configArgIndex = process.argv.indexOf("--config");
|
|
20
32
|
const configPath = path.resolve(
|
|
@@ -24,41 +36,29 @@ function main() {
|
|
|
24
36
|
const projectRoot = path.dirname(configPath);
|
|
25
37
|
|
|
26
38
|
if (!fs.existsSync(configPath)) {
|
|
27
|
-
|
|
39
|
+
throw new Error(`[Codegen Error] Config file not found: ${configPath}`);
|
|
28
40
|
}
|
|
29
41
|
|
|
30
42
|
const config = loadConfig(configPath);
|
|
31
43
|
const sourceEntries = resolveNamespaces(config);
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
let compiledFiles: string[];
|
|
35
|
-
|
|
36
|
-
try {
|
|
37
|
-
const prepared = prepareDictionaryEntries(projectRoot, sourceEntries, generatedDirRelative);
|
|
38
|
-
resolvedEntries = prepared.resolvedEntries;
|
|
39
|
-
compiledFiles = prepared.compiledFiles;
|
|
40
|
-
} catch (error) {
|
|
41
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
42
|
-
fail(message);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const entries = resolvedEntries;
|
|
44
|
+
const deliveryOutputRelative = resolveDeliveryOutputDir(config);
|
|
45
|
+
const delivery = config.delivery ?? "canonical";
|
|
46
46
|
const isSingle = Boolean(config.dictionary);
|
|
47
|
-
const { loadOnInitSet, lazyEntries, hasLazy } = resolveLoadOnInit(config, entries, isSingle);
|
|
48
|
-
|
|
49
|
-
const typesOutputPath = path.resolve(projectRoot, config.typesOutput);
|
|
50
|
-
const dictionaryOutputPath = path.resolve(projectRoot, config.dictionaryOutput);
|
|
51
|
-
const instanceOutputPath = path.resolve(projectRoot, config.instanceOutput);
|
|
52
47
|
|
|
53
|
-
const analysisResult = analyzeDictionaries(projectRoot,
|
|
48
|
+
const analysisResult = analyzeDictionaries(projectRoot, sourceEntries);
|
|
54
49
|
if (!analysisResult.ok) {
|
|
55
50
|
process.exit(1);
|
|
56
51
|
}
|
|
57
52
|
|
|
58
|
-
const { paramsByNamespace, argsSpecByNamespace, locales } =
|
|
53
|
+
const { paramsByNamespace, argsSpecByNamespace, locales, dictionariesByNamespace } =
|
|
54
|
+
analysisResult.analysis;
|
|
59
55
|
|
|
60
|
-
if (config.localeFallback
|
|
61
|
-
|
|
56
|
+
if (config.localeFallback) {
|
|
57
|
+
const localeFallbackIssues = getCodegenLocaleFallbackIssues(config.localeFallback, locales);
|
|
58
|
+
if (localeFallbackIssues.length > 0) {
|
|
59
|
+
reportCodegenIssues(localeFallbackIssues);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
const paramsTypeName = config.paramsTypeName;
|
|
@@ -68,14 +68,55 @@ function main() {
|
|
|
68
68
|
const localeFallbackTypeName = `${localeTypeName}Fallback`;
|
|
69
69
|
const factoryName = config.factoryName ?? "createI18n";
|
|
70
70
|
const importExtension = resolveImportExtension(config);
|
|
71
|
-
const typesModule = toModuleBasename(typesOutputPath);
|
|
72
71
|
|
|
73
72
|
const requestLocales = collectRequestLocales(locales, config.localeFallback);
|
|
74
|
-
const
|
|
73
|
+
const requestLocalesList = [...requestLocales].sort();
|
|
74
|
+
|
|
75
|
+
if (delivery === "custom" && config.deliveryArtifacts) {
|
|
76
|
+
const deliveryArtifactsIssues = getDeliveryArtifactsIssues(
|
|
77
|
+
config.deliveryArtifacts,
|
|
78
|
+
requestLocales
|
|
79
|
+
);
|
|
80
|
+
if (deliveryArtifactsIssues.length > 0) {
|
|
81
|
+
reportCodegenIssues(deliveryArtifactsIssues);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const {
|
|
87
|
+
resolvedEntries: entries,
|
|
88
|
+
splitPathsByNamespace,
|
|
89
|
+
compiledFiles,
|
|
90
|
+
} = prepareDictionaryEntries(projectRoot, sourceEntries, deliveryOutputRelative, {
|
|
91
|
+
dictionariesByNamespace,
|
|
92
|
+
delivery,
|
|
93
|
+
localeFallback: config.localeFallback,
|
|
94
|
+
requestLocales: delivery === "split-by-locale" ? requestLocalesList : undefined,
|
|
95
|
+
deliveryArtifacts: delivery === "custom" ? config.deliveryArtifacts : undefined,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const { loadOnInitSet, lazyEntries, hasLazy } = resolveLoadOnInit(config, entries, isSingle);
|
|
99
|
+
|
|
100
|
+
const typesOutputPath = path.resolve(projectRoot, config.typesOutput);
|
|
101
|
+
const dictionaryOutputPath = path.resolve(projectRoot, config.dictionaryOutput);
|
|
102
|
+
const instanceOutputPath = path.resolve(projectRoot, config.instanceOutput);
|
|
103
|
+
const typesModule = toModuleBasename(typesOutputPath);
|
|
104
|
+
|
|
105
|
+
const requestLocaleUnion = requestLocalesList
|
|
75
106
|
.sort()
|
|
76
107
|
.map((locale) => `'${locale}'`)
|
|
77
108
|
.join(" | ");
|
|
78
109
|
|
|
110
|
+
const deliveryAreaNames =
|
|
111
|
+
delivery === "custom" && config.deliveryArtifacts
|
|
112
|
+
? Object.keys(config.deliveryArtifacts).sort()
|
|
113
|
+
: undefined;
|
|
114
|
+
const deliveryAreaTypeName =
|
|
115
|
+
delivery === "custom" ? schemaTypeName.replace(/Schema$/, "DeliveryArea") : undefined;
|
|
116
|
+
const deliveryAreaUnion = deliveryAreaNames
|
|
117
|
+
? deliveryAreaNames.map((area) => `'${area}'`).join(" | ")
|
|
118
|
+
: undefined;
|
|
119
|
+
|
|
79
120
|
const eagerEntries = hasLazy
|
|
80
121
|
? entries.filter((entry) => loadOnInitSet.has(entry.namespace))
|
|
81
122
|
: entries;
|
|
@@ -97,6 +138,15 @@ function main() {
|
|
|
97
138
|
localeFallback: localeFallbackForEmit,
|
|
98
139
|
paramsByNamespace,
|
|
99
140
|
requestLocaleUnion,
|
|
141
|
+
...(deliveryAreaTypeName && deliveryAreaUnion
|
|
142
|
+
? {
|
|
143
|
+
deliveryAreaTypeName,
|
|
144
|
+
deliveryAreaUnion,
|
|
145
|
+
...(delivery === "custom" && config.deliveryArtifacts
|
|
146
|
+
? { deliveryArtifacts: config.deliveryArtifacts }
|
|
147
|
+
: {}),
|
|
148
|
+
}
|
|
149
|
+
: {}),
|
|
100
150
|
hasLazy,
|
|
101
151
|
loadOnInitSet,
|
|
102
152
|
lazyEntries,
|
|
@@ -111,7 +161,14 @@ function main() {
|
|
|
111
161
|
dictionaryOutputPath,
|
|
112
162
|
typesOutputPath,
|
|
113
163
|
schemaTypeName,
|
|
164
|
+
localeTypeName,
|
|
114
165
|
importExtension,
|
|
166
|
+
delivery,
|
|
167
|
+
splitPathsByNamespace,
|
|
168
|
+
...(delivery === "split-by-locale" ? { requestLocales: requestLocalesList } : {}),
|
|
169
|
+
...(delivery === "custom" && deliveryAreaTypeName && deliveryAreaNames
|
|
170
|
+
? { deliveryAreaTypeName, deliveryAreaNames }
|
|
171
|
+
: {}),
|
|
115
172
|
});
|
|
116
173
|
|
|
117
174
|
const instanceContent = formatInstanceFile({
|
|
@@ -127,16 +184,12 @@ function main() {
|
|
|
127
184
|
hasLocaleType: Boolean(requestLocaleUnion),
|
|
128
185
|
namespaceNames: entries.map((entry) => entry.namespace),
|
|
129
186
|
importExtension,
|
|
187
|
+
delivery,
|
|
130
188
|
});
|
|
131
189
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
fs.mkdirSync(path.dirname(dictionaryOutputPath), { recursive: true });
|
|
136
|
-
fs.writeFileSync(dictionaryOutputPath, dictionaryContent);
|
|
137
|
-
|
|
138
|
-
fs.mkdirSync(path.dirname(instanceOutputPath), { recursive: true });
|
|
139
|
-
fs.writeFileSync(instanceOutputPath, instanceContent);
|
|
190
|
+
writeFileIfChanged(typesOutputPath, typesContent);
|
|
191
|
+
writeFileIfChanged(dictionaryOutputPath, dictionaryContent);
|
|
192
|
+
writeFileIfChanged(instanceOutputPath, instanceContent);
|
|
140
193
|
|
|
141
194
|
const generatedFiles = [
|
|
142
195
|
path.relative(projectRoot, typesOutputPath),
|
|
@@ -155,8 +208,7 @@ function main() {
|
|
|
155
208
|
importExtension
|
|
156
209
|
);
|
|
157
210
|
|
|
158
|
-
|
|
159
|
-
fs.writeFileSync(dictionarySchemaOutputPath, dictionarySchemaContent);
|
|
211
|
+
writeFileIfChanged(dictionarySchemaOutputPath, dictionarySchemaContent);
|
|
160
212
|
generatedFiles.push(path.relative(projectRoot, dictionarySchemaOutputPath));
|
|
161
213
|
}
|
|
162
214
|
|
|
@@ -170,16 +222,23 @@ function main() {
|
|
|
170
222
|
...entry,
|
|
171
223
|
absolutePath: path.resolve(projectRoot, entry.filePath),
|
|
172
224
|
}));
|
|
173
|
-
const namespaceLoadersContent = formatNamespaceLoadersFile(
|
|
174
|
-
namespaceLoadersOutputPath,
|
|
175
|
-
lazyEntriesWithPaths,
|
|
225
|
+
const namespaceLoadersContent = formatNamespaceLoadersFile({
|
|
226
|
+
loadersOutputPath: namespaceLoadersOutputPath,
|
|
227
|
+
lazyEntries: lazyEntriesWithPaths,
|
|
176
228
|
schemaTypeName,
|
|
229
|
+
localeTypeName,
|
|
177
230
|
typesModule,
|
|
178
|
-
importExtension
|
|
179
|
-
|
|
231
|
+
importExtension,
|
|
232
|
+
projectRoot,
|
|
233
|
+
delivery,
|
|
234
|
+
splitPathsByNamespace,
|
|
235
|
+
...(delivery === "split-by-locale" ? { requestLocales: requestLocalesList } : {}),
|
|
236
|
+
...(delivery === "custom" && deliveryAreaTypeName && deliveryAreaNames
|
|
237
|
+
? { deliveryAreaTypeName, deliveryAreaNames }
|
|
238
|
+
: {}),
|
|
239
|
+
});
|
|
180
240
|
|
|
181
|
-
|
|
182
|
-
fs.writeFileSync(namespaceLoadersOutputPath, namespaceLoadersContent);
|
|
241
|
+
writeFileIfChanged(namespaceLoadersOutputPath, namespaceLoadersContent);
|
|
183
242
|
generatedFiles.push(path.relative(projectRoot, namespaceLoadersOutputPath));
|
|
184
243
|
}
|
|
185
244
|
|
|
@@ -189,4 +248,9 @@ function main() {
|
|
|
189
248
|
}
|
|
190
249
|
}
|
|
191
250
|
|
|
192
|
-
|
|
251
|
+
try {
|
|
252
|
+
main();
|
|
253
|
+
} catch (error) {
|
|
254
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
255
|
+
process.exit(1);
|
|
256
|
+
}
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
mergeVariableMetaAcrossLocales,
|
|
7
7
|
type VariableSpec,
|
|
8
8
|
} from "../icu/extract-variables.js";
|
|
9
|
-
import type { NamespaceEntry } from "./types.js";
|
|
9
|
+
import type { DictionaryJson, NamespaceEntry } from "./types.js";
|
|
10
10
|
import { readDictionaryFile } from "./read-dictionary.js";
|
|
11
11
|
|
|
12
12
|
export function paramsTypeForVariables(variables: VariableSpec): string {
|
|
@@ -26,8 +26,13 @@ export interface DictionaryAnalysis {
|
|
|
26
26
|
paramsByNamespace: Record<string, Record<string, string>>;
|
|
27
27
|
argsSpecByNamespace: Record<string, Record<string, VariableSpec>>;
|
|
28
28
|
locales: Set<string>;
|
|
29
|
+
dictionariesByNamespace: Record<string, DictionaryJson>;
|
|
29
30
|
}
|
|
30
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Phase 1 of codegen: read source dictionaries, validate ICU syntax, infer param types.
|
|
34
|
+
* Output feeds type emission, optional `DICTIONARY_SPEC`, and `prepareDictionaryEntries`.
|
|
35
|
+
*/
|
|
31
36
|
export function analyzeDictionaries(
|
|
32
37
|
projectRoot: string,
|
|
33
38
|
entries: NamespaceEntry[]
|
|
@@ -35,6 +40,7 @@ export function analyzeDictionaries(
|
|
|
35
40
|
const paramsByNamespace: Record<string, Record<string, string>> = {};
|
|
36
41
|
const argsSpecByNamespace: Record<string, Record<string, VariableSpec>> = {};
|
|
37
42
|
const locales = new Set<string>();
|
|
43
|
+
const dictionariesByNamespace: Record<string, DictionaryJson> = {};
|
|
38
44
|
let hasErrors = false;
|
|
39
45
|
|
|
40
46
|
for (const entry of entries) {
|
|
@@ -49,6 +55,7 @@ export function analyzeDictionaries(
|
|
|
49
55
|
}
|
|
50
56
|
|
|
51
57
|
const dictionary = readDictionaryFile(absolutePath);
|
|
58
|
+
dictionariesByNamespace[entry.namespace] = dictionary;
|
|
52
59
|
paramsByNamespace[entry.namespace] = {};
|
|
53
60
|
argsSpecByNamespace[entry.namespace] = {};
|
|
54
61
|
|
|
@@ -98,6 +105,6 @@ export function analyzeDictionaries(
|
|
|
98
105
|
|
|
99
106
|
return {
|
|
100
107
|
ok: true,
|
|
101
|
-
analysis: { paramsByNamespace, argsSpecByNamespace, locales },
|
|
108
|
+
analysis: { paramsByNamespace, argsSpecByNamespace, locales, dictionariesByNamespace },
|
|
102
109
|
};
|
|
103
110
|
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { validateLocaleFallback } from "../resolve-locale.js";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Union of locales found in dictionaries plus every locale referenced by `localeFallback`.
|
|
5
|
+
* Drives split-by-locale partitioning and the generated locale union type.
|
|
6
|
+
*/
|
|
3
7
|
export function collectRequestLocales(
|
|
4
8
|
dictionaryLocales: Set<string>,
|
|
5
9
|
fallback?: Record<string, string | null>
|
|
@@ -19,30 +23,35 @@ export function collectRequestLocales(
|
|
|
19
23
|
return all;
|
|
20
24
|
}
|
|
21
25
|
|
|
22
|
-
export
|
|
26
|
+
export type LocaleFallbackIssue = {
|
|
27
|
+
path: (string | number)[];
|
|
28
|
+
message: string;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/** Pre-emit validation gate for `localeFallback` config against discovered dictionary locales. */
|
|
32
|
+
export function getCodegenLocaleFallbackIssues(
|
|
23
33
|
fallback: Record<string, string | null>,
|
|
24
34
|
dictionaryLocales: Set<string>
|
|
25
|
-
):
|
|
26
|
-
|
|
35
|
+
): LocaleFallbackIssue[] {
|
|
36
|
+
const issues: LocaleFallbackIssue[] = [];
|
|
27
37
|
|
|
28
38
|
try {
|
|
29
39
|
validateLocaleFallback(fallback);
|
|
30
40
|
} catch (error) {
|
|
31
41
|
const message = error instanceof Error ? error.message : String(error);
|
|
32
|
-
|
|
33
|
-
hasErrors = true;
|
|
42
|
+
issues.push({ path: ["localeFallback"], message });
|
|
34
43
|
}
|
|
35
44
|
|
|
36
45
|
for (const [locale, target] of Object.entries(fallback)) {
|
|
37
46
|
if (target !== null && !(target in fallback) && !dictionaryLocales.has(target)) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
47
|
+
issues.push({
|
|
48
|
+
path: ["localeFallback", locale],
|
|
49
|
+
message: `localeFallback: "${locale}" points to "${target}" which is not defined in the fallback map or dictionary locales`,
|
|
50
|
+
});
|
|
42
51
|
}
|
|
43
52
|
}
|
|
44
53
|
|
|
45
|
-
return
|
|
54
|
+
return issues;
|
|
46
55
|
}
|
|
47
56
|
|
|
48
57
|
export function formatLocaleFallbackBlock(
|
|
@@ -7,6 +7,10 @@ export function buildRequiredLocales(
|
|
|
7
7
|
return [...collectRequestLocales(dictionaryLocales, configFallback)].sort();
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Completes `localeFallback` with explicit `null` entries for every request locale,
|
|
12
|
+
* producing a stable map emitted into `i18n-types.generated.ts`.
|
|
13
|
+
*/
|
|
10
14
|
export function enrichLocaleFallback(
|
|
11
15
|
dictionaryLocales: Set<string>,
|
|
12
16
|
configFallback: Record<string, string | null>
|
package/src/codegen/paths.ts
CHANGED
|
@@ -6,10 +6,10 @@ import { CodegenConfig } from "./codegen-config-schema.js";
|
|
|
6
6
|
export const GENERATED_FILE_BANNER = "// Automatically generated code. Do not edit manually.\n";
|
|
7
7
|
export const DEFAULT_IMPORT_EXTENSION: ImportExtension = "none";
|
|
8
8
|
|
|
9
|
-
export function
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
export function reportCodegenIssues(issues: readonly { message: string }[]): void {
|
|
10
|
+
for (const issue of issues) {
|
|
11
|
+
console.error(`[Codegen Error] ${issue.message}`);
|
|
12
|
+
}
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export function toImportPath(fromFile: string, toFile: string): string {
|
|
@@ -27,7 +27,7 @@ export function resolveImportExtension(
|
|
|
27
27
|
): ImportExtension {
|
|
28
28
|
const extension = config.importExtension ?? DEFAULT_IMPORT_EXTENSION;
|
|
29
29
|
if (!SUPPORTED_IMPORT_EXTENSIONS.includes(extension)) {
|
|
30
|
-
|
|
30
|
+
throw new Error(
|
|
31
31
|
`[Codegen Error] importExtension must be "none", ".ts", or ".js", got ${JSON.stringify(extension)}.`
|
|
32
32
|
);
|
|
33
33
|
}
|
|
@@ -52,3 +52,7 @@ export function toImportIdentifier(namespace: string): string {
|
|
|
52
52
|
}
|
|
53
53
|
return `${safe}Ns`;
|
|
54
54
|
}
|
|
55
|
+
|
|
56
|
+
export function toLocaleObjectKey(locale: string): string {
|
|
57
|
+
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(locale) ? locale : JSON.stringify(locale);
|
|
58
|
+
}
|
|
@@ -95,7 +95,7 @@ function writeTscProject(tempDir: string) {
|
|
|
95
95
|
);
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
describe("codegen
|
|
98
|
+
describe("codegen projectDictionaryLocales + setNamespace", () => {
|
|
99
99
|
describe("generated instance output", () => {
|
|
100
100
|
let tempDir: string;
|
|
101
101
|
|
|
@@ -112,7 +112,7 @@ describe("codegen projectLocales + setNamespace", () => {
|
|
|
112
112
|
const factory = readFileSync(join(tempDir, "src/i18n/instance.generated.ts"), "utf8");
|
|
113
113
|
expect(factory).toContain('dictionary: AppSchema["billing"]');
|
|
114
114
|
expect(factory).toContain(
|
|
115
|
-
"
|
|
115
|
+
"projectDictionaryLocalesCore(dictionary, locales, LOCALE_FALLBACK)"
|
|
116
116
|
);
|
|
117
117
|
});
|
|
118
118
|
|
|
@@ -138,14 +138,14 @@ describe("codegen projectLocales + setNamespace", () => {
|
|
|
138
138
|
expect(tsc.status, `${tsc.stdout}\n${tsc.stderr}`).toBe(0);
|
|
139
139
|
});
|
|
140
140
|
|
|
141
|
-
it("passes tsc when replacing the full schema with
|
|
141
|
+
it("passes tsc when replacing the full schema with projectDictionaryLocales before setAll", () => {
|
|
142
142
|
tempDir = mkdtempSync(join(tmpdir(), "xndrjs-i18n-project-locales-"));
|
|
143
143
|
setupMultiCodegenFixture(tempDir);
|
|
144
144
|
|
|
145
145
|
writeFileSync(
|
|
146
146
|
join(tempDir, "src/hydrate-all.ts"),
|
|
147
147
|
[
|
|
148
|
-
`import { createI18n,
|
|
148
|
+
`import { createI18n, projectDictionaryLocales } from "./i18n/instance.generated.js";`,
|
|
149
149
|
`import { defaultDictionary } from "./i18n/dictionary.generated.js";`,
|
|
150
150
|
`import type { AppSchema } from "./i18n/i18n-types.generated.js";`,
|
|
151
151
|
`import billingDictionary from "./i18n/translations/billing.json";`,
|
|
@@ -157,7 +157,7 @@ describe("codegen projectLocales + setNamespace", () => {
|
|
|
157
157
|
`} satisfies AppSchema;`,
|
|
158
158
|
``,
|
|
159
159
|
`const i18n = createI18n(defaultDictionary);`,
|
|
160
|
-
`i18n.setAll(
|
|
160
|
+
`i18n.setAll(projectDictionaryLocales(fullDictionary, ["en"]));`,
|
|
161
161
|
].join("\n")
|
|
162
162
|
);
|
|
163
163
|
|