wuchale 0.23.4 → 0.24.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 (46) hide show
  1. package/dist/adapter-utils/mixed-visitor.d.ts +21 -14
  2. package/dist/adapter-utils/mixed-visitor.js +110 -101
  3. package/dist/adapter-vanilla/index.js +8 -6
  4. package/dist/adapter-vanilla/inertvisitors.d.ts +31 -0
  5. package/dist/adapter-vanilla/inertvisitors.js +86 -0
  6. package/dist/adapter-vanilla/transformer.d.ts +67 -95
  7. package/dist/adapter-vanilla/transformer.js +288 -241
  8. package/dist/adapters.d.ts +16 -12
  9. package/dist/adapters.js +33 -13
  10. package/dist/ai/gemini.js +1 -1
  11. package/dist/ai/index.js +3 -1
  12. package/dist/bundlers/vite.d.ts +1 -1
  13. package/dist/bundlers/vite.js +3 -3
  14. package/dist/cli/check.js +1 -1
  15. package/dist/cli/index.js +12 -5
  16. package/dist/cli/status.js +1 -1
  17. package/dist/config.d.ts +2 -1
  18. package/dist/config.js +2 -2
  19. package/dist/handler/files.d.ts +11 -13
  20. package/dist/handler/files.js +37 -44
  21. package/dist/handler/index.d.ts +7 -4
  22. package/dist/handler/index.js +85 -62
  23. package/dist/handler/state.d.ts +10 -11
  24. package/dist/handler/state.js +40 -28
  25. package/dist/handler/url.d.ts +10 -13
  26. package/dist/handler/url.js +51 -80
  27. package/dist/hub.d.ts +1 -1
  28. package/dist/hub.js +16 -14
  29. package/dist/index.d.ts +3 -3
  30. package/dist/index.js +2 -2
  31. package/dist/load-utils/index.d.ts +6 -8
  32. package/dist/load-utils/index.js +11 -11
  33. package/dist/load-utils/pure.d.ts +2 -2
  34. package/dist/load-utils/server.d.ts +3 -3
  35. package/dist/load-utils/server.js +4 -7
  36. package/dist/pofile.d.ts +5 -5
  37. package/dist/pofile.js +23 -35
  38. package/dist/storage.d.ts +6 -3
  39. package/dist/storage.js +98 -0
  40. package/dist/url.d.ts +12 -10
  41. package/dist/url.js +206 -31
  42. package/package.json +4 -5
  43. package/src/adapter-vanilla/loaders/bundle.js +1 -1
  44. package/src/adapter-vanilla/loaders/server.js +3 -3
  45. package/src/adapter-vanilla/loaders/vite.js +2 -2
  46. package/src/adapter-vanilla/loaders/vite.ssr.js +3 -3
@@ -1,34 +1,12 @@
1
- import { compile as compileUrlPattern, match as matchUrlPattern, pathToRegexp, stringify, } from 'path-to-regexp';
1
+ import { isDeepStrictEqual } from 'node:util';
2
2
  import { getKey } from '../adapters.js';
3
- import { compileTranslation } from '../compile.js';
4
3
  import { newItem } from '../storage.js';
