@x18n/node 0.0.14 → 0.1.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 (2) hide show
  1. package/dist/index.mjs +112 -10
  2. package/package.json +3 -3
package/dist/index.mjs CHANGED
@@ -9,12 +9,13 @@ import { glob } from "tinyglobby";
9
9
  import { parse, stringify } from "yaml";
10
10
  import { placeholderFromShape } from "@x18n/builtins/placeholders";
11
11
  import regexScopes from "@x18n/builtins/scanners/regex-scopes";
12
+ import { execFile, spawn } from "node:child_process";
12
13
  import process from "node:process";
13
14
  import { watch } from "chokidar";
14
15
  import ignore from "ignore";
15
16
  import { createBuiltins } from "@x18n/builtins";
16
17
  import { Buffer } from "node:buffer";
17
- import { execFile, spawn } from "node:child_process";
18
+ import { yamlReviewStore } from "@x18n/builtins/reviews";
18
19
  import { applyEdits, modify, parse as parse$1 } from "jsonc-parser";
19
20
  //#region src/yaml-plugin.ts
20
21
  /**
@@ -287,6 +288,24 @@ async function loadGitignore(root) {
287
288
  });
288
289
  };
289
290
  }
291
+ function gitConfig(cwd, key) {
292
+ return new Promise((resolve) => {
293
+ execFile("git", ["config", key], {
294
+ cwd,
295
+ encoding: "utf8"
296
+ }, (error, stdout) => {
297
+ resolve(error ? void 0 : stdout.trim() || void 0);
298
+ });
299
+ });
300
+ }
301
+ /** The git `user.name` and `user.email`, or undefined without git or a configured user. */
302
+ async function gitIdentity(cwd) {
303
+ const [name, email] = await Promise.all([gitConfig(cwd, "user.name"), gitConfig(cwd, "user.email")]);
304
+ return name === void 0 && email === void 0 ? void 0 : {
305
+ ...name !== void 0 && { name },
306
+ ...email !== void 0 && { email }
307
+ };
308
+ }
290
309
  /** A persistent cache in one JSON file. Only entries used in this session are written back, so it stays bounded. */
291
310
  function createFileCache(dir) {
292
311
  const file = join(dir, "cache.json");
@@ -324,6 +343,7 @@ function createNodeHost(root, options = {}) {
324
343
  const cacheDir = options.cacheDir === false ? void 0 : options.cacheDir ?? join(absoluteRoot, "node_modules/.cache/x18n");
325
344
  const cache = cacheDir ? createFileCache(join(cacheDir, hashText(absoluteRoot))) : void 0;
326
345
  const full = (path) => resolve(absoluteRoot, path);
346
+ let identity;
327
347
  return {
328
348
  root: absoluteRoot,
329
349
  logger: options.logger,
@@ -383,6 +403,7 @@ function createNodeHost(root, options = {}) {
383
403
  watcher.close();
384
404
  } };
385
405
  },
406
+ identity: () => identity ??= gitIdentity(absoluteRoot),
386
407
  flush: async () => cache?.flush()
387
408
  };
388
409
  }
@@ -768,7 +789,7 @@ const frameworkPresets = {
768
789
  "ui5": "ui5",
769
790
  "lingui": "lingui",
770
791
  "jekyll": "jekyll",
771
- "i18n-js": "i18n-js"
792
+ "i18n-js": "ruby-i18n-js"
772
793
  };
