poly-lexis 0.6.0 → 0.8.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/dist/cli/translations.js +145 -12
- package/dist/cli/translations.js.map +1 -1
- package/dist/index.d.ts +16 -1
- package/dist/index.js +24 -2
- package/dist/index.js.map +1 -1
- package/dist/scripts/verify-translations.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/translations.js
CHANGED
|
@@ -852,11 +852,28 @@ var init_init = __esm({
|
|
|
852
852
|
// src/translations/cli/generate-types.ts
|
|
853
853
|
var generate_types_exports = {};
|
|
854
854
|
__export(generate_types_exports, {
|
|
855
|
+
extractPluralBaseKeys: () => extractPluralBaseKeys,
|
|
855
856
|
generateTranslationTypes: () => generateTranslationTypes
|
|
856
857
|
});
|
|
857
858
|
import { execSync } from "child_process";
|
|
858
859
|
import * as fs3 from "fs";
|
|
859
860
|
import * as path4 from "path";
|
|
861
|
+
function extractPluralBaseKeys(keys) {
|
|
862
|
+
const keySet = new Set(keys);
|
|
863
|
+
const baseKeys = /* @__PURE__ */ new Set();
|
|
864
|
+
for (const key of keys) {
|
|
865
|
+
for (const suffix of PLURAL_SUFFIXES) {
|
|
866
|
+
if (key.endsWith(suffix)) {
|
|
867
|
+
const baseKey = key.slice(0, -suffix.length);
|
|
868
|
+
if (baseKey && !keySet.has(baseKey)) {
|
|
869
|
+
baseKeys.add(baseKey);
|
|
870
|
+
}
|
|
871
|
+
break;
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
return Array.from(baseKeys);
|
|
876
|
+
}
|
|
860
877
|
function generateTranslationTypes(projectRoot = process.cwd()) {
|
|
861
878
|
console.log("=====");
|
|
862
879
|
console.time("i18n types generated");
|
|
@@ -880,11 +897,13 @@ function generateTranslationTypes(projectRoot = process.cwd()) {
|
|
|
880
897
|
const keys = Object.keys(translations[namespace] || {});
|
|
881
898
|
allKeys = allKeys.concat(keys);
|
|
882
899
|
}
|
|
900
|
+
const pluralBaseKeys = extractPluralBaseKeys(allKeys);
|
|
901
|
+
allKeys = allKeys.concat(pluralBaseKeys);
|
|
883
902
|
const outputDir = path4.dirname(outputFilePath);
|
|
884
903
|
if (!fs3.existsSync(outputDir)) {
|
|
885
904
|
fs3.mkdirSync(outputDir, { recursive: true });
|
|
886
905
|
}
|
|
887
|
-
const typeString = typeTemplate(allKeys, namespaces);
|
|
906
|
+
const typeString = typeTemplate(allKeys, namespaces, config.languages);
|
|
888
907
|
fs3.writeFileSync(outputFilePath, typeString, "utf8");
|
|
889
908
|
console.log(`Generated types with ${allKeys.length} keys and ${namespaces.length} namespaces`);
|
|
890
909
|
console.log(`Output: ${outputFilePath}`);
|
|
@@ -899,19 +918,22 @@ function generateTranslationTypes(projectRoot = process.cwd()) {
|
|
|
899
918
|
console.timeEnd("i18n types generated");
|
|
900
919
|
console.log("=====");
|
|
901
920
|
}
|
|
902
|
-
var typeTemplate;
|
|
921
|
+
var PLURAL_SUFFIXES, typeTemplate;
|
|
903
922
|
var init_generate_types = __esm({
|
|
904
923
|
"src/translations/cli/generate-types.ts"() {
|
|
905
924
|
"use strict";
|
|
906
925
|
init_esm_shims();
|
|
907
926
|
init_utils();
|
|
908
927
|
init_init();
|
|
909
|
-
|
|
928
|
+
PLURAL_SUFFIXES = ["_zero", "_one", "_two", "_few", "_many", "_other"];
|
|
929
|
+
typeTemplate = (translationKeys, namespaceKeys, languages) => `
|
|
910
930
|
export const translationKeys = [${translationKeys.map((key) => `"${key}"`).join(", ")}] as const;
|
|
911
931
|
export const namespaceKeys = [${namespaceKeys.map((key) => `"${key}"`).join(", ")}] as const;
|
|
932
|
+
export const languages = [${languages.map((lang) => `"${lang}"`).join(", ")}] as const;
|
|
912
933
|
|
|
913
934
|
export type TranslationKey = typeof translationKeys[number];
|
|
914
935
|
export type TranslationNamespace = typeof namespaceKeys[number];
|
|
936
|
+
export type Language = typeof languages[number];
|
|
915
937
|
`;
|
|
916
938
|
}
|
|
917
939
|
});
|
|
@@ -1198,11 +1220,97 @@ var init_find_unused = __esm({
|
|
|
1198
1220
|
}
|
|
1199
1221
|
});
|
|
1200
1222
|
|
|
1223
|
+
// src/translations/cli/find-duplicates.ts
|
|
1224
|
+
var find_duplicates_exports = {};
|
|
1225
|
+
__export(find_duplicates_exports, {
|
|
1226
|
+
findDuplicates: () => findDuplicates,
|
|
1227
|
+
printDuplicateKeysResult: () => printDuplicateKeysResult
|
|
1228
|
+
});
|
|
1229
|
+
import * as path11 from "path";
|
|
1230
|
+
function findDuplicates(projectRoot = process.cwd()) {
|
|
1231
|
+
const config = loadConfig(projectRoot);
|
|
1232
|
+
const translationsPath = path11.join(projectRoot, config.translationsPath);
|
|
1233
|
+
const sourceLanguage = config.sourceLanguage;
|
|
1234
|
+
const namespaces = getNamespaces(translationsPath, sourceLanguage);
|
|
1235
|
+
const sourceTranslations = readTranslations(translationsPath, sourceLanguage);
|
|
1236
|
+
console.log("=====");
|
|
1237
|
+
console.log("Finding duplicate translations (common namespace)");
|
|
1238
|
+
console.log("=====");
|
|
1239
|
+
console.log(`Source language: ${sourceLanguage}`);
|
|
1240
|
+
console.log(`Namespaces: ${namespaces.join(", ")}`);
|
|
1241
|
+
console.log("=====");
|
|
1242
|
+
const commonValues = sourceTranslations["common"] || {};
|
|
1243
|
+
if (Object.keys(commonValues).length === 0) {
|
|
1244
|
+
console.log("No common namespace found or it is empty.");
|
|
1245
|
+
return { duplicates: [], totalKeysChecked: 0 };
|
|
1246
|
+
}
|
|
1247
|
+
const valueToCommonKey = /* @__PURE__ */ new Map();
|
|
1248
|
+
for (const [key, value] of Object.entries(commonValues)) {
|
|
1249
|
+
if (!valueToCommonKey.has(value)) {
|
|
1250
|
+
valueToCommonKey.set(value, key);
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
const duplicates = [];
|
|
1254
|
+
let totalKeysChecked = 0;
|
|
1255
|
+
for (const namespace of namespaces) {
|
|
1256
|
+
if (namespace === "common") continue;
|
|
1257
|
+
const nsValues = sourceTranslations[namespace] || {};
|
|
1258
|
+
for (const [key, value] of Object.entries(nsValues)) {
|
|
1259
|
+
totalKeysChecked++;
|
|
1260
|
+
const commonKey = valueToCommonKey.get(value);
|
|
1261
|
+
if (commonKey) {
|
|
1262
|
+
duplicates.push({
|
|
1263
|
+
namespace,
|
|
1264
|
+
key,
|
|
1265
|
+
commonKey,
|
|
1266
|
+
value
|
|
1267
|
+
});
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
console.log("=====");
|
|
1272
|
+
return { duplicates, totalKeysChecked };
|
|
1273
|
+
}
|
|
1274
|
+
function printDuplicateKeysResult(result) {
|
|
1275
|
+
if (result.duplicates.length === 0) {
|
|
1276
|
+
console.log("\u2713 No duplicate values found across namespaces!");
|
|
1277
|
+
return;
|
|
1278
|
+
}
|
|
1279
|
+
console.log(`
|
|
1280
|
+
\u26A0 Found ${result.duplicates.length} values duplicated from common:
|
|
1281
|
+
`);
|
|
1282
|
+
const byNamespace = /* @__PURE__ */ new Map();
|
|
1283
|
+
for (const dup of result.duplicates) {
|
|
1284
|
+
const items = byNamespace.get(dup.namespace) || [];
|
|
1285
|
+
items.push(dup);
|
|
1286
|
+
byNamespace.set(dup.namespace, items);
|
|
1287
|
+
}
|
|
1288
|
+
for (const [namespace, items] of byNamespace) {
|
|
1289
|
+
console.log(` ${namespace}:`);
|
|
1290
|
+
for (const item of items) {
|
|
1291
|
+
console.log(` - ${item.key} \u2192 common.${item.commonKey} ("${item.value}")`);
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
console.log(`
|
|
1295
|
+
\u{1F4CA} Summary:`);
|
|
1296
|
+
console.log(` Keys checked: ${result.totalKeysChecked}`);
|
|
1297
|
+
console.log(` Duplicates found: ${result.duplicates.length}`);
|
|
1298
|
+
console.log("=====");
|
|
1299
|
+
}
|
|
1300
|
+
var init_find_duplicates = __esm({
|
|
1301
|
+
"src/translations/cli/find-duplicates.ts"() {
|
|
1302
|
+
"use strict";
|
|
1303
|
+
init_esm_shims();
|
|
1304
|
+
init_utils();
|
|
1305
|
+
init_init();
|
|
1306
|
+
}
|
|
1307
|
+
});
|
|
1308
|
+
|
|
1201
1309
|
// src/cli/translations.ts
|
|
1202
1310
|
init_esm_shims();
|
|
1203
1311
|
import "dotenv/config";
|
|
1204
1312
|
import * as fs7 from "fs";
|
|
1205
|
-
import * as
|
|
1313
|
+
import * as path12 from "path";
|
|
1206
1314
|
import { parseArgs } from "util";
|
|
1207
1315
|
import { confirm as confirm2, input as input2, select as select2 } from "@inquirer/prompts";
|
|
1208
1316
|
|
|
@@ -2108,9 +2216,10 @@ Smart translation management - automatically handles initialization, validation,
|
|
|
2108
2216
|
auto-filling, and type generation based on your project's current state.
|
|
2109
2217
|
|
|
2110
2218
|
Commands:
|
|
2111
|
-
(none)
|
|
2112
|
-
add
|
|
2113
|
-
find-unused
|
|
2219
|
+
(none) Smart mode - validates, fills, and generates types
|
|
2220
|
+
add Add a new translation key
|
|
2221
|
+
find-unused Find translation keys that are not used in the codebase
|
|
2222
|
+
find-duplicates Find values duplicated from the common namespace
|
|
2114
2223
|
|
|
2115
2224
|
Options (Smart Mode):
|
|
2116
2225
|
-a, --auto-fill Auto-fill missing translations with DeepL or Google Translate
|
|
@@ -2160,6 +2269,9 @@ Examples:
|
|
|
2160
2269
|
# Find unused translation keys
|
|
2161
2270
|
translations find-unused
|
|
2162
2271
|
|
|
2272
|
+
# Find values duplicated from common namespace
|
|
2273
|
+
translations find-duplicates
|
|
2274
|
+
|
|
2163
2275
|
What happens in smart mode:
|
|
2164
2276
|
1. Checks if translations are initialized (creates .translationsrc.json if needed)
|
|
2165
2277
|
2. Validates all translations against source language
|
|
@@ -2182,12 +2294,24 @@ if (command === "find-unused") {
|
|
|
2182
2294
|
process.exit(1);
|
|
2183
2295
|
}
|
|
2184
2296
|
})();
|
|
2297
|
+
} else if (command === "find-duplicates") {
|
|
2298
|
+
(async () => {
|
|
2299
|
+
try {
|
|
2300
|
+
const { findDuplicates: findDuplicates2, printDuplicateKeysResult: printDuplicateKeysResult2 } = await Promise.resolve().then(() => (init_find_duplicates(), find_duplicates_exports));
|
|
2301
|
+
console.log("\n\u{1F50D} Finding duplicate translations (common namespace)...\n");
|
|
2302
|
+
const result = findDuplicates2(process.cwd());
|
|
2303
|
+
printDuplicateKeysResult2(result);
|
|
2304
|
+
} catch (error) {
|
|
2305
|
+
console.error("Error:", error instanceof Error ? error.message : error);
|
|
2306
|
+
process.exit(1);
|
|
2307
|
+
}
|
|
2308
|
+
})();
|
|
2185
2309
|
} else if (command === "add") {
|
|
2186
2310
|
if (!values.namespace && !values.key && !values.value) {
|
|
2187
2311
|
(async () => {
|
|
2188
2312
|
try {
|
|
2189
2313
|
console.log("\n\u2728 Add a new translation key\n");
|
|
2190
|
-
const configPath =
|
|
2314
|
+
const configPath = path12.join(process.cwd(), ".translationsrc.json");
|
|
2191
2315
|
const isInitialized = fs7.existsSync(configPath);
|
|
2192
2316
|
if (!isInitialized) {
|
|
2193
2317
|
console.log("\u26A0\uFE0F Translation structure not initialized.");
|
|
@@ -2204,7 +2328,7 @@ if (command === "find-unused") {
|
|
|
2204
2328
|
}
|
|
2205
2329
|
}
|
|
2206
2330
|
const config = loadConfig(process.cwd());
|
|
2207
|
-
const translationsPath =
|
|
2331
|
+
const translationsPath = path12.join(process.cwd(), config.translationsPath);
|
|
2208
2332
|
const existingNamespaces = getNamespaces(translationsPath, config.sourceLanguage);
|
|
2209
2333
|
let namespace;
|
|
2210
2334
|
if (existingNamespaces.length > 0) {
|
|
@@ -2327,7 +2451,7 @@ if (command === "find-unused") {
|
|
|
2327
2451
|
} else {
|
|
2328
2452
|
const hasFlags = values["auto-fill"] || values.language || values["skip-types"] || values["dry-run"];
|
|
2329
2453
|
if (hasFlags) {
|
|
2330
|
-
const configPath =
|
|
2454
|
+
const configPath = path12.join(process.cwd(), ".translationsrc.json");
|
|
2331
2455
|
const config = fs7.existsSync(configPath) ? loadConfig(process.cwd()) : { provider: "deepl" };
|
|
2332
2456
|
const provider = config.provider || "deepl";
|
|
2333
2457
|
const envVarName = provider === "google" ? "GOOGLE_TRANSLATE_API_KEY" : "DEEPL_API_KEY";
|
|
@@ -2353,7 +2477,7 @@ if (command === "find-unused") {
|
|
|
2353
2477
|
} else {
|
|
2354
2478
|
(async () => {
|
|
2355
2479
|
try {
|
|
2356
|
-
const configPath =
|
|
2480
|
+
const configPath = path12.join(process.cwd(), ".translationsrc.json");
|
|
2357
2481
|
const isInitialized = fs7.existsSync(configPath);
|
|
2358
2482
|
console.log("\n\u{1F30D} Translation Management\n");
|
|
2359
2483
|
const action = await select2({
|
|
@@ -2374,6 +2498,11 @@ if (command === "find-unused") {
|
|
|
2374
2498
|
value: "find-unused",
|
|
2375
2499
|
description: "Find translation keys not used in the codebase"
|
|
2376
2500
|
},
|
|
2501
|
+
{
|
|
2502
|
+
name: "\u{1F50E} Find duplicate values",
|
|
2503
|
+
value: "find-duplicates",
|
|
2504
|
+
description: "Find values duplicated from the common namespace"
|
|
2505
|
+
},
|
|
2377
2506
|
{
|
|
2378
2507
|
name: "\u{1F916} Auto-fill missing translations",
|
|
2379
2508
|
value: "autofill",
|
|
@@ -2413,7 +2542,7 @@ if (command === "find-unused") {
|
|
|
2413
2542
|
}
|
|
2414
2543
|
}
|
|
2415
2544
|
const config = loadConfig(process.cwd());
|
|
2416
|
-
const translationsPath =
|
|
2545
|
+
const translationsPath = path12.join(process.cwd(), config.translationsPath);
|
|
2417
2546
|
const existingNamespaces = getNamespaces(translationsPath, config.sourceLanguage);
|
|
2418
2547
|
let namespace;
|
|
2419
2548
|
if (existingNamespaces.length > 0) {
|
|
@@ -2523,6 +2652,10 @@ if (command === "find-unused") {
|
|
|
2523
2652
|
const { findUnusedKeys: findUnusedKeys2, printUnusedKeysResult: printUnusedKeysResult2 } = await Promise.resolve().then(() => (init_find_unused(), find_unused_exports));
|
|
2524
2653
|
const result = findUnusedKeys2(process.cwd());
|
|
2525
2654
|
printUnusedKeysResult2(result);
|
|
2655
|
+
} else if (action === "find-duplicates") {
|
|
2656
|
+
const { findDuplicates: findDuplicates2, printDuplicateKeysResult: printDuplicateKeysResult2 } = await Promise.resolve().then(() => (init_find_duplicates(), find_duplicates_exports));
|
|
2657
|
+
const result = findDuplicates2(process.cwd());
|
|
2658
|
+
printDuplicateKeysResult2(result);
|
|
2526
2659
|
} else if (action === "types") {
|
|
2527
2660
|
console.log("\u{1F4DD} Generating TypeScript types...\n");
|
|
2528
2661
|
const { generateTranslationTypes: generateTranslationTypes2 } = await Promise.resolve().then(() => (init_generate_types(), generate_types_exports));
|