@radicalstorage-lingo/cli 0.3.1 → 0.4.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.
Files changed (3) hide show
  1. package/README.md +6 -0
  2. package/dist/cli.cjs +94 -16
  3. package/package.json +10 -2
package/README.md CHANGED
@@ -52,6 +52,12 @@ npx lingo pull
52
52
 
53
53
  Untranslated (missing, empty) values are never written — a key is either
54
54
  pulled with real text or omitted entirely.
55
+ - `split` — `none` (default): `{outDir}/{lang}.json`. `namespace`:
56
+ `{outDir}/{lang}/{namespace}.json` per top-level namespace, keys relative to
57
+ it, `_root.json` for keys without one (next-i18next's `public/locales`
58
+ layout).
59
+ - `languageMap` — file/folder names instead of Lingo's codes, e.g.
60
+ `{ "zh-CN": "zh_cn", "pt-PT": "pt" }`.
55
61
 
56
62
  ## Commands
57
63
 
package/dist/cli.cjs CHANGED
@@ -21067,6 +21067,31 @@ function getCodec(id) {
21067
21067
  }
21068
21068
  var FORMAT_LABELS = Object.fromEntries(codecs.map((c2) => [c2.id, c2.label]));
21069
21069
 
21070
+ // ../formats/src/util/bundle.ts
21071
+ var ROOT_NAMESPACE = "_root";
21072
+ function bundleMessages(rows, namespace) {
21073
+ const out = {};
21074
+ for (const row of rows) {
21075
+ if (row.value.trim().length === 0) continue;
21076
+ const path = row.namespacePath ?? null;
21077
+ if (namespace === void 0) {
21078
+ out[path ? `${path}.${row.key}` : row.key] = row.value;
21079
+ } else if (namespace === ROOT_NAMESPACE) {
21080
+ if (!path) out[row.key] = row.value;
21081
+ } else if (path === namespace) {
21082
+ out[row.key] = row.value;
21083
+ } else if (path?.startsWith(`${namespace}.`)) {
21084
+ out[`${path.slice(namespace.length + 1)}.${row.key}`] = row.value;
21085
+ }
21086
+ }
21087
+ return out;
21088
+ }
21089
+ function topLevelNamespaces(rows) {
21090
+ const names = /* @__PURE__ */ new Set();
21091
+ for (const row of rows) names.add(row.namespacePath ? row.namespacePath.split(".")[0] : ROOT_NAMESPACE);
21092
+ return [...names].sort();
21093
+ }
21094
+
21070
21095
  // src/api.ts
21071
21096
  var import_node_fs = require("fs");
21072
21097
  var import_node_os = require("os");
