poly-lexis 0.6.0 → 0.8.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/dist/cli/translations.js +141 -10
- package/dist/cli/translations.js.map +1 -1
- package/dist/index.d.ts +16 -1
- package/dist/index.js +20 -0
- 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,6 +897,8 @@ 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 });
|
|
@@ -899,13 +918,14 @@ 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();
|
|
928
|
+
PLURAL_SUFFIXES = ["_zero", "_one", "_two", "_few", "_many", "_other"];
|
|
909
929
|
typeTemplate = (translationKeys, namespaceKeys) => `
|
|
910
930
|
export const translationKeys = [${translationKeys.map((key) => `"${key}"`).join(", ")}] as const;
|
|
911
931
|
export const namespaceKeys = [${namespaceKeys.map((key) => `"${key}"`).join(", ")}] as const;
|
|
@@ -1198,11 +1218,97 @@ var init_find_unused = __esm({
|
|
|
1198
1218
|
}
|
|
1199
1219
|
});
|
|
1200
1220
|
|
|
1221
|
+
// src/translations/cli/find-duplicates.ts
|
|
1222
|
+
var find_duplicates_exports = {};
|
|
1223
|
+
__export(find_duplicates_exports, {
|
|
1224
|
+
findDuplicates: () => findDuplicates,
|
|
1225
|
+
printDuplicateKeysResult: () => printDuplicateKeysResult
|
|
1226
|
+
});
|
|
1227
|
+
import * as path11 from "path";
|
|
1228
|
+
function findDuplicates(projectRoot = process.cwd()) {
|
|
1229
|
+
const config = loadConfig(projectRoot);
|
|
1230
|
+
const translationsPath = path11.join(projectRoot, config.translationsPath);
|
|
1231
|
+
const sourceLanguage = config.sourceLanguage;
|
|
1232
|
+
const namespaces = getNamespaces(translationsPath, sourceLanguage);
|
|
1233
|
+
const sourceTranslations = readTranslations(translationsPath, sourceLanguage);
|
|
1234
|
+
console.log("=====");
|
|
1235
|
+
console.log("Finding duplicate translations (common namespace)");
|
|
1236
|
+
console.log("=====");
|
|
1237
|
+
console.log(`Source language: ${sourceLanguage}`);
|
|
1238
|
+
console.log(`Namespaces: ${namespaces.join(", ")}`);
|
|
1239
|
+
console.log("=====");
|
|
1240
|
+
const commonValues = sourceTranslations["common"] || {};
|
|
1241
|
+
if (Object.keys(commonValues).length === 0) {
|
|
1242
|
+
console.log("No common namespace found or it is empty.");
|
|
1243
|
+
return { duplicates: [], totalKeysChecked: 0 };
|
|
1244
|
+
}
|
|
1245
|
+
const valueToCommonKey = /* @__PURE__ */ new Map();
|
|
1246
|
+
for (const [key, value] of Object.entries(commonValues)) {
|
|
1247
|
+
if (!valueToCommonKey.has(value)) {
|
|
1248
|
+
valueToCommonKey.set(value, key);
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
const duplicates = [];
|
|
1252
|
+
let totalKeysChecked = 0;
|
|
1253
|
+
for (const namespace of namespaces) {
|
|
1254
|
+
if (namespace === "common") continue;
|
|
1255
|
+
const nsValues = sourceTranslations[namespace] || {};
|
|
1256
|
+
for (const [key, value] of Object.entries(nsValues)) {
|
|
1257
|
+
totalKeysChecked++;
|
|
1258
|
+
const commonKey = valueToCommonKey.get(value);
|
|
1259
|
+
if (commonKey) {
|
|
1260
|
+
duplicates.push({
|
|
1261
|
+
namespace,
|
|
1262
|
+
key,
|
|
1263
|
+
commonKey,
|
|
1264
|
+
value
|
|
1265
|
+
});
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
console.log("=====");
|
|
1270
|
+
return { duplicates, totalKeysChecked };
|
|
1271
|
+
}
|
|
1272
|
+
function printDuplicateKeysResult(result) {
|
|
1273
|
+
if (result.duplicates.length === 0) {
|
|
1274
|
+
console.log("\u2713 No duplicate values found across namespaces!");
|
|
1275
|
+
return;
|
|
1276
|
+
}
|
|
1277
|
+
console.log(`
|
|
1278
|
+
\u26A0 Found ${result.duplicates.length} values duplicated from common:
|
|
1279
|
+
`);
|
|
1280
|
+
const byNamespace = /* @__PURE__ */ new Map();
|
|
1281
|
+
for (const dup of result.duplicates) {
|
|
1282
|
+
const items = byNamespace.get(dup.namespace) || [];
|
|
1283
|
+
items.push(dup);
|
|
1284
|
+
byNamespace.set(dup.namespace, items);
|
|
1285
|
+
}
|
|
1286
|
+
for (const [namespace, items] of byNamespace) {
|
|
1287
|
+
console.log(` ${namespace}:`);
|
|
1288
|
+
for (const item of items) {
|
|
1289
|
+
console.log(` - ${item.key} \u2192 common.${item.commonKey} ("${item.value}")`);
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
console.log(`
|
|
1293
|
+
\u{1F4CA} Summary:`);
|
|
1294
|
+
console.log(` Keys checked: ${result.totalKeysChecked}`);
|
|
1295
|
+
console.log(` Duplicates found: ${result.duplicates.length}`);
|
|
1296
|
+
console.log("=====");
|
|
1297
|
+
}
|
|
1298
|
+
var init_find_duplicates = __esm({
|
|
1299
|
+
"src/translations/cli/find-duplicates.ts"() {
|
|
1300
|
+
"use strict";
|
|
1301
|
+
init_esm_shims();
|
|
1302
|
+
init_utils();
|
|
1303
|
+
init_init();
|
|
1304
|
+
}
|
|
1305
|
+
});
|
|
1306
|
+
|
|
1201
1307
|
// src/cli/translations.ts
|
|
1202
1308
|
init_esm_shims();
|
|
1203
1309
|
import "dotenv/config";
|
|
1204
1310
|
import * as fs7 from "fs";
|
|
1205
|
-
import * as
|
|
1311
|
+
import * as path12 from "path";
|
|
1206
1312
|
import { parseArgs } from "util";
|
|
1207
1313
|
import { confirm as confirm2, input as input2, select as select2 } from "@inquirer/prompts";
|
|
1208
1314
|
|
|
@@ -2108,9 +2214,10 @@ Smart translation management - automatically handles initialization, validation,
|
|
|
2108
2214
|
auto-filling, and type generation based on your project's current state.
|
|
2109
2215
|
|
|
2110
2216
|
Commands:
|
|
2111
|
-
(none)
|
|
2112
|
-
add
|
|
2113
|
-
find-unused
|
|
2217
|
+
(none) Smart mode - validates, fills, and generates types
|
|
2218
|
+
add Add a new translation key
|
|
2219
|
+
find-unused Find translation keys that are not used in the codebase
|
|
2220
|
+
find-duplicates Find values duplicated from the common namespace
|
|
2114
2221
|
|
|
2115
2222
|
Options (Smart Mode):
|
|
2116
2223
|
-a, --auto-fill Auto-fill missing translations with DeepL or Google Translate
|
|
@@ -2160,6 +2267,9 @@ Examples:
|
|
|
2160
2267
|
# Find unused translation keys
|
|
2161
2268
|
translations find-unused
|
|
2162
2269
|
|
|
2270
|
+
# Find values duplicated from common namespace
|
|
2271
|
+
translations find-duplicates
|
|
2272
|
+
|
|
2163
2273
|
What happens in smart mode:
|
|
2164
2274
|
1. Checks if translations are initialized (creates .translationsrc.json if needed)
|
|
2165
2275
|
2. Validates all translations against source language
|
|
@@ -2182,12 +2292,24 @@ if (command === "find-unused") {
|
|
|
2182
2292
|
process.exit(1);
|
|
2183
2293
|
}
|
|
2184
2294
|
})();
|
|
2295
|
+
} else if (command === "find-duplicates") {
|
|
2296
|
+
(async () => {
|
|
2297
|
+
try {
|
|
2298
|
+
const { findDuplicates: findDuplicates2, printDuplicateKeysResult: printDuplicateKeysResult2 } = await Promise.resolve().then(() => (init_find_duplicates(), find_duplicates_exports));
|
|
2299
|
+
console.log("\n\u{1F50D} Finding duplicate translations (common namespace)...\n");
|
|
2300
|
+
const result = findDuplicates2(process.cwd());
|
|
2301
|
+
printDuplicateKeysResult2(result);
|
|
2302
|
+
} catch (error) {
|
|
2303
|
+
console.error("Error:", error instanceof Error ? error.message : error);
|
|
2304
|
+
process.exit(1);
|
|
2305
|
+
}
|
|
2306
|
+
})();
|
|
2185
2307
|
} else if (command === "add") {
|
|
2186
2308
|
if (!values.namespace && !values.key && !values.value) {
|
|
2187
2309
|
(async () => {
|
|
2188
2310
|
try {
|
|
2189
2311
|
console.log("\n\u2728 Add a new translation key\n");
|
|
2190
|
-
const configPath =
|
|
2312
|
+
const configPath = path12.join(process.cwd(), ".translationsrc.json");
|
|
2191
2313
|
const isInitialized = fs7.existsSync(configPath);
|
|
2192
2314
|
if (!isInitialized) {
|
|
2193
2315
|
console.log("\u26A0\uFE0F Translation structure not initialized.");
|
|
@@ -2204,7 +2326,7 @@ if (command === "find-unused") {
|
|
|
2204
2326
|
}
|
|
2205
2327
|
}
|
|
2206
2328
|
const config = loadConfig(process.cwd());
|
|
2207
|
-
const translationsPath =
|
|
2329
|
+
const translationsPath = path12.join(process.cwd(), config.translationsPath);
|
|
2208
2330
|
const existingNamespaces = getNamespaces(translationsPath, config.sourceLanguage);
|
|
2209
2331
|
let namespace;
|
|
2210
2332
|
if (existingNamespaces.length > 0) {
|
|
@@ -2327,7 +2449,7 @@ if (command === "find-unused") {
|
|
|
2327
2449
|
} else {
|
|
2328
2450
|
const hasFlags = values["auto-fill"] || values.language || values["skip-types"] || values["dry-run"];
|
|
2329
2451
|
if (hasFlags) {
|
|
2330
|
-
const configPath =
|
|
2452
|
+
const configPath = path12.join(process.cwd(), ".translationsrc.json");
|
|
2331
2453
|
const config = fs7.existsSync(configPath) ? loadConfig(process.cwd()) : { provider: "deepl" };
|
|
2332
2454
|
const provider = config.provider || "deepl";
|
|
2333
2455
|
const envVarName = provider === "google" ? "GOOGLE_TRANSLATE_API_KEY" : "DEEPL_API_KEY";
|
|
@@ -2353,7 +2475,7 @@ if (command === "find-unused") {
|
|
|
2353
2475
|
} else {
|
|
2354
2476
|
(async () => {
|
|
2355
2477
|
try {
|
|
2356
|
-
const configPath =
|
|
2478
|
+
const configPath = path12.join(process.cwd(), ".translationsrc.json");
|
|
2357
2479
|
const isInitialized = fs7.existsSync(configPath);
|
|
2358
2480
|
console.log("\n\u{1F30D} Translation Management\n");
|
|
2359
2481
|
const action = await select2({
|
|
@@ -2374,6 +2496,11 @@ if (command === "find-unused") {
|
|
|
2374
2496
|
value: "find-unused",
|
|
2375
2497
|
description: "Find translation keys not used in the codebase"
|
|
2376
2498
|
},
|
|
2499
|
+
{
|
|
2500
|
+
name: "\u{1F50E} Find duplicate values",
|
|
2501
|
+
value: "find-duplicates",
|
|
2502
|
+
description: "Find values duplicated from the common namespace"
|
|
2503
|
+
},
|
|
2377
2504
|
{
|
|
2378
2505
|
name: "\u{1F916} Auto-fill missing translations",
|
|
2379
2506
|
value: "autofill",
|
|
@@ -2413,7 +2540,7 @@ if (command === "find-unused") {
|
|
|
2413
2540
|
}
|
|
2414
2541
|
}
|
|
2415
2542
|
const config = loadConfig(process.cwd());
|
|
2416
|
-
const translationsPath =
|
|
2543
|
+
const translationsPath = path12.join(process.cwd(), config.translationsPath);
|
|
2417
2544
|
const existingNamespaces = getNamespaces(translationsPath, config.sourceLanguage);
|
|
2418
2545
|
let namespace;
|
|
2419
2546
|
if (existingNamespaces.length > 0) {
|
|
@@ -2523,6 +2650,10 @@ if (command === "find-unused") {
|
|
|
2523
2650
|
const { findUnusedKeys: findUnusedKeys2, printUnusedKeysResult: printUnusedKeysResult2 } = await Promise.resolve().then(() => (init_find_unused(), find_unused_exports));
|
|
2524
2651
|
const result = findUnusedKeys2(process.cwd());
|
|
2525
2652
|
printUnusedKeysResult2(result);
|
|
2653
|
+
} else if (action === "find-duplicates") {
|
|
2654
|
+
const { findDuplicates: findDuplicates2, printDuplicateKeysResult: printDuplicateKeysResult2 } = await Promise.resolve().then(() => (init_find_duplicates(), find_duplicates_exports));
|
|
2655
|
+
const result = findDuplicates2(process.cwd());
|
|
2656
|
+
printDuplicateKeysResult2(result);
|
|
2526
2657
|
} else if (action === "types") {
|
|
2527
2658
|
console.log("\u{1F4DD} Generating TypeScript types...\n");
|
|
2528
2659
|
const { generateTranslationTypes: generateTranslationTypes2 } = await Promise.resolve().then(() => (init_generate_types(), generate_types_exports));
|