@moku-labs/web 1.14.0 → 1.15.1
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.
- package/dist/browser.d.mts +27 -26
- package/dist/browser.mjs +220 -181
- package/dist/index.cjs +2478 -2376
- package/dist/index.d.cts +63 -29
- package/dist/index.d.mts +63 -29
- package/dist/index.mjs +2478 -2376
- package/package.json +1 -1
package/dist/browser.d.mts
CHANGED
|
@@ -230,6 +230,28 @@ type Api$3 = {
|
|
|
230
230
|
t(locale: string, key: string): string;
|
|
231
231
|
};
|
|
232
232
|
//#endregion
|
|
233
|
+
//#region src/plugins/i18n/index.d.ts
|
|
234
|
+
/**
|
|
235
|
+
* Internationalization plugin — locale registry plus a flat translation helper
|
|
236
|
+
* with default-locale fallback. Pure config-as-data (no state or events);
|
|
237
|
+
* consumed read-only by content, router, head, and build.
|
|
238
|
+
*
|
|
239
|
+
* @example Register locales and translations
|
|
240
|
+
* ```ts
|
|
241
|
+
* const app = createApp({
|
|
242
|
+
* pluginConfigs: {
|
|
243
|
+
* i18n: {
|
|
244
|
+
* locales: ["en", "uk"],
|
|
245
|
+
* defaultLocale: "en",
|
|
246
|
+
* localeNames: { en: "English", uk: "Українська" },
|
|
247
|
+
* translations: { uk: { "nav.home": "Головна" } }
|
|
248
|
+
* }
|
|
249
|
+
* }
|
|
250
|
+
* });
|
|
251
|
+
* ```
|
|
252
|
+
*/
|
|
253
|
+
declare const i18nPlugin: import("@moku-labs/core").PluginInstance<"i18n", Config$3, Record<string, never>, Api$3, {}> & Record<never, never>;
|
|
254
|
+
//#endregion
|
|
233
255
|
//#region src/plugins/router/iso-match.d.ts
|
|
234
256
|
/**
|
|
235
257
|
* A compiled, engine-agnostic path matcher: the same `.exec({ pathname })` shape the
|
|
@@ -1191,28 +1213,6 @@ type SpaApi = {
|
|
|
1191
1213
|
*/
|
|
1192
1214
|
declare const sitePlugin: import("@moku-labs/core").PluginInstance<"site", Config$4, Record<string, never>, Api$4, {}> & Record<never, never>;
|
|
1193
1215
|
//#endregion
|
|
1194
|
-
//#region src/plugins/i18n/index.d.ts
|
|
1195
|
-
/**
|
|
1196
|
-
* Internationalization plugin — locale registry plus a flat translation helper
|
|
1197
|
-
* with default-locale fallback. Pure config-as-data (no state or events);
|
|
1198
|
-
* consumed read-only by content, router, head, and build.
|
|
1199
|
-
*
|
|
1200
|
-
* @example Register locales and translations
|
|
1201
|
-
* ```ts
|
|
1202
|
-
* const app = createApp({
|
|
1203
|
-
* pluginConfigs: {
|
|
1204
|
-
* i18n: {
|
|
1205
|
-
* locales: ["en", "uk"],
|
|
1206
|
-
* defaultLocale: "en",
|
|
1207
|
-
* localeNames: { en: "English", uk: "Українська" },
|
|
1208
|
-
* translations: { uk: { "nav.home": "Головна" } }
|
|
1209
|
-
* }
|
|
1210
|
-
* }
|
|
1211
|
-
* });
|
|
1212
|
-
* ```
|
|
1213
|
-
*/
|
|
1214
|
-
declare const i18nPlugin: import("@moku-labs/core").PluginInstance<"i18n", Config$3, Record<string, never>, Api$3, {}> & Record<never, never>;
|
|
1215
|
-
//#endregion
|
|
1216
1216
|
//#region src/plugins/router/builders/route-builder.d.ts
|
|
1217
1217
|
/**
|
|
1218
1218
|
* Create a fluent route builder from a URL pattern string. Captures the pattern
|
|
@@ -1276,8 +1276,8 @@ declare function createUrls<T extends RouteMap>(routes: T, defaultLocale?: strin
|
|
|
1276
1276
|
/**
|
|
1277
1277
|
* Router plugin — typed, named route definitions with locale-aware URL generation
|
|
1278
1278
|
* and matching. Author routes with {@link route}, then register them the normal config
|
|
1279
|
-
* way via `pluginConfigs.router.routes` (compiled at init). Depends on site (base URL)
|
|
1280
|
-
*
|
|
1279
|
+
* way via `pluginConfigs.router.routes` (compiled at init). Depends on site (base URL);
|
|
1280
|
+
* i18n (locales) is OPTIONAL — falls back to a single default locale ("en") when absent.
|
|
1281
1281
|
*
|
|
1282
1282
|
* @example Register routes via config, then start/build
|
|
1283
1283
|
* ```ts
|
|
@@ -1380,7 +1380,7 @@ declare function buildArticleHead(articleMeta: ArticleMeta, canonicalUrl: string
|
|
|
1380
1380
|
* Head plugin — composes per-route `<head>` metadata (title template, Open Graph,
|
|
1381
1381
|
* Twitter cards, canonical, hreflang). Use the re-exported SEO primitives
|
|
1382
1382
|
* ({@link meta}, {@link og}, {@link twitter}, …) inside a route's `.head()`.
|
|
1383
|
-
* Depends on site
|
|
1383
|
+
* Depends on site and router; i18n is OPTIONAL (single default-locale fallback when absent).
|
|
1384
1384
|
*
|
|
1385
1385
|
* @example Set global head defaults
|
|
1386
1386
|
* ```ts
|
|
@@ -2090,7 +2090,8 @@ type Api = {
|
|
|
2090
2090
|
* (locale fallback, draft filtering, sort, caching, events) lives here; source I/O +
|
|
2091
2091
|
* the Markdown pipeline live in a {@link ContentProvider} you compose (like `env`
|
|
2092
2092
|
* providers). The shell imports zero node code, so `contentPlugin` is browser-safe.
|
|
2093
|
-
*
|
|
2093
|
+
* i18n is OPTIONAL (single default-locale fallback when absent); emits `content:ready`
|
|
2094
|
+
* and `content:invalidated`.
|
|
2094
2095
|
*
|
|
2095
2096
|
* @example Compose the node filesystem provider with a content dir + Shiki theme
|
|
2096
2097
|
* ```ts
|
package/dist/browser.mjs
CHANGED
|
@@ -49,166 +49,9 @@ const createPlugin$1 = coreConfig.createPlugin;
|
|
|
49
49
|
*/
|
|
50
50
|
const createCore = coreConfig.createCore;
|
|
51
51
|
//#endregion
|
|
52
|
-
//#region src/plugins/i18n/api.ts
|
|
53
|
-
/** Error prefix for all i18n lifecycle failures. */
|
|
54
|
-
const ERROR_PREFIX$8 = "[web]";
|
|
55
|
-
/**
|
|
56
|
-
* Validates the resolved i18n config (fail-fast at `createApp`). Throws when
|
|
57
|
-
* `locales` is empty or when `defaultLocale` is not a member of `locales`.
|
|
58
|
-
* Errors use the `[web]` prefix with an actionable remediation line.
|
|
59
|
-
*
|
|
60
|
-
* @param ctx - Plugin context carrying the resolved {@link Config}.
|
|
61
|
-
* @param ctx.config - The resolved i18n {@link Config}.
|
|
62
|
-
* @throws {Error} If `locales` is empty or `defaultLocale` is not in `locales`.
|
|
63
|
-
* @example
|
|
64
|
-
* ```ts
|
|
65
|
-
* validateI18nConfig({ config: { locales: ["en"], defaultLocale: "en" } });
|
|
66
|
-
* ```
|
|
67
|
-
*/
|
|
68
|
-
function validateI18nConfig(ctx) {
|
|
69
|
-
const { locales, defaultLocale } = ctx.config;
|
|
70
|
-
if (locales.length === 0) throw new Error(`${ERROR_PREFIX$8} i18n.locales must contain at least one locale.\n Set pluginConfigs.i18n.locales to a non-empty array, e.g. ["en"].`);
|
|
71
|
-
if (!locales.includes(defaultLocale)) throw new Error(`${ERROR_PREFIX$8} i18n.defaultLocale "${defaultLocale}" is not in i18n.locales [${locales.join(", ")}].\n Set pluginConfigs.i18n.defaultLocale to one of the configured locales, or add "${defaultLocale}" to i18n.locales.`);
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* Creates the i18n plugin API surface — locale registry accessors plus the
|
|
75
|
-
* `t()` translator with default-locale fallback. Every method is a pure read
|
|
76
|
-
* of `ctx.config`; none mutate, and `t()` always returns a string.
|
|
77
|
-
*
|
|
78
|
-
* @param ctx - Plugin context carrying the resolved {@link Config}.
|
|
79
|
-
* @param ctx.config - The resolved i18n {@link Config}.
|
|
80
|
-
* @returns The {@link Api} accessor surface mounted at `app.i18n`.
|
|
81
|
-
* @example
|
|
82
|
-
* ```ts
|
|
83
|
-
* const api = createI18nApi({ config: { locales: ["en"], defaultLocale: "en" } });
|
|
84
|
-
* api.t("en", "nav.home");
|
|
85
|
-
* ```
|
|
86
|
-
*/
|
|
87
|
-
function createI18nApi(ctx) {
|
|
88
|
-
const { config } = ctx;
|
|
89
|
-
return {
|
|
90
|
-
/**
|
|
91
|
-
* Returns the configured supported locales in declared order.
|
|
92
|
-
*
|
|
93
|
-
* @returns The configured `locales` list (priority/display order).
|
|
94
|
-
* @example
|
|
95
|
-
* ```ts
|
|
96
|
-
* api.locales(); // ["en", "uk"]
|
|
97
|
-
* ```
|
|
98
|
-
*/
|
|
99
|
-
locales() {
|
|
100
|
-
return config.locales;
|
|
101
|
-
},
|
|
102
|
-
/**
|
|
103
|
-
* Returns the fallback locale used when a requested locale is absent.
|
|
104
|
-
*
|
|
105
|
-
* @returns The configured `defaultLocale`.
|
|
106
|
-
* @example
|
|
107
|
-
* ```ts
|
|
108
|
-
* api.defaultLocale(); // "en"
|
|
109
|
-
* ```
|
|
110
|
-
*/
|
|
111
|
-
defaultLocale() {
|
|
112
|
-
return config.defaultLocale;
|
|
113
|
-
},
|
|
114
|
-
/**
|
|
115
|
-
* Membership guard: whether `x` is one of the supported locales
|
|
116
|
-
* (case-sensitive).
|
|
117
|
-
*
|
|
118
|
-
* @param x - Candidate locale code.
|
|
119
|
-
* @returns `true` if `x ∈ locales`, else `false`.
|
|
120
|
-
* @example
|
|
121
|
-
* ```ts
|
|
122
|
-
* api.isLocale("uk"); // true
|
|
123
|
-
* ```
|
|
124
|
-
*/
|
|
125
|
-
isLocale(x) {
|
|
126
|
-
return config.locales.includes(x);
|
|
127
|
-
},
|
|
128
|
-
/**
|
|
129
|
-
* Human-readable display name for a locale.
|
|
130
|
-
*
|
|
131
|
-
* @param locale - Locale code to look up.
|
|
132
|
-
* @returns The display name, or `undefined` if unmapped.
|
|
133
|
-
* @example
|
|
134
|
-
* ```ts
|
|
135
|
-
* api.localeName("uk"); // "Українська"
|
|
136
|
-
* ```
|
|
137
|
-
*/
|
|
138
|
-
localeName(locale) {
|
|
139
|
-
return config.localeNames?.[locale];
|
|
140
|
-
},
|
|
141
|
-
/**
|
|
142
|
-
* Open Graph `og:locale` value for a locale.
|
|
143
|
-
*
|
|
144
|
-
* @param locale - Locale code to look up.
|
|
145
|
-
* @returns The `og:locale` value (e.g. `"en_US"`), or `undefined` if unmapped.
|
|
146
|
-
* @example
|
|
147
|
-
* ```ts
|
|
148
|
-
* api.ogLocale("en"); // "en_US"
|
|
149
|
-
* ```
|
|
150
|
-
*/
|
|
151
|
-
ogLocale(locale) {
|
|
152
|
-
return config.ogLocaleMap?.[locale];
|
|
153
|
-
},
|
|
154
|
-
/**
|
|
155
|
-
* Translate `key` for `locale` with a deterministic fallback chain
|
|
156
|
-
* (requested locale → default locale → the key itself). The default-locale
|
|
157
|
-
* lookup is skipped when `locale === defaultLocale`.
|
|
158
|
-
*
|
|
159
|
-
* @param locale - Requested locale code.
|
|
160
|
-
* @param key - Translation key (e.g. `"nav.home"`).
|
|
161
|
-
* @returns The translated value, the default-locale value, or `key`.
|
|
162
|
-
* @example
|
|
163
|
-
* ```ts
|
|
164
|
-
* api.t("uk", "nav.home"); // "Головна"
|
|
165
|
-
* ```
|
|
166
|
-
*/
|
|
167
|
-
t(locale, key) {
|
|
168
|
-
const exact = config.translations?.[locale]?.[key];
|
|
169
|
-
if (exact !== void 0) return exact;
|
|
170
|
-
if (locale !== config.defaultLocale) {
|
|
171
|
-
const fallback = config.translations?.[config.defaultLocale]?.[key];
|
|
172
|
-
if (fallback !== void 0) return fallback;
|
|
173
|
-
}
|
|
174
|
-
return key;
|
|
175
|
-
}
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
/**
|
|
179
|
-
* Internationalization plugin — locale registry plus a flat translation helper
|
|
180
|
-
* with default-locale fallback. Pure config-as-data (no state or events);
|
|
181
|
-
* consumed read-only by content, router, head, and build.
|
|
182
|
-
*
|
|
183
|
-
* @example Register locales and translations
|
|
184
|
-
* ```ts
|
|
185
|
-
* const app = createApp({
|
|
186
|
-
* pluginConfigs: {
|
|
187
|
-
* i18n: {
|
|
188
|
-
* locales: ["en", "uk"],
|
|
189
|
-
* defaultLocale: "en",
|
|
190
|
-
* localeNames: { en: "English", uk: "Українська" },
|
|
191
|
-
* translations: { uk: { "nav.home": "Головна" } }
|
|
192
|
-
* }
|
|
193
|
-
* }
|
|
194
|
-
* });
|
|
195
|
-
* ```
|
|
196
|
-
*/
|
|
197
|
-
const i18nPlugin = createPlugin$1("i18n", {
|
|
198
|
-
config: {
|
|
199
|
-
locales: ["en"],
|
|
200
|
-
defaultLocale: "en",
|
|
201
|
-
localeNames: {},
|
|
202
|
-
ogLocaleMap: {},
|
|
203
|
-
translations: {}
|
|
204
|
-
},
|
|
205
|
-
onInit: validateI18nConfig,
|
|
206
|
-
api: createI18nApi
|
|
207
|
-
});
|
|
208
|
-
//#endregion
|
|
209
52
|
//#region src/plugins/site/api.ts
|
|
210
53
|
/** Error prefix for all site lifecycle/validation failures. */
|
|
211
|
-
const ERROR_PREFIX$
|
|
54
|
+
const ERROR_PREFIX$8 = "[web]";
|
|
212
55
|
/** URL protocols that qualify a parsed URL as an absolute http/https URL. */
|
|
213
56
|
const HTTP_PROTOCOLS = new Set(["http:", "https:"]);
|
|
214
57
|
/**
|
|
@@ -307,8 +150,8 @@ function isAbsoluteUrl(value) {
|
|
|
307
150
|
* ```
|
|
308
151
|
*/
|
|
309
152
|
function validateSiteConfig(ctx) {
|
|
310
|
-
if (!isNonEmpty(ctx.config.name)) throw new Error(`${ERROR_PREFIX$
|
|
311
|
-
if (!isAbsoluteUrl(ctx.config.url)) throw new Error(`${ERROR_PREFIX$
|
|
153
|
+
if (!isNonEmpty(ctx.config.name)) throw new Error(`${ERROR_PREFIX$8} site.name is required.\n Provide a non-empty site name in pluginConfigs.site.name.`);
|
|
154
|
+
if (!isAbsoluteUrl(ctx.config.url)) throw new Error(`${ERROR_PREFIX$8} site.url must be a valid absolute URL (http/https), received ${JSON.stringify(ctx.config.url)}.\n Provide an absolute URL in pluginConfigs.site.url, e.g. "https://blog.dev".`);
|
|
312
155
|
}
|
|
313
156
|
/**
|
|
314
157
|
* Creates the site plugin API surface — read-only accessors over frozen config
|
|
@@ -421,6 +264,202 @@ const sitePlugin = createPlugin$1("site", {
|
|
|
421
264
|
api: createSiteApi
|
|
422
265
|
});
|
|
423
266
|
//#endregion
|
|
267
|
+
//#region src/plugins/i18n/api.ts
|
|
268
|
+
/** Error prefix for all i18n lifecycle failures. */
|
|
269
|
+
const ERROR_PREFIX$7 = "[web]";
|
|
270
|
+
/**
|
|
271
|
+
* The framework's default i18n config — a single `"en"` locale with empty lookup
|
|
272
|
+
* maps. Used both as the i18n plugin's `config` default and as the source for
|
|
273
|
+
* {@link fallbackI18n}, so "no i18n config" and "no i18n plugin" resolve identically.
|
|
274
|
+
*
|
|
275
|
+
* @example
|
|
276
|
+
* ```ts
|
|
277
|
+
* createI18nApi({ config: DEFAULT_I18N_CONFIG }).defaultLocale(); // "en"
|
|
278
|
+
* ```
|
|
279
|
+
*/
|
|
280
|
+
const DEFAULT_I18N_CONFIG = {
|
|
281
|
+
locales: ["en"],
|
|
282
|
+
defaultLocale: "en",
|
|
283
|
+
localeNames: {},
|
|
284
|
+
ogLocaleMap: {},
|
|
285
|
+
translations: {}
|
|
286
|
+
};
|
|
287
|
+
/**
|
|
288
|
+
* Validates the resolved i18n config (fail-fast at `createApp`). Throws when
|
|
289
|
+
* `locales` is empty or when `defaultLocale` is not a member of `locales`.
|
|
290
|
+
* Errors use the `[web]` prefix with an actionable remediation line.
|
|
291
|
+
*
|
|
292
|
+
* @param ctx - Plugin context carrying the resolved {@link Config}.
|
|
293
|
+
* @param ctx.config - The resolved i18n {@link Config}.
|
|
294
|
+
* @throws {Error} If `locales` is empty or `defaultLocale` is not in `locales`.
|
|
295
|
+
* @example
|
|
296
|
+
* ```ts
|
|
297
|
+
* validateI18nConfig({ config: { locales: ["en"], defaultLocale: "en" } });
|
|
298
|
+
* ```
|
|
299
|
+
*/
|
|
300
|
+
function validateI18nConfig(ctx) {
|
|
301
|
+
const { locales, defaultLocale } = ctx.config;
|
|
302
|
+
if (locales.length === 0) throw new Error(`${ERROR_PREFIX$7} i18n.locales must contain at least one locale.\n Set pluginConfigs.i18n.locales to a non-empty array, e.g. ["en"].`);
|
|
303
|
+
if (!locales.includes(defaultLocale)) throw new Error(`${ERROR_PREFIX$7} i18n.defaultLocale "${defaultLocale}" is not in i18n.locales [${locales.join(", ")}].\n Set pluginConfigs.i18n.defaultLocale to one of the configured locales, or add "${defaultLocale}" to i18n.locales.`);
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Creates the i18n plugin API surface — locale registry accessors plus the
|
|
307
|
+
* `t()` translator with default-locale fallback. Every method is a pure read
|
|
308
|
+
* of `ctx.config`; none mutate, and `t()` always returns a string.
|
|
309
|
+
*
|
|
310
|
+
* @param ctx - Plugin context carrying the resolved {@link Config}.
|
|
311
|
+
* @param ctx.config - The resolved i18n {@link Config}.
|
|
312
|
+
* @returns The {@link Api} accessor surface mounted at `app.i18n`.
|
|
313
|
+
* @example
|
|
314
|
+
* ```ts
|
|
315
|
+
* const api = createI18nApi({ config: { locales: ["en"], defaultLocale: "en" } });
|
|
316
|
+
* api.t("en", "nav.home");
|
|
317
|
+
* ```
|
|
318
|
+
*/
|
|
319
|
+
function createI18nApi(ctx) {
|
|
320
|
+
const { config } = ctx;
|
|
321
|
+
return {
|
|
322
|
+
/**
|
|
323
|
+
* Returns the configured supported locales in declared order.
|
|
324
|
+
*
|
|
325
|
+
* @returns The configured `locales` list (priority/display order).
|
|
326
|
+
* @example
|
|
327
|
+
* ```ts
|
|
328
|
+
* api.locales(); // ["en", "uk"]
|
|
329
|
+
* ```
|
|
330
|
+
*/
|
|
331
|
+
locales() {
|
|
332
|
+
return config.locales;
|
|
333
|
+
},
|
|
334
|
+
/**
|
|
335
|
+
* Returns the fallback locale used when a requested locale is absent.
|
|
336
|
+
*
|
|
337
|
+
* @returns The configured `defaultLocale`.
|
|
338
|
+
* @example
|
|
339
|
+
* ```ts
|
|
340
|
+
* api.defaultLocale(); // "en"
|
|
341
|
+
* ```
|
|
342
|
+
*/
|
|
343
|
+
defaultLocale() {
|
|
344
|
+
return config.defaultLocale;
|
|
345
|
+
},
|
|
346
|
+
/**
|
|
347
|
+
* Membership guard: whether `x` is one of the supported locales
|
|
348
|
+
* (case-sensitive).
|
|
349
|
+
*
|
|
350
|
+
* @param x - Candidate locale code.
|
|
351
|
+
* @returns `true` if `x ∈ locales`, else `false`.
|
|
352
|
+
* @example
|
|
353
|
+
* ```ts
|
|
354
|
+
* api.isLocale("uk"); // true
|
|
355
|
+
* ```
|
|
356
|
+
*/
|
|
357
|
+
isLocale(x) {
|
|
358
|
+
return config.locales.includes(x);
|
|
359
|
+
},
|
|
360
|
+
/**
|
|
361
|
+
* Human-readable display name for a locale.
|
|
362
|
+
*
|
|
363
|
+
* @param locale - Locale code to look up.
|
|
364
|
+
* @returns The display name, or `undefined` if unmapped.
|
|
365
|
+
* @example
|
|
366
|
+
* ```ts
|
|
367
|
+
* api.localeName("uk"); // "Українська"
|
|
368
|
+
* ```
|
|
369
|
+
*/
|
|
370
|
+
localeName(locale) {
|
|
371
|
+
return config.localeNames?.[locale];
|
|
372
|
+
},
|
|
373
|
+
/**
|
|
374
|
+
* Open Graph `og:locale` value for a locale.
|
|
375
|
+
*
|
|
376
|
+
* @param locale - Locale code to look up.
|
|
377
|
+
* @returns The `og:locale` value (e.g. `"en_US"`), or `undefined` if unmapped.
|
|
378
|
+
* @example
|
|
379
|
+
* ```ts
|
|
380
|
+
* api.ogLocale("en"); // "en_US"
|
|
381
|
+
* ```
|
|
382
|
+
*/
|
|
383
|
+
ogLocale(locale) {
|
|
384
|
+
return config.ogLocaleMap?.[locale];
|
|
385
|
+
},
|
|
386
|
+
/**
|
|
387
|
+
* Translate `key` for `locale` with a deterministic fallback chain
|
|
388
|
+
* (requested locale → default locale → the key itself). The default-locale
|
|
389
|
+
* lookup is skipped when `locale === defaultLocale`.
|
|
390
|
+
*
|
|
391
|
+
* @param locale - Requested locale code.
|
|
392
|
+
* @param key - Translation key (e.g. `"nav.home"`).
|
|
393
|
+
* @returns The translated value, the default-locale value, or `key`.
|
|
394
|
+
* @example
|
|
395
|
+
* ```ts
|
|
396
|
+
* api.t("uk", "nav.home"); // "Головна"
|
|
397
|
+
* ```
|
|
398
|
+
*/
|
|
399
|
+
t(locale, key) {
|
|
400
|
+
const exact = config.translations?.[locale]?.[key];
|
|
401
|
+
if (exact !== void 0) return exact;
|
|
402
|
+
if (locale !== config.defaultLocale) {
|
|
403
|
+
const fallback = config.translations?.[config.defaultLocale]?.[key];
|
|
404
|
+
if (fallback !== void 0) return fallback;
|
|
405
|
+
}
|
|
406
|
+
return key;
|
|
407
|
+
}
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* The i18n API a consumer sees when the i18n plugin is NOT composed: a single
|
|
412
|
+
* default locale (`"en"`) with empty maps. `locales()` is `["en"]`,
|
|
413
|
+
* `defaultLocale()` is `"en"`, and every map lookup misses (`undefined`, or the
|
|
414
|
+
* key for `t()`). Identical to composing the i18n plugin with its defaults — which
|
|
415
|
+
* is what makes i18n optional: `router`/`head`/`content`/`build` fall back to this
|
|
416
|
+
* when `ctx.has("i18n")` is false, leaving every downstream call unchanged.
|
|
417
|
+
*
|
|
418
|
+
* @example
|
|
419
|
+
* ```ts
|
|
420
|
+
* const i18n = ctx.has("i18n") ? ctx.require(i18nPlugin) : fallbackI18n;
|
|
421
|
+
* i18n.locales(); // ["en"]
|
|
422
|
+
* ```
|
|
423
|
+
*/
|
|
424
|
+
const fallbackI18n = createI18nApi({ config: DEFAULT_I18N_CONFIG });
|
|
425
|
+
//#endregion
|
|
426
|
+
//#region src/plugins/i18n/index.ts
|
|
427
|
+
/**
|
|
428
|
+
* i18n — Micro tier. Multi-file layout (index wiring + api.ts + types.ts) so
|
|
429
|
+
* index.ts stays within the ≤30-line wiring-only hook; logic lives in api.ts.
|
|
430
|
+
*
|
|
431
|
+
* Locale registry + flat translation helper with default-locale fallback.
|
|
432
|
+
* Pure config-as-data: no state, no events, no lifecycle resources.
|
|
433
|
+
* Consumed read-only by content/router/head/build via `ctx.require(i18nPlugin)`.
|
|
434
|
+
*
|
|
435
|
+
* @file i18n plugin wiring harness.
|
|
436
|
+
* @see README.md
|
|
437
|
+
*/
|
|
438
|
+
/**
|
|
439
|
+
* Internationalization plugin — locale registry plus a flat translation helper
|
|
440
|
+
* with default-locale fallback. Pure config-as-data (no state or events);
|
|
441
|
+
* consumed read-only by content, router, head, and build.
|
|
442
|
+
*
|
|
443
|
+
* @example Register locales and translations
|
|
444
|
+
* ```ts
|
|
445
|
+
* const app = createApp({
|
|
446
|
+
* pluginConfigs: {
|
|
447
|
+
* i18n: {
|
|
448
|
+
* locales: ["en", "uk"],
|
|
449
|
+
* defaultLocale: "en",
|
|
450
|
+
* localeNames: { en: "English", uk: "Українська" },
|
|
451
|
+
* translations: { uk: { "nav.home": "Головна" } }
|
|
452
|
+
* }
|
|
453
|
+
* }
|
|
454
|
+
* });
|
|
455
|
+
* ```
|
|
456
|
+
*/
|
|
457
|
+
const i18nPlugin = createPlugin$1("i18n", {
|
|
458
|
+
config: DEFAULT_I18N_CONFIG,
|
|
459
|
+
onInit: validateI18nConfig,
|
|
460
|
+
api: createI18nApi
|
|
461
|
+
});
|
|
462
|
+
//#endregion
|
|
424
463
|
//#region src/plugins/router/iso-match.ts
|
|
425
464
|
/**
|
|
426
465
|
* Parse a single path segment into its `{…}` placeholder, or `false` for a static
|
|
@@ -1004,8 +1043,9 @@ function compileRoutes(input) {
|
|
|
1004
1043
|
const ERROR_PREFIX$5 = "[web] router";
|
|
1005
1044
|
/**
|
|
1006
1045
|
* Validate a route map and compile it into the matcher table on `ctx.state`,
|
|
1007
|
-
* resolving the global render `mode` + site base URL + i18n locales
|
|
1008
|
-
*
|
|
1046
|
+
* resolving the global render `mode` + site base URL + i18n locales (or the single
|
|
1047
|
+
* default-locale fallback when i18n is not composed) at call time. Called by the
|
|
1048
|
+
* router's `onInit` to compile `config.routes`. Re-calling replaces the table.
|
|
1009
1049
|
*
|
|
1010
1050
|
* @param ctx - The router register context (state + global mode + require).
|
|
1011
1051
|
* @param routes - The route map to compile (an `import * as routes` namespace works).
|
|
@@ -1017,7 +1057,7 @@ const ERROR_PREFIX$5 = "[web] router";
|
|
|
1017
1057
|
*/
|
|
1018
1058
|
function registerRoutes(ctx, routes) {
|
|
1019
1059
|
validateRoutes(routes);
|
|
1020
|
-
const i18n = ctx.require(i18nPlugin);
|
|
1060
|
+
const i18n = ctx.has("i18n") ? ctx.require(i18nPlugin) : fallbackI18n;
|
|
1021
1061
|
ctx.state.table = compileRoutes({
|
|
1022
1062
|
routes,
|
|
1023
1063
|
mode: ctx.global.mode,
|
|
@@ -1442,8 +1482,8 @@ function createState$2(_ctx) {
|
|
|
1442
1482
|
/**
|
|
1443
1483
|
* Router plugin — typed, named route definitions with locale-aware URL generation
|
|
1444
1484
|
* and matching. Author routes with {@link route}, then register them the normal config
|
|
1445
|
-
* way via `pluginConfigs.router.routes` (compiled at init). Depends on site (base URL)
|
|
1446
|
-
*
|
|
1485
|
+
* way via `pluginConfigs.router.routes` (compiled at init). Depends on site (base URL);
|
|
1486
|
+
* i18n (locales) is OPTIONAL — falls back to a single default locale ("en") when absent.
|
|
1447
1487
|
*
|
|
1448
1488
|
* @example Register routes via config, then start/build
|
|
1449
1489
|
* ```ts
|
|
@@ -1456,7 +1496,7 @@ function createState$2(_ctx) {
|
|
|
1456
1496
|
* ```
|
|
1457
1497
|
*/
|
|
1458
1498
|
const routerPlugin = createPlugin$1("router", {
|
|
1459
|
-
depends: [sitePlugin
|
|
1499
|
+
depends: [sitePlugin],
|
|
1460
1500
|
helpers: {
|
|
1461
1501
|
route,
|
|
1462
1502
|
defineRoutes,
|
|
@@ -1898,7 +1938,8 @@ function serializeHead(elements) {
|
|
|
1898
1938
|
*
|
|
1899
1939
|
* The `render` method pulls `site`/`i18n`/`router` via `ctx.require` at call time,
|
|
1900
1940
|
* composes the head element set via the shared `compose.ts` module, and serializes
|
|
1901
|
-
* it to a string. It holds no resource and caches no subscription.
|
|
1941
|
+
* it to a string. It holds no resource and caches no subscription. `i18n` is
|
|
1942
|
+
* OPTIONAL — a single default-locale fallback is used when it is not composed.
|
|
1902
1943
|
*/
|
|
1903
1944
|
/** Error prefix for head API invariant failures. */
|
|
1904
1945
|
const ERROR_PREFIX$4 = "[web] head";
|
|
@@ -1950,7 +1991,7 @@ function createApi$1(ctx) {
|
|
|
1950
1991
|
data,
|
|
1951
1992
|
defaults: readDefaults(ctx.state),
|
|
1952
1993
|
site: ctx.require(sitePlugin),
|
|
1953
|
-
i18n: ctx.require(i18nPlugin),
|
|
1994
|
+
i18n: ctx.has("i18n") ? ctx.require(i18nPlugin) : fallbackI18n,
|
|
1954
1995
|
router: ctx.require(routerPlugin)
|
|
1955
1996
|
}));
|
|
1956
1997
|
},
|
|
@@ -1970,7 +2011,8 @@ function createApi$1(ctx) {
|
|
|
1970
2011
|
*/
|
|
1971
2012
|
siteHead(input) {
|
|
1972
2013
|
const site = ctx.require(sitePlugin);
|
|
1973
|
-
const
|
|
2014
|
+
const i18n = ctx.has("i18n") ? ctx.require(i18nPlugin) : fallbackI18n;
|
|
2015
|
+
const ogLocale = input.locale === void 0 ? void 0 : i18n.ogLocale(input.locale);
|
|
1974
2016
|
return serializeHead(composeSiteHead({
|
|
1975
2017
|
site,
|
|
1976
2018
|
defaults: readDefaults(ctx.state),
|
|
@@ -2107,7 +2149,7 @@ function createState$1(_ctx) {
|
|
|
2107
2149
|
* Head plugin — composes per-route `<head>` metadata (title template, Open Graph,
|
|
2108
2150
|
* Twitter cards, canonical, hreflang). Use the re-exported SEO primitives
|
|
2109
2151
|
* ({@link meta}, {@link og}, {@link twitter}, …) inside a route's `.head()`.
|
|
2110
|
-
* Depends on site
|
|
2152
|
+
* Depends on site and router; i18n is OPTIONAL (single default-locale fallback when absent).
|
|
2111
2153
|
*
|
|
2112
2154
|
* @example Set global head defaults
|
|
2113
2155
|
* ```ts
|
|
@@ -2123,11 +2165,7 @@ function createState$1(_ctx) {
|
|
|
2123
2165
|
* ```
|
|
2124
2166
|
*/
|
|
2125
2167
|
const headPlugin = createPlugin$1("head", {
|
|
2126
|
-
depends: [
|
|
2127
|
-
sitePlugin,
|
|
2128
|
-
i18nPlugin,
|
|
2129
|
-
routerPlugin
|
|
2130
|
-
],
|
|
2168
|
+
depends: [sitePlugin, routerPlugin],
|
|
2131
2169
|
helpers: headHelpers,
|
|
2132
2170
|
config: defaultConfig,
|
|
2133
2171
|
createState: createState$1,
|
|
@@ -3959,7 +3997,8 @@ function articleNotFound(slug, locale) {
|
|
|
3959
3997
|
return /* @__PURE__ */ new Error(`[web] content article "${slug}" not found for locale "${locale}".\n Looked for ${slug}/${locale}.md and the default-locale fallback.`);
|
|
3960
3998
|
}
|
|
3961
3999
|
/**
|
|
3962
|
-
* Plugin `api` factory: resolves i18n via `ctx.require
|
|
4000
|
+
* Plugin `api` factory: resolves i18n via `ctx.require` (or the single default-locale
|
|
4001
|
+
* fallback when i18n is not composed), merges `config.providers` into
|
|
3963
4002
|
* one source, assembles the kernel-free {@link ContentApiContext}, and delegates to
|
|
3964
4003
|
* {@link createContentApi}. Referenced directly as the plugin's `api` so index.ts stays
|
|
3965
4004
|
* wiring-only. Imports no node code (the provider owns it).
|
|
@@ -3972,7 +4011,7 @@ function articleNotFound(slug, locale) {
|
|
|
3972
4011
|
* ```
|
|
3973
4012
|
*/
|
|
3974
4013
|
function contentApi(ctx) {
|
|
3975
|
-
const i18nApi = ctx.require(i18nPlugin);
|
|
4014
|
+
const i18nApi = ctx.has("i18n") ? ctx.require(i18nPlugin) : fallbackI18n;
|
|
3976
4015
|
/**
|
|
3977
4016
|
* Active locale codes from i18n.
|
|
3978
4017
|
*
|
|
@@ -4377,8 +4416,8 @@ function validateContentConfig(config) {
|
|
|
4377
4416
|
* @file content — Complex Plugin skeleton (wiring-only).
|
|
4378
4417
|
*
|
|
4379
4418
|
* Markdown pipeline: discover, parse frontmatter, render to sanitized HTML, and
|
|
4380
|
-
* expose a locale-keyed Article model.
|
|
4381
|
-
* and `content:invalidated`.
|
|
4419
|
+
* expose a locale-keyed Article model. i18n is OPTIONAL (single default-locale
|
|
4420
|
+
* fallback when absent). Emits `content:ready` and `content:invalidated`.
|
|
4382
4421
|
* @see README.md
|
|
4383
4422
|
*/
|
|
4384
4423
|
/**
|
|
@@ -4386,7 +4425,8 @@ function validateContentConfig(config) {
|
|
|
4386
4425
|
* (locale fallback, draft filtering, sort, caching, events) lives here; source I/O +
|
|
4387
4426
|
* the Markdown pipeline live in a {@link ContentProvider} you compose (like `env`
|
|
4388
4427
|
* providers). The shell imports zero node code, so `contentPlugin` is browser-safe.
|
|
4389
|
-
*
|
|
4428
|
+
* i18n is OPTIONAL (single default-locale fallback when absent); emits `content:ready`
|
|
4429
|
+
* and `content:invalidated`.
|
|
4390
4430
|
*
|
|
4391
4431
|
* @example Compose the node filesystem provider with a content dir + Shiki theme
|
|
4392
4432
|
* ```ts
|
|
@@ -4402,7 +4442,6 @@ function validateContentConfig(config) {
|
|
|
4402
4442
|
* ```
|
|
4403
4443
|
*/
|
|
4404
4444
|
const contentPlugin = createPlugin$1("content", {
|
|
4405
|
-
depends: [i18nPlugin],
|
|
4406
4445
|
events: contentEvents,
|
|
4407
4446
|
config: defaultContentConfig,
|
|
4408
4447
|
createState: createContentState,
|