@gtkx/cli 1.6.0 → 2.0.0-beta.1
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 +5 -5
- package/dist/codegen/freshness.d.ts.map +1 -1
- package/dist/codegen/freshness.js +45 -16
- package/dist/codegen/freshness.js.map +1 -1
- package/dist/codegen/reference.d.ts.map +1 -1
- package/dist/codegen/reference.js +1 -7
- package/dist/codegen/reference.js.map +1 -1
- package/dist/codegen/report.d.ts.map +1 -1
- package/dist/codegen/report.js +5 -16
- package/dist/codegen/report.js.map +1 -1
- package/dist/codegen/run-codegen.d.ts +3 -5
- package/dist/codegen/run-codegen.d.ts.map +1 -1
- package/dist/codegen/run-codegen.js +23 -100
- package/dist/codegen/run-codegen.js.map +1 -1
- package/dist/codegen/store-resolver.d.ts +0 -1
- package/dist/codegen/store-resolver.d.ts.map +1 -1
- package/dist/codegen/store-resolver.js +8 -17
- package/dist/codegen/store-resolver.js.map +1 -1
- package/dist/commands/docs.d.ts.map +1 -1
- package/dist/commands/docs.js +5 -15
- package/dist/commands/docs.js.map +1 -1
- package/dist/deploy/freedesktop/copyright.d.ts.map +1 -1
- package/dist/deploy/freedesktop/copyright.js +2 -13
- package/dist/deploy/freedesktop/copyright.js.map +1 -1
- package/dist/deploy/freedesktop/xml.d.ts.map +1 -1
- package/dist/deploy/freedesktop/xml.js +21 -25
- package/dist/deploy/freedesktop/xml.js.map +1 -1
- package/dist/deploy/node-runtime/license.d.ts.map +1 -1
- package/dist/deploy/node-runtime/license.js +2 -9
- package/dist/deploy/node-runtime/license.js.map +1 -1
- package/dist/deploy/notices/packages.d.ts.map +1 -1
- package/dist/deploy/notices/packages.js +3 -10
- package/dist/deploy/notices/packages.js.map +1 -1
- package/dist/deploy/payload/copy-tree.d.ts.map +1 -1
- package/dist/deploy/payload/copy-tree.js +3 -3
- package/dist/deploy/payload/copy-tree.js.map +1 -1
- package/dist/deploy/payload/stage.d.ts.map +1 -1
- package/dist/deploy/payload/stage.js +1 -3
- package/dist/deploy/payload/stage.js.map +1 -1
- package/dist/deploy/settings/build-manifest.d.ts.map +1 -1
- package/dist/deploy/settings/build-manifest.js +4 -15
- package/dist/deploy/settings/build-manifest.js.map +1 -1
- package/dist/deploy/settings/libraries.d.ts.map +1 -1
- package/dist/deploy/settings/libraries.js +2 -19
- package/dist/deploy/settings/libraries.js.map +1 -1
- package/dist/deploy/settings/paths.d.ts +1 -2
- package/dist/deploy/settings/paths.d.ts.map +1 -1
- package/dist/deploy/settings/paths.js +5 -15
- package/dist/deploy/settings/paths.js.map +1 -1
- package/dist/deploy/settings/screenshots.d.ts.map +1 -1
- package/dist/deploy/settings/screenshots.js +6 -31
- package/dist/deploy/settings/screenshots.js.map +1 -1
- package/dist/deploy/targets/flatpak-pnpm.js +4 -4
- package/dist/deploy/targets/flatpak-pnpm.js.map +1 -1
- package/dist/deploy/targets/flatpak-source.d.ts.map +1 -1
- package/dist/deploy/targets/flatpak-source.js +4 -4
- package/dist/deploy/targets/flatpak-source.js.map +1 -1
- package/dist/deploy/targets/flatpak-sources.js +1 -1
- package/dist/deploy/targets/flatpak-sources.js.map +1 -1
- package/dist/dev/runner-main.d.ts.map +1 -1
- package/dist/dev/runner-main.js +1 -2
- package/dist/dev/runner-main.js.map +1 -1
- package/dist/dev/schema-dir.d.ts +1 -1
- package/dist/dev/schema-dir.d.ts.map +1 -1
- package/dist/dev/schema-dir.js +2 -4
- package/dist/dev/schema-dir.js.map +1 -1
- package/dist/i18n/catalogs.d.ts.map +1 -1
- package/dist/i18n/catalogs.js +2 -1
- package/dist/i18n/catalogs.js.map +1 -1
- package/dist/i18n/metadata-templates.d.ts.map +1 -1
- package/dist/i18n/metadata-templates.js +2 -1
- package/dist/i18n/metadata-templates.js.map +1 -1
- package/dist/i18n/source-messages.d.ts +3 -21
- package/dist/i18n/source-messages.d.ts.map +1 -1
- package/dist/i18n/source-messages.js +66 -85
- package/dist/i18n/source-messages.js.map +1 -1
- package/dist/i18n/types.d.ts +6 -7
- package/dist/i18n/types.d.ts.map +1 -1
- package/dist/i18n/types.js +83 -96
- package/dist/i18n/types.js.map +1 -1
- package/dist/internal/docs-elements.js +2 -2
- package/dist/internal/docs-elements.js.map +1 -1
- package/dist/internal/icon-path.d.ts.map +1 -1
- package/dist/internal/icon-path.js +5 -16
- package/dist/internal/icon-path.js.map +1 -1
- package/dist/internal/source-imports.d.ts.map +1 -1
- package/dist/internal/source-imports.js +10 -41
- package/dist/internal/source-imports.js.map +1 -1
- package/dist/settings/render.d.ts +0 -1
- package/dist/settings/render.d.ts.map +1 -1
- package/dist/settings/render.js +1 -11
- package/dist/settings/render.js.map +1 -1
- package/dist/settings/schema.d.ts +2 -2
- package/dist/settings/schema.d.ts.map +1 -1
- package/dist/settings/schema.js +21 -62
- package/dist/settings/schema.js.map +1 -1
- package/dist/vite-plugins/asset-imports.d.ts +1 -2
- package/dist/vite-plugins/asset-imports.d.ts.map +1 -1
- package/dist/vite-plugins/asset-imports.js +19 -32
- package/dist/vite-plugins/asset-imports.js.map +1 -1
- package/dist/vite-plugins/asset-specifier.d.ts +1 -3
- package/dist/vite-plugins/asset-specifier.d.ts.map +1 -1
- package/dist/vite-plugins/asset-specifier.js +1 -4
- package/dist/vite-plugins/asset-specifier.js.map +1 -1
- package/dist/vite-plugins/i18n.d.ts.map +1 -1
- package/dist/vite-plugins/i18n.js +2 -1
- package/dist/vite-plugins/i18n.js.map +1 -1
- package/dist/vite-plugins/index.d.ts.map +1 -1
- package/dist/vite-plugins/index.js +2 -4
- package/dist/vite-plugins/index.js.map +1 -1
- package/dist/vite-plugins/resources.d.ts.map +1 -1
- package/dist/vite-plugins/resources.js +32 -97
- package/dist/vite-plugins/resources.js.map +1 -1
- package/dist/vite-plugins/settings-worker-env.d.ts.map +1 -1
- package/dist/vite-plugins/settings-worker-env.js +2 -4
- package/dist/vite-plugins/settings-worker-env.js.map +1 -1
- package/dist/vite-plugins/settings.d.ts +1 -2
- package/dist/vite-plugins/settings.d.ts.map +1 -1
- package/dist/vite-plugins/settings.js +8 -14
- package/dist/vite-plugins/settings.js.map +1 -1
- package/dist/vite-plugins/undeclared-library.d.ts.map +1 -1
- package/dist/vite-plugins/undeclared-library.js +10 -13
- package/dist/vite-plugins/undeclared-library.js.map +1 -1
- package/dist/vite-plugins/worker.d.ts.map +1 -1
- package/dist/vite-plugins/worker.js +0 -1
- package/dist/vite-plugins/worker.js.map +1 -1
- package/package.json +21 -15
- package/src/codegen/freshness.ts +66 -17
- package/src/codegen/reference.ts +1 -8
- package/src/codegen/report.ts +5 -21
- package/src/codegen/run-codegen.ts +28 -145
- package/src/codegen/store-resolver.ts +8 -24
- package/src/commands/docs.ts +5 -17
- package/src/deploy/freedesktop/copyright.ts +2 -14
- package/src/deploy/freedesktop/xml.ts +24 -32
- package/src/deploy/node-runtime/license.ts +2 -8
- package/src/deploy/notices/packages.ts +3 -11
- package/src/deploy/payload/copy-tree.ts +3 -4
- package/src/deploy/payload/stage.ts +2 -6
- package/src/deploy/settings/build-manifest.ts +4 -16
- package/src/deploy/settings/libraries.ts +1 -22
- package/src/deploy/settings/paths.ts +6 -16
- package/src/deploy/settings/screenshots.ts +8 -46
- package/src/deploy/targets/flatpak-pnpm.ts +4 -4
- package/src/deploy/targets/flatpak-source.ts +4 -4
- package/src/deploy/targets/flatpak-sources.ts +1 -1
- package/src/dev/runner-main.ts +1 -2
- package/src/dev/schema-dir.ts +2 -4
- package/src/i18n/catalogs.ts +2 -1
- package/src/i18n/metadata-templates.ts +2 -1
- package/src/i18n/source-messages.ts +77 -115
- package/src/i18n/types.ts +97 -131
- package/src/internal/docs-elements.ts +2 -2
- package/src/internal/icon-path.ts +6 -19
- package/src/internal/source-imports.ts +14 -50
- package/src/settings/render.ts +1 -13
- package/src/settings/schema.ts +19 -81
- package/src/vite-plugins/asset-imports.ts +20 -44
- package/src/vite-plugins/asset-specifier.ts +0 -5
- package/src/vite-plugins/i18n.ts +2 -1
- package/src/vite-plugins/index.ts +2 -4
- package/src/vite-plugins/resources.ts +37 -140
- package/src/vite-plugins/settings-worker-env.ts +2 -4
- package/src/vite-plugins/settings.ts +11 -20
- package/src/vite-plugins/undeclared-library.ts +14 -20
- package/src/vite-plugins/worker.ts +0 -1
- package/dist/i18n/binding-extractor.d.ts +0 -30
- package/dist/i18n/binding-extractor.d.ts.map +0 -1
- package/dist/i18n/binding-extractor.js +0 -1324
- package/dist/i18n/binding-extractor.js.map +0 -1
- package/dist/i18n/extractor-plugin.d.ts +0 -8
- package/dist/i18n/extractor-plugin.d.ts.map +0 -1
- package/dist/i18n/extractor-plugin.js +0 -247
- package/dist/i18n/extractor-plugin.js.map +0 -1
- package/dist/internal/data-dir.d.ts +0 -4
- package/dist/internal/data-dir.d.ts.map +0 -1
- package/dist/internal/data-dir.js +0 -52
- package/dist/internal/data-dir.js.map +0 -1
- package/dist/vite-plugins/animated.d.ts +0 -5
- package/dist/vite-plugins/animated.d.ts.map +0 -1
- package/dist/vite-plugins/animated.js +0 -325
- package/dist/vite-plugins/animated.js.map +0 -1
- package/src/i18n/binding-extractor.ts +0 -1969
- package/src/i18n/extractor-plugin.ts +0 -413
- package/src/internal/data-dir.ts +0 -70
- package/src/vite-plugins/animated.ts +0 -486
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type { ExtractedKey,
|
|
2
|
-
import {
|
|
1
|
+
import type { ExtractedKey, ExtractedKeysMap, Logger, Plugin } from "i18next-cli";
|
|
2
|
+
import { isPathInside, isPathWithin, toPosixPath } from "@gtkx/utils";
|
|
3
|
+
import { runExtractor } from "i18next-cli";
|
|
3
4
|
import {
|
|
4
5
|
existsSync,
|
|
5
6
|
mkdirSync,
|
|
@@ -8,12 +9,12 @@ import {
|
|
|
8
9
|
rmSync,
|
|
9
10
|
writeFileSync,
|
|
10
11
|
} from "node:fs";
|
|
11
|
-
import { dirname, isAbsolute, join, relative, resolve
|
|
12
|
+
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
12
13
|
import type { CatalogProject } from "./catalogs.js";
|
|
13
14
|
import { runCliTool } from "../internal/run-cli-tool.js";
|
|
14
15
|
import { replaceCatalogTemplate } from "./catalog-template.js";
|
|
15
|
-
import { gtkxExtractorPlugin } from "./extractor-plugin.js";
|
|
16
16
|
import { metadataTemplateFiles } from "./metadata-templates.js";
|
|
17
|
+
import { clearI18nResources, i18nToolkitConfig } from "./types.js";
|
|
17
18
|
|
|
18
19
|
type SourceLocation = {
|
|
19
20
|
file: string;
|
|
@@ -23,17 +24,11 @@ type SourceLocation = {
|
|
|
23
24
|
|
|
24
25
|
type SourceMessage = {
|
|
25
26
|
context: string | null;
|
|
26
|
-
kind: "default" | "plural" | "pluralDefaults" | "point";
|
|
27
27
|
locations: SourceLocation[];
|
|
28
|
-
msgid: string;
|
|
29
28
|
namespace: string;
|
|
30
29
|
plural: string | null;
|
|
31
30
|
singular: string;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
type SourceCatalog = {
|
|
35
|
-
messages: SourceMessage[];
|
|
36
|
-
sourceFiles: string[];
|
|
31
|
+
sourceKey: string;
|
|
37
32
|
};
|
|
38
33
|
|
|
39
34
|
type SyntheticEntry = {
|
|
@@ -45,7 +40,6 @@ type SourceExtraction = {
|
|
|
45
40
|
messages: SourceMessage[];
|
|
46
41
|
output: string;
|
|
47
42
|
project: CatalogProject;
|
|
48
|
-
sourceFiles: string[];
|
|
49
43
|
workDir: string;
|
|
50
44
|
};
|
|
51
45
|
|
|
@@ -54,7 +48,6 @@ type CatalogTemplateExtraction = {
|
|
|
54
48
|
output?: string | undefined;
|
|
55
49
|
project: CatalogProject;
|
|
56
50
|
shouldPreserveMetadataMessages: boolean;
|
|
57
|
-
sourceFiles: string[];
|
|
58
51
|
};
|
|
59
52
|
|
|
60
53
|
type PluralVariant = {
|
|
@@ -94,31 +87,6 @@ const quietLogger = (): { logger: Logger; reports: string[] } => {
|
|
|
94
87
|
};
|
|
95
88
|
};
|
|
96
89
|
|
|
97
|
-
const extractorConfig = (sourceFiles: string[]): I18nextToolkitConfig => {
|
|
98
|
-
const integration = gtkxExtractorPlugin();
|
|
99
|
-
|
|
100
|
-
return {
|
|
101
|
-
locales: ["en"],
|
|
102
|
-
plugins: [integration.plugin],
|
|
103
|
-
extract: {
|
|
104
|
-
input: sourceFiles,
|
|
105
|
-
output: "unused/{{language}}/{{namespace}}.json",
|
|
106
|
-
contextSeparator: CONTEXT_SEPARATOR,
|
|
107
|
-
defaultNS: DEFAULT_NAMESPACE,
|
|
108
|
-
disablePlurals: false,
|
|
109
|
-
extractFromComments: false,
|
|
110
|
-
functions: [],
|
|
111
|
-
generateBasePluralForms: false,
|
|
112
|
-
keySeparator: ".",
|
|
113
|
-
nsSeparator: false,
|
|
114
|
-
pluralSeparator: "_",
|
|
115
|
-
transComponents: integration.transComponents,
|
|
116
|
-
useTranslationNames: [],
|
|
117
|
-
warnOnConflicts: "error",
|
|
118
|
-
},
|
|
119
|
-
};
|
|
120
|
-
};
|
|
121
|
-
|
|
122
90
|
const splitContext = (key: string): { context: string | null; msgid: string } => {
|
|
123
91
|
const index = key.lastIndexOf(CONTEXT_SEPARATOR);
|
|
124
92
|
|
|
@@ -155,17 +123,22 @@ const normalizedLocations = (locations: SourceLocation[]): SourceLocation[] => {
|
|
|
155
123
|
};
|
|
156
124
|
|
|
157
125
|
const pointSource = (entry: ExtractedKey, msgid: string): string => {
|
|
158
|
-
if (
|
|
126
|
+
if (typeof entry.defaultValue !== "string") {
|
|
159
127
|
return msgid;
|
|
160
128
|
}
|
|
161
129
|
|
|
162
|
-
|
|
130
|
+
if (entry.explicitDefault === true) {
|
|
131
|
+
return entry.defaultValue;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return entry.defaultValue === msgid || msgid.endsWith(`.${entry.defaultValue}`)
|
|
135
|
+
? msgid
|
|
136
|
+
: entry.defaultValue;
|
|
163
137
|
};
|
|
164
138
|
|
|
165
139
|
const pointMessage = (entry: ExtractedKey): SourceMessage => {
|
|
166
140
|
const { context, msgid } = splitContext(entry.key);
|
|
167
141
|
validateIdentity(context, msgid);
|
|
168
|
-
const isDefaultExplicit = entry.explicitDefault === true;
|
|
169
142
|
const singular = pointSource(entry, msgid);
|
|
170
143
|
|
|
171
144
|
if (singular.length === 0) {
|
|
@@ -174,12 +147,11 @@ const pointMessage = (entry: ExtractedKey): SourceMessage => {
|
|
|
174
147
|
|
|
175
148
|
return {
|
|
176
149
|
context,
|
|
177
|
-
kind: isDefaultExplicit ? "default" : "point",
|
|
178
150
|
locations: normalizedLocations(entry.locations ?? []),
|
|
179
|
-
msgid,
|
|
180
151
|
namespace: namespaceFor(entry),
|
|
181
152
|
plural: null,
|
|
182
153
|
singular,
|
|
154
|
+
sourceKey: msgid,
|
|
183
155
|
};
|
|
184
156
|
};
|
|
185
157
|
|
|
@@ -242,8 +214,7 @@ const pluralMessage = ({ baseKey, namespace, variants }: PluralGroup): SourceMes
|
|
|
242
214
|
const { one, other } = pairedVariants(variants);
|
|
243
215
|
const { context, msgid } = splitContext(baseKey);
|
|
244
216
|
validateIdentity(context, msgid);
|
|
245
|
-
const
|
|
246
|
-
const singular = hasStableKey ? one.defaultValue : msgid;
|
|
217
|
+
const singular = one.defaultValue;
|
|
247
218
|
const plural = other.defaultValue;
|
|
248
219
|
|
|
249
220
|
if (singular === plural || singular.length === 0 || plural.length === 0) {
|
|
@@ -252,12 +223,11 @@ const pluralMessage = ({ baseKey, namespace, variants }: PluralGroup): SourceMes
|
|
|
252
223
|
|
|
253
224
|
return {
|
|
254
225
|
context,
|
|
255
|
-
kind: hasStableKey ? "pluralDefaults" : "plural",
|
|
256
226
|
locations: normalizedLocations(variants.flatMap((variant) => variant.locations)),
|
|
257
|
-
msgid,
|
|
258
227
|
namespace,
|
|
259
228
|
plural,
|
|
260
229
|
singular,
|
|
230
|
+
sourceKey: msgid,
|
|
261
231
|
};
|
|
262
232
|
};
|
|
263
233
|
|
|
@@ -295,52 +265,79 @@ const sourceMessages = (entries: ExtractedKey[]): SourceMessage[] => {
|
|
|
295
265
|
const compareMessages = (left: SourceMessage, right: SourceMessage): number =>
|
|
296
266
|
left.namespace.localeCompare(right.namespace) ||
|
|
297
267
|
(left.context ?? "").localeCompare(right.context ?? "") ||
|
|
298
|
-
left.msgid.localeCompare(right.msgid) ||
|
|
299
268
|
left.singular.localeCompare(right.singular) ||
|
|
300
269
|
(left.plural ?? "").localeCompare(right.plural ?? "");
|
|
301
270
|
|
|
271
|
+
const inferLocations = (message: SourceMessage, sources: Map<string, string>): SourceMessage => {
|
|
272
|
+
if (message.locations.length > 0) {
|
|
273
|
+
return message;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const locations: SourceLocation[] = [];
|
|
277
|
+
|
|
278
|
+
for (const [file, source] of sources) {
|
|
279
|
+
const index = source.indexOf(message.sourceKey);
|
|
280
|
+
|
|
281
|
+
if (index !== -1) {
|
|
282
|
+
locations.push({ file, line: source.slice(0, index).split("\n").length });
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return { ...message, locations };
|
|
287
|
+
};
|
|
288
|
+
|
|
302
289
|
const normalizedSourceFiles = (root: string, paths: string[]): string[] => {
|
|
303
290
|
const files = paths
|
|
304
291
|
.map((path) => resolve(root, path))
|
|
305
|
-
.filter((path) =>
|
|
306
|
-
const projectPath = relative(root, path);
|
|
307
|
-
|
|
308
|
-
return projectPath !== ".." && !projectPath.startsWith(`..${sep}`) && !isAbsolute(projectPath);
|
|
309
|
-
});
|
|
292
|
+
.filter((path) => isPathWithin(root, path));
|
|
310
293
|
|
|
311
294
|
return new Set(files).values().toArray().toSorted((left, right) => left.localeCompare(right));
|
|
312
295
|
};
|
|
313
296
|
|
|
314
|
-
const findSourceMessages = async (sourceFiles: string[]): Promise<SourceMessage[]> => {
|
|
297
|
+
const findSourceMessages = async (root: string, sourceFiles: string[]): Promise<SourceMessage[]> => {
|
|
315
298
|
if (sourceFiles.length === 0) {
|
|
299
|
+
clearI18nResources(root);
|
|
300
|
+
|
|
316
301
|
return [];
|
|
317
302
|
}
|
|
318
303
|
|
|
319
|
-
|
|
304
|
+
let extracted: ExtractedKeysMap | undefined;
|
|
320
305
|
const { logger, reports } = quietLogger();
|
|
321
|
-
const { allKeys } = await findKeys(extractorConfig(sourceFiles), logger, fileErrors);
|
|
322
306
|
|
|
323
|
-
|
|
324
|
-
|
|
307
|
+
const capture: Plugin = {
|
|
308
|
+
name: "gtkx-gettext",
|
|
309
|
+
onEnd(keys) {
|
|
310
|
+
extracted = keys;
|
|
311
|
+
},
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
const result = await runExtractor(i18nToolkitConfig(root, sourceFiles, [capture]), {
|
|
315
|
+
logger,
|
|
316
|
+
quiet: true,
|
|
317
|
+
syncPrimaryWithDefaults: true,
|
|
318
|
+
trustDerivedDefaults: true,
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
if (extracted === undefined || reports.length > 0 || result.hasErrors) {
|
|
322
|
+
throw new Error(reports.join("\n") || "i18next extraction failed");
|
|
325
323
|
}
|
|
326
324
|
|
|
327
|
-
|
|
325
|
+
const sources = new Map(sourceFiles.map((file) => [file, readFileSync(file, "utf8")]));
|
|
326
|
+
|
|
327
|
+
return sourceMessages(extracted.values().toArray())
|
|
328
|
+
.map((message) => inferLocations(message, sources))
|
|
329
|
+
.toSorted(compareMessages);
|
|
328
330
|
};
|
|
329
331
|
|
|
330
332
|
const projectPath = (root: string, path: string): string | null => {
|
|
331
333
|
const absolute = isAbsolute(path) ? resolve(path) : resolve(root, path);
|
|
332
334
|
const projectRelative = relative(root, absolute);
|
|
333
335
|
|
|
334
|
-
if (
|
|
335
|
-
projectRelative === "" ||
|
|
336
|
-
projectRelative === ".." ||
|
|
337
|
-
projectRelative.startsWith(`..${sep}`) ||
|
|
338
|
-
isAbsolute(projectRelative)
|
|
339
|
-
) {
|
|
336
|
+
if (!isPathInside(root, absolute)) {
|
|
340
337
|
return null;
|
|
341
338
|
}
|
|
342
339
|
|
|
343
|
-
return projectRelative
|
|
340
|
+
return toPosixPath(projectRelative);
|
|
344
341
|
};
|
|
345
342
|
|
|
346
343
|
const writePotfiles = (project: CatalogProject, sourceFiles: string[]): string => {
|
|
@@ -371,9 +368,6 @@ const renderCatalogCall = (message: SourceMessage): string => {
|
|
|
371
368
|
: `pgettext(${JSON.stringify(message.context)}, ${msgid});`;
|
|
372
369
|
};
|
|
373
370
|
|
|
374
|
-
const readSources = (sourceFiles: string[]): Map<string, string> =>
|
|
375
|
-
new Map(sourceFiles.map((path) => [path, readFileSync(path, "utf8")]));
|
|
376
|
-
|
|
377
371
|
const locatedOwners = (
|
|
378
372
|
project: CatalogProject,
|
|
379
373
|
message: SourceMessage,
|
|
@@ -393,49 +387,23 @@ const locatedOwners = (
|
|
|
393
387
|
return unique.values().toArray();
|
|
394
388
|
};
|
|
395
389
|
|
|
396
|
-
const inferredOwner = (
|
|
397
|
-
project: CatalogProject,
|
|
398
|
-
message: SourceMessage,
|
|
399
|
-
sources: Map<string, string>,
|
|
400
|
-
): { path: string; line: number } | null => {
|
|
401
|
-
for (const [path, source] of sources) {
|
|
402
|
-
const index = source.indexOf(message.msgid);
|
|
403
|
-
|
|
404
|
-
if (index !== -1) {
|
|
405
|
-
return {
|
|
406
|
-
path: projectPath(project.root, path) ?? SYNTHETIC_FILENAME,
|
|
407
|
-
line: source.slice(0, index).split("\n").length,
|
|
408
|
-
};
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
return null;
|
|
413
|
-
};
|
|
414
|
-
|
|
415
390
|
const sourceOwners = (
|
|
416
391
|
project: CatalogProject,
|
|
417
392
|
message: SourceMessage,
|
|
418
|
-
sources: Map<string, string>,
|
|
419
393
|
): { path: string; line: number | undefined }[] => {
|
|
420
394
|
const located = locatedOwners(project, message);
|
|
421
395
|
|
|
422
|
-
|
|
423
|
-
return located;
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
return [inferredOwner(project, message, sources) ?? { path: SYNTHETIC_FILENAME, line: undefined }];
|
|
396
|
+
return located.length > 0 ? located : [{ path: SYNTHETIC_FILENAME, line: undefined }];
|
|
427
397
|
};
|
|
428
398
|
|
|
429
399
|
const syntheticEntries = (
|
|
430
400
|
project: CatalogProject,
|
|
431
401
|
messages: SourceMessage[],
|
|
432
|
-
sourceFiles: string[],
|
|
433
402
|
): Map<string, SyntheticEntry[]> => {
|
|
434
|
-
const sources = readSources(sourceFiles);
|
|
435
403
|
const entries: Map<string, SyntheticEntry[]> = new Map();
|
|
436
404
|
|
|
437
405
|
for (const message of messages) {
|
|
438
|
-
for (const owner of sourceOwners(project, message
|
|
406
|
+
for (const owner of sourceOwners(project, message)) {
|
|
439
407
|
const owned = entries.get(owner.path) ?? [];
|
|
440
408
|
owned.push({ call: renderCatalogCall(message), line: owner.line });
|
|
441
409
|
entries.set(owner.path, owned);
|
|
@@ -492,9 +460,8 @@ const writeSyntheticSources = (
|
|
|
492
460
|
workDir: string,
|
|
493
461
|
project: CatalogProject,
|
|
494
462
|
messages: SourceMessage[],
|
|
495
|
-
sourceFiles: string[],
|
|
496
463
|
): string => {
|
|
497
|
-
const entries = syntheticEntries(project, messages
|
|
464
|
+
const entries = syntheticEntries(project, messages);
|
|
498
465
|
const paths: string[] = [];
|
|
499
466
|
|
|
500
467
|
for (const [path, owned] of entries) {
|
|
@@ -510,8 +477,8 @@ const writeSyntheticSources = (
|
|
|
510
477
|
return potfiles;
|
|
511
478
|
};
|
|
512
479
|
|
|
513
|
-
const extractSourceMessages = ({ project, messages,
|
|
514
|
-
const potfilesPath = writeSyntheticSources(workDir, project, messages
|
|
480
|
+
const extractSourceMessages = ({ project, messages, output, workDir }: SourceExtraction): void => {
|
|
481
|
+
const potfilesPath = writeSyntheticSources(workDir, project, messages);
|
|
515
482
|
|
|
516
483
|
runCliTool({
|
|
517
484
|
tool: "xgettext",
|
|
@@ -555,7 +522,6 @@ const joinMetadataFragment = (output: string, fragment: string): void => {
|
|
|
555
522
|
const extractCatalogTemplate = ({
|
|
556
523
|
project,
|
|
557
524
|
messages,
|
|
558
|
-
sourceFiles,
|
|
559
525
|
shouldPreserveMetadataMessages,
|
|
560
526
|
output = resolve(project.poDir, `${project.domain}.pot`),
|
|
561
527
|
}: CatalogTemplateExtraction): void => {
|
|
@@ -567,10 +533,10 @@ const extractCatalogTemplate = ({
|
|
|
567
533
|
if (shouldPreserveMetadataMessages && hasPreviousTemplate) {
|
|
568
534
|
const fragment = join(workDir, "metadata.pot");
|
|
569
535
|
extractMetadataFragment(project, output, fragment);
|
|
570
|
-
extractSourceMessages({ project, messages,
|
|
536
|
+
extractSourceMessages({ project, messages, output: source, workDir });
|
|
571
537
|
joinMetadataFragment(source, fragment);
|
|
572
538
|
} else {
|
|
573
|
-
extractSourceMessages({ project, messages,
|
|
539
|
+
extractSourceMessages({ project, messages, output: source, workDir });
|
|
574
540
|
}
|
|
575
541
|
|
|
576
542
|
replaceCatalogTemplate(source, output);
|
|
@@ -583,26 +549,22 @@ const extractSourceCatalogTo = async (
|
|
|
583
549
|
project: CatalogProject,
|
|
584
550
|
paths: string[],
|
|
585
551
|
output: string,
|
|
586
|
-
): Promise<
|
|
552
|
+
): Promise<void> => {
|
|
587
553
|
const sourceFiles = normalizedSourceFiles(project.root, paths);
|
|
588
|
-
const messages = await findSourceMessages(sourceFiles);
|
|
554
|
+
const messages = await findSourceMessages(project.root, sourceFiles);
|
|
589
555
|
writePotfiles(project, sourceFiles);
|
|
590
|
-
extractCatalogTemplate({ project, messages,
|
|
591
|
-
|
|
592
|
-
return { messages, sourceFiles };
|
|
556
|
+
extractCatalogTemplate({ project, messages, shouldPreserveMetadataMessages: false, output });
|
|
593
557
|
};
|
|
594
558
|
|
|
595
559
|
const extractSourceCatalog = async (
|
|
596
560
|
project: CatalogProject,
|
|
597
561
|
paths: string[],
|
|
598
562
|
shouldPreserveMetadataMessages = true,
|
|
599
|
-
): Promise<
|
|
563
|
+
): Promise<void> => {
|
|
600
564
|
const sourceFiles = normalizedSourceFiles(project.root, paths);
|
|
601
|
-
const messages = await findSourceMessages(sourceFiles);
|
|
565
|
+
const messages = await findSourceMessages(project.root, sourceFiles);
|
|
602
566
|
writePotfiles(project, sourceFiles);
|
|
603
|
-
extractCatalogTemplate({ project, messages,
|
|
604
|
-
|
|
605
|
-
return { messages, sourceFiles };
|
|
567
|
+
extractCatalogTemplate({ project, messages, shouldPreserveMetadataMessages });
|
|
606
568
|
};
|
|
607
569
|
|
|
608
|
-
export {
|
|
570
|
+
export { extractSourceCatalog, extractSourceCatalogTo };
|
package/src/i18n/types.ts
CHANGED
|
@@ -1,154 +1,120 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { I18nextToolkitConfig, Logger, Plugin } from "i18next-cli";
|
|
2
|
+
import { runTypesGenerator } from "i18next-cli";
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
4
|
import { dirname, join } from "node:path";
|
|
3
|
-
import type { SourceMessage } from "./source-messages.js";
|
|
4
|
-
|
|
5
|
-
type I18nTypesResult = {
|
|
6
|
-
isWritten: boolean;
|
|
7
|
-
path: string;
|
|
8
|
-
};
|
|
9
5
|
|
|
6
|
+
const CONTEXT_SEPARATOR = "\u{4}";
|
|
7
|
+
const DEFAULT_NAMESPACE = "translation";
|
|
10
8
|
const I18N_TYPES_FILENAME = "i18n.d.ts";
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
const stringType = (values: string[]): string =>
|
|
14
|
-
[...new Set(values)].map((value) => JSON.stringify(value)).join(" | ");
|
|
15
|
-
|
|
16
|
-
const contextField = (context: string | null): string =>
|
|
17
|
-
context === null ? "" : ` context: ${JSON.stringify(context)};`;
|
|
18
|
-
|
|
19
|
-
const hasInterpolation = (values: string[]): boolean => values.some((value) => INTERPOLATION_PATTERN.test(value));
|
|
9
|
+
const I18N_RESOURCES_FILENAME = "i18n-resources.d.ts";
|
|
20
10
|
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
11
|
+
const generatedDir = (root: string): string => join(root, "node_modules", ".gtkx");
|
|
12
|
+
const i18nResourceDir = (root: string): string => join(generatedDir(root), "i18n");
|
|
13
|
+
const i18nTypesPath = (root: string): string => join(generatedDir(root), I18N_TYPES_FILENAME);
|
|
14
|
+
const i18nResourcesPath = (root: string): string => join(generatedDir(root), I18N_RESOURCES_FILENAME);
|
|
24
15
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
`required: ${String(isRequired)}; value: ${JSON.stringify(message.msgid)} }`
|
|
28
|
-
);
|
|
16
|
+
const clearI18nResources = (root: string): void => {
|
|
17
|
+
rmSync(i18nResourceDir(root), { force: true, recursive: true });
|
|
29
18
|
};
|
|
30
19
|
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
20
|
+
const i18nToolkitConfig = (
|
|
21
|
+
root: string,
|
|
22
|
+
sourceFiles: string[],
|
|
23
|
+
plugins: Plugin[] = [],
|
|
24
|
+
): I18nextToolkitConfig => ({
|
|
25
|
+
locales: ["en"],
|
|
26
|
+
plugins,
|
|
27
|
+
extract: {
|
|
28
|
+
contextSeparator: CONTEXT_SEPARATOR,
|
|
29
|
+
defaultNS: DEFAULT_NAMESPACE,
|
|
30
|
+
disablePlurals: false,
|
|
31
|
+
extractFromComments: false,
|
|
32
|
+
functions: ["t"],
|
|
33
|
+
generateBasePluralForms: false,
|
|
34
|
+
input: sourceFiles,
|
|
35
|
+
keySeparator: ".",
|
|
36
|
+
nsSeparator: false,
|
|
37
|
+
output: join(i18nResourceDir(root), "{{language}}", "{{namespace}}.json"),
|
|
38
|
+
pluralSeparator: "_",
|
|
39
|
+
primaryLanguage: "en",
|
|
40
|
+
removeUnusedKeys: true,
|
|
41
|
+
transComponents: ["Trans", "TransWithoutContext"],
|
|
42
|
+
useTranslationNames: ["useTranslation"],
|
|
43
|
+
warnOnConflicts: "error",
|
|
44
|
+
},
|
|
45
|
+
types: {
|
|
46
|
+
input: join(i18nResourceDir(root), "en", "*.json"),
|
|
47
|
+
output: i18nTypesPath(root),
|
|
48
|
+
resourcesFile: i18nResourcesPath(root),
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const declarations = (): string => `import type Resources from "./i18n-resources.js";
|
|
53
|
+
|
|
54
|
+
declare module "@gtkx/i18n" {
|
|
55
|
+
interface I18nResources extends Resources {}
|
|
56
|
+
}
|
|
57
|
+
`;
|
|
58
|
+
|
|
59
|
+
const writeChanged = (path: string, content: string): void => {
|
|
60
|
+
let current: string | null;
|
|
35
61
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
);
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
const requirePlural = (message: SourceMessage): string => {
|
|
44
|
-
if (message.plural === null) {
|
|
45
|
-
throw new Error("Plural translation metadata requires a plural source string");
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return message.plural;
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
const pluralVariant = (message: SourceMessage): string => {
|
|
52
|
-
const plural = requirePlural(message);
|
|
53
|
-
const values = [message.singular, plural];
|
|
54
|
-
const isInterpolated = hasInterpolation(values);
|
|
55
|
-
|
|
56
|
-
return (
|
|
57
|
-
`{${contextField(message.context)} defaultValue: ${JSON.stringify(plural)}; ` +
|
|
58
|
-
`interpolated: ${String(isInterpolated)}; kind: "plural"; required: true; ` +
|
|
59
|
-
`value: ${stringType(values)} }`
|
|
60
|
-
);
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
const pluralDefaultsVariant = (message: SourceMessage): string => {
|
|
64
|
-
const plural = requirePlural(message);
|
|
65
|
-
const values = [message.singular, plural];
|
|
66
|
-
const isInterpolated = hasInterpolation(values);
|
|
67
|
-
|
|
68
|
-
return (
|
|
69
|
-
`{${contextField(message.context)} defaultValueOne: ${JSON.stringify(message.singular)}; ` +
|
|
70
|
-
`defaultValueOther: ${JSON.stringify(plural)}; interpolated: ${String(isInterpolated)}; ` +
|
|
71
|
-
`kind: "pluralDefaults"; required: true; value: ${stringType(values)} }`
|
|
72
|
-
);
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
const variantsFor = (message: SourceMessage): string[] => {
|
|
76
|
-
if (message.kind === "plural") {
|
|
77
|
-
return [pluralVariant(message)];
|
|
62
|
+
try {
|
|
63
|
+
current = readFileSync(path, "utf8");
|
|
64
|
+
} catch {
|
|
65
|
+
current = null;
|
|
78
66
|
}
|
|
79
67
|
|
|
80
|
-
if (
|
|
81
|
-
return
|
|
68
|
+
if (current === content) {
|
|
69
|
+
return;
|
|
82
70
|
}
|
|
83
71
|
|
|
84
|
-
|
|
72
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
73
|
+
writeFileSync(path, content);
|
|
85
74
|
};
|
|
86
75
|
|
|
87
|
-
const
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
76
|
+
const typegenLogger = (): { errors: string[]; logger: Logger } => {
|
|
77
|
+
const errors: string[] = [];
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
errors,
|
|
81
|
+
logger: {
|
|
82
|
+
error(value) {
|
|
83
|
+
errors.push(String(value));
|
|
84
|
+
},
|
|
85
|
+
info() {
|
|
86
|
+
return;
|
|
87
|
+
},
|
|
88
|
+
warn() {
|
|
89
|
+
return;
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
};
|
|
103
93
|
};
|
|
104
94
|
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
];
|
|
95
|
+
const emitI18nTypes = async (root: string): Promise<void> => {
|
|
96
|
+
const typesPath = i18nTypesPath(root);
|
|
97
|
+
const resourcesPath = i18nResourcesPath(root);
|
|
98
|
+
writeChanged(typesPath, declarations());
|
|
99
|
+
const { errors, logger } = typegenLogger();
|
|
100
|
+
await runTypesGenerator(i18nToolkitConfig(root, []), { logger, quiet: true });
|
|
111
101
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
return [...generatedHeader(), "export {};", ""].join("\n");
|
|
102
|
+
if (errors.length > 0 || !existsSync(typesPath) || !existsSync(resourcesPath)) {
|
|
103
|
+
throw new Error(errors.join("\n") || "i18next type generation did not produce its outputs");
|
|
115
104
|
}
|
|
116
|
-
|
|
117
|
-
return [
|
|
118
|
-
...generatedHeader(),
|
|
119
|
-
'import "@gtkx/i18n";',
|
|
120
|
-
"",
|
|
121
|
-
'declare module "@gtkx/i18n" {',
|
|
122
|
-
" interface TranslationRegistry {",
|
|
123
|
-
...renderRegistry(messages),
|
|
124
|
-
" }",
|
|
125
|
-
"}",
|
|
126
|
-
"",
|
|
127
|
-
].join("\n");
|
|
128
105
|
};
|
|
129
106
|
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
return readFileSync(path, "utf8");
|
|
135
|
-
} catch {
|
|
136
|
-
return null;
|
|
137
|
-
}
|
|
107
|
+
const clearI18nTypes = (root: string): void => {
|
|
108
|
+
rmSync(i18nTypesPath(root), { force: true });
|
|
109
|
+
rmSync(i18nResourcesPath(root), { force: true });
|
|
110
|
+
clearI18nResources(root);
|
|
138
111
|
};
|
|
139
112
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
mkdirSync(dirname(path), { recursive: true });
|
|
149
|
-
writeFileSync(path, content);
|
|
150
|
-
|
|
151
|
-
return { isWritten: true, path };
|
|
113
|
+
export {
|
|
114
|
+
I18N_TYPES_FILENAME,
|
|
115
|
+
clearI18nResources,
|
|
116
|
+
clearI18nTypes,
|
|
117
|
+
emitI18nTypes,
|
|
118
|
+
i18nToolkitConfig,
|
|
119
|
+
i18nTypesPath,
|
|
152
120
|
};
|
|
153
|
-
|
|
154
|
-
export { I18N_TYPES_FILENAME, emitI18nTypes, i18nTypesPath };
|
|
@@ -7,13 +7,13 @@ type DocsElements = {
|
|
|
7
7
|
};
|
|
8
8
|
|
|
9
9
|
const resolveDocsElements = async (cwd: string): Promise<DocsElements> => {
|
|
10
|
-
const { gi
|
|
10
|
+
const { gi } = resolveStore(cwd);
|
|
11
11
|
|
|
12
12
|
if (!existsSync(gi.storeDir)) {
|
|
13
13
|
return { props: {}, omittedProps: {} };
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
const { props, omittedProps } = await readBuiltinElements(
|
|
16
|
+
const { props, omittedProps } = await readBuiltinElements();
|
|
17
17
|
|
|
18
18
|
return { props, omittedProps };
|
|
19
19
|
};
|