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
package/dist/storage.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { getKey } from './adapters.js';
1
2
  export function fillTranslations(item, locales) {
2
3
  for (const loc of locales) {
3
4
  // fill empty translations
@@ -41,3 +42,100 @@ export function migrateStorage(fromStorages, toStorage) {
41
42
  };
42
43
  };
43
44
  }
45
+ const keyAndFiles = (storages) => ({
46
+ key: storages.map(s => s.key).join(),
47
+ files: storages.flatMap(s => s.files),
48
+ });
49
+ export function storageByType(storages) {
50
+ return async (opts) => {
51
+ const promises = [];
52
+ const types = [];
53
+ for (const [typ, storage] of Object.entries(storages)) {
54
+ types.push(typ);
55
+ promises.push(storage(opts));
56
+ }
57
+ const all = await Promise.all(promises);
58
+ const byType = new Map(types.map((t, i) => [t, all[i]]));
59
+ return {
60
+ ...keyAndFiles(all),
61
+ load: async () => {
62
+ const loadeds = await Promise.all(all.map(st => st.load()));
63
+ return {
64
+ pluralRules: loadeds.find(l => l.pluralRules)?.pluralRules,
65
+ items: loadeds.flatMap(l => l.items),
66
+ };
67
+ },
68
+ save: async ({ items, pluralRules }) => {
69
+ const urls = [];
70
+ const msgs = [];
71
+ for (const item of items) {
72
+ ;
73
+ (itemIsUrl(item) ? urls : msgs).push(item);
74
+ }
75
+ const promises = [];
76
+ if (urls.length) {
77
+ promises.push(byType.get('url').save({ items: urls, pluralRules }));
78
+ }
79
+ if (msgs.length) {
80
+ promises.push(byType.get('message').save({ items: msgs, pluralRules }));
81
+ }
82
+ await Promise.all(promises);
83
+ },
84
+ };
85
+ };
86
+ }
87
+ export function mergeItemsByKey(allItems, sourceLocale) {
88
+ const items = new Map();
89
+ for (const allItms of allItems) {
90
+ for (const item of allItms) {
91
+ const sourceTransl = item.translations.get(sourceLocale);
92
+ if (!sourceTransl) {
93
+ throw new Error(`Source translation not found for in ${JSON.stringify(Array.from(item.translations.entries()))}`);
94
+ }
95
+ const key = getKey(sourceTransl, item.context);
96
+ const itemFull = items.get(key);
97
+ if (!itemFull) {
98
+ items.set(key, item);
99
+ continue;
100
+ }
101
+ for (const [locale, transl] of item.translations) {
102
+ if (transl && !itemFull.translations.get(locale)) {
103
+ itemFull.translations.set(locale, transl);
104
+ }
105
+ }
106
+ }
107
+ }
108
+ return Array.from(items.values());
109
+ }
110
+ export function storageByLocale(storages) {
111
+ return async (opts) => {
112
+ const promises = [];
113
+ for (const [locales, storage] of storages) {
114
+ promises.push(storage({ ...opts, locales }));
115
+ }
116
+ const localesLeft = new Set(opts.locales);
117
+ const all = await Promise.all(promises);
118
+ for (const [locales] of storages) {
119
+ for (const loc of locales) {
120
+ localesLeft.delete(loc);
121
+ }
122
+ }
123
+ return {
124
+ ...keyAndFiles(all),
125
+ load: async () => {
126
+ let pluralRules;
127
+ const itemsToMerge = [];
128
+ for (const loaded of await Promise.all(all.map(st => st.load()))) {
129
+ if (!pluralRules) {
130
+ pluralRules = loaded.pluralRules;
131
+ }
132
+ itemsToMerge.push(loaded.items);
133
+ }
134
+ return { pluralRules, items: mergeItemsByKey(itemsToMerge, opts.sourceLocale) };
135
+ },
136
+ save: async (data) => {
137
+ await Promise.all(all.map(s => s.save(data)));
138
+ },
139
+ };
140
+ };
141
+ }
package/dist/url.d.ts CHANGED
@@ -1,17 +1,19 @@
1
- export type URLLocalizer = (url: string, locale: string) => string;
2
- export declare const localizeDefault: URLLocalizer;
3
- export declare const deLocalizeDefault: <L extends string>(path: string, locales: L[]) => [string, L | null];
4
- type MatchParams = Partial<Record<string, string | string[]>>;
5
- export declare const fillParams: (params: MatchParams, destPattern: string) => string;
1
+ export type Pattern = (string | number)[];
2
+ export declare function compilePattern(pattern: string): Pattern;
3
+ export declare function matchPattern(pattern: Pattern, url: string): false | string[];
4
+ export declare function stringifyPattern(pattern: Pattern, dynamics: readonly string[]): string;
6
5
  export type URLManifestItem = [
7
- string,
8
- string[]
9
- ] | [string];
6
+ Pattern,
7
+ Pattern[]
8
+ ] | [Pattern];
10
9
  export type URLManifest = URLManifestItem[];
