@prettifystudio/lyngo-cli 0.1.0 → 0.1.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.
Files changed (2) hide show
  1. package/bin/lyngo +70 -0
  2. package/package.json +1 -1
package/bin/lyngo CHANGED
@@ -386,9 +386,77 @@ async function init(opts) {
386
386
  project = await chooseLanguages(project, opts);
387
387
  await scaffold(project);
388
388
  setUpClaudeCode(top, remote);
389
+ await fillMissingCatalogs(project, opts);
389
390
  console.log('Done. Build as usual; lyngo localize runs when your agent finishes, or run it yourself before committing.');
390
391
  }
391
392
 
393
+ // A language added today has no catalog in the repository yet; the first
394
+ // `lyngo localize` would create one with the keys of the changed files only
395
+ // (pingcrm, 2026-10-12: 11 of 103 keys). Say so, and fill it on request.
396
+ async function fillMissingCatalogs(project, opts) {
397
+ const folder = catalogFolder(project);
398
+ const primary = project.primary_language || 'en';
399
+ const primaryPath = path.join(folder, primary + '.json');
400
+ if (!readIfFile(primaryPath)) return;
401
+ const targets = (project.languages || []).filter((l) => l !== primary);
402
+ const missing = targets.filter((l) => !readIfFile(path.join(folder, l + '.json')));
403
+ if (!missing.length) return;
404
+ const keys = Object.keys(parseCatalog(primaryPath) || {}).length;
405
+ console.log(`No catalog yet for ${missing.join(', ')}: ${folder}/<code>.json is missing, and ${primary}.json holds ${keys} keys that need translating.`);
406
+ const go = opts.yes ? 'y' : await ask(`Translate ${missing.length === 1 ? 'it' : 'them'} now? [Y/n] `);
407
+ if (go !== null && go !== '' && !/^y/i.test(go)) { console.log(`Later: lyngo translate ${missing.join(' ')}`); return; }
408
+ if (go === null) { console.log(`Run: lyngo translate ${missing.join(' ')}`); return; }
409
+ for (const code of missing) await translateLanguage(code, project);
410
+ }
411
+
412
+ function catalogFolder(project) { return ((project.i18n && project.i18n.catalog_folder) || 'lang').replace(/\/+$/, ''); }
413
+ function parseCatalog(p) { try { return JSON.parse(readIfFile(p) || ''); } catch { return null; } }
414
+
415
+ // ── translate: fill a language's catalog from the primary one ─────────────
416
+ async function translate(opts) {
417
+ const top = root();
418
+ process.chdir(top);
419
+ readToken();
420
+ const codes = opts._;
421
+ if (!codes.length) fail('usage: lyngo translate <code> [<code>...] (fills every key the language lacks)');
422
+ const st = await call('status', {});
423
+ const project = { primary_language: st.primary_language, languages: [st.primary_language, ...(st.languages || [])], i18n: st.i18n };
424
+ for (const code of codes) {
425
+ if (!project.languages.includes(code)) fail(`${code} is not one of the project's languages (${project.languages.join(', ')}). Add it with lyngo init --languages.`);
426
+ await translateLanguage(code, project);
427
+ }
428
+ }
429
+
430
+ async function translateLanguage(code, project) {
431
+ const folder = catalogFolder(project);
432
+ const primary = project.primary_language || 'en';
433
+ const primaryPath = path.join(folder, primary + '.json');
434
+ const targetPath = path.join(folder, code + '.json');
435
+ const source = parseCatalog(primaryPath);
436
+ if (!source) fail(`${primaryPath} is missing or not valid JSON.`);
437
+ const current = parseCatalog(targetPath) || {};
438
+ const missing = Object.keys(source).filter((k) => !(k in current) || String(current[k]).trim() === '');
439
+ if (!missing.length) { console.log(`${code}: complete, ${Object.keys(source).length} keys.`); return; }
440
+ let translated = 0, memory = 0, flagged = [];
441
+ // Chunks keep each request under the server's time; the returned catalog carries forward.
442
+ for (let i = 0; i < missing.length; i += 100) {
443
+ const r = await call('translate', {
444
+ keys: missing.slice(i, i + 100),
445
+ languages: [code],
446
+ catalogs: [{ path: primaryPath, content: readIfFile(primaryPath) }, { path: targetPath, content: readIfFile(targetPath) || '{}' }],
447
+ });
448
+ const out = (r.catalogs || []).find((c) => c.path === targetPath || c.path.endsWith('/' + code + '.json'));
449
+ if (!out) fail(`the server returned no catalog for ${code}.`);
450
+ fs.mkdirSync(path.dirname(targetPath), { recursive: true });
451
+ fs.writeFileSync(targetPath, out.content);
452
+ const l = (r.report && r.report.languages && r.report.languages[code]) || {};
453
+ translated += l.translated || 0; memory += l.from_memory || 0; flagged.push(...(l.flagged || []));
454
+ process.stdout.write(`\r${code}: ${Math.min(i + 100, missing.length)}/${missing.length}`);
455
+ }
456
+ const flaggedText = flagged.length ? `; ${flagged.length} flagged for review: ${flagged.slice(0, 3).map((x) => JSON.stringify(x.key || x)).join(', ')}` : '';
457
+ console.log(`\r${code}: ${translated} translated, ${memory} from memory${flaggedText} -> ${targetPath}`);
458
+ }
459
+
392
460
  // ── localize, check, status ────────────────────────────────────────────────
393
461
 
394
462
  function changedFiles(top) {
@@ -482,6 +550,7 @@ const HELP = `lyngo ${version()}: localization for your repository, from the ter
482
550
  (--languages are the target languages; the source stays en unless --primary)
483
551
  lyngo localize [--languages fr,de] [--dry]
484
552
  localize and translate what changed in the working tree
553
+ lyngo translate <code> [<code>…] fill every key a language's catalog lacks, from the primary catalog
485
554
  lyngo check <file> the user-facing strings of one file
486
555
  lyngo status the project as Lyngo knows it
487
556
 
@@ -521,6 +590,7 @@ function parse(argv) {
521
590
  case 'whoami': await whoami(); break;
522
591
  case 'init': await init(opts); break;
523
592
  case 'localize': await localize(opts); break;
593
+ case 'translate': await translate(opts); break;
524
594
  case 'check': await check(opts._[0]); break;
525
595
  case 'status': await status(); break;
526
596
  default: console.error(`lyngo: unknown command "${cmd}".\n\n${HELP}`); process.exitCode = 1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prettifystudio/lyngo-cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Localization like code: Lyngo wraps user-facing text, reuses your project's keys and translates the catalogs, from the terminal.",
5
5
  "bin": {
6
6
  "lyngo": "bin/lyngo"