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.
@@ -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
- typeTemplate = (translationKeys, namespaceKeys) => `
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 path11 from "path";
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) 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
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 = path11.join(process.cwd(), ".translationsrc.json");
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 = path11.join(process.cwd(), config.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 = path11.join(process.cwd(), ".translationsrc.json");
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 = path11.join(process.cwd(), ".translationsrc.json");
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 = path11.join(process.cwd(), config.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));