@x18n/node 0.0.12 → 0.0.14

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 +108 -9
  2. package/package.json +3 -3
package/dist/index.mjs CHANGED
@@ -3,11 +3,12 @@ import { existsSync, readFileSync } from "node:fs";
3
3
  import { mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
4
4
  import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
5
5
  import { fileURLToPath, pathToFileURL } from "node:url";
6
- import { compileYamlPlugin } from "@x18n/builtins/yaml-plugin";
7
6
  import { PluginFileSchema, createProject, errors, hashText, isX18nError, resolveConfig, validate } from "@x18n/core";
8
7
  import { createJiti } from "jiti";
9
8
  import { glob } from "tinyglobby";
10
9
  import { parse, stringify } from "yaml";
10
+ import { placeholderFromShape } from "@x18n/builtins/placeholders";
11
+ import regexScopes from "@x18n/builtins/scanners/regex-scopes";
11
12
  import process from "node:process";
12
13
  import { watch } from "chokidar";
13
14
  import ignore from "ignore";
@@ -15,6 +16,76 @@ import { createBuiltins } from "@x18n/builtins";
15
16
  import { Buffer } from "node:buffer";
16
17
  import { execFile, spawn } from "node:child_process";
17
18
  import { applyEdits, modify, parse as parse$1 } from "jsonc-parser";
19
+ //#region src/yaml-plugin.ts
20
+ /**
21
+ * Compiles a validated YAML plugin into plugin objects. Each section maps to the
22
+ * options of a built-in factory, so moving to a TS plugin is a small change.
23
+ */
24
+ function compileYamlPlugin(data) {
25
+ const replacements = (data.normalizeKey ?? []).map((rule) => ({
26
+ from: new RegExp(rule.replace.pattern, "g"),
27
+ to: rule.replace.with
28
+ }));
29
+ const localeInfo = data.localeInfo;
30
+ const plugin = {
31
+ name: data.name,
32
+ config(draft) {
33
+ const defaults = data.config;
34
+ if (defaults?.sourceLocale) draft.sourceLocale = defaults.sourceLocale;
35
+ if (defaults?.locales) draft.locales = defaults.locales;
36
+ if (defaults?.resources && draft.resources.length === 0) draft.resources = defaults.resources;
37
+ if (defaults?.keyStyle) draft.keyStyle = defaults.keyStyle;
38
+ if (defaults?.namespace) draft.namespace = {
39
+ separator: defaults.namespace.separator ?? draft.namespace.separator,
40
+ default: defaults.namespace.default ?? draft.namespace.default,
41
+ fallback: defaults.namespace.fallback ?? draft.namespace.fallback,
42
+ byFile: {
43
+ ...draft.namespace.byFile,
44
+ ...defaults.namespace.byFile
45
+ },
46
+ fromPath: defaults.namespace.fromPath ?? draft.namespace.fromPath
47
+ };
48
+ draft.usage = {
49
+ ...draft.usage,
50
+ calls: [...draft.usage.calls, ...data.scanners?.calls ?? []],
51
+ regex: [...draft.usage.regex, ...data.scanners?.regex ?? []]
52
+ };
53
+ if (data.extract?.templates) draft.extract = {
54
+ ...draft.extract,
55
+ templates: [...draft.extract.templates ?? [], ...data.extract.templates]
56
+ };
57
+ },
58
+ derivedKeys: data.derivedKeys,
59
+ placeholders: data.placeholders?.map(placeholderFromShape),
60
+ normalizeKey: replacements.length > 0 ? (raw) => replacements.reduce((text, rule) => text.replace(rule.from, rule.to), raw) : void 0,
61
+ localeInfo: localeInfo ? (code) => {
62
+ const info = localeInfo[code];
63
+ return info ? {
64
+ ...info.flag ? { flag: info.flag } : {},
65
+ ...info.displayName ? { displayName: info.displayName } : {}
66
+ } : void 0;
67
+ } : void 0
68
+ };
69
+ const plugins = [plugin, ...data.scopes ? [regexScopes(data.scopes.regex, `${data.name}-scopes`)] : []];
70
+ const detect = data.detect;
71
+ if (!detect) return plugins.length === 1 ? plugin : {
72
+ name: data.name,
73
+ plugins
74
+ };
75
+ return {
76
+ name: data.name,
77
+ exclusive: data.exclusive,
78
+ plugins,
79
+ async detect(ctx) {
80
+ const deps = detect.dependencies;
81
+ if (deps?.any && !ctx.hasAnyDependency(deps.any)) return false;
82
+ if (deps?.all && !deps.all.every(ctx.hasDependency)) return false;
83
+ for (const pattern of detect.files ?? []) if (!await ctx.fileExists(pattern)) return false;
84
+ return true;
85
+ }
86
+ };
87
+ }
88
+ //#endregion
18
89
  //#region src/config.ts
19
90
  const configFileNames = [
20
91
  "x18n.config.ts",
@@ -43,6 +114,15 @@ function findConfigUpward(cwd) {
43
114
  dir = parent;
44
115
  }
45
116
  }
117
+ /** One config file per directory below `dir`, shallowest first. */
118
+ async function findConfigsBelow(dir) {
119
+ const files = await glob(configFileNames.map((name) => `**/${name}`), {
120
+ cwd: dir,
121
+ absolute: true,
122
+ ignore: ["**/node_modules/**"]
123
+ });
124
+ return [...new Set(files.map((file) => dirname(file)))].sort((a, b) => a.split("/").length - b.split("/").length || a.localeCompare(b)).flatMap((found) => findConfigFile(found) ?? []);
125
+ }
46
126
  function resolveOwn(specifier) {
47
127
  try {
48
128
  return fileURLToPath(import.meta.resolve(specifier));
@@ -153,7 +233,9 @@ async function manifestRootsOf(root, gitRoot) {
153
233
  }
154
234
  //#endregion
155
235
  //#region src/host.ts
156
- const toPosix = (path) => sep === "\\" ? path.replace(/\\/g, "/") : path;
236
+ function toPosix(path) {
237
+ return sep === "\\" ? path.replace(/\\/g, "/") : path;
238
+ }
157
239
  function isNotFound(error) {
158
240
  return error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "EISDIR");
159
241
  }
@@ -719,10 +801,17 @@ const keygenStyles = {
719
801
  "kebab-case": "kebab"
720
802
  };
721
803
  const editorSettings = {
722
- annotations: "x18n.annotations",
723
- annotationInPlace: "x18n.inPlace",
724
- annotationMaxLength: "x18n.annotationMaxLength",
725
- displayLanguage: "x18n.displayLocale"
804
+ "annotations": "x18n.annotations",
805
+ "annotationInPlace": "x18n.annotationInPlace",
806
+ "annotationInPlaceFullMatch": "x18n.annotationInPlaceFullMatch",
807
+ "annotationMaxLength": "x18n.annotationMaxLength",
808
+ "displayLanguage": "x18n.displayLocale",
809
+ "showFlags": "x18n.showFlags",
810
+ "theme.annotation": "x18n.theme.annotation",
811
+ "theme.annotationMissing": "x18n.theme.annotationMissing",
812
+ "theme.annotationBorder": "x18n.theme.annotationBorder",
813
+ "theme.annotationMissingBorder": "x18n.theme.annotationMissingBorder",
814
+ "theme.annotationInPlaceFullMatch": "x18n.theme.annotationInPlaceFullMatch"
726
815
  };
727
816
  const handledByDefaults = [
728
817
  "readonly",
@@ -731,7 +820,6 @@ const handledByDefaults = [
731
820
  "includeSubfolders",
732
821
  "ignoreFiles",
733
822
  "editor.preferEditor",
734
- "showFlags",
735
823
  "annotationDelimiter",
736
824
  "languageTagSystem",
737
825
  "targetPickingStrategy",
@@ -753,7 +841,9 @@ async function readFirst(root, paths) {
753
841
  function isRecord(value) {
754
842
  return typeof value === "object" && value !== null && !Array.isArray(value);
755
843
  }
756
- const asStrings = (value) => (Array.isArray(value) ? value : value === void 0 ? [] : [value]).filter((v) => typeof v === "string");
844
+ function asStrings(value) {
845
+ return (Array.isArray(value) ? value : value === void 0 ? [] : [value]).filter((v) => typeof v === "string");
846
+ }
757
847
  /** Setting prefixes by priority: the i18n-ally-next fork renames `i18n-ally.*` keys, so its value is the newest. */
758
848
  const settingPrefixes = [
759
849
  "i18n-ally-next",
@@ -1121,6 +1211,15 @@ function applyDefaults(draft, config) {
1121
1211
  }
1122
1212
  async function projectSpecs(rootConfigFile, cwd, upward) {
1123
1213
  if (!rootConfigFile) {
1214
+ const nested = await findConfigsBelow(cwd);
1215
+ if (nested.length) {
1216
+ const specs = [];
1217
+ for (const file of nested) if (!specs.some((spec) => spec.configFile === file)) specs.push(...(await projectSpecs(file, cwd, false)).specs);
1218
+ return {
1219
+ root: resolve(cwd),
1220
+ specs
1221
+ };
1222
+ }
1124
1223
  const settingsRoot = findI18nAllyRoot(cwd, upward);
1125
1224
  const fallback = settingsRoot && await i18nAllyFallback(settingsRoot);
1126
1225
  if (settingsRoot && fallback) return {
@@ -1173,7 +1272,7 @@ async function loadWorkspace(options) {
1173
1272
  const configFile = options.configFile ? resolve(options.cwd, options.configFile) : options.upward === false ? findConfigFile(resolve(options.cwd)) : findConfigUpward(options.cwd);
1174
1273
  const { root, specs } = await projectSpecs(configFile, options.cwd, options.upward !== false);
1175
1274
  const gitRoot = findGitRoot(root);
1176
- const defaults = options.builtins ?? createBuiltins();
1275
+ const defaults = options.builtins ?? await createBuiltins();
1177
1276
  const builtins = gitRoot ? {
1178
1277
  ...defaults,
1179
1278
  plugins: [...defaults.plugins, gitHistory()]
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@x18n/node",
3
3
  "type": "module",
4
- "version": "0.0.12",
4
+ "version": "0.0.14",
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.12",
29
- "@x18n/core": "0.0.12",
28
+ "@x18n/builtins": "0.0.14",
29
+ "@x18n/core": "0.0.14",
30
30
  "chokidar": "^6.0.0",
31
31
  "ignore": "^7.0.13",
32
32
  "jiti": "^2.7.0",