tera-system-ui 0.1.41 → 0.1.61
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/components/accordion/Accordion.d.ts +15 -13
- package/dist/components/accordion/index.d.ts +1 -0
- package/dist/components/avatar/Avatar.d.ts +5 -3
- package/dist/components/avatar/index.d.ts +1 -0
- package/dist/components/brand-logo/BrandLogo.d.ts +5 -2
- package/dist/components/brand-logo/index.d.ts +1 -0
- package/dist/components/button/Button.d.ts +58 -14
- package/dist/components/button/Button.js +90 -27
- package/dist/components/button/Button.svelte +35 -30
- package/dist/components/button/Button.svelte.d.ts +1 -4
- package/dist/components/button/index.d.ts +1 -0
- package/dist/components/checkbox/Checkbox.d.ts +5 -2
- package/dist/components/checkbox/Checkbox.svelte +15 -8
- package/dist/components/checkbox/index.d.ts +1 -0
- package/dist/components/combobox/Combobox.d.ts +5 -2
- package/dist/components/combobox/index.d.ts +1 -0
- package/dist/components/dialog/Dialog.d.ts +8 -6
- package/dist/components/dialog/index.d.ts +1 -0
- package/dist/components/dropdown-menu/DropdownMenu.d.ts +26 -8
- package/dist/components/dropdown-menu/index.d.ts +1 -0
- package/dist/components/header/Header.d.ts +6 -3
- package/dist/components/header/Header.svelte.d.ts +1 -1
- package/dist/components/header/index.d.ts +1 -0
- package/dist/components/input/Input.d.ts +22 -6
- package/dist/components/input/Input.js +19 -10
- package/dist/components/input/Input.svelte +42 -9
- package/dist/components/input/index.d.ts +1 -0
- package/dist/components/label/Label.d.ts +33 -3
- package/dist/components/label/Label.js +14 -3
- package/dist/components/label/Label.svelte +7 -7
- package/dist/components/label/Label.svelte.d.ts +2 -2
- package/dist/components/label/index.d.ts +1 -0
- package/dist/components/language-picker-button/LanguagePickerButton.d.ts +5 -2
- package/dist/components/language-picker-button/index.d.ts +1 -0
- package/dist/components/light-dark-toggle/LightDarkToggle.d.ts +5 -2
- package/dist/components/light-dark-toggle/index.d.ts +1 -0
- package/dist/components/popover/Popover.d.ts +6 -3
- package/dist/components/popover/Popover.js +0 -1
- package/dist/components/popover/index.d.ts +1 -0
- package/dist/components/popover-responsive/PopoverResponsive.d.ts +5 -2
- package/dist/components/popover-responsive/index.d.ts +1 -0
- package/dist/components/select/Select.d.ts +19 -5
- package/dist/components/select/Select.js +18 -9
- package/dist/components/select/Select.svelte +17 -4
- package/dist/components/select/Select.svelte.d.ts +1 -1
- package/dist/components/select/index.d.ts +1 -0
- package/dist/components/side-navigation/SideNavigation.d.ts +6 -3
- package/dist/components/side-navigation/index.d.ts +1 -1
- package/dist/components/slider/Slider.d.ts +7 -4
- package/dist/components/slider/Slider.svelte +26 -51
- package/dist/components/slider/index.d.ts +1 -0
- package/dist/components/star-rating/StarRating.d.ts +5 -2
- package/dist/components/star-rating/StarRating.svelte +5 -5
- package/dist/components/star-rating/index.d.ts +1 -0
- package/dist/components/switch/Switch.d.ts +6 -2
- package/dist/components/switch/Switch.svelte +13 -7
- package/dist/components/switch/index.d.ts +1 -0
- package/dist/components/tabs/Tabs.svelte.d.ts +23 -3
- package/dist/components/tabs/index.d.ts +1 -0
- package/dist/components/tera-ui-context/TeraUiContext.d.ts +4 -6
- package/dist/components/tera-ui-context/index.d.ts +1 -0
- package/dist/components/text-area/TextArea.d.ts +22 -8
- package/dist/components/text-area/TextArea.js +19 -10
- package/dist/components/text-area/TextArea.svelte +36 -30
- package/dist/components/text-area/index.d.ts +1 -0
- package/dist/components/user-avatar-with-menu/UserAvatarWithMenu.d.ts +5 -2
- package/dist/components/user-avatar-with-menu/index.d.ts +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/paraglide/README.md +93 -0
- package/dist/paraglide/messages/_index.d.ts +8 -7
- package/dist/paraglide/messages/_index.js +361 -360
- package/dist/paraglide/messages/ar.d.ts +9 -7
- package/dist/paraglide/messages/ar.js +16 -15
- package/dist/paraglide/messages/bg.d.ts +9 -7
- package/dist/paraglide/messages/bg.js +16 -15
- package/dist/paraglide/messages/bn.d.ts +9 -7
- package/dist/paraglide/messages/bn.js +16 -15
- package/dist/paraglide/messages/ca.d.ts +9 -7
- package/dist/paraglide/messages/ca.js +16 -15
- package/dist/paraglide/messages/cs.d.ts +9 -7
- package/dist/paraglide/messages/cs.js +16 -15
- package/dist/paraglide/messages/da.d.ts +9 -7
- package/dist/paraglide/messages/da.js +16 -15
- package/dist/paraglide/messages/de.d.ts +9 -7
- package/dist/paraglide/messages/de.js +16 -15
- package/dist/paraglide/messages/el.d.ts +9 -7
- package/dist/paraglide/messages/el.js +16 -15
- package/dist/paraglide/messages/en.d.ts +9 -7
- package/dist/paraglide/messages/en.js +16 -15
- package/dist/paraglide/messages/es.d.ts +9 -7
- package/dist/paraglide/messages/es.js +16 -15
- package/dist/paraglide/messages/fi.d.ts +9 -7
- package/dist/paraglide/messages/fi.js +16 -15
- package/dist/paraglide/messages/fr.d.ts +9 -7
- package/dist/paraglide/messages/fr.js +16 -15
- package/dist/paraglide/messages/he.d.ts +9 -7
- package/dist/paraglide/messages/he.js +16 -15
- package/dist/paraglide/messages/hi.d.ts +9 -7
- package/dist/paraglide/messages/hi.js +16 -15
- package/dist/paraglide/messages/hu.d.ts +9 -7
- package/dist/paraglide/messages/hu.js +16 -15
- package/dist/paraglide/messages/id.d.ts +9 -7
- package/dist/paraglide/messages/id.js +16 -15
- package/dist/paraglide/messages/it.d.ts +9 -7
- package/dist/paraglide/messages/it.js +16 -15
- package/dist/paraglide/messages/ja.d.ts +9 -7
- package/dist/paraglide/messages/ja.js +16 -15
- package/dist/paraglide/messages/ko.d.ts +9 -7
- package/dist/paraglide/messages/ko.js +16 -15
- package/dist/paraglide/messages/lt.d.ts +9 -7
- package/dist/paraglide/messages/lt.js +16 -15
- package/dist/paraglide/messages/lv.d.ts +9 -7
- package/dist/paraglide/messages/lv.js +16 -15
- package/dist/paraglide/messages/ms.d.ts +9 -7
- package/dist/paraglide/messages/ms.js +16 -15
- package/dist/paraglide/messages/nl.d.ts +9 -7
- package/dist/paraglide/messages/nl.js +16 -15
- package/dist/paraglide/messages/no.d.ts +9 -7
- package/dist/paraglide/messages/no.js +16 -15
- package/dist/paraglide/messages/pl.d.ts +9 -7
- package/dist/paraglide/messages/pl.js +16 -15
- package/dist/paraglide/messages/pt.d.ts +9 -7
- package/dist/paraglide/messages/pt.js +16 -15
- package/dist/paraglide/messages/ro.d.ts +9 -7
- package/dist/paraglide/messages/ro.js +16 -15
- package/dist/paraglide/messages/ru.d.ts +9 -7
- package/dist/paraglide/messages/ru.js +16 -15
- package/dist/paraglide/messages/sk.d.ts +9 -7
- package/dist/paraglide/messages/sk.js +16 -15
- package/dist/paraglide/messages/sl.d.ts +9 -7
- package/dist/paraglide/messages/sl.js +16 -15
- package/dist/paraglide/messages/sq.d.ts +9 -7
- package/dist/paraglide/messages/sq.js +16 -15
- package/dist/paraglide/messages/sr.d.ts +9 -7
- package/dist/paraglide/messages/sr.js +16 -15
- package/dist/paraglide/messages/sv.d.ts +9 -7
- package/dist/paraglide/messages/sv.js +16 -15
- package/dist/paraglide/messages/sw.d.ts +9 -7
- package/dist/paraglide/messages/sw.js +16 -15
- package/dist/paraglide/messages/ta.d.ts +9 -7
- package/dist/paraglide/messages/ta.js +16 -15
- package/dist/paraglide/messages/te.d.ts +9 -7
- package/dist/paraglide/messages/te.js +16 -15
- package/dist/paraglide/messages/th.d.ts +9 -7
- package/dist/paraglide/messages/th.js +16 -15
- package/dist/paraglide/messages/tl.d.ts +9 -7
- package/dist/paraglide/messages/tl.js +16 -15
- package/dist/paraglide/messages/tr.d.ts +9 -7
- package/dist/paraglide/messages/tr.js +16 -15
- package/dist/paraglide/messages/uk.d.ts +9 -7
- package/dist/paraglide/messages/uk.js +16 -15
- package/dist/paraglide/messages/vi.d.ts +9 -7
- package/dist/paraglide/messages/vi.js +16 -15
- package/dist/paraglide/messages/zh-CN.d.ts +9 -7
- package/dist/paraglide/messages/zh-CN.js +16 -15
- package/dist/paraglide/messages/zh-TW.d.ts +9 -7
- package/dist/paraglide/messages/zh-TW.js +16 -15
- package/dist/paraglide/messages.js +1 -1
- package/dist/paraglide/registry.js +1 -1
- package/dist/paraglide/runtime.d.ts +221 -45
- package/dist/paraglide/runtime.js +378 -65
- package/dist/paraglide/server.d.ts +40 -4
- package/dist/paraglide/server.js +83 -46
- package/dist/tera-i18n/projects/tera-system-ui/project.inlang/.meta.json +3 -0
- package/dist/tera-i18n/projects/tera-system-ui/project.inlang/README.md +103 -0
- package/dist/themes/tera-ui-base.css +70 -7
- package/dist/types/index.d.ts +25 -0
- package/dist/types/index.js +3 -0
- package/package.json +150 -36
- package/scripts/add-component-template.js +1 -1
- package/scripts/generate-ts-index.js +38 -12
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
/* eslint-disable */
|
|
2
2
|
|
|
3
3
|
/** @type {any} */
|
|
4
4
|
const URLPattern = {}
|
|
@@ -245,6 +245,9 @@ export let serverAsyncLocalStorage = undefined;
|
|
|
245
245
|
export const disableAsyncLocalStorage = false;
|
|
246
246
|
export const experimentalMiddlewareLocaleSplitting = false;
|
|
247
247
|
export const isServer = import.meta.env?.SSR ?? typeof window === 'undefined';
|
|
248
|
+
/** @type {Locale | undefined} */
|
|
249
|
+
// @ts-ignore - injected by bundlers at compile time
|
|
250
|
+
export const experimentalStaticLocale = undefined;
|
|
248
251
|
/**
|
|
249
252
|
* Sets the server side async local storage.
|
|
250
253
|
*
|
|
@@ -281,6 +284,12 @@ let localeInitiallySet = false;
|
|
|
281
284
|
/**
|
|
282
285
|
* Get the current locale.
|
|
283
286
|
*
|
|
287
|
+
* The locale is resolved using your configured strategies (URL, cookie, localStorage, etc.)
|
|
288
|
+
* in the order they are defined. In SSR contexts, the locale is retrieved from AsyncLocalStorage
|
|
289
|
+
* which is set by the `paraglideMiddleware()`.
|
|
290
|
+
*
|
|
291
|
+
* @see https://inlang.com/m/gerre34r/library-inlang-paraglideJs/strategy - Configure locale detection strategies
|
|
292
|
+
*
|
|
284
293
|
* @example
|
|
285
294
|
* if (getLocale() === 'de') {
|
|
286
295
|
* console.log('Germany 🇩🇪');
|
|
@@ -291,6 +300,9 @@ let localeInitiallySet = false;
|
|
|
291
300
|
* @type {() => Locale}
|
|
292
301
|
*/
|
|
293
302
|
export let getLocale = () => {
|
|
303
|
+
if (experimentalStaticLocale !== undefined) {
|
|
304
|
+
return assertIsLocale(experimentalStaticLocale);
|
|
305
|
+
}
|
|
294
306
|
/** @type {string | undefined} */
|
|
295
307
|
let locale;
|
|
296
308
|
// if running in a server-side rendering context
|
|
@@ -331,7 +343,15 @@ export let getLocale = () => {
|
|
|
331
343
|
}
|
|
332
344
|
else if (isCustomStrategy(strat) && customClientStrategies.has(strat)) {
|
|
333
345
|
const handler = customClientStrategies.get(strat);
|
|
334
|
-
|
|
346
|
+
if (handler) {
|
|
347
|
+
const result = handler.getLocale();
|
|
348
|
+
// Handle both sync and async results - skip async in sync getLocale
|
|
349
|
+
if (result instanceof Promise) {
|
|
350
|
+
// Can't await in sync function, skip async strategies
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
locale = result;
|
|
354
|
+
}
|
|
335
355
|
}
|
|
336
356
|
// check if match, else continue loop
|
|
337
357
|
if (locale !== undefined) {
|
|
@@ -348,17 +368,17 @@ export let getLocale = () => {
|
|
|
348
368
|
throw new Error("No locale found. Read the docs https://inlang.com/m/gerre34r/library-inlang-paraglideJs/errors#no-locale-found");
|
|
349
369
|
};
|
|
350
370
|
/**
|
|
351
|
-
* Overwrite the
|
|
371
|
+
* Overwrite the `getLocale()` function.
|
|
352
372
|
*
|
|
353
|
-
* Use this function to overwrite how the locale is resolved.
|
|
354
|
-
*
|
|
355
|
-
*
|
|
373
|
+
* Use this function to overwrite how the locale is resolved. This is useful
|
|
374
|
+
* for custom locale resolution or advanced use cases like SSG with concurrent rendering.
|
|
375
|
+
*
|
|
376
|
+
* @see https://inlang.com/m/gerre34r/library-inlang-paraglideJs/strategy
|
|
356
377
|
*
|
|
357
378
|
* @example
|
|
358
379
|
* overwriteGetLocale(() => {
|
|
359
|
-
* // resolve the locale from a cookie. fallback to the base locale.
|
|
360
380
|
* return Cookies.get('locale') ?? baseLocale
|
|
361
|
-
* }
|
|
381
|
+
* });
|
|
362
382
|
*
|
|
363
383
|
* @type {(fn: () => Locale) => void}
|
|
364
384
|
*/
|
|
@@ -366,13 +386,37 @@ export const overwriteGetLocale = (fn) => {
|
|
|
366
386
|
getLocale = fn;
|
|
367
387
|
};
|
|
368
388
|
|
|
389
|
+
/**
|
|
390
|
+
* Navigates to the localized URL, or reloads the current page
|
|
391
|
+
*
|
|
392
|
+
* @param {string} [newLocation] The new location
|
|
393
|
+
* @return {undefined}
|
|
394
|
+
*/
|
|
395
|
+
const navigateOrReload = (newLocation) => {
|
|
396
|
+
if (newLocation) {
|
|
397
|
+
// reload the page by navigating to the new url
|
|
398
|
+
window.location.href = newLocation;
|
|
399
|
+
}
|
|
400
|
+
else {
|
|
401
|
+
// reload the page to reflect the new locale
|
|
402
|
+
window.location.reload();
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
/**
|
|
406
|
+
* @typedef {(newLocale: Locale, options?: { reload?: boolean }) => void | Promise<void>} SetLocaleFn
|
|
407
|
+
*/
|
|
369
408
|
/**
|
|
370
409
|
* Set the locale.
|
|
371
410
|
*
|
|
372
|
-
*
|
|
373
|
-
*
|
|
374
|
-
*
|
|
375
|
-
* to reflect the new locale.
|
|
411
|
+
* Updates the locale using your configured strategies (cookie, localStorage, URL, etc.).
|
|
412
|
+
* By default, this reloads the page on the client to reflect the new locale. Reloading
|
|
413
|
+
* can be disabled by passing `reload: false` as an option, but you'll need to ensure
|
|
414
|
+
* the UI updates to reflect the new locale.
|
|
415
|
+
*
|
|
416
|
+
* If any custom strategy's `setLocale` function is async, then this function
|
|
417
|
+
* will become async as well.
|
|
418
|
+
*
|
|
419
|
+
* @see https://inlang.com/m/gerre34r/library-inlang-paraglideJs/strategy
|
|
376
420
|
*
|
|
377
421
|
* @example
|
|
378
422
|
* setLocale('en');
|
|
@@ -380,7 +424,7 @@ export const overwriteGetLocale = (fn) => {
|
|
|
380
424
|
* @example
|
|
381
425
|
* setLocale('en', { reload: false });
|
|
382
426
|
*
|
|
383
|
-
* @type {
|
|
427
|
+
* @type {SetLocaleFn}
|
|
384
428
|
*/
|
|
385
429
|
export let setLocale = (newLocale, options) => {
|
|
386
430
|
const optionsWithDefaults = {
|
|
@@ -389,6 +433,7 @@ export let setLocale = (newLocale, options) => {
|
|
|
389
433
|
};
|
|
390
434
|
// locale is already set
|
|
391
435
|
// https://github.com/opral/inlang-paraglide-js/issues/430
|
|
436
|
+
/** @type {Locale | undefined} */
|
|
392
437
|
let currentLocale;
|
|
393
438
|
try {
|
|
394
439
|
currentLocale = getLocale();
|
|
@@ -396,6 +441,8 @@ export let setLocale = (newLocale, options) => {
|
|
|
396
441
|
catch {
|
|
397
442
|
// do nothing, no locale has been set yet.
|
|
398
443
|
}
|
|
444
|
+
/** @type {Array<Promise<any>>} */
|
|
445
|
+
const customSetLocalePromises = [];
|
|
399
446
|
/** @type {string | undefined} */
|
|
400
447
|
let newLocation = undefined;
|
|
401
448
|
for (const strat of strategy) {
|
|
@@ -411,9 +458,11 @@ export let setLocale = (newLocale, options) => {
|
|
|
411
458
|
typeof window === "undefined") {
|
|
412
459
|
continue;
|
|
413
460
|
}
|
|
414
|
-
const domain = cookieDomain || window.location.hostname;
|
|
415
461
|
// set the cookie
|
|
416
|
-
|
|
462
|
+
const cookieString = `${cookieName}=${newLocale}; path=/; max-age=${cookieMaxAge}`;
|
|
463
|
+
document.cookie = cookieDomain
|
|
464
|
+
? `${cookieString}; domain=${cookieDomain}`
|
|
465
|
+
: cookieString;
|
|
417
466
|
}
|
|
418
467
|
else if (strat === "baseLocale") {
|
|
419
468
|
// nothing to be set here. baseLocale is only a fallback
|
|
@@ -442,22 +491,34 @@ export let setLocale = (newLocale, options) => {
|
|
|
442
491
|
}
|
|
443
492
|
else if (isCustomStrategy(strat) && customClientStrategies.has(strat)) {
|
|
444
493
|
const handler = customClientStrategies.get(strat);
|
|
445
|
-
handler
|
|
494
|
+
if (handler) {
|
|
495
|
+
let result = handler.setLocale(newLocale);
|
|
496
|
+
// Handle async setLocale
|
|
497
|
+
if (result instanceof Promise) {
|
|
498
|
+
result = result.catch((error) => {
|
|
499
|
+
throw new Error(`Custom strategy "${strat}" setLocale failed.`, {
|
|
500
|
+
cause: error,
|
|
501
|
+
});
|
|
502
|
+
});
|
|
503
|
+
customSetLocalePromises.push(result);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
446
506
|
}
|
|
447
507
|
}
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
window.location.href = newLocation;
|
|
455
|
-
}
|
|
456
|
-
else {
|
|
457
|
-
// reload the page to reflect the new locale
|
|
458
|
-
window.location.reload();
|
|
508
|
+
const runReload = () => {
|
|
509
|
+
if (!isServer &&
|
|
510
|
+
optionsWithDefaults.reload &&
|
|
511
|
+
window.location &&
|
|
512
|
+
newLocale !== currentLocale) {
|
|
513
|
+
navigateOrReload(newLocation);
|
|
459
514
|
}
|
|
515
|
+
};
|
|
516
|
+
if (customSetLocalePromises.length) {
|
|
517
|
+
return Promise.all(customSetLocalePromises).then(() => {
|
|
518
|
+
runReload();
|
|
519
|
+
});
|
|
460
520
|
}
|
|
521
|
+
runReload();
|
|
461
522
|
return;
|
|
462
523
|
};
|
|
463
524
|
/**
|
|
@@ -472,10 +533,10 @@ export let setLocale = (newLocale, options) => {
|
|
|
472
533
|
* return Cookies.set('locale', newLocale)
|
|
473
534
|
* });
|
|
474
535
|
*
|
|
475
|
-
* @param {
|
|
536
|
+
* @param {SetLocaleFn} fn
|
|
476
537
|
*/
|
|
477
538
|
export const overwriteSetLocale = (fn) => {
|
|
478
|
-
setLocale = fn;
|
|
539
|
+
setLocale = /** @type {SetLocaleFn} */ (fn);
|
|
479
540
|
};
|
|
480
541
|
|
|
481
542
|
/**
|
|
@@ -522,7 +583,11 @@ export let overwriteGetUrlOrigin = (fn) => {
|
|
|
522
583
|
* @returns {locale is Locale}
|
|
523
584
|
*/
|
|
524
585
|
export function isLocale(locale) {
|
|
525
|
-
|
|
586
|
+
if (typeof locale !== "string")
|
|
587
|
+
return false;
|
|
588
|
+
return !locale
|
|
589
|
+
? false
|
|
590
|
+
: locales.some((item) => item.toLowerCase() === locale.toLowerCase());
|
|
526
591
|
}
|
|
527
592
|
|
|
528
593
|
/**
|
|
@@ -533,10 +598,15 @@ export function isLocale(locale) {
|
|
|
533
598
|
* @throws {Error} If the input is not a locale.
|
|
534
599
|
*/
|
|
535
600
|
export function assertIsLocale(input) {
|
|
536
|
-
if (
|
|
601
|
+
if (typeof input !== "string") {
|
|
602
|
+
throw new Error(`Invalid locale: ${input}. Expected a string.`);
|
|
603
|
+
}
|
|
604
|
+
const lowerInput = input.toLowerCase();
|
|
605
|
+
const matchedLocale = locales.find((item) => item.toLowerCase() === lowerInput);
|
|
606
|
+
if (!matchedLocale) {
|
|
537
607
|
throw new Error(`Invalid locale: ${input}. Expected one of: ${locales.join(", ")}`);
|
|
538
608
|
}
|
|
539
|
-
return
|
|
609
|
+
return matchedLocale;
|
|
540
610
|
}
|
|
541
611
|
|
|
542
612
|
/**
|
|
@@ -549,6 +619,9 @@ export function assertIsLocale(input) {
|
|
|
549
619
|
* they are defined. If a strategy returns an invalid locale,
|
|
550
620
|
* it will fall back to the next strategy.
|
|
551
621
|
*
|
|
622
|
+
* Note: Custom server strategies are not supported in this synchronous version.
|
|
623
|
+
* Use `extractLocaleFromRequestAsync` if you need custom server strategies with async getLocale methods.
|
|
624
|
+
*
|
|
552
625
|
* @example
|
|
553
626
|
* const locale = extractLocaleFromRequest(request);
|
|
554
627
|
*
|
|
@@ -581,9 +654,10 @@ export const extractLocaleFromRequest = (request) => {
|
|
|
581
654
|
else if (strat === "localStorage") {
|
|
582
655
|
continue;
|
|
583
656
|
}
|
|
584
|
-
else if (isCustomStrategy(strat)
|
|
585
|
-
|
|
586
|
-
|
|
657
|
+
else if (isCustomStrategy(strat)) {
|
|
658
|
+
// Custom strategies are not supported in sync version
|
|
659
|
+
// Use extractLocaleFromRequestAsync for custom server strategies
|
|
660
|
+
continue;
|
|
587
661
|
}
|
|
588
662
|
if (locale !== undefined) {
|
|
589
663
|
if (!isLocale(locale)) {
|
|
@@ -597,6 +671,57 @@ export const extractLocaleFromRequest = (request) => {
|
|
|
597
671
|
throw new Error("No locale found. There is an error in your strategy. Try adding 'baseLocale' as the very last strategy. Read more here https://inlang.com/m/gerre34r/library-inlang-paraglideJs/errors#no-locale-found");
|
|
598
672
|
};
|
|
599
673
|
|
|
674
|
+
/**
|
|
675
|
+
* Asynchronously extracts a locale from a request.
|
|
676
|
+
*
|
|
677
|
+
* This function supports async custom server strategies, unlike the synchronous
|
|
678
|
+
* `extractLocaleFromRequest`. Use this function when you have custom server strategies
|
|
679
|
+
* that need to perform asynchronous operations (like database calls) in their getLocale method.
|
|
680
|
+
*
|
|
681
|
+
* The function first processes any custom server strategies asynchronously, then falls back
|
|
682
|
+
* to the synchronous `extractLocaleFromRequest` for all other strategies.
|
|
683
|
+
*
|
|
684
|
+
* @see {@link https://github.com/opral/inlang-paraglide-js/issues/527#issuecomment-2978151022}
|
|
685
|
+
*
|
|
686
|
+
* @example
|
|
687
|
+
* // Basic usage
|
|
688
|
+
* const locale = await extractLocaleFromRequestAsync(request);
|
|
689
|
+
*
|
|
690
|
+
* @example
|
|
691
|
+
* // With custom async server strategy
|
|
692
|
+
* defineCustomServerStrategy("custom-database", {
|
|
693
|
+
* getLocale: async (request) => {
|
|
694
|
+
* const userId = extractUserIdFromRequest(request);
|
|
695
|
+
* return await getUserLocaleFromDatabase(userId);
|
|
696
|
+
* }
|
|
697
|
+
* });
|
|
698
|
+
*
|
|
699
|
+
* const locale = await extractLocaleFromRequestAsync(request);
|
|
700
|
+
*
|
|
701
|
+
* @type {(request: Request) => Promise<Locale>}
|
|
702
|
+
*/
|
|
703
|
+
export const extractLocaleFromRequestAsync = async (request) => {
|
|
704
|
+
/** @type {string|undefined} */
|
|
705
|
+
let locale;
|
|
706
|
+
// Process custom strategies first, in order
|
|
707
|
+
for (const strat of strategy) {
|
|
708
|
+
if (isCustomStrategy(strat) && customServerStrategies.has(strat)) {
|
|
709
|
+
const handler = customServerStrategies.get(strat);
|
|
710
|
+
if (handler) {
|
|
711
|
+
/** @type {string|undefined} */
|
|
712
|
+
locale = await handler.getLocale(request);
|
|
713
|
+
}
|
|
714
|
+
// If we got a valid locale from this custom strategy, use it
|
|
715
|
+
if (locale !== undefined && isLocale(locale)) {
|
|
716
|
+
return assertIsLocale(locale);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
// If no custom strategy provided a valid locale, fall back to sync version
|
|
721
|
+
locale = extractLocaleFromRequest(request);
|
|
722
|
+
return assertIsLocale(locale);
|
|
723
|
+
};
|
|
724
|
+
|
|
600
725
|
/**
|
|
601
726
|
* Extracts a cookie from the document.
|
|
602
727
|
*
|
|
@@ -768,6 +893,8 @@ function defaultUrlPatternExtractLocale(url) {
|
|
|
768
893
|
* For client-side UI components, use `localizeHref()` instead, which provides
|
|
769
894
|
* a more convenient API with relative paths and automatic locale detection.
|
|
770
895
|
*
|
|
896
|
+
* @see https://inlang.com/m/gerre34r/library-inlang-paraglideJs/i18n-routing
|
|
897
|
+
*
|
|
771
898
|
* @example
|
|
772
899
|
* ```typescript
|
|
773
900
|
* // Server middleware example
|
|
@@ -874,6 +1001,8 @@ function localizeUrlDefaultPattern(url, options) {
|
|
|
874
1001
|
* For client-side UI components, use `deLocalizeHref()` instead, which provides
|
|
875
1002
|
* a more convenient API with relative paths.
|
|
876
1003
|
*
|
|
1004
|
+
* @see https://inlang.com/m/gerre34r/library-inlang-paraglideJs/i18n-routing
|
|
1005
|
+
*
|
|
877
1006
|
* @example
|
|
878
1007
|
* ```typescript
|
|
879
1008
|
* // Server middleware example
|
|
@@ -1065,6 +1194,122 @@ export function aggregateGroups(match) {
|
|
|
1065
1194
|
};
|
|
1066
1195
|
}
|
|
1067
1196
|
|
|
1197
|
+
/**
|
|
1198
|
+
* @typedef {object} ShouldRedirectServerInput
|
|
1199
|
+
* @property {Request} request
|
|
1200
|
+
* @property {string | URL} [url]
|
|
1201
|
+
* @property {ReturnType<typeof assertIsLocale>} [locale]
|
|
1202
|
+
*
|
|
1203
|
+
* @typedef {object} ShouldRedirectClientInput
|
|
1204
|
+
* @property {undefined} [request]
|
|
1205
|
+
* @property {string | URL} [url]
|
|
1206
|
+
* @property {ReturnType<typeof assertIsLocale>} [locale]
|
|
1207
|
+
*
|
|
1208
|
+
* @typedef {ShouldRedirectServerInput | ShouldRedirectClientInput} ShouldRedirectInput
|
|
1209
|
+
*
|
|
1210
|
+
* @typedef {object} ShouldRedirectResult
|
|
1211
|
+
* @property {boolean} shouldRedirect - Indicates whether the consumer should perform a redirect.
|
|
1212
|
+
* @property {ReturnType<typeof assertIsLocale>} locale - Locale resolved using the configured strategies.
|
|
1213
|
+
* @property {URL | undefined} redirectUrl - Destination URL when a redirect is required.
|
|
1214
|
+
*/
|
|
1215
|
+
/**
|
|
1216
|
+
* Determines whether a redirect is required to align the current URL with the active locale.
|
|
1217
|
+
*
|
|
1218
|
+
* This helper mirrors the logic that powers `paraglideMiddleware`, but works in both server
|
|
1219
|
+
* and client environments. It evaluates the configured strategies in order, computes the
|
|
1220
|
+
* canonical localized URL, and reports when the current URL does not match.
|
|
1221
|
+
*
|
|
1222
|
+
* When called in the browser without arguments, the current `window.location.href` is used.
|
|
1223
|
+
*
|
|
1224
|
+
* @see https://inlang.com/m/gerre34r/library-inlang-paraglideJs/i18n-routing#client-side-redirects
|
|
1225
|
+
*
|
|
1226
|
+
* @example
|
|
1227
|
+
* // Client side usage (e.g. TanStack Router beforeLoad hook)
|
|
1228
|
+
* async function beforeLoad({ location }) {
|
|
1229
|
+
* const decision = await shouldRedirect({ url: location.href });
|
|
1230
|
+
*
|
|
1231
|
+
* if (decision.shouldRedirect) {
|
|
1232
|
+
* throw redirect({ to: decision.redirectUrl.href });
|
|
1233
|
+
* }
|
|
1234
|
+
* }
|
|
1235
|
+
*
|
|
1236
|
+
* @example
|
|
1237
|
+
* // Server side usage with a Request
|
|
1238
|
+
* export async function handle(request) {
|
|
1239
|
+
* const decision = await shouldRedirect({ request });
|
|
1240
|
+
*
|
|
1241
|
+
* if (decision.shouldRedirect) {
|
|
1242
|
+
* return Response.redirect(decision.redirectUrl, 307);
|
|
1243
|
+
* }
|
|
1244
|
+
*
|
|
1245
|
+
* return render(request, decision.locale);
|
|
1246
|
+
* }
|
|
1247
|
+
*
|
|
1248
|
+
* @param {ShouldRedirectInput} [input]
|
|
1249
|
+
* @returns {Promise<ShouldRedirectResult>}
|
|
1250
|
+
*/
|
|
1251
|
+
export async function shouldRedirect(input = {}) {
|
|
1252
|
+
const locale = /** @type {ReturnType<typeof assertIsLocale>} */ (await resolveLocale(input));
|
|
1253
|
+
if (!strategy.includes("url")) {
|
|
1254
|
+
return { shouldRedirect: false, locale, redirectUrl: undefined };
|
|
1255
|
+
}
|
|
1256
|
+
const currentUrl = resolveUrl(input);
|
|
1257
|
+
const localizedUrl = localizeUrl(currentUrl.href, { locale });
|
|
1258
|
+
const shouldRedirectToLocalizedUrl = normalizeUrl(localizedUrl.href) !== normalizeUrl(currentUrl.href);
|
|
1259
|
+
return {
|
|
1260
|
+
shouldRedirect: shouldRedirectToLocalizedUrl,
|
|
1261
|
+
locale,
|
|
1262
|
+
redirectUrl: shouldRedirectToLocalizedUrl ? localizedUrl : undefined,
|
|
1263
|
+
};
|
|
1264
|
+
}
|
|
1265
|
+
/**
|
|
1266
|
+
* Resolves the locale either from the provided input or by using the configured strategies.
|
|
1267
|
+
*
|
|
1268
|
+
* @param {ShouldRedirectInput} input
|
|
1269
|
+
* @returns {Promise<ReturnType<typeof assertIsLocale>>}
|
|
1270
|
+
*/
|
|
1271
|
+
async function resolveLocale(input) {
|
|
1272
|
+
if (input.locale) {
|
|
1273
|
+
return assertIsLocale(input.locale);
|
|
1274
|
+
}
|
|
1275
|
+
if (input.request) {
|
|
1276
|
+
return extractLocaleFromRequestAsync(input.request);
|
|
1277
|
+
}
|
|
1278
|
+
return getLocale();
|
|
1279
|
+
}
|
|
1280
|
+
/**
|
|
1281
|
+
* Resolves the current URL from the provided input or runtime context.
|
|
1282
|
+
*
|
|
1283
|
+
* @param {ShouldRedirectInput} input
|
|
1284
|
+
* @returns {URL}
|
|
1285
|
+
*/
|
|
1286
|
+
function resolveUrl(input) {
|
|
1287
|
+
if (input.request) {
|
|
1288
|
+
return new URL(input.request.url);
|
|
1289
|
+
}
|
|
1290
|
+
if (input.url instanceof URL) {
|
|
1291
|
+
return new URL(input.url.href);
|
|
1292
|
+
}
|
|
1293
|
+
if (typeof input.url === "string") {
|
|
1294
|
+
return new URL(input.url, getUrlOrigin());
|
|
1295
|
+
}
|
|
1296
|
+
if (typeof window !== "undefined" && window?.location?.href) {
|
|
1297
|
+
return new URL(window.location.href);
|
|
1298
|
+
}
|
|
1299
|
+
throw new Error("shouldRedirect() requires either a request, an absolute URL, or must run in a browser environment.");
|
|
1300
|
+
}
|
|
1301
|
+
/**
|
|
1302
|
+
* Normalize url for comparison by stripping the trailing slash.
|
|
1303
|
+
*
|
|
1304
|
+
* @param {string} url
|
|
1305
|
+
* @returns {string}
|
|
1306
|
+
*/
|
|
1307
|
+
function normalizeUrl(url) {
|
|
1308
|
+
const urlObj = new URL(url);
|
|
1309
|
+
urlObj.pathname = urlObj.pathname.replace(/\/$/, "");
|
|
1310
|
+
return urlObj.href;
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1068
1313
|
/**
|
|
1069
1314
|
* High-level URL localization function optimized for client-side UI usage.
|
|
1070
1315
|
*
|
|
@@ -1076,6 +1321,8 @@ export function aggregateGroups(match) {
|
|
|
1076
1321
|
* - Automatically detects current locale if not specified
|
|
1077
1322
|
* - Handles string input/output instead of URL objects
|
|
1078
1323
|
*
|
|
1324
|
+
* @see https://inlang.com/m/gerre34r/library-inlang-paraglideJs/i18n-routing
|
|
1325
|
+
*
|
|
1079
1326
|
* @example
|
|
1080
1327
|
* ```typescript
|
|
1081
1328
|
* // In a React/Vue/Svelte component
|
|
@@ -1134,6 +1381,8 @@ export function localizeHref(href, options) {
|
|
|
1134
1381
|
* - Returns relative paths when possible
|
|
1135
1382
|
* - Handles string input/output instead of URL objects
|
|
1136
1383
|
*
|
|
1384
|
+
* @see https://inlang.com/m/gerre34r/library-inlang-paraglideJs/i18n-routing
|
|
1385
|
+
*
|
|
1137
1386
|
* @example
|
|
1138
1387
|
* ```typescript
|
|
1139
1388
|
* // In a React/Vue/Svelte component
|
|
@@ -1161,7 +1410,6 @@ export function localizeHref(href, options) {
|
|
|
1161
1410
|
*
|
|
1162
1411
|
* @param {string} href - The href to de-localize (can be relative or absolute)
|
|
1163
1412
|
* @returns {string} The de-localized href, relative if input was relative
|
|
1164
|
-
* @see deLocalizeUrl - For low-level URL de-localization in server contexts
|
|
1165
1413
|
*/
|
|
1166
1414
|
export function deLocalizeHref(href) {
|
|
1167
1415
|
const url = new URL(href, getUrlOrigin());
|
|
@@ -1188,26 +1436,47 @@ export function trackMessageCall(safeModuleId, locale) {
|
|
|
1188
1436
|
}
|
|
1189
1437
|
|
|
1190
1438
|
/**
|
|
1191
|
-
* Generates
|
|
1439
|
+
* Generates localized URL variants for all provided URLs based on your configured locales and URL patterns.
|
|
1192
1440
|
*
|
|
1193
|
-
* This is
|
|
1194
|
-
*
|
|
1441
|
+
* This function is essential for Static Site Generation (SSG) where you need to tell your framework
|
|
1442
|
+
* which pages to pre-render at build time. It's also useful for generating sitemaps and
|
|
1443
|
+
* `<link rel="alternate" hreflang>` tags for SEO.
|
|
1444
|
+
*
|
|
1445
|
+
* The function respects your `urlPatterns` configuration - if you have translated pathnames
|
|
1446
|
+
* (e.g., `/about` → `/ueber-uns` for German), it will generate the correct localized paths.
|
|
1447
|
+
*
|
|
1448
|
+
* @see https://inlang.com/m/gerre34r/library-inlang-paraglideJs/static-site-generation
|
|
1195
1449
|
*
|
|
1196
1450
|
* @example
|
|
1197
|
-
*
|
|
1198
|
-
* const
|
|
1199
|
-
* "
|
|
1200
|
-
* "
|
|
1451
|
+
* // Basic usage - generate all locale variants for a list of paths
|
|
1452
|
+
* const localizedUrls = generateStaticLocalizedUrls([
|
|
1453
|
+
* "/",
|
|
1454
|
+
* "/about",
|
|
1455
|
+
* "/blog/post-1",
|
|
1201
1456
|
* ]);
|
|
1202
|
-
*
|
|
1203
|
-
*
|
|
1204
|
-
* urls[2].href // => "https://example.com/de/about"
|
|
1205
|
-
* urls[3].href // => "https://example.com/de/blog"
|
|
1206
|
-
* ...
|
|
1207
|
-
* ```
|
|
1457
|
+
* // Returns URL objects for each locale:
|
|
1458
|
+
* // ["/en/", "/de/", "/en/about", "/de/about", "/en/blog/post-1", "/de/blog/post-1"]
|
|
1208
1459
|
*
|
|
1209
|
-
* @
|
|
1210
|
-
*
|
|
1460
|
+
* @example
|
|
1461
|
+
* // Use with framework SSG APIs
|
|
1462
|
+
* // SvelteKit
|
|
1463
|
+
* export function entries() {
|
|
1464
|
+
* const paths = ["/", "/about", "/contact"];
|
|
1465
|
+
* return generateStaticLocalizedUrls(paths).map(url => ({
|
|
1466
|
+
* locale: extractLocaleFromUrl(url)
|
|
1467
|
+
* }));
|
|
1468
|
+
* }
|
|
1469
|
+
*
|
|
1470
|
+
* @example
|
|
1471
|
+
* // Sitemap generation
|
|
1472
|
+
* const allPages = ["/", "/about", "/blog"];
|
|
1473
|
+
* const sitemapUrls = generateStaticLocalizedUrls(allPages);
|
|
1474
|
+
*
|
|
1475
|
+
* @param {(string | URL)[]} urls - List of canonical URLs or paths to generate localized versions for.
|
|
1476
|
+
* Can be absolute URLs (`https://example.com/about`) or paths (`/about`).
|
|
1477
|
+
* Paths are resolved against `http://localhost` internally.
|
|
1478
|
+
* @returns {URL[]} Array of URL objects representing all localized variants.
|
|
1479
|
+
* The order follows each input URL with all its locale variants before moving to the next URL.
|
|
1211
1480
|
*/
|
|
1212
1481
|
export function generateStaticLocalizedUrls(urls) {
|
|
1213
1482
|
const localizedUrls = new Set();
|
|
@@ -1290,52 +1559,54 @@ export function generateStaticLocalizedUrls(urls) {
|
|
|
1290
1559
|
* @typedef {Array<Strategy>} Strategies
|
|
1291
1560
|
*/
|
|
1292
1561
|
/**
|
|
1293
|
-
* @typedef {{ getLocale: (request?: Request) => string | undefined }} CustomServerStrategyHandler
|
|
1562
|
+
* @typedef {{ getLocale: (request?: Request) => Promise<string | undefined> | (string | undefined) }} CustomServerStrategyHandler
|
|
1294
1563
|
*/
|
|
1295
1564
|
/**
|
|
1296
|
-
* @typedef {{ getLocale: () => string | undefined, setLocale: (locale: string) => void }} CustomClientStrategyHandler
|
|
1565
|
+
* @typedef {{ getLocale: () => Promise<string|undefined> | (string | undefined), setLocale: (locale: string) => Promise<void> | void }} CustomClientStrategyHandler
|
|
1297
1566
|
*/
|
|
1567
|
+
/** @type {Map<string, CustomServerStrategyHandler>} */
|
|
1298
1568
|
export const customServerStrategies = new Map();
|
|
1569
|
+
/** @type {Map<string, CustomClientStrategyHandler>} */
|
|
1299
1570
|
export const customClientStrategies = new Map();
|
|
1300
1571
|
/**
|
|
1301
1572
|
* Checks if the given strategy is a custom strategy.
|
|
1302
1573
|
*
|
|
1303
1574
|
* @param {any} strategy The name of the custom strategy to validate.
|
|
1304
|
-
* Must be a string that starts with "custom-" followed by alphanumeric characters.
|
|
1575
|
+
* Must be a string that starts with "custom-" followed by alphanumeric characters, hyphens, or underscores.
|
|
1305
1576
|
* @returns {boolean} Returns true if it is a custom strategy, false otherwise.
|
|
1306
1577
|
*/
|
|
1307
1578
|
export function isCustomStrategy(strategy) {
|
|
1308
|
-
return typeof strategy === "string" && /^custom-[A-Za-z0-
|
|
1579
|
+
return (typeof strategy === "string" && /^custom-[A-Za-z0-9_-]+$/.test(strategy));
|
|
1309
1580
|
}
|
|
1310
1581
|
/**
|
|
1311
1582
|
* Defines a custom strategy that is executed on the server.
|
|
1312
1583
|
*
|
|
1313
|
-
* @
|
|
1314
|
-
*
|
|
1584
|
+
* @see https://inlang.com/m/gerre34r/library-inlang-paraglideJs/strategy#write-your-own-strategy
|
|
1585
|
+
*
|
|
1586
|
+
* @param {any} strategy The name of the custom strategy to define. Must follow the pattern custom-name with alphanumeric characters, hyphens, or underscores.
|
|
1315
1587
|
* @param {CustomServerStrategyHandler} handler The handler for the custom strategy, which should implement
|
|
1316
|
-
* the method
|
|
1588
|
+
* the method getLocale.
|
|
1317
1589
|
* @returns {void}
|
|
1318
1590
|
*/
|
|
1319
1591
|
export function defineCustomServerStrategy(strategy, handler) {
|
|
1320
1592
|
if (!isCustomStrategy(strategy)) {
|
|
1321
|
-
throw new Error(`Invalid custom strategy: "${strategy}". Must be a custom strategy following the pattern custom
|
|
1322
|
-
" where <name> contains only alphanumeric characters.");
|
|
1593
|
+
throw new Error(`Invalid custom strategy: "${strategy}". Must be a custom strategy following the pattern custom-name.`);
|
|
1323
1594
|
}
|
|
1324
1595
|
customServerStrategies.set(strategy, handler);
|
|
1325
1596
|
}
|
|
1326
1597
|
/**
|
|
1327
1598
|
* Defines a custom strategy that is executed on the client.
|
|
1328
1599
|
*
|
|
1329
|
-
* @
|
|
1330
|
-
*
|
|
1600
|
+
* @see https://inlang.com/m/gerre34r/library-inlang-paraglideJs/strategy#write-your-own-strategy
|
|
1601
|
+
*
|
|
1602
|
+
* @param {any} strategy The name of the custom strategy to define. Must follow the pattern custom-name with alphanumeric characters, hyphens, or underscores.
|
|
1331
1603
|
* @param {CustomClientStrategyHandler} handler The handler for the custom strategy, which should implement the
|
|
1332
|
-
* methods
|
|
1604
|
+
* methods getLocale and setLocale.
|
|
1333
1605
|
* @returns {void}
|
|
1334
1606
|
*/
|
|
1335
1607
|
export function defineCustomClientStrategy(strategy, handler) {
|
|
1336
1608
|
if (!isCustomStrategy(strategy)) {
|
|
1337
|
-
throw new Error(`Invalid custom strategy: "${strategy}". Must be a custom strategy following the pattern custom
|
|
1338
|
-
" where <name> contains only alphanumeric characters.");
|
|
1609
|
+
throw new Error(`Invalid custom strategy: "${strategy}". Must be a custom strategy following the pattern custom-name.`);
|
|
1339
1610
|
}
|
|
1340
1611
|
customClientStrategies.set(strategy, handler);
|
|
1341
1612
|
}
|
|
@@ -1351,3 +1622,45 @@ export function defineCustomClientStrategy(strategy, handler) {
|
|
|
1351
1622
|
* @typedef {(typeof locales)[number]} Locale
|
|
1352
1623
|
*/
|
|
1353
1624
|
|
|
1625
|
+
/**
|
|
1626
|
+
* A branded type representing a localized string.
|
|
1627
|
+
*
|
|
1628
|
+
* Message functions return this type instead of `string`, enabling TypeScript
|
|
1629
|
+
* to distinguish translated strings from regular strings at compile time.
|
|
1630
|
+
* This allows you to enforce that only properly localized content is used
|
|
1631
|
+
* in your UI components.
|
|
1632
|
+
*
|
|
1633
|
+
* Since `LocalizedString` is a branded subtype of `string`, it remains fully
|
|
1634
|
+
* backward compatible—you can pass it anywhere a `string` is expected.
|
|
1635
|
+
*
|
|
1636
|
+
* @example
|
|
1637
|
+
* // Enforce localized strings in your components
|
|
1638
|
+
* function PageTitle(props: { title: LocalizedString }) {
|
|
1639
|
+
* return <h1>{props.title}</h1>
|
|
1640
|
+
* }
|
|
1641
|
+
*
|
|
1642
|
+
* // ✅ Correct: using a message function
|
|
1643
|
+
* <PageTitle title={m.welcome_title()} />
|
|
1644
|
+
*
|
|
1645
|
+
* // ❌ Type error: raw strings are not LocalizedString
|
|
1646
|
+
* <PageTitle title="Welcome" />
|
|
1647
|
+
*
|
|
1648
|
+
* @example
|
|
1649
|
+
* // LocalizedString is assignable to string (backward compatible)
|
|
1650
|
+
* const localized: LocalizedString = m.greeting()
|
|
1651
|
+
* const str: string = localized // ✅ works fine
|
|
1652
|
+
*
|
|
1653
|
+
* // But string is not assignable to LocalizedString
|
|
1654
|
+
* const raw: LocalizedString = "Hello" // ❌ Type error
|
|
1655
|
+
*
|
|
1656
|
+
* @example
|
|
1657
|
+
* // Catches accidental string concatenation
|
|
1658
|
+
* function showMessage(msg: LocalizedString) { ... }
|
|
1659
|
+
*
|
|
1660
|
+
* showMessage(m.hello()) // ✅
|
|
1661
|
+
* showMessage("Hello " + userName) // ❌ Type error
|
|
1662
|
+
* showMessage(m.hello_user({ name: userName })) // ✅ use params instead
|
|
1663
|
+
*
|
|
1664
|
+
* @typedef {string & { readonly __brand: 'LocalizedString' }} LocalizedString
|
|
1665
|
+
*/
|
|
1666
|
+
|