773
794
  const languageExtensions = {
774
795
  javascript: [
@@ -1012,7 +1033,7 @@ function presetsOf(r, notes) {
1012
1033
  }
1013
1034
  function notesOf(settings, notes) {
1014
1035
  if ([...settings.keys()].some((key) => key.startsWith("translate."))) notes.push("`translate.*` settings were dropped: x18n writes translate prompts instead (`x18n prompt translate --to <locale>`).");
1015
- if ([...settings.keys()].some((key) => key.startsWith("review."))) notes.push("Review settings move to `review.*` once reviews ship (1.x); they were not migrated yet.");
1036
+ if (settings.has("review.removeCommentOnResolved")) notes.push("`i18n-ally.review.removeCommentOnResolved` was dropped: x18n keeps resolved reviews and hides them with `x18n review list --open`.");
1016
1037
  for (const key of handledByDefaults) if (settings.has(key)) notes.push(`\`i18n-ally.${key}\` is handled by defaults or dropped.`);
1017
1038
  }
1018
1039
  function mapConfig(settings, parts, notes) {
@@ -1059,8 +1080,85 @@ function mapConfig(settings, parts, notes) {
1059
1080
  ...r.str("defaultNamespace") ? { namespace: { default: r.str("defaultNamespace") } } : {},
1060
1081
  ...Object.keys(usage).length ? { usage } : {},
1061
1082
  ...write ? { write } : {},
1062
- ...Object.keys(extract).length ? { extract } : {}
1083
+ ...Object.keys(extract).length ? { extract } : {},
1084
+ ...reviewOf(r)
1085
+ };
1086
+ }
1087
+ function reviewOf(r) {
1088
+ const enabled = r.get("review.enabled");
1089
+ const author = {
1090
+ ...r.str("review.user.name") && { name: r.str("review.user.name") },
1091
+ ...r.str("review.user.email") && { email: r.str("review.user.email") }
1092
+ };
1093
+ const review = {
1094
+ ...typeof enabled === "boolean" && { enabled },
1095
+ ...Object.keys(author).length > 0 && { author }
1063
1096
  };
1097
+ return Object.keys(review).length > 0 ? { review } : {};
1098
+ }
1099
+ const reviewTypes = {
1100
+ comment: "comment",
1101
+ approve: "approve",
1102
+ request_change: "request-change"
1103
+ };
1104
+ function str(value) {
1105
+ return typeof value === "string" ? value : void 0;
1106
+ }
1107
+ function recordOf(value) {
1108
+ return isRecord(value) ? value : {};
1109
+ }
1110
+ /** One i18n-ally comment; one with a `suggestion` becomes a suggestion. */
1111
+ function convertComment(comment) {
1112
+ const user = recordOf(comment.user);
1113
+ const name = str(user.name);
1114
+ const email = str(user.email);
1115
+ const text = str(comment.comment);
1116
+ const suggestion = str(comment.suggestion);
1117
+ return {
1118
+ id: str(comment.id) ?? hashText(JSON.stringify(comment)).slice(0, 6),
1119
+ type: suggestion === void 0 ? reviewTypes[str(comment.type) ?? "comment"] ?? "comment" : "suggestion",
1120
+ ...(name !== void 0 || email !== void 0) && { author: {
1121
+ ...name !== void 0 && { name },
1122
+ ...email !== void 0 && { email }
1123
+ } },
1124
+ time: str(comment.time) ?? (/* @__PURE__ */ new Date(0)).toISOString(),
1125
+ ...text !== void 0 && { text },
1126
+ ...suggestion !== void 0 && { suggestion },
1127
+ ...comment.resolved === true && { resolved: true }
1128
+ };
1129
+ }
1130
+ /** The comments of one key and locale, with the translation candidate as a last suggestion. */
1131
+ function convertLocale(key, locale, item) {
1132
+ const comments = (Array.isArray(item.comments) ? item.comments : []).filter(isRecord).map(convertComment);
1133
+ const candidate = recordOf(item.translation_candidate);
1134
+ const text = str(candidate.text);
1135
+ if (text !== void 0) comments.push({
1136
+ id: hashText(`${key}:${locale}:${text}`).slice(0, 6),
1137
+ type: "suggestion",
1138
+ time: str(candidate.time) ?? (/* @__PURE__ */ new Date(0)).toISOString(),
1139
+ text: "Translation candidate from i18n-ally",
1140
+ suggestion: text
1141
+ });
1142
+ return comments;
1143
+ }
1144
+ /** i18n-ally's `reviews.<key>.locales.<locale>.comments` as x18n threads; translation candidates become suggestions. */
1145
+ function convertI18nAllyReviews(text, notes) {
1146
+ const reviews = recordOf(recordOf(parse(text)).reviews);
1147
+ const threads = {};
1148
+ let descriptions = 0;
1149
+ for (const [key, entry] of Object.entries(reviews)) {
1150
+ const { description, locales } = recordOf(entry);
1151
+ if (description !== void 0) descriptions++;
1152
+ for (const [locale, item] of Object.entries(recordOf(locales))) {
1153
+ const comments = convertLocale(key, locale, recordOf(item));
1154
+ if (comments.length > 0) (threads[key] ??= {})[locale] = comments;
1155
+ }
1156
+ }
1157
+ if (descriptions > 0) notes.push(`${descriptions} key description${descriptions === 1 ? " was" : "s were"} dropped: x18n reviews have no key descriptions.`);
1158
+ return yamlReviewStore.serialize({
1159
+ threads,
1160
+ sources: {}
1161
+ });
1064
1162
  }
1065
1163
  /** The x18n config that a project's i18n-ally settings and custom framework mean, or undefined without any. */
1066
1164
  async function readI18nAllyConfig(root) {
@@ -1107,15 +1205,15 @@ async function migrateI18nAlly(root, options = {}) {
1107
1205
  };
1108
1206
  const { config, plugin } = found;
1109
1207
  const notes = [...found.notes];
1110
- const reviews = (await readFirst(root, [".vscode/i18n-ally-next-reviews.yml", ".vscode/i18n-ally-reviews.yml"]))?.text;
1208
+ const reviews = await readFirst(root, [".vscode/i18n-ally-next-reviews.yml", ".vscode/i18n-ally-reviews.yml"]);
1111
1209
  const files = [];
1112
1210
  const finalConfig = plugin !== void 0 ? {
1113
1211
  ...config,
1114
- plugins: ["./x18n/custom.x18n-plugin.yml"]
1212
+ plugins: ["./.x18n/custom.x18n-plugin.yml"]
1115
1213
  } : config;
1116
1214
  files.push(configFile(finalConfig, options.format ?? "ts"));
1117
1215
  if (plugin) files.push({
1118
- path: "x18n/custom.x18n-plugin.yml",
1216
+ path: ".x18n/custom.x18n-plugin.yml",
1119
1217
  content: `# yaml-language-server: $schema=../node_modules/@x18n/core/schema/plugin.json\n${stringify(plugin)}`
1120
1218
  });
1121
1219
  let vscodeText = await readText(join(root, ".vscode/settings.json"));
@@ -1136,14 +1234,18 @@ async function migrateI18nAlly(root, options = {}) {
1136
1234
  if (reviews !== void 0 && !existsSync(join(root, ".x18n/reviews.yml"))) {
1137
1235
  files.push({
1138
1236
  path: ".x18n/reviews.yml",
1139
- content: reviews
1237
+ content: convertI18nAllyReviews(reviews.text, notes)
1140
1238
  });
1141
- notes.push("Reviews were copied to `.x18n/reviews.yml`; x18n reads them once reviews ship (1.x).");
1239
+ notes.push("Reviews were converted to `.x18n/reviews.yml`.");
1142
1240
  }
1143
1241
  return {
1144
1242
  files,
1145
1243
  notes,
1146
- removable: [...keys, ...found.customFramework ? [found.customFramework] : []]
1244
+ removable: [
1245
+ ...keys,
1246
+ ...found.customFramework ? [found.customFramework] : [],
1247
+ ...reviews ? [reviews.path] : []
1248
+ ]
1147
1249
  };
1148
1250
  }
1149
1251
  //#endregion
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@x18n/node",
3
3
  "type": "module",
4
- "version": "0.0.14",
4
+ "version": "0.1.0",
5
5
  "description": "Node.js host for x18n: file system access, config and plugin loading",
6
6
  "author": "Anthony Fu <anthonyfu117@hotmail.com>",
7
7
  "license": "MIT",
@@ -25,8 +25,8 @@
25
25
  "node": ">=22"
26
26
  },
27
27
  "dependencies": {
28
- "@x18n/builtins": "0.0.14",
29
- "@x18n/core": "0.0.14",
28
+ "@x18n/builtins": "0.1.0",
29
+ "@x18n/core": "0.1.0",
30
30
  "chokidar": "^6.0.0",
31
31
  "ignore": "^7.0.13",
32
32
  "jiti": "^2.7.0",