@xndrjs/i18n-react 0.8.1 → 0.8.2-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 +125 -0
- package/dist/cli/codegen.js +305 -0
- package/dist/cli/codegen.js.map +1 -0
- package/package.json +6 -9
- package/bin/codegen.mjs +0 -17
- package/src/codegen/emit/react-bindings-file.test.ts +0 -44
- package/src/codegen/emit/react-bindings-file.ts +0 -223
- package/src/codegen/generate-react-bindings.ts +0 -86
- package/src/codegen/react-codegen-config.test.ts +0 -39
- package/src/codegen/react-codegen-config.ts +0 -70
- package/src/codegen/write-file-if-changed.ts +0 -16
- package/src/create-load-coordinator.test.ts +0 -421
- package/src/create-load-coordinator.ts +0 -340
- package/src/create-scoped-t.ts +0 -50
- package/src/index.ts +0 -18
- package/src/namespace-load-gate.test.tsx +0 -608
- package/src/namespace-load-gate.tsx +0 -265
- package/src/root-context.tsx +0 -21
- package/src/root-provider.tsx +0 -65
- package/src/runtime.test.tsx +0 -88
- package/src/types.ts +0 -138
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
|
|
3
|
-
export const GENERATED_FILE_BANNER = "// Automatically generated code. Do not edit manually.\n";
|
|
4
|
-
|
|
5
|
-
export type ImportExtension = "none" | ".ts" | ".js";
|
|
6
|
-
|
|
7
|
-
export function importExtensionSuffix(importExtension: ImportExtension): string {
|
|
8
|
-
return importExtension === "none" ? "" : importExtension;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function toModuleBasename(filePath: string): string {
|
|
12
|
-
return path.basename(filePath).replace(/\.tsx?$/, "");
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function toRelativeModuleImport(
|
|
16
|
-
moduleBasename: string,
|
|
17
|
-
importExtension: ImportExtension
|
|
18
|
-
): string {
|
|
19
|
-
return `./${moduleBasename}${importExtensionSuffix(importExtension)}`;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface ReactBindingsFileOptions {
|
|
23
|
-
factoryName: string;
|
|
24
|
-
paramsTypeName: string;
|
|
25
|
-
schemaTypeName: string;
|
|
26
|
-
localeTypeName: string;
|
|
27
|
-
instanceImport: string;
|
|
28
|
-
typesImport: string;
|
|
29
|
-
/** When `"fetch"`, {@link I18nRoot} requires `fetchImpl`. */
|
|
30
|
-
loaderStrategy?: "import" | "fetch";
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/** When `Ns` is `[]`, reject every `t(...)` call so refactoring can clear namespaces and re-add only what is used. */
|
|
34
|
-
function formatMultiScopedTType(
|
|
35
|
-
schemaTypeName: string,
|
|
36
|
-
paramsTypeName: string,
|
|
37
|
-
localeTypeName: string
|
|
38
|
-
): string {
|
|
39
|
-
const emptyBranch = `(namespace: never, key: never, ...args: never[]) => string`;
|
|
40
|
-
return (
|
|
41
|
-
`type ScopedT<Ns extends readonly AppNamespace[]> = Ns extends readonly []\n` +
|
|
42
|
-
` ? ${emptyBranch}\n` +
|
|
43
|
-
` : I18nScopeMultiForLocale<\n` +
|
|
44
|
-
` SchemaForNamespaces<${schemaTypeName}, Ns>,\n` +
|
|
45
|
-
` ParamsForNamespaces<${schemaTypeName}, ${paramsTypeName}, Ns>,\n` +
|
|
46
|
-
` ${localeTypeName},\n` +
|
|
47
|
-
` ${localeTypeName}\n` +
|
|
48
|
-
`>["t"];\n\n`
|
|
49
|
-
);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function formatI18nRoot(options: {
|
|
53
|
-
factoryName: string;
|
|
54
|
-
localeTypeName: string;
|
|
55
|
-
loaderStrategy: "import" | "fetch";
|
|
56
|
-
}): string {
|
|
57
|
-
const { factoryName, localeTypeName, loaderStrategy } = options;
|
|
58
|
-
|
|
59
|
-
if (loaderStrategy === "fetch") {
|
|
60
|
-
return (
|
|
61
|
-
`export function I18nRoot({\n` +
|
|
62
|
-
` locale,\n` +
|
|
63
|
-
` children,\n` +
|
|
64
|
-
` state,\n` +
|
|
65
|
-
` dictionary,\n` +
|
|
66
|
-
` fetchImpl,\n` +
|
|
67
|
-
`}: {\n` +
|
|
68
|
-
` locale: ${localeTypeName};\n` +
|
|
69
|
-
` children: ReactNode;\n` +
|
|
70
|
-
` state?: I18nCreateInput | undefined;\n` +
|
|
71
|
-
` dictionary?: Record<string, unknown> | undefined;\n` +
|
|
72
|
-
` fetchImpl: FetchArtifact;\n` +
|
|
73
|
-
`}) {\n` +
|
|
74
|
-
` return (\n` +
|
|
75
|
-
` <I18nRootProvider\n` +
|
|
76
|
-
` createI18n={${factoryName}}\n` +
|
|
77
|
-
` locale={locale}\n` +
|
|
78
|
-
` fetchImpl={fetchImpl}\n` +
|
|
79
|
-
` {...(state !== undefined ? { state } : {})}\n` +
|
|
80
|
-
` {...(dictionary !== undefined ? { dictionary } : {})}\n` +
|
|
81
|
-
` >\n` +
|
|
82
|
-
` {children}\n` +
|
|
83
|
-
` </I18nRootProvider>\n` +
|
|
84
|
-
` );\n` +
|
|
85
|
-
`}\n`
|
|
86
|
-
);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return (
|
|
90
|
-
`export function I18nRoot({\n` +
|
|
91
|
-
` locale,\n` +
|
|
92
|
-
` children,\n` +
|
|
93
|
-
` state,\n` +
|
|
94
|
-
` dictionary,\n` +
|
|
95
|
-
`}: {\n` +
|
|
96
|
-
` locale: ${localeTypeName};\n` +
|
|
97
|
-
` children: ReactNode;\n` +
|
|
98
|
-
` state?: I18nCreateInput | undefined;\n` +
|
|
99
|
-
` dictionary?: Record<string, unknown> | undefined;\n` +
|
|
100
|
-
`}) {\n` +
|
|
101
|
-
` return (\n` +
|
|
102
|
-
` <I18nRootProvider\n` +
|
|
103
|
-
` createI18n={${factoryName}}\n` +
|
|
104
|
-
` locale={locale}\n` +
|
|
105
|
-
` {...(state !== undefined ? { state } : {})}\n` +
|
|
106
|
-
` {...(dictionary !== undefined ? { dictionary } : {})}\n` +
|
|
107
|
-
` >\n` +
|
|
108
|
-
` {children}\n` +
|
|
109
|
-
` </I18nRootProvider>\n` +
|
|
110
|
-
` );\n` +
|
|
111
|
-
`}\n`
|
|
112
|
-
);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Emits typed root + withI18n / I18n for split-by-locale or custom delivery.
|
|
117
|
-
* Every namespace loads through the handle + namespaceLoaders.
|
|
118
|
-
* Partition→area mapping for custom delivery is injected into the handle by core codegen.
|
|
119
|
-
*/
|
|
120
|
-
export function formatReactBindingsFile(options: ReactBindingsFileOptions): string {
|
|
121
|
-
const {
|
|
122
|
-
factoryName,
|
|
123
|
-
localeTypeName,
|
|
124
|
-
instanceImport,
|
|
125
|
-
paramsTypeName,
|
|
126
|
-
schemaTypeName,
|
|
127
|
-
typesImport,
|
|
128
|
-
loaderStrategy = "import",
|
|
129
|
-
} = options;
|
|
130
|
-
|
|
131
|
-
const createInputImport =
|
|
132
|
-
loaderStrategy === "fetch"
|
|
133
|
-
? `import type { FetchArtifact, I18nCreateInput } from "@xndrjs/i18n";\n`
|
|
134
|
-
: `import type { I18nCreateInput } from "@xndrjs/i18n";\n`;
|
|
135
|
-
|
|
136
|
-
return (
|
|
137
|
-
`${GENERATED_FILE_BANNER}` +
|
|
138
|
-
`"use client";\n\n` +
|
|
139
|
-
`import {\n` +
|
|
140
|
-
` type ForwardedRef,\n` +
|
|
141
|
-
` type ForwardRefExoticComponent,\n` +
|
|
142
|
-
` type ReactNode,\n` +
|
|
143
|
-
` type RefAttributes,\n` +
|
|
144
|
-
`} from "react";\n` +
|
|
145
|
-
`import {\n` +
|
|
146
|
-
` createI18nLoadGate,\n` +
|
|
147
|
-
` I18nRootProvider,\n` +
|
|
148
|
-
` useI18nRootContext,\n` +
|
|
149
|
-
`} from "@xndrjs/i18n-react";\n` +
|
|
150
|
-
createInputImport +
|
|
151
|
-
`import type {\n` +
|
|
152
|
-
` I18nScopeMultiForLocale,\n` +
|
|
153
|
-
` ParamsForNamespaces,\n` +
|
|
154
|
-
` SchemaForNamespaces,\n` +
|
|
155
|
-
`} from "@xndrjs/i18n";\n` +
|
|
156
|
-
`import { ${factoryName} } from "${instanceImport}";\n` +
|
|
157
|
-
`import type { ${paramsTypeName}, ${schemaTypeName}, ${localeTypeName} } from "${typesImport}";\n` +
|
|
158
|
-
`\n` +
|
|
159
|
-
`export function useI18nRoot(): ReturnType<typeof ${factoryName}> {\n` +
|
|
160
|
-
` return useI18nRootContext().handle as ReturnType<typeof ${factoryName}>;\n` +
|
|
161
|
-
`}\n\n` +
|
|
162
|
-
`type AppNamespace = keyof ${schemaTypeName} & string;\n\n` +
|
|
163
|
-
formatMultiScopedTType(schemaTypeName, paramsTypeName, localeTypeName) +
|
|
164
|
-
`type I18nInjected<Ns extends readonly AppNamespace[]> = {\n` +
|
|
165
|
-
` t: ScopedT<Ns>;\n` +
|
|
166
|
-
` locale: ${localeTypeName};\n` +
|
|
167
|
-
` pendingLocale?: ${localeTypeName};\n` +
|
|
168
|
-
` error?: unknown;\n` +
|
|
169
|
-
` retry?: () => void;\n` +
|
|
170
|
-
`};\n` +
|
|
171
|
-
`export type I18nProps<Ns extends readonly AppNamespace[]> = I18nInjected<Ns>;\n\n` +
|
|
172
|
-
`export type WithI18nFallback<P extends object> = ReactNode | ((props: P) => ReactNode);\n\n` +
|
|
173
|
-
`const gate = createI18nLoadGate({\n` +
|
|
174
|
-
` useLoadArgs: () => {\n` +
|
|
175
|
-
` const root = useI18nRootContext();\n` +
|
|
176
|
-
` const { handle, coordinator, locale } = root;\n` +
|
|
177
|
-
` return {\n` +
|
|
178
|
-
` coordinator,\n` +
|
|
179
|
-
` engineRef: handle,\n` +
|
|
180
|
-
` partition: locale,\n` +
|
|
181
|
-
` locale,\n` +
|
|
182
|
-
` load: (namespaces: readonly string[]) =>\n` +
|
|
183
|
-
` handle.load({\n` +
|
|
184
|
-
` namespaces: namespaces as [AppNamespace, ...AppNamespace[]],\n` +
|
|
185
|
-
` locale,\n` +
|
|
186
|
-
` }),\n` +
|
|
187
|
-
` tryResolveSync: (namespaces: readonly string[]) =>\n` +
|
|
188
|
-
` handle.peek({\n` +
|
|
189
|
-
` namespaces: namespaces as [AppNamespace, ...AppNamespace[]],\n` +
|
|
190
|
-
` locale,\n` +
|
|
191
|
-
` }),\n` +
|
|
192
|
-
` };\n` +
|
|
193
|
-
` },\n` +
|
|
194
|
-
`});\n\n` +
|
|
195
|
-
`export function withI18n<\n` +
|
|
196
|
-
` P extends object = object,\n` +
|
|
197
|
-
` R = never,\n` +
|
|
198
|
-
` const Ns extends readonly AppNamespace[] = readonly AppNamespace[],\n` +
|
|
199
|
-
`>(\n` +
|
|
200
|
-
` options: {\n` +
|
|
201
|
-
` namespaces: Ns;\n` +
|
|
202
|
-
` fallback?: WithI18nFallback<P>;\n` +
|
|
203
|
-
` renderError?: (args: { error: unknown; retry: () => void; props: P }) => ReactNode;\n` +
|
|
204
|
-
` },\n` +
|
|
205
|
-
` render: (props: P, i18n: I18nProps<Ns>, ref?: ForwardedRef<R>) => ReactNode\n` +
|
|
206
|
-
`): ForwardRefExoticComponent<P & RefAttributes<R>> {\n` +
|
|
207
|
-
` return gate.withI18n(options, render as never) as ForwardRefExoticComponent<\n` +
|
|
208
|
-
` P & RefAttributes<R>\n` +
|
|
209
|
-
` >;\n` +
|
|
210
|
-
`}\n\n` +
|
|
211
|
-
`export function I18n<\n` +
|
|
212
|
-
` const Ns extends readonly AppNamespace[],\n` +
|
|
213
|
-
`>(props: {\n` +
|
|
214
|
-
` namespaces: Ns;\n` +
|
|
215
|
-
` fallback?: ReactNode;\n` +
|
|
216
|
-
` renderError?: (args: { error: unknown; retry: () => void }) => ReactNode;\n` +
|
|
217
|
-
` children: (value: I18nProps<Ns>) => ReactNode;\n` +
|
|
218
|
-
`}) {\n` +
|
|
219
|
-
` return gate.I18n(props as never);\n` +
|
|
220
|
-
`}\n\n` +
|
|
221
|
-
formatI18nRoot({ factoryName, localeTypeName, loaderStrategy })
|
|
222
|
-
);
|
|
223
|
-
}
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { loadConfig } from "@xndrjs/i18n/codegen";
|
|
4
|
-
import type { CodegenConfig } from "@xndrjs/i18n/codegen";
|
|
5
|
-
import { resolveCodegenPaths } from "@xndrjs/i18n/codegen";
|
|
6
|
-
import {
|
|
7
|
-
formatReactBindingsFile,
|
|
8
|
-
toModuleBasename,
|
|
9
|
-
toRelativeModuleImport,
|
|
10
|
-
} from "./emit/react-bindings-file.js";
|
|
11
|
-
import {
|
|
12
|
-
defaultReactConfigPath,
|
|
13
|
-
loadReactCodegenConfig,
|
|
14
|
-
resolveReactBindingsOutputPath,
|
|
15
|
-
} from "./react-codegen-config.js";
|
|
16
|
-
import { writeFileIfChanged } from "./write-file-if-changed.js";
|
|
17
|
-
|
|
18
|
-
function readCliFlag(flag: string): string | undefined {
|
|
19
|
-
const index = process.argv.indexOf(flag);
|
|
20
|
-
if (index < 0) {
|
|
21
|
-
return undefined;
|
|
22
|
-
}
|
|
23
|
-
const value = process.argv[index + 1];
|
|
24
|
-
if (value === undefined || value.startsWith("-")) {
|
|
25
|
-
throw new Error(`[i18n-react Codegen Error] Missing value for ${flag}`);
|
|
26
|
-
}
|
|
27
|
-
return value;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* React bindings codegen CLI.
|
|
32
|
-
*
|
|
33
|
-
* Reads `i18n.codegen.json` from `@xndrjs/i18n` and optionally `i18n-react.codegen.json`.
|
|
34
|
-
* Output path: `--out` > react config `output` > default next to instance under `output/`.
|
|
35
|
-
*/
|
|
36
|
-
function main(): void {
|
|
37
|
-
const configPath = path.resolve(
|
|
38
|
-
process.cwd(),
|
|
39
|
-
readCliFlag("--config") ?? "i18n/i18n.codegen.json"
|
|
40
|
-
);
|
|
41
|
-
const projectRoot = path.dirname(configPath);
|
|
42
|
-
|
|
43
|
-
if (!fs.existsSync(configPath)) {
|
|
44
|
-
throw new Error(`[i18n-react Codegen Error] Config file not found: ${configPath}`);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const config: CodegenConfig = loadConfig(configPath);
|
|
48
|
-
const paths = resolveCodegenPaths(config);
|
|
49
|
-
|
|
50
|
-
const instanceImport = toRelativeModuleImport(toModuleBasename(paths.instanceOutput), "none");
|
|
51
|
-
const typesImport = toRelativeModuleImport(toModuleBasename(paths.typesOutput), "none");
|
|
52
|
-
|
|
53
|
-
const reactConfigPath = path.resolve(
|
|
54
|
-
projectRoot,
|
|
55
|
-
readCliFlag("--react-config") ?? defaultReactConfigPath(configPath)
|
|
56
|
-
);
|
|
57
|
-
const reactConfig = loadReactCodegenConfig(reactConfigPath);
|
|
58
|
-
const cliOut = readCliFlag("--out");
|
|
59
|
-
const reactOutputRelative = resolveReactBindingsOutputPath({
|
|
60
|
-
instanceOutput: paths.instanceOutput,
|
|
61
|
-
...(cliOut ? { cliOut } : {}),
|
|
62
|
-
reactConfig,
|
|
63
|
-
});
|
|
64
|
-
const reactOutputPath = path.resolve(projectRoot, reactOutputRelative);
|
|
65
|
-
|
|
66
|
-
const content = formatReactBindingsFile({
|
|
67
|
-
factoryName: paths.factoryName,
|
|
68
|
-
paramsTypeName: paths.paramsTypeName,
|
|
69
|
-
schemaTypeName: paths.schemaTypeName,
|
|
70
|
-
localeTypeName: paths.localeTypeName,
|
|
71
|
-
instanceImport,
|
|
72
|
-
typesImport,
|
|
73
|
-
loaderStrategy: config.loaderStrategy ?? "import",
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
const written = writeFileIfChanged(reactOutputPath, content);
|
|
77
|
-
const displayPath = path.relative(projectRoot, reactOutputPath);
|
|
78
|
-
|
|
79
|
-
if (written) {
|
|
80
|
-
console.log(`[i18n-react Codegen] Wrote ${displayPath}`);
|
|
81
|
-
} else {
|
|
82
|
-
console.log(`[i18n-react Codegen] Unchanged ${displayPath}`);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
main();
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
defaultReactBindingsOutput,
|
|
4
|
-
defaultReactConfigPath,
|
|
5
|
-
resolveReactBindingsOutputPath,
|
|
6
|
-
} from "./react-codegen-config.js";
|
|
7
|
-
|
|
8
|
-
describe("react-codegen-config", () => {
|
|
9
|
-
it("defaults output next to instanceOutput", () => {
|
|
10
|
-
expect(defaultReactBindingsOutput("generated/instance.generated.ts")).toBe(
|
|
11
|
-
"generated/react-bindings.generated.tsx"
|
|
12
|
-
);
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
it("prefers --out over react config and default", () => {
|
|
16
|
-
expect(
|
|
17
|
-
resolveReactBindingsOutputPath({
|
|
18
|
-
instanceOutput: "generated/instance.generated.ts",
|
|
19
|
-
cliOut: "custom/bindings.tsx",
|
|
20
|
-
reactConfig: { output: "ignored.tsx" },
|
|
21
|
-
})
|
|
22
|
-
).toBe("custom/bindings.tsx");
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it("uses react config output when --out is omitted", () => {
|
|
26
|
-
expect(
|
|
27
|
-
resolveReactBindingsOutputPath({
|
|
28
|
-
instanceOutput: "generated/instance.generated.ts",
|
|
29
|
-
reactConfig: { output: "generated/custom-react.tsx" },
|
|
30
|
-
})
|
|
31
|
-
).toBe("generated/custom-react.tsx");
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it("derives default react config path from i18n.codegen.json", () => {
|
|
35
|
-
expect(defaultReactConfigPath("/app/i18n/i18n.codegen.json")).toBe(
|
|
36
|
-
"/app/i18n/i18n-react.codegen.json"
|
|
37
|
-
);
|
|
38
|
-
});
|
|
39
|
-
});
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { z } from "zod";
|
|
4
|
-
|
|
5
|
-
export const DEFAULT_REACT_BINDINGS_BASENAME = "react-bindings.generated.tsx";
|
|
6
|
-
|
|
7
|
-
const reactCodegenConfigSchema = z
|
|
8
|
-
.object({
|
|
9
|
-
output: z.string().min(1).optional(),
|
|
10
|
-
})
|
|
11
|
-
.strict();
|
|
12
|
-
|
|
13
|
-
export type ReactCodegenConfig = z.infer<typeof reactCodegenConfigSchema>;
|
|
14
|
-
|
|
15
|
-
export function defaultReactBindingsOutput(instanceOutput: string): string {
|
|
16
|
-
return path.join(path.dirname(instanceOutput), DEFAULT_REACT_BINDINGS_BASENAME);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/** Loads optional `i18n-react.codegen.json` when present. */
|
|
20
|
-
export function loadReactCodegenConfig(configPath: string): ReactCodegenConfig {
|
|
21
|
-
if (!fs.existsSync(configPath)) {
|
|
22
|
-
return {};
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
let raw: unknown;
|
|
26
|
-
try {
|
|
27
|
-
raw = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
28
|
-
} catch (error) {
|
|
29
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
30
|
-
throw new Error(
|
|
31
|
-
`[i18n-react Codegen Error] Failed to parse react config JSON (${configPath}): ${message}`
|
|
32
|
-
);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const result = reactCodegenConfigSchema.safeParse(raw);
|
|
36
|
-
if (!result.success) {
|
|
37
|
-
const issueLines = result.error.issues.map((issue) => {
|
|
38
|
-
const issuePath = issue.path.length > 0 ? issue.path.join(".") : "(root)";
|
|
39
|
-
return ` - ${issuePath}: ${issue.message}`;
|
|
40
|
-
});
|
|
41
|
-
throw new Error(
|
|
42
|
-
["[i18n-react Codegen Error] Invalid i18n-react.codegen.json:", ...issueLines].join("\n")
|
|
43
|
-
);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return result.data;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function resolveReactBindingsOutputPath(options: {
|
|
50
|
-
instanceOutput: string;
|
|
51
|
-
cliOut?: string;
|
|
52
|
-
reactConfig?: ReactCodegenConfig;
|
|
53
|
-
}): string {
|
|
54
|
-
if (options.cliOut !== undefined) {
|
|
55
|
-
return options.cliOut;
|
|
56
|
-
}
|
|
57
|
-
if (options.reactConfig?.output !== undefined) {
|
|
58
|
-
return options.reactConfig.output;
|
|
59
|
-
}
|
|
60
|
-
return defaultReactBindingsOutput(options.instanceOutput);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export function defaultReactConfigPath(i18nConfigPath: string): string {
|
|
64
|
-
const dir = path.dirname(i18nConfigPath);
|
|
65
|
-
const base = path.basename(i18nConfigPath, path.extname(i18nConfigPath));
|
|
66
|
-
if (base === "i18n.codegen") {
|
|
67
|
-
return path.join(dir, "i18n-react.codegen.json");
|
|
68
|
-
}
|
|
69
|
-
return path.join(dir, `${base}.react.codegen.json`);
|
|
70
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
|
|
4
|
-
/** Writes only when content changed — stable mtimes for watchers. */
|
|
5
|
-
export function writeFileIfChanged(absolutePath: string, content: string): boolean {
|
|
6
|
-
if (fs.existsSync(absolutePath)) {
|
|
7
|
-
const currentContent = fs.readFileSync(absolutePath, "utf8");
|
|
8
|
-
if (currentContent === content) {
|
|
9
|
-
return false;
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
fs.mkdirSync(path.dirname(absolutePath), { recursive: true });
|
|
14
|
-
fs.writeFileSync(absolutePath, content);
|
|
15
|
-
return true;
|
|
16
|
-
}
|