@@ -25268,8 +25293,19 @@ var configSchema = external_exports.object({
25268
25293
  languages: external_exports.array(external_exports.string().min(2)).default([]),
25269
25294
  /** Format the pulled files are written in. */
25270
25295
  format: external_exports.enum(["json-nested", "json-flat"]).default("json-nested"),
25271
- /** Directory for pulled bundles (one file per language). */
25296
+ /** Directory for pulled bundles. */
25272
25297
  outDir: external_exports.string().default("messages"),
25298
+ /**
25299
+ * File layout of `lingo pull`:
25300
+ * - `none` (default): `{outDir}/{lang}.json`, every key under its full path;
25301
+ * - `namespace`: `{outDir}/{lang}/{namespace}.json` per top-level namespace,
25302
+ * keys relative to it (`_root.json` for keys without one), the layout of
25303
+ * next-i18next's `public/locales` and i18next namespace loading. Same content
25304
+ * as the API's `/bundles/{lang}/{namespace}`.
25305
+ */
25306
+ split: external_exports.enum(["none", "namespace"]).default("none"),
25307
+ /** Names to use for language files/folders instead of Lingo's codes, e.g. `{ "zh-CN": "zh_cn", "pt-PT": "pt" }`. */
25308
+ languageMap: external_exports.record(external_exports.string()).default({}),
25273
25309
  /** Where the generated types module is written. */
25274
25310
  typesOut: external_exports.string().default(".lingo/types.ts"),
25275
25311
  /**
@@ -25289,7 +25325,7 @@ function parseConfig(raw) {
25289
25325
  }
25290
25326
  function bundlePath(config, language) {
25291
25327
  const ext = config.format === "json-flat" || config.format === "json-nested" ? ".json" : ".json";
25292
- return `${config.outDir}/${language}${ext}`;
25328
+ return `${config.outDir}/${config.languageMap[language] ?? language}${ext}`;
25293
25329
  }
25294
25330
  function resolveOutDir(flagOut, config) {
25295
25331
  return flagOut ?? config.outDir;
@@ -26683,6 +26719,31 @@ async function runInitWizard(opts) {
26683
26719
  );
26684
26720
  }
26685
26721
 
26722
+ // src/layout.ts
26723
+ function localLanguage(config, language) {
26724
+ return config.languageMap[language] ?? language;
26725
+ }
26726
+ function bundleFiles(config, language, rows, namespaces = topLevelNamespaces(rows)) {
26727
+ const lang = localLanguage(config, language);
26728
+ if (config.split === "namespace") {
26729
+ return namespaces.map((namespace) => ({
26730
+ file: `${config.outDir}/${lang}/${namespace}.json`,
26731
+ namespace,
26732
+ messages: bundleMessages(rows, namespace)
26733
+ }));
26734
+ }
26735
+ return [{ file: bundlePath(config, language), namespace: null, messages: bundleMessages(rows) }];
26736
+ }
26737
+ function joinNamespaceFiles(files) {
26738
+ const out = {};
26739
+ for (const { namespace, messages } of files) {
26740
+ for (const [key, value] of Object.entries(messages)) {
26741
+ out[namespace && namespace !== ROOT_NAMESPACE ? `${namespace}.${key}` : key] = value;
26742
+ }
26743
+ }
26744
+ return out;
26745
+ }
26746
+
26686
26747
  // src/messages.ts
26687
26748
  function fullKey(row) {
26688
26749
  return row.namespacePath ? `${row.namespacePath}.${row.key}` : row.key;
@@ -26718,12 +26779,21 @@ async function resolveProjectLanguages(api, config) {
26718
26779
  const project = await api.getProject(config.projectId);
26719
26780
  return resolveLanguages(config.languages, project);
26720
26781
  }
26721
- function readLocalMap(config, language) {
26722
- const file = bundlePath(config, language);
26723
- if (!(0, import_node_fs4.existsSync)(file)) return {};
26782
+ function readBundleFile(config, file) {
26724
26783
  const resource = getCodec(config.format).parse((0, import_node_fs4.readFileSync)(file, "utf-8"));
26725
26784
  return Object.fromEntries(resource.messages.map((m) => [m.key, m.value]));
26726
26785
  }
26786
+ function readLocalMap(config, language) {
26787
+ if (config.split === "namespace") {
26788
+ const dir = `${config.outDir}/${localLanguage(config, language)}`;
26789
+ if (!(0, import_node_fs4.existsSync)(dir)) return {};
26790
+ return joinNamespaceFiles(
26791
+ (0, import_node_fs4.readdirSync)(dir).filter((f) => f.endsWith(".json")).map((f) => ({ namespace: (0, import_node_path3.basename)(f, ".json"), messages: readBundleFile(config, `${dir}/${f}`) }))
26792
+ );
26793
+ }
26794
+ const file = bundlePath(config, language);
26795
+ return (0, import_node_fs4.existsSync)(file) ? readBundleFile(config, file) : {};
26796
+ }
26727
26797
  async function readRemoteMap(api, config, language, status) {
26728
26798
  const rows = await api.listTranslations(config.projectId, language, status);
26729
26799
  return Object.fromEntries(rowsToMessages(rows).map((m) => [m.key, m.value]));
@@ -26793,19 +26863,25 @@ async function pullCommand(opts = {}) {
26793
26863
  if (cfg.languages.length === 0) {
26794
26864
  console.log(import_picocolors.default.dim(`\u2192 pulling all project languages: ${languages.join(", ")}`));
26795
26865
  }
26866
+ const fetched = [];
26796
26867
  for (const language of languages) {
26797
- const rows = await api.listTranslations(
26798
- cfg.projectId,
26799
- language,
26800
- cfg.pull === "approved" ? "approved" : void 0
26801
- );
26868
+ const status = cfg.pull === "approved" ? "approved" : void 0;
26869
+ fetched.push({ language, rows: await api.listTranslations(cfg.projectId, language, status) });
26870
+ }
26871
+ const namespaces = [...new Set(fetched.flatMap((f) => topLevelNamespaces(f.rows)))].sort();
26872
+ for (const { language, rows } of fetched) {
26802
26873
  const messages = rowsToMessages(rows);
26803
26874
  const skipped = rows.length - messages.length;
26804
- const file = bundlePath(cfg, language);
26805
- (0, import_node_fs4.mkdirSync)((0, import_node_path3.dirname)(file), { recursive: true });
26806
- (0, import_node_fs4.writeFileSync)(file, codec.serialize({ messages, language }));
26875
+ const files = bundleFiles(cfg, language, rows, namespaces);
26876
+ for (const { file, messages: map } of files) {
26877
+ (0, import_node_fs4.mkdirSync)((0, import_node_path3.dirname)(file), { recursive: true });
26878
+ const list = Object.entries(map).map(([key, value]) => ({ key, value }));
26879
+ (0, import_node_fs4.writeFileSync)(file, codec.serialize({ messages: list, language }));
26880
+ }
26807
26881
  const skippedNote = skipped > 0 ? `, ${skipped} untranslated skipped` : "";
26808
- console.log(import_picocolors.default.green(`\u2713 ${file} (${messages.length} keys${skippedNote})`));
26882
+ const summary = `${messages.length} keys${skippedNote}`;
26883
+ const line = cfg.split === "namespace" ? `${cfg.outDir}/${localLanguage(cfg, language)}/ \u2014 ${files.length} namespace file${files.length === 1 ? "" : "s"}, ${summary}` : `${files[0].file} (${summary})`;
26884
+ console.log(import_picocolors.default.green(`\u2713 ${line}`));
26809
26885
  if (typeEntries.length === 0) {
26810
26886
  typeEntries = messages.map((m) => ({ key: m.key, sample: m.value }));
26811
26887
  }
@@ -26891,12 +26967,14 @@ async function pushCommand(opts) {
26891
26967
  // package.json
26892
26968
  var package_default = {
26893
26969
  name: "@radicalstorage-lingo/cli",
26894
- version: "0.3.1",
26970
+ version: "0.4.0",
26895
26971
  type: "module",
26896
26972
  description: "Lingo CLI \u2014 pull/push translations as JSON at build time.",
26897
26973
  license: "MIT",
26974
+ repository: { type: "git", url: "git+https://github.com/CashcallPro/lingo.git", directory: "packages/cli" },
26975
+ homepage: "https://github.com/CashcallPro/lingo#readme",
26976
+ bugs: { url: "https://github.com/CashcallPro/lingo/issues" },
26898
26977
  author: "Amin Ahrabian",
26899
- homepage: "https://lingo-app-amin.fly.dev",
26900
26978
  keywords: [
26901
26979
  "i18n",
26902
26980
  "internationalization",
package/package.json CHANGED
@@ -1,11 +1,19 @@
1
1
  {
2
2
  "name": "@radicalstorage-lingo/cli",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "description": "Lingo CLI — pull/push translations as JSON at build time.",
6
6
  "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/CashcallPro/lingo.git",
10
+ "directory": "packages/cli"
11
+ },
12
+ "homepage": "https://github.com/CashcallPro/lingo#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/CashcallPro/lingo/issues"
15
+ },
7
16
  "author": "Amin Ahrabian",
8
- "homepage": "https://lingo-app-amin.fly.dev",
9
17
  "keywords": [
10
18
  "i18n",
11
19
  "internationalization",