5
- export function patternFromTranslate(patternTranslated, keys) {
6
- const compiledTranslatedPatt = compileTranslation(patternTranslated, patternTranslated);
7
- if (typeof compiledTranslatedPatt === 'string') {
8
- return compiledTranslatedPatt;
9
- }
10
- const urlTokens = compiledTranslatedPatt.map(part => {
11
- if (typeof part === 'number') {
12
- return keys[part];
13
- }
14
- return { type: 'text', value: part };
15
- });
16
- return stringify({ tokens: urlTokens });
17
- }
18
- export function patternToTranslate(pattern) {
19
- const { keys } = pathToRegexp(pattern);
20
- const compile = compileUrlPattern(pattern, { encode: false });
21
- const paramsReplace = {};
22
- for (const [i, { name }] of keys.entries()) {
23
- paramsReplace[name] = `{${i}}`;
24
- }
25
- return compile(paramsReplace);
26
- }
4
+ import { compilePattern, matchPattern, stringifyPattern } from '../url.js';
27
5
  export class URLHandler {
28
- patternKeys = new Map();
29
6
  locales;
30
7
  sourceLocale;
31
8
  patterns = [];
9
+ compiledPatterns = [];
32
10
  constructor(locales, sourceLocale, urlConf) {
33
11
  this.locales = locales;
34
12
  this.sourceLocale = sourceLocale;
@@ -36,43 +14,33 @@ export class URLHandler {
36
14
  this.patterns = urlConf.patterns;
37
15
  }
38
16
  }
39
- buildManifest = (catalog) =>
40
- // order of catalogs should be based on locales
41
- this.patterns.map(patt => {
42
- const catalogPattKey = this.patternKeys.get(patt);
43
- const { keys } = pathToRegexp(patt);
44
- const locPatterns = [];
45
- const item = catalog.get(catalogPattKey);
46
- for (const loc of this.locales) {
47
- let pattern = patt;
48
- const transl = item?.translations?.get(loc);
49
- if (transl) {
50
- const patternTranslated = transl[0] || item.translations.get(this.sourceLocale)[0];
51
- pattern = patternFromTranslate(patternTranslated, keys);
17
+ buildManifest = () => {
18
+ // order of catalogs should be based on locales
19
+ const manifest = [];
20
+ for (let i = 0; i < this.patterns.length; i++) {
21
+ const locPatterns = [];
22
+ const compiledPatts = this.compiledPatterns[i];
23
+ const compiledPattBase = compiledPatts.get(this.sourceLocale);
24
+ for (const loc of this.locales) {
25
+ const locCompiledPatt = compiledPatts.get(loc);
26
+ locPatterns.push(locCompiledPatt);
52
27
  }
53
- locPatterns.push(pattern);
54
- }
55
- if (locPatterns.some(p => p !== patt)) {
56
- return [patt, locPatterns];
28
+ const notAllSame = locPatterns.some(p => !isDeepStrictEqual(p, compiledPattBase));
29
+ manifest.push(notAllSame ? [compiledPattBase, locPatterns] : [compiledPattBase]);
57
30
  }
58
- return [patt];
59
- });
60
- initPatterns = async (adapterKey, catalog, aiQueue) => {
61
- const urlPatternsForTranslate = this.patterns.map(patternToTranslate);
31
+ return manifest;
32
+ };
33
+ initPatterns = async (adapterKey, catalog, fallbackChains, aiQueue) => {
62
34
  const urlPatternCatKeys = [];
63
35
  const toTranslate = [];
64
36
  let needWriteCatalog = false;
65
- for (const [i, locPattern] of urlPatternsForTranslate.entries()) {
66
- let context;
67
- if (locPattern !== this.patterns[i]) {
68
- context = `original: ${this.patterns[i]}`;
69
- }
70
- const key = getKey([locPattern], context);
37
+ const toCompile = [];
38
+ for (const [i, pattern] of this.patterns.entries()) {
39
+ const key = getKey([pattern]);
71
40
  urlPatternCatKeys[i] = key;
72
- this.patternKeys.set(this.patterns[i], key); // save for href translate
73
41
  let item = catalog.get(key);
74
42
  if (!item) {
75
- item = newItem({ id: [locPattern], context }, this.locales);
43
+ item = newItem({ id: [pattern] }, this.locales);
76
44
  catalog.set(key, item);
77
45
  needWriteCatalog = true;
78
46
  }
@@ -80,11 +48,12 @@ export class URLHandler {
80
48
  item.urlAdapters.push(adapterKey);
81
49
  needWriteCatalog = true;
82
50
  }
83
- item.translations.set(this.sourceLocale, [locPattern]);
84
- if (locPattern.search(/\p{L}/u) === -1) {
51
+ item.translations.set(this.sourceLocale, [pattern]);
52
+ toCompile.push(item);
53
+ if (pattern.search(/\p{L}/u) === -1) {
85
54
  for (const loc of this.locales) {
86
55
  if (loc !== this.sourceLocale) {
87
- item.translations.set(loc, [locPattern]);
56
+ item.translations.set(loc, [pattern]);
88
57
  }
89
58
  }
90
59
  continue;
@@ -103,41 +72,43 @@ export class URLHandler {
103
72
  aiQueue.add(toTranslate);
104
73
  await aiQueue.running;
105
74
  }
75
+ // for matching hrefs
76
+ for (const item of toCompile) {
77
+ const compiled = new Map();
78
+ for (const locale of this.locales) {
79
+ for (const loc of fallbackChains.get(locale) ?? [locale, this.sourceLocale]) {
80
+ const pattern = item.translations.get(loc)?.[0];
81
+ if (pattern) {
82
+ compiled.set(locale, compilePattern(pattern));
83
+ break;
84
+ }
85
+ }
86
+ }
87
+ this.compiledPatterns.push(compiled);
88
+ }
106
89
  return needWriteCatalog;
107
90
  };
108
91
  match = (url) => {
109
- for (const pattern of this.patterns) {
110
- if (matchUrlPattern(pattern, { decode: false })(url)) {
111
- return pattern;
92
+ for (const [i, pattern] of this.compiledPatterns.entries()) {
93
+ const dynamics = matchPattern(pattern.get(this.sourceLocale), url);
94
+ if (dynamics) {
95
+ return [i, dynamics];
112
96
  }
113
97
  }
114
98
  return null;
115
99
  };
116
- matchToCompile = (key, catalog, locale) => {
100
+ matchToCompile = (key, locale) => {
117
101
  // e.g. key: /items/foo/{0}
118
102
  const toCompile = key;
119
103
  const relevantPattern = this.match(key);
120
104
  if (relevantPattern == null) {
121
105
  return toCompile;
122
106
  }
123
- // e.g. relevantPattern: /items/:rest
124
- const patternItem = catalog.get(this.patternKeys.get(relevantPattern));
125
- if (!patternItem) {
126
- return toCompile;
127
- }
128
- // e.g. patternItem.id: /items/{0}
129
- const matchedUrl = matchUrlPattern(relevantPattern, { decode: false })(key);
130
- // e.g. matchUrl.params: {rest: 'foo/{0}'}
131
- if (!matchedUrl) {
132
- return toCompile;
133
- }
134
- const translatedPattern = patternItem.translations.get(locale)[0] || patternItem.translations.get(this.sourceLocale)[0];
135
- // e.g. translatedPattern: /elementos/{0}
136
- const { keys } = pathToRegexp(relevantPattern);
137
- const translatedPattUrl = patternFromTranslate(translatedPattern, keys);
138
- // e.g. translatedPattUrl: /elementos/:rest
139
- const compileTranslated = compileUrlPattern(translatedPattUrl, { encode: false });
140
- return compileTranslated(matchedUrl.params);
141
- // e.g. return /elementos/foo/{0}
107
+ // e.g. relevantPattern: [index of /items/**, [foo/{0}]]
108
+ const [i, dynamics] = relevantPattern;
109
+ const translatedCompiled = this.compiledPatterns[i].get(locale);
110
+ // e.g. translatedCompiled: [/elementos, 0]
111
+ return stringifyPattern(translatedCompiled, dynamics);
112
+ // e.g. /elementos/foo/{0}
142
113
  };
143
114
  }
package/dist/hub.d.ts CHANGED
@@ -47,7 +47,7 @@ type TransformErrFormatter = (e: Error, adapterKey: string, filename: string) =>
47
47
  export declare class Hub {
48
48
  #private;
49
49
  private constructor();
50
- static create: (mode: Mode, loadConfig: ConfigLoader, root: string, hmrDelayThreshold?: number, fs?: FS, formatTransformErr?: TransformErrFormatter) => Promise<Hub>;
50
+ static create: (mode: Mode, loadConfig: ConfigLoader, root: string, modifyAdapters?: string[], hmrDelayThreshold?: number, fs?: FS, formatTransformErr?: TransformErrFormatter) => Promise<Hub>;
51
51
  onFileChange: (file: string, read: () => string | Promise<string>) => Promise<FileChangeInfo | undefined>;
52
52
  transform: (code: string, filePath: string, forServer?: boolean) => ReturnType<AdapterHandler["transform"]>;
53
53
  directVisit(clean: boolean, watch: boolean, sync: boolean): Promise<void>;
package/dist/hub.js CHANGED
@@ -7,7 +7,7 @@ import { glob } from 'tinyglobby';
7
7
  import { compileTranslation, isEquivalent } from './compile.js';
8
8
  import { defaultFS } from './fs.js';
9
9
  import { dataFileName, generatedDir, getLoaderPath, globConfToArgs, normalizeSep } from './handler/files.js';
10
- import { AdapterHandler, getLoadIDs } from './handler/index.js';
10
+ import { AdapterHandler, getLoadIDs, newItemsAllowed } from './handler/index.js';
11
11
  import { SharedState } from './handler/state.js';
12
12
  import { color, Logger } from './log.js';
13
13
  import { itemIsObsolete, itemIsUrl } from './storage.js';
@@ -25,22 +25,21 @@ async function initGenDirWithData(config, fs, root) {
25
25
  // data file
26
26
  await fs.write(resolve(localesDirAbs, dataFileName), [
27
27
  `/** @typedef {('${config.locales.join("'|'")}')} Locale */`,
28
- `/** @type {Locale[]} */`,
28
+ `/** @type {[Locale, ...Locale[]]} */`,
29
29
  `export const locales = ['${config.locales.join("','")}']`,
30
30
  ].join('\n'));
31
31
  }
32
- async function getSharedState(sharedStates, config, fs, root, adapter, key, sourceLocale) {
32
+ async function getSharedState(sharedStates, config, fs, root, adapter, key, sourceLocale, allowNewItems) {
33
33
  const storage = await adapter.storage({
34
34
  locales: config.locales,
35
35
  root,
36
36
  localesDir: config.localesDir,
37
37
  sourceLocale: sourceLocale,
38
- haveUrl: adapter.url != null,
39
38
  fs,
40
39
  });
41
40
  let sharedState = sharedStates.get(storage.key);
42
41
  if (sharedState == null) {
43
- sharedState = new SharedState(storage, key, sourceLocale);
42
+ sharedState = new SharedState(storage, key, sourceLocale, allowNewItems);
44
43
  sharedStates.set(storage.key, sharedState);
45
44
  }
46
45
  else {
@@ -69,7 +68,7 @@ export class Hub {
69
68
  this.#lastSourceTriggeredCatalogWrite = performance.now();
70
69
  };
71
70
  const adapter = handler.adapter;
72
- if (adapter.granularLoad) {
71
+ if (adapter.loading.granular) {
73
72
  this.#granularLoadHandlers.push(handler);
74
73
  }
75
74
  else {
@@ -106,7 +105,7 @@ export class Hub {
106
105
  }
107
106
  }
108
107
  }
109
- static create = async (mode, loadConfig, root, hmrDelayThreshold = 1000, fs = defaultFS, formatTransformErr = e => e) => {
108
+ static create = async (mode, loadConfig, root, modifyAdapters = [], hmrDelayThreshold = 1000, fs = defaultFS, formatTransformErr = e => e) => {
110
109
  const config = await loadConfig();
111
110
  const adaptersData = Object.entries(config.adapters);
112
111
  if (adaptersData.length === 0) {
@@ -124,7 +123,9 @@ export class Hub {
124
123
  adapter,
125
124
  key,
126
125
  sourceLocale,
127
- sharedState: await getSharedState(sharedStates, config, fs, root, adapter, key, sourceLocale),
126
+ sharedState: await getSharedState(sharedStates, config, fs, root, adapter, key, sourceLocale, newItemsAllowed(mode, config.dev)),
127
+ devMode: config.dev,
128
+ modifyInplace: modifyAdapters.includes(key),
128
129
  });
129
130
  handlers.set(key, handler);
130
131
  }
@@ -144,12 +145,12 @@ export class Hub {
144
145
  const updateTxt = await read();
145
146
  const update = JSON.parse(updateTxt);
146
147
  this.#opts.log.info(`${logPrefix} config update received: ${color.cyan(updateTxt)}`);
147
- if (update.hmr !== undefined) {
148
- this.#opts.config.hmr = update.hmr;
148
+ if (update.dev !== undefined) {
149
+ this.#opts.config.dev = update.dev;
149
150
  }
150
151
  return ignoreChange;
151
152
  }
152
- if (!this.#opts.config.hmr) {
153
+ if (!this.#opts.config.dev) {
153
154
  return;
154
155
  }
155
156
  // This is mainly to make sure that catalog file changes result in a page reload with new catalogs
@@ -183,7 +184,7 @@ export class Hub {
183
184
  await handler.loadStorage();
184
185
  await handler.compile(this.#hmrVersion);
185
186
  }
186
- const [loadIDs] = getLoadIDs(handler.adapter, handler.key, handler.sharedState.ownerKey, handler.granularState.byID.values(), handler.sourceLocale);
187
+ const loadIDs = getLoadIDs(handler.adapter, handler.granularState.byID.values(), handler.sourceLocale);
187
188
  for (const loc of this.#opts.config.locales) {
188
189
  for (const loadID of loadIDs) {
189
190
  changeInfo.invalidate.add(normalizeSep(handler.files.getCompiledFilePath(loc, loadID)));
@@ -193,7 +194,7 @@ export class Hub {
193
194
  return changeInfo;
194
195
  };
195
196
  transform = async (code, filePath, forServer = false) => {
196
- if (this.#opts.mode === 'dev' && !this.#opts.config.hmr) {
197
+ if (this.#opts.mode === 'dev' && !this.#opts.config.dev) {
197
198
  return [{}, false];
198
199
  }
199
200
  const filename = normalizeSep(relative(this.#opts.root, filePath));
@@ -224,7 +225,8 @@ export class Hub {
224
225
  return updated;
225
226
  };
226
227
  async #directVisitHandler(handler, clean, sync, existingFilesByOwner) {
227
- const filePaths = await glob(...globConfToArgs(handler.adapter.files, this.#opts.root, this.#opts.config.localesDir, handler.adapter.outDir));
228
+ const [patterns, ignore] = globConfToArgs(handler.adapter.files, this.#opts.config.localesDir);
229
+ const filePaths = await glob(patterns, { ignore, cwd: this.#opts.root });
228
230
  let existingFiles = existingFilesByOwner.get(handler.sharedState.ownerKey);
229
231
  if (existingFiles) {
230
232
  for (const file of filePaths) {
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- export type { Adapter, AdapterArgs, AdapterPassThruOpts, CodePattern, CreateHeuristicOpts, DecideReactiveDetails, HeuristicDetails, HeuristicDetailsBase, HeuristicFunc, HeuristicResult, LoaderChoice, Message, MessageType, RuntimeConf, RuntimeExpr as CatalogExpr, TransformOutput, TransformOutputCode, URLConf, UrlMatcher, } from './adapters.js';
2
- export { createHeuristic, defaultGenerateLoadID, defaultHeuristic, defaultHeuristicOpts, getKey, IndexTracker, } from './adapters.js';
1
+ export type { Adapter, AdapterArgs, AdapterPassThruOpts, CodePattern, CreateHeuristicOpts, DecideReactiveDetails, HeuristicDetails, HeuristicDetailsBase, HeuristicFunc, HeuristicResult, LoaderChoice, LoadGroupPatt, Message, MessageType, RuntimeConf, RuntimeExpr, TransformCtx, TransformOutput, TransformOutputCode, URLConf, UrlMatcher, } from './adapters.js';
2
+ export { createHeuristic, defaultHeuristic, defaultHeuristicOpts, getKey, IndexTracker, } from './adapters.js';
3
3
  export { gemini } from './ai/gemini.js';
4
4
  export type { CompiledElement, Composite, CompositePayload, Mixed, } from './compile.js';
5
5
  export { type Config, type DeepPartial, defaultConfig, defineConfig, fillDefaults, getConfig, } from './config.js';
@@ -12,4 +12,4 @@ export { Hub } from './hub.js';
12
12
  export { Logger } from './log.js';
13
13
  export { pofile } from './pofile.js';
14
14
  export type { Catalog, CatalogStorage, FileRef, FileRefEntry, Item, LoadData, PluralRule, PluralRules, SaveData, StorageFactory, StorageFactoryOpts, } from './storage.js';
15
- export { defaultPluralRule, migrateStorage } from './storage.js';
15
+ export { defaultPluralRule, mergeItemsByKey, migrateStorage, storageByLocale, storageByType } from './storage.js';
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export { createHeuristic, defaultGenerateLoadID, defaultHeuristic, defaultHeuristicOpts, getKey, IndexTracker, } from './adapters.js';
1
+ export { createHeuristic, defaultHeuristic, defaultHeuristicOpts, getKey, IndexTracker, } from './adapters.js';
2
2
  export { gemini } from './ai/gemini.js';
3
3
  export { defaultConfig, defineConfig, fillDefaults, getConfig, } from './config.js';
4
4
  export { generatedDir, normalizeSep } from './handler/files.js';
@@ -7,4 +7,4 @@ export { URLHandler } from './handler/url.js';
7
7
  export { Hub } from './hub.js';
8
8
  export { Logger } from './log.js';
9
9
  export { pofile } from './pofile.js';
10
- export { defaultPluralRule, migrateStorage } from './storage.js';
10
+ export { defaultPluralRule, mergeItemsByKey, migrateStorage, storageByLocale, storageByType } from './storage.js';
@@ -1,22 +1,20 @@
1
1
  import { type CatalogModule, type Runtime } from '../runtime.js';
2
- export type LoaderFunc = (loadID: string, locale: string) => CatalogModule | Promise<CatalogModule>;
2
+ export type LoaderFunc = (loadID: number, locale: string) => CatalogModule | Promise<CatalogModule>;
3
3
  export type RuntimeCollection = {
4
- get: (loadID: string) => Runtime;
5
- set: (loadID: string, catalog: Runtime) => void;
4
+ get: (loadID: number) => Runtime;
5
+ set: (loadID: number, runtime: Runtime) => void;
6
6
  };
7
7
  export type LoaderState = {
8
8
  load: LoaderFunc;
9
- catalogs: {
10
- [loadID: string]: CatalogModule | undefined;
11
- };
9
+ catalogs: (CatalogModule | undefined)[];
12
10
  collection: RuntimeCollection;
13
11
  };
14
- export declare function defaultCollection(store: Record<string, Runtime>): RuntimeCollection;
12
+ export declare function defaultCollection(store: Runtime[]): RuntimeCollection;
15
13
  /**
16
14
  * - `key` is a unique identifier for the group
17
15
  * - `loadIDs` and `load` MUST be imported from the loader virtual modules or proxies.
18
16
  */
19
- export declare function registerLoaders(key: string, load: LoaderFunc, loadIDs: string[], collection?: RuntimeCollection): (fileID: string) => Runtime;
17
+ export declare function registerLoaders(key: string, load: LoaderFunc, loadCount: number, collection?: RuntimeCollection): (loadID?: number) => Runtime;
20
18
  export declare function commitLocale(locale: string): void;
21
19
  /**
22
20
  * Loads catalogs using registered async loaders.
@@ -14,21 +14,21 @@ const emptyRuntime = toRuntime();
14
14
  * - `key` is a unique identifier for the group
15
15
  * - `loadIDs` and `load` MUST be imported from the loader virtual modules or proxies.
16
16
  */
17
- export function registerLoaders(key, load, loadIDs, collection) {
17
+ export function registerLoaders(key, load, loadCount, collection) {
18
18
  states[key] = {
19
19
  load,
20
- catalogs: Object.fromEntries(loadIDs.map(id => [id])),
21
- collection: collection ?? defaultCollection({}),
20
+ catalogs: Array(loadCount).fill(undefined),
21
+ collection: collection ?? defaultCollection([]),
22
22
  };
23
- for (const id of loadIDs) {
23
+ for (let id = 0; id < loadCount; id++) {
24
24
  states[key].collection.set(id, emptyRuntime);
25
25
  }
26
- return loadID => states[key].collection.get(loadID);
26
+ return (loadID = 0) => states[key].collection.get(loadID);
27
27
  }
28
28
  /* Sets the most recently loaded locale as the current one */
29
29
  export function commitLocale(locale) {
30
30
  for (const state of Object.values(states)) {
31
- for (const [loadID, catalog] of Object.entries(state.catalogs)) {
31
+ for (const [loadID, catalog] of state.catalogs.entries()) {
32
32
  state.collection.set(loadID, toRuntime(catalog, locale));
33
33
  }
34
34
  }
@@ -42,9 +42,9 @@ export async function loadLocale(locale, commit = true) {
42
42
  const promises = [];
43
43
  const statesArr = [];
44
44
  for (const state of Object.values(states)) {
45
- for (const loadID of Object.keys(state.catalogs)) {
46
- promises.push(state.load(loadID, locale));
47
- statesArr.push([loadID, state]);
45
+ for (let id = 0; id < state.catalogs.length; id++) {
46
+ promises.push(state.load(id, locale));
47
+ statesArr.push([id, state]);
48
48
  }
49
49
  }
50
50
  for (const [i, loaded] of (await Promise.all(promises)).entries()) {
@@ -61,8 +61,8 @@ export async function loadLocale(locale, commit = true) {
61
61
  */
62
62
  export function loadLocaleSync(locale, commit = true) {
63
63
  for (const state of Object.values(states)) {
64
- for (const loadID of Object.keys(state.catalogs)) {
65
- state.catalogs[loadID] = state.load(loadID, locale);
64
+ for (let id = 0; id < state.catalogs.length; id++) {
65
+ state.catalogs[id] = state.load(id, locale);
66
66
  }
67
67
  }
68
68
  commit && commitLocale(locale);
@@ -1,5 +1,5 @@
1
1
  import type { CatalogModule } from '../runtime.js';
2
2
  import type { LoaderFunc } from './index.js';
3
- export type CatalogsByID = Record<string, CatalogModule>;
3
+ export type CatalogsByID = Record<number, CatalogModule>;
4
4
  /** No-side effect way to load catalogs. Can be used for multiple file IDs. */
5
- export declare function loadCatalogs(locale: string, loadIDs: string[], loadCatalog: LoaderFunc): Promise<CatalogsByID>;
5
+ export declare function loadCatalogs(locale: string, loadIDs: number[], loadCatalog: LoaderFunc): Promise<CatalogsByID>;
@@ -1,9 +1,9 @@
1
1
  import { AsyncLocalStorage } from 'node:async_hooks';
2
2
  import { type Runtime } from '../runtime.js';
3
3
  import type { LoaderFunc } from './index.js';
4
- type LoadedRuntimes = Record<string, Record<string, Runtime>>;
4
+ type LoadedRuntimes = Record<string, Runtime[]>;
5
5
  export declare const runtimeCtx: AsyncLocalStorage<LoadedRuntimes>;
6
- export declare function currentRuntime(key: string, loadID: string): Runtime;
7
- export declare function loadLocales(key: string, loadIDs: string[], load: LoaderFunc, locales: string[]): Promise<(loadID: string) => Runtime>;
6
+ export declare function currentRuntime(key: string, loadID: number): Runtime;
7
+ export declare function loadLocales(key: string, loadCount: number, load: LoaderFunc, locales: string[]): Promise<(loadID?: number) => Runtime>;
8
8
  export declare function runWithLocale<T>(locale: string, func: () => T): Promise<T>;
9
9
  export {};
@@ -19,23 +19,20 @@ export function currentRuntime(key, loadID) {
19
19
  warningShown[warnKey] = true;
20
20
  return emptyRuntime;
21
21
  }
22
- export async function loadLocales(key, loadIDs, load, locales) {
23
- if (loadIDs == null) {
24
- loadIDs = [key];
25
- }
22
+ export async function loadLocales(key, loadCount, load, locales) {
26
23
  for (const locale of locales) {
27
24
  if (!(locale in runtimes)) {
28
25
  runtimes[locale] = {};
29
26
  }
30
27
  const loaded = runtimes[locale];
31
28
  if (!(key in loaded)) {
32
- loaded[key] = {};
29
+ loaded[key] = [];
33
30
  }
34
- for (const id of loadIDs) {
31
+ for (let id = 0; id < loadCount; id++) {
35
32
  loaded[key][id] = toRuntime(await load(id, locale), locale);
36
33
  }
37
34
  }
38
- return (loadID) => currentRuntime(key, loadID);
35
+ return (loadID = 0) => currentRuntime(key, loadID);
39
36
  }
40
37
  export async function runWithLocale(locale, func) {
41
38
  return await runtimeCtx.run(runtimes[locale], func);
package/dist/pofile.d.ts CHANGED
@@ -2,21 +2,21 @@ import PO from 'pofile';
2
2
  import { type PluralRule, type SaveData, type StorageFactory, type StorageFactoryOpts } from './storage.js';
3
3
  export type POItem = InstanceType<typeof PO.Item>;
4
4
  export type POFileOptions = {
5
- dir: string;
6
- separateUrls: boolean;
5
+ /** in the form like 'path/to/dir/{locale}.po' */
6
+ location: string;
7
7
  };
8
8
  export declare const defaultOpts: POFileOptions;
9
9
  type POHeaders = Record<string, string | undefined>;
10
10
  export declare class POFile {
11
11
  key: string;
12
12
  opts: StorageFactoryOpts & POFileOptions;
13
- filesByLoc: Map<string, [string, string]>;
13
+ filesByLoc: Map<string, string>;
14
14
  files: string[];
15
15
  fileExistsCache: Map<string, boolean>;
16
16
  constructor(opts: StorageFactoryOpts & POFileOptions);
17
- loadRaw(locale: string, url: boolean): Promise<PO | null>;
17
+ loadRaw(locale: string): Promise<PO | null>;
18
18
  load(): Promise<SaveData>;
19
- saveRaw(items: POItem[], headers: POHeaders, locale: string, url: boolean): Promise<void>;
19
+ saveRaw(items: POItem[], headers: POHeaders, locale: string): Promise<void>;
20
20
  save(data: SaveData): Promise<void>;
21
21
  getHeaders(locale: string, pluralRule: PluralRule): POHeaders;
22
22
  }
package/dist/pofile.js CHANGED
@@ -1,8 +1,8 @@
1
- import { resolve } from 'node:path';
1
+ import { dirname, resolve } from 'node:path';
2
2
  import PO from 'pofile';
3
3
  import { getKey } from './adapters.js';
4
4
  import { fillDefaults } from './config.js';
5
- import { itemIsObsolete, itemIsUrl, } from './storage.js';
5
+ import { itemIsObsolete, } from './storage.js';
6
6
  const urlAdapterFlagPrefix = 'url:';
7
7
  function join(parts, sep) {
8
8
  return parts.map(s => s.replaceAll('\\', '\\\\').replaceAll(sep, `\\${sep}`)).join(sep);
@@ -79,7 +79,7 @@ function poitemToItemCommons(poi) {
79
79
  }
80
80
  for (const c of commSp.slice(phStart)) {
81
81
  const [i, ph] = split(c, ': ', 2);
82
- refEnt.placeholders.push([Number(i), ph]);
82
+ refEnt.placeholders.push([i, ph]);
83
83
  }
84
84
  lastRef.refs.push(refEnt);
85
85
  }
@@ -124,8 +124,7 @@ function poitemsToItems(poItems, locales, sourceLocale) {
124
124
  return items;
125
125
  }
126
126
  export const defaultOpts = {
127
- dir: 'src/locales',
128
- separateUrls: true,
127
+ location: 'src/locales/{locale}.po',
129
128
  };
130
129
  export class POFile {
131
130
  key;
@@ -135,16 +134,16 @@ export class POFile {
135
134
  fileExistsCache = new Map();
136
135
  constructor(opts) {
137
136
  this.opts = opts;
138
- opts.dir = resolve(opts.root, opts.dir);
139
- this.key = opts.dir;
137
+ opts.location = resolve(opts.root, opts.location);
138
+ this.key = opts.location;
140
139
  for (const locale of opts.locales) {
141
- const locFiles = [resolve(opts.dir, `${locale}.po`), resolve(opts.dir, `${locale}.url.po`)];
142
- this.filesByLoc.set(locale, locFiles);
143
- this.files.push(...locFiles);
140
+ const location = opts.location.replace('{locale}', locale);
141
+ this.filesByLoc.set(locale, location);
142
+ this.files.push(location);
144
143
  }
145
144
  }
146
- async loadRaw(locale, url) {
147
- const filename = this.filesByLoc.get(locale)[Number(url)];
145
+ async loadRaw(locale) {
146
+ const filename = this.filesByLoc.get(locale);
148
147
  const content = await this.opts.fs.read(filename);
149
148
  this.fileExistsCache.set(filename, content != null);
150
149
  return content == null ? null : PO.parse(content);
@@ -155,7 +154,7 @@ export class POFile {
155
154
  const poItems = new Map();
156
155
  // first, group by key
157
156
  for (const locale of this.opts.locales) {
158
- const po = await this.loadRaw(locale, false);
157
+ const po = await this.loadRaw(locale);
159
158
  if (po == null) {
160
159
  continue;
161
160
  }
@@ -165,10 +164,6 @@ export class POFile {
165
164
  pluralRule.nplurals = Number(pluralRule.nplurals);
166
165
  pluralRules.set(locale, pluralRule);
167
166
  }
168
- if (this.opts.separateUrls && this.opts.haveUrl) {
169
- const poUrl = await this.loadRaw(locale, true);
170
- poUrl && po.items.push(...poUrl.items);
171
- }
172
167
  for (const poItem of po.items) {
173
168
  const key = getKey(getItemId(poItem), poItem.msgctxt);
174
169
  if (!poItems.has(key)) {
@@ -182,8 +177,8 @@ export class POFile {
182
177
  pluralRules,
183
178
  };
184
179
  }
185
- async saveRaw(items, headers, locale, url) {
186
- const filename = this.filesByLoc.get(locale)[Number(url)];
180
+ async saveRaw(items, headers, locale) {
181
+ const filename = this.filesByLoc.get(locale);
187
182
  if (items.length === 0) {
188
183
  if (this.fileExistsCache.get(filename) === false) {
189
184
  return;
@@ -199,25 +194,14 @@ export class POFile {
199
194
  this.fileExistsCache.set(filename, true);
200
195
  }
201
196
  async save(data) {
202
- const useSeparateUrls = this.opts.separateUrls && this.opts.haveUrl;
203
- await Promise.all(this.opts.locales.flatMap(locale => {
197
+ await Promise.all(this.opts.locales.map(locale => {
204
198
  const poItems = [];
205
- const poItemsUrl = [];
206
199
  for (const item of data.items) {
207
200
  const poItem = itemToPOItem(item, locale, this.opts.sourceLocale);
208
- if (itemIsUrl(item) && useSeparateUrls) {
209
- poItemsUrl.push(poItem);
210
- }
211
- else {
212
- poItems.push(poItem);
213
- }
201
+ poItems.push(poItem);
214
202
  }
215
203
  const headers = this.getHeaders(locale, data.pluralRules.get(locale));
216
- const saveJobs = [this.saveRaw(poItems, headers, locale, false)];
217
- if (useSeparateUrls) {
218
- saveJobs.push(this.saveRaw(poItemsUrl, headers, locale, true));
219
- }
220
- return saveJobs;
204
+ return this.saveRaw(poItems, headers, locale);
221
205
  }));
222
206
  }
223
207
  getHeaders(locale, pluralRule) {
@@ -238,8 +222,12 @@ export class POFile {
238
222
  }
239
223
  export function pofile(pofOpts = {}) {
240
224
  return async (opts) => {
241
- const fullOpts = fillDefaults(pofOpts, { ...defaultOpts, dir: opts.localesDir });
242
- await opts.fs.mkdir(resolve(opts.root, fullOpts.dir)); // create once
225
+ const defaultLocation = resolve(opts.root, opts.localesDir, '{locale}.po');
226
+ const fullOpts = fillDefaults(pofOpts, { ...defaultOpts, location: defaultLocation });
227
+ await opts.fs.mkdir(dirname(resolve(opts.root, fullOpts.location))); // create once
228
+ if (!fullOpts.location.includes('{locale}')) {
229
+ throw new Error('PO file `location` config has to include `{locale}`');
230
+ }
243
231
  return new POFile({ ...opts, ...fullOpts });
244
232
  };
245
233
  }
package/dist/storage.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { FS } from './fs.js';
2
2
  export type FileRefEntry = {
3
3
  link?: string | undefined;
4
- placeholders: [number, string][];
4
+ placeholders: [string, string][];
5
5
  };
6
6
  export type FileRef = {
7
7
  file: string;
@@ -54,10 +54,13 @@ export type StorageFactoryOpts = {
54
54
  root: string;
55
55
  /** shared locale artifacts directory from the top-level config */
56
56
  localesDir: string;
57
- /** whether the url is configured, can use to load separate url files */
58
- haveUrl: boolean;
59
57
  sourceLocale: string;
60
58
  fs: FS;
61
59
  };
62
60
  export type StorageFactory = (opts: StorageFactoryOpts) => CatalogStorage | Promise<CatalogStorage>;
63
61
  export declare function migrateStorage(fromStorages: StorageFactory[], toStorage: StorageFactory): StorageFactory;
62
+ type ItemType = 'message' | 'url';
63
+ export declare function storageByType(storages: Record<ItemType, StorageFactory>): StorageFactory;
64
+ export declare function mergeItemsByKey(allItems: Item[][], sourceLocale: string): Item[];
65
+ export declare function storageByLocale(storages: [string[], StorageFactory][]): StorageFactory;
66
+ export {};