11
10
  type MatchResult<L extends string> = {
12
11
  path: string | null;
13
- params: MatchParams;
14
- altPatterns: Record<L, string>;
12
+ params: string[];
13
+ altPatterns: Record<L, Pattern>;
15
14
  };
16
15
  export declare function URLMatcher<L extends string>(manifest: URLManifest, locales: L[]): (url: string, locale: L | null) => MatchResult<L>;
16
+ export type URLLocalizer = (url: string, locale: string) => string;
17
+ export declare const localizeDefault: URLLocalizer;
18
+ export declare const deLocalizeDefault: <L extends string>(path: string, locales: L[]) => [string, L | null];
17
19
  export {};
package/dist/url.js CHANGED
@@ -1,36 +1,191 @@
1
- import { compile, match } from 'path-to-regexp';
2
- // public default, used when localized: true
3
- export const localizeDefault = (path, loc) => {
4
- const localized = `/${loc}${path}`;
5
- if (!localized.endsWith('/')) {
6
- return localized;
1
+ // $ node --import ../testing/resolve.ts %n.test.ts
2
+ const wilds = ['/**', '/*', '*', '/?', '?']; // longer should be first
3
+ // corresponsing indices
4
+ const [DOUBLE, SINGLESL, SINGLE, OPTIONALSL, OPTIONAL] = [0, 1, 2, 3, 4];
5
+ // meanings
6
+ const [HASDOUBLE, HASSINGLE, SINGLESMIN, OPTIONALMAX, HASOPTIONAL] = [0, 1, 2, -2, -1];
7
+ const DEFAULT_PREV_WILD = [false, -1, false, -1, false];
8
+ export function compilePattern(pattern) {
9
+ const parts = [];
10
+ if (pattern.length > 1 && pattern.endsWith('/')) {
11
+ pattern = pattern.slice(0, -1);
7
12
  }
8
- return localized.slice(0, -1);
9
- };
10
- export const deLocalizeDefault = (path, locales) => {
11
- let iSecondSlash = path.indexOf('/', 2);
12
- if (iSecondSlash === -1) {
13
- iSecondSlash = path.length;
13
+ let prevProps = DEFAULT_PREV_WILD.slice();
14
+ for (let i = 0; i < pattern.length; i++) {
15
+ let iWildInPatMin = -1;
16
+ let wildIdxMin = null;
17
+ for (let wi = 0; wi < wilds.length; wi++) {
18
+ const iWildInPat = pattern.indexOf(wilds[wi], i);
19
+ if (iWildInPat === -1 || (iWildInPatMin !== -1 && iWildInPat >= iWildInPatMin)) {
20
+ continue;
21
+ }
22
+ iWildInPatMin = iWildInPat;
23
+ wildIdxMin = wi;
24
+ }
25
+ if (wildIdxMin === null) {
26
+ parts.push(pattern.slice(i));
27
+ prevProps = DEFAULT_PREV_WILD.slice();
28
+ break;
29
+ }
30
+ if (iWildInPatMin > i) {
31
+ const slice = pattern.slice(i, iWildInPatMin);
32
+ if (slice !== '/' || i === 0) {
33
+ parts.push(slice);
34
+ prevProps = DEFAULT_PREV_WILD.slice();
35
+ }
36
+ }
37
+ if (wildIdxMin === DOUBLE) {
38
+ if (!prevProps[DOUBLE]) {
39
+ prevProps[DOUBLE] = true;
40
+ parts.push(HASDOUBLE);
41
+ }
42
+ }
43
+ else if (wildIdxMin === OPTIONAL) {
44
+ if (!prevProps[OPTIONAL]) {
45
+ prevProps[OPTIONAL] = true;
46
+ parts.push(HASOPTIONAL);
47
+ }
48
+ }
49
+ else if (wildIdxMin === OPTIONALSL) {
50
+ if (prevProps[OPTIONALSL] === -1) {
51
+ prevProps[OPTIONALSL] = parts.length;
52
+ parts.push(OPTIONALMAX);
53
+ }
54
+ else {
55
+ ;
56
+ parts[prevProps[OPTIONALSL]]--;
57
+ }
58
+ }
59
+ else if (wildIdxMin === SINGLE) {
60
+ if (!prevProps[SINGLE]) {
61
+ prevProps[SINGLE] = true;
62
+ parts.push(HASSINGLE);
63
+ }
64
+ }
65
+ else if (wildIdxMin === SINGLESL) {
66
+ if (prevProps[SINGLESL] === -1) {
67
+ prevProps[SINGLESL] = parts.length;
68
+ parts.push(SINGLESMIN);
69
+ }
70
+ else {
71
+ ;
72
+ parts[prevProps[SINGLESL]]++;
73
+ }
74
+ }
75
+ i = iWildInPatMin + wilds[wildIdxMin].length - 1;
14
76
  }
15
- const locale = path.slice(1, iSecondSlash);
16
- if (!locales.includes(locale)) {
17
- return [path, null];
77
+ return parts;
78
+ }
79
+ function slashCheckFails(url, fromI, toI, singles, optionals, double, single) {
80
+ if (fromI === toI) {
81
+ return singles > 0 || single;
18
82
  }
19
- const rest = path.slice(1 + locale.length);
20
- return [rest || '/', locale];
21
- };
22
- const getParams = (path, pattern) => {
23
- const matched = match(pattern, { decode: false })(path);
24
- if (!matched) {
25
- return;
83
+ if (singles === 0 && optionals === 0) {
84
+ return false;
26
85
  }
27
- return matched.params;
28
- };
29
- export const fillParams = (params, destPattern) => {
30
- const compiled = compile(destPattern, { encode: false });
31
- return compiled(params);
32
- };
33
- const noMatchRes = { path: null, altPatterns: {}, params: {} };
86
+ let slashes = 0;
87
+ for (let i = url.indexOf('/', fromI); i !== -1 && i < toI; i = url.indexOf('/', i + 1)) {
88
+ slashes++;
89
+ }
90
+ if (toI - fromI === slashes) {
91
+ return true;
92
+ }
93
+ return slashes < singles || (!double && slashes - optionals > singles);
94
+ }
95
+ export function matchPattern(pattern, url) {
96
+ let endI = url.length;
97
+ if (endI > 1 && url.endsWith('/')) {
98
+ endI--;
99
+ }
100
+ let hasDoubleLast = false;
101
+ let singlesLast = 0;
102
+ let optionalsLast = 0;
103
+ let hasSingleLast = false;
104
+ let hasOptionalLast = false;
105
+ let lastI = 0;
106
+ const dynamics = [];
107
+ for (const patt of pattern) {
108
+ if (typeof patt === 'number') {
109
+ if (patt === HASDOUBLE) {
110
+ hasDoubleLast = true;
111
+ }
112
+ else if (patt <= OPTIONALMAX) {
113
+ optionalsLast = OPTIONALMAX - patt + 1;
114
+ }
115
+ else if (patt === HASOPTIONAL) {
116
+ hasOptionalLast = true;
117
+ }
118
+ else if (patt === HASSINGLE) {
119
+ hasSingleLast = true;
120
+ }
121
+ else {
122
+ singlesLast = patt - SINGLESMIN + 1;
123
+ }
124
+ continue;
125
+ }
126
+ const singles = singlesLast;
127
+ const optionals = optionalsLast;
128
+ const hasDouble = hasDoubleLast;
129
+ const hasSingle = hasSingleLast;
130
+ const hasOptional = hasOptionalLast;
131
+ singlesLast = 0;
132
+ optionalsLast = 0;
133
+ hasDoubleLast = false;
134
+ hasOptionalLast = false;
135
+ hasSingleLast = false;
136
+ const i = url.indexOf(patt, lastI);
137
+ const prevI = lastI;
138
+ lastI = i + patt.length;
139
+ if (i === -1) {
140
+ return false;
141
+ }
142
+ if (!hasDouble && !hasOptional && !hasSingle && singles === 0 && optionals === 0) {
143
+ if (i > 0) {
144
+ return false;
145
+ }
146
+ continue;
147
+ }
148
+ if (slashCheckFails(url, prevI, i, singles, optionals, hasDouble, hasSingle)) {
149
+ return false;
150
+ }
151
+ dynamics.push(url.slice(prevI, i));
152
+ }
153
+ if (lastI === endI) {
154
+ if (singlesLast > 0 || lastI === 0) {
155
+ return false;
156
+ }
157
+ if (!hasDoubleLast) {
158
+ return dynamics;
159
+ }
160
+ }
161
+ if (!hasDoubleLast && !hasOptionalLast && !hasSingleLast && singlesLast === 0 && optionalsLast === 0) {
162
+ return false;
163
+ }
164
+ if (slashCheckFails(url, lastI, endI, singlesLast, optionalsLast, hasDoubleLast, hasSingleLast)) {
165
+ return false;
166
+ }
167
+ dynamics.push(url.slice(lastI, endI));
168
+ return dynamics;
169
+ }
170
+ export function stringifyPattern(pattern, dynamics) {
171
+ let i = 0;
172
+ let lastIsDynamic = false;
173
+ let assembled = '';
174
+ for (const p of pattern) {
175
+ if (typeof p === 'string') {
176
+ assembled += p;
177
+ lastIsDynamic = false;
178
+ continue;
179
+ }
180
+ if (lastIsDynamic) {
181
+ continue;
182
+ }
183
+ assembled += dynamics[i++] ?? ''; // undefined when optional and []
184
+ lastIsDynamic = true;
185
+ }
186
+ return assembled;
187
+ }
188
+ const noMatchRes = { path: null, altPatterns: {}, params: [] };
34
189
  export function URLMatcher(manifest, locales) {
35
190
  const manifestWithLocales = manifest.map(([pattern, localized]) => {
36
191
  localized ??= locales.map(_ => pattern);
@@ -42,11 +197,31 @@ export function URLMatcher(manifest, locales) {
42
197
  return noMatchRes;
43
198
  }
44
199
  for (const [pattern, altPatterns] of manifestWithLocales) {
45
- const params = getParams(url, altPatterns[locale]);
200
+ const params = matchPattern(altPatterns[locale], url);
46
201
  if (params) {
47
- return { path: fillParams(params, pattern), params, altPatterns };
202
+ return { path: stringifyPattern(pattern, params), params, altPatterns };
48
203
  }
49
204
  }
50
205
  return noMatchRes;
51
206
  };
52
207
  }
208
+ // public default, used when localized: true
209
+ export const localizeDefault = (path, loc) => {
210
+ const localized = `/${loc}${path}`;
211
+ if (!localized.endsWith('/')) {
212
+ return localized;
213
+ }
214
+ return localized.slice(0, -1);
215
+ };
216
+ export const deLocalizeDefault = (path, locales) => {
217
+ let iSecondSlash = path.indexOf('/', 2);
218
+ if (iSecondSlash === -1) {
219
+ iSecondSlash = path.length;
220
+ }
221
+ const locale = path.slice(1, iSecondSlash);
222
+ if (!locales.includes(locale)) {
223
+ return [path, null];
224
+ }
225
+ const rest = path.slice(1 + locale.length);
226
+ return [rest || '/', locale];
227
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wuchale",
3
- "version": "0.23.4",
3
+ "version": "0.24.0",
4
4
  "description": "Protobuf-like i18n from plain code",
5
5
  "scripts": {
6
6
  "dev": "tsc --watch",
@@ -84,17 +84,16 @@
84
84
  "author": "K1DV5",
85
85
  "license": "MIT",
86
86
  "dependencies": {
87
- "@sveltejs/acorn-typescript": "^1.0.9",
87
+ "@sveltejs/acorn-typescript": "^1.0.10",
88
88
  "acorn": "^8.16.0",
89
89
  "chokidar": "^5.0.0",
90
90
  "magic-string": "^0.30.21",
91
- "path-to-regexp": "^8.4.0",
92
91
  "picomatch": "^4.0.4",
93
92
  "pofile": "^1.1.4",
94
- "tinyglobby": "^0.2.16"
93
+ "tinyglobby": "^0.2.17"
95
94
  },
96
95
  "devDependencies": {
97
- "@types/node": "~24.12.2",
96
+ "@types/node": "~24.13.1",
98
97
  "@types/picomatch": "^4.0.3",
99
98
  "typescript": "^6.0.3"
100
99
  },
@@ -11,7 +11,7 @@ export function setLocale(newLocale) {
11
11
  }
12
12
 
13
13
  /**
14
- * @param {{ [locale: string]: import("wuchale/runtime").CatalogModule }} catalogs
14
+ * @param {{ [locale in import('${DATA}').Locale]: import("wuchale/runtime").CatalogModule }} catalogs
15
15
  */
16
16
  export const getRuntime = catalogs => toRuntime(catalogs[locale], locale)
17
17
  export const getRuntimeRx = getRuntime
@@ -1,10 +1,10 @@
1
1
  import { loadLocales } from 'wuchale/load-utils/server'
2
2
  import { locales } from '${DATA}'
3
- import { loadCatalog, loadIDs } from '${PROXY_SYNC}'
3
+ import { loadCatalog, loadCount } from '${PROXY_SYNC}'
4
4
 
5
- export { loadCatalog, loadIDs }
5
+ export { loadCatalog, loadCount }
6
6
  export const key = '${KEY}'
7
7
 
8
8
  // two exports
9
- export const getRuntime = await loadLocales(key, loadIDs, loadCatalog, locales)
9
+ export const getRuntime = await loadLocales(key, loadCount, loadCatalog, locales)
10
10
  export const getRuntimeRx = getRuntime
@@ -1,8 +1,8 @@
1
1
  import { registerLoaders } from 'wuchale/load-utils'
2
- import { loadCatalog, loadIDs } from '${PROXY}'
2
+ import { loadCatalog, loadCount } from '${PROXY}'
3
3
 
4
4
  const key = '${KEY}'
5
5
 
6
6
  // two exports. can be used anywhere
7
- export const getRuntime = registerLoaders(key, loadCatalog, loadIDs)
7
+ export const getRuntime = registerLoaders(key, loadCatalog, loadCount)
8
8
  export const getRuntimeRx = getRuntime
@@ -1,9 +1,9 @@
1
1
  import { currentRuntime } from 'wuchale/load-utils/server'
2
- import { loadCatalog, loadIDs } from '${PROXY_SYNC}'
2
+ import { loadCatalog, loadCount } from '${PROXY_SYNC}'
3
3
 
4
4
  export const key = '${KEY}'
5
- export { loadCatalog, loadIDs } // for loading before runWithLocale
5
+ export { loadCatalog, loadCount } // for loading before runWithLocale
6
6
 
7
7
  // two exports, same function
8
- export const getRuntime = (/** @type {string} */ loadID) => currentRuntime(key, loadID)
8
+ export const getRuntime = (loadID = 0) => currentRuntime(key, loadID)
9
9
  export const getRuntimeRx = getRuntime