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.
@@ -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 path11 from "path";
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) Smart mode - validates, fills, and generates types
2112
- add Add a new translation key
2113
- find-unused Find translation keys that are not used in the codebase
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 = path11.join(process.cwd(), ".translationsrc.json");
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 = path11.join(process.cwd(), config.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 = path11.join(process.cwd(), ".translationsrc.json");
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 = path11.join(process.cwd(), ".translationsrc.json");
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 = path11.join(process.cwd(), config.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));