intor 2.4.14 → 2.4.16
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/core/export/index.js +2 -1
- package/dist/core/src/core/locale/canonicalize-locale.js +23 -0
- package/dist/core/src/core/locale/match-locale.js +57 -0
- package/dist/core/src/core/locale/parse-locale.js +27 -0
- package/dist/core/src/routing/inbound/resolve-locale/resolve-locale.js +3 -3
- package/dist/express/src/core/locale/canonicalize-locale.js +23 -0
- package/dist/express/src/core/locale/match-locale.js +57 -0
- package/dist/express/src/core/locale/parse-locale.js +27 -0
- package/dist/express/src/routing/inbound/resolve-locale/resolve-locale.js +3 -3
- package/dist/fastify/src/core/locale/canonicalize-locale.js +23 -0
- package/dist/fastify/src/core/locale/match-locale.js +57 -0
- package/dist/fastify/src/core/locale/parse-locale.js +27 -0
- package/dist/fastify/src/routing/inbound/resolve-locale/resolve-locale.js +3 -3
- package/dist/hono/src/core/locale/canonicalize-locale.js +23 -0
- package/dist/hono/src/core/locale/match-locale.js +57 -0
- package/dist/hono/src/core/locale/parse-locale.js +27 -0
- package/dist/hono/src/routing/inbound/resolve-locale/resolve-locale.js +3 -3
- package/dist/next/src/adapters/next/server/get-locale.js +4 -4
- package/dist/next/src/core/locale/canonicalize-locale.js +23 -0
- package/dist/next/src/core/locale/match-locale.js +57 -0
- package/dist/next/src/core/locale/parse-locale.js +27 -0
- package/dist/next/src/routing/inbound/resolve-locale/resolve-locale.js +3 -3
- package/dist/react/src/client/react/provider/intor-provider.js +6 -0
- package/dist/react/src/client/shared/helpers/get-client-locale.js +2 -2
- package/dist/react/src/core/locale/canonicalize-locale.js +23 -0
- package/dist/react/src/core/locale/match-locale.js +57 -0
- package/dist/react/src/core/locale/parse-locale.js +27 -0
- package/dist/svelte/src/client/shared/helpers/get-client-locale.js +2 -2
- package/dist/svelte/src/client/svelte/provider/create-intor-store.js +3 -1
- package/dist/svelte/src/core/locale/canonicalize-locale.js +23 -0
- package/dist/svelte/src/core/locale/match-locale.js +57 -0
- package/dist/svelte/src/core/locale/parse-locale.js +27 -0
- package/dist/svelte-kit/src/core/locale/canonicalize-locale.js +23 -0
- package/dist/svelte-kit/src/core/locale/match-locale.js +57 -0
- package/dist/svelte-kit/src/core/locale/parse-locale.js +27 -0
- package/dist/svelte-kit/src/routing/inbound/resolve-locale/resolve-locale.js +3 -3
- package/dist/types/export/index.d.ts +2 -1
- package/dist/types/src/core/index.d.ts +2 -1
- package/dist/types/src/core/locale/canonicalize-locale.d.ts +11 -0
- package/dist/types/src/core/locale/index.d.ts +1 -0
- package/dist/types/src/core/locale/match-locale.d.ts +16 -0
- package/dist/types/src/core/locale/parse-locale.d.ts +14 -0
- package/dist/types/src/core/utils/index.d.ts +1 -1
- package/dist/types/src/core/utils/normalizers/index.d.ts +0 -1
- package/dist/vue/src/client/shared/helpers/get-client-locale.js +2 -2
- package/dist/vue/src/client/vue/provider/intor-provider.js +21 -14
- package/dist/vue/src/core/locale/canonicalize-locale.js +23 -0
- package/dist/vue/src/core/locale/match-locale.js +57 -0
- package/dist/vue/src/core/locale/parse-locale.js +27 -0
- package/package.json +27 -27
- package/dist/core/src/core/utils/normalizers/normalize-locale.js +0 -59
- package/dist/express/src/core/utils/normalizers/normalize-locale.js +0 -59
- package/dist/fastify/src/core/utils/normalizers/normalize-locale.js +0 -59
- package/dist/hono/src/core/utils/normalizers/normalize-locale.js +0 -59
- package/dist/next/src/core/utils/normalizers/normalize-locale.js +0 -59
- package/dist/react/src/core/utils/normalizers/normalize-locale.js +0 -59
- package/dist/svelte/src/core/utils/normalizers/normalize-locale.js +0 -59
- package/dist/svelte-kit/src/core/utils/normalizers/normalize-locale.js +0 -59
- package/dist/types/src/core/utils/normalizers/normalize-locale.d.ts +0 -23
- package/dist/vue/src/core/utils/normalizers/normalize-locale.js +0 -59
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parses a canonical locale tag into structural components.
|
|
3
|
+
*
|
|
4
|
+
* - Uses `Intl.Locale` when available.
|
|
5
|
+
* - Falls back to minimal parsing (language only) if unavailable.
|
|
6
|
+
*
|
|
7
|
+
* This function does not validate or match locales.
|
|
8
|
+
*/
|
|
9
|
+
function parseLocale(tag) {
|
|
10
|
+
try {
|
|
11
|
+
if (typeof Intl !== "undefined" && typeof Intl.Locale === "function") {
|
|
12
|
+
const locale = new Intl.Locale(tag);
|
|
13
|
+
return {
|
|
14
|
+
language: locale.language,
|
|
15
|
+
script: locale.script,
|
|
16
|
+
region: locale.region,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// fallback below
|
|
22
|
+
}
|
|
23
|
+
const parts = tag.split("-");
|
|
24
|
+
return { language: parts[0].toLowerCase() };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export { parseLocale };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import '../../../core/error/intor-error.js';
|
|
2
|
-
import {
|
|
2
|
+
import { matchLocale } from '../../../core/locale/match-locale.js';
|
|
3
3
|
import 'logry';
|
|
4
4
|
import 'p-limit';
|
|
5
5
|
import 'intor-translator';
|
|
@@ -15,7 +15,7 @@ function resolveLocale(config, context) {
|
|
|
15
15
|
const { localeSources } = config.routing.inbound;
|
|
16
16
|
for (const source of localeSources) {
|
|
17
17
|
const locale = context[source]?.locale;
|
|
18
|
-
const normalized =
|
|
18
|
+
const normalized = matchLocale(locale, config.supportedLocales);
|
|
19
19
|
if (!normalized)
|
|
20
20
|
continue;
|
|
21
21
|
return {
|
|
@@ -24,7 +24,7 @@ function resolveLocale(config, context) {
|
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
26
|
// Fallback: detected is always available
|
|
27
|
-
const fallback =
|
|
27
|
+
const fallback = matchLocale(context.detected.locale, config.supportedLocales) ||
|
|
28
28
|
config.defaultLocale;
|
|
29
29
|
return {
|
|
30
30
|
locale: fallback,
|
|
@@ -57,6 +57,12 @@ function IntorProvider({ value: { config, locale: initialLocale, messages, handl
|
|
|
57
57
|
// ---------------------------------------------------------------------------
|
|
58
58
|
useLocaleEffects(config, locale);
|
|
59
59
|
useMessagesEffects(config, locale, setRuntimeMessages, setInternalIsLoading);
|
|
60
|
+
// Sync internal locale with external prop
|
|
61
|
+
React.useEffect(() => {
|
|
62
|
+
if (initialLocale !== locale)
|
|
63
|
+
setLocaleState(initialLocale);
|
|
64
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
65
|
+
}, [initialLocale]);
|
|
60
66
|
return (jsx(IntorContext.Provider, { value: { config, locale, setLocale, translator }, children: children }));
|
|
61
67
|
}
|
|
62
68
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import '../../../core/error/intor-error.js';
|
|
2
|
-
import {
|
|
2
|
+
import { matchLocale } from '../../../core/locale/match-locale.js';
|
|
3
3
|
import 'logry';
|
|
4
4
|
import 'p-limit';
|
|
5
5
|
import 'intor-translator';
|
|
@@ -16,7 +16,7 @@ function getClientLocale(config) {
|
|
|
16
16
|
// Locale from browser preference
|
|
17
17
|
const browserLocale = detectBrowserLocale();
|
|
18
18
|
const localeCandidate = cookieLocale || browserLocale;
|
|
19
|
-
return
|
|
19
|
+
return matchLocale(localeCandidate, supportedLocales) || defaultLocale;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export { getClientLocale };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonicalizes a BCP 47 locale string.
|
|
3
|
+
*
|
|
4
|
+
* - Uses `Intl.getCanonicalLocales` when available.
|
|
5
|
+
* - Returns the original input if `Intl` is unavailable.
|
|
6
|
+
* - Returns `undefined` for invalid locale input.
|
|
7
|
+
*
|
|
8
|
+
* This function performs normalization only.
|
|
9
|
+
* It does not perform matching or fallback.
|
|
10
|
+
*/
|
|
11
|
+
function canonicalizeLocale(input) {
|
|
12
|
+
try {
|
|
13
|
+
if (typeof Intl === "undefined" || !Intl.getCanonicalLocales) {
|
|
14
|
+
return input;
|
|
15
|
+
}
|
|
16
|
+
return Intl.getCanonicalLocales(input)[0];
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export { canonicalizeLocale };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { canonicalizeLocale } from './canonicalize-locale.js';
|
|
2
|
+
import { parseLocale } from './parse-locale.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Matches a locale candidate against a list of supported locales.
|
|
6
|
+
*
|
|
7
|
+
* Resolution order:
|
|
8
|
+
* 1. Exact canonical match
|
|
9
|
+
* 2. Same language + same script
|
|
10
|
+
* 3. Same language only (only if candidate has no script)
|
|
11
|
+
*
|
|
12
|
+
* Returns `undefined` if no match is found.
|
|
13
|
+
*
|
|
14
|
+
* Notes:
|
|
15
|
+
* - Matching is deterministic and order-sensitive.
|
|
16
|
+
* - Does not perform automatic fallback to default locale.
|
|
17
|
+
* - Does not cross script boundaries.
|
|
18
|
+
*/
|
|
19
|
+
function matchLocale(locale, supportedLocales = []) {
|
|
20
|
+
if (!locale || supportedLocales.length === 0)
|
|
21
|
+
return;
|
|
22
|
+
const canonicalCandidate = canonicalizeLocale(locale);
|
|
23
|
+
if (!canonicalCandidate)
|
|
24
|
+
return;
|
|
25
|
+
const candidateParts = parseLocale(canonicalCandidate);
|
|
26
|
+
const supportedCanonical = supportedLocales.flatMap((original) => {
|
|
27
|
+
const canonical = canonicalizeLocale(original);
|
|
28
|
+
if (!canonical)
|
|
29
|
+
return [];
|
|
30
|
+
return [{ original, canonical, parts: parseLocale(canonical) }];
|
|
31
|
+
});
|
|
32
|
+
// 1. Exact match
|
|
33
|
+
for (const s of supportedCanonical) {
|
|
34
|
+
if (s.canonical === canonicalCandidate) {
|
|
35
|
+
return s.original;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// 2. Same language + same script
|
|
39
|
+
if (candidateParts.script) {
|
|
40
|
+
for (const s of supportedCanonical) {
|
|
41
|
+
if (s.parts.language === candidateParts.language &&
|
|
42
|
+
s.parts.script === candidateParts.script) {
|
|
43
|
+
return s.original;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
// 3. Same language only
|
|
49
|
+
for (const s of supportedCanonical) {
|
|
50
|
+
if (s.parts.language === candidateParts.language) {
|
|
51
|
+
return s.original;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export { matchLocale };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parses a canonical locale tag into structural components.
|
|
3
|
+
*
|
|
4
|
+
* - Uses `Intl.Locale` when available.
|
|
5
|
+
* - Falls back to minimal parsing (language only) if unavailable.
|
|
6
|
+
*
|
|
7
|
+
* This function does not validate or match locales.
|
|
8
|
+
*/
|
|
9
|
+
function parseLocale(tag) {
|
|
10
|
+
try {
|
|
11
|
+
if (typeof Intl !== "undefined" && typeof Intl.Locale === "function") {
|
|
12
|
+
const locale = new Intl.Locale(tag);
|
|
13
|
+
return {
|
|
14
|
+
language: locale.language,
|
|
15
|
+
script: locale.script,
|
|
16
|
+
region: locale.region,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// fallback below
|
|
22
|
+
}
|
|
23
|
+
const parts = tag.split("-");
|
|
24
|
+
return { language: parts[0].toLowerCase() };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export { parseLocale };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import '../../../core/error/intor-error.js';
|
|
2
|
-
import {
|
|
2
|
+
import { matchLocale } from '../../../core/locale/match-locale.js';
|
|
3
3
|
import 'logry';
|
|
4
4
|
import 'p-limit';
|
|
5
5
|
import 'intor-translator';
|
|
@@ -16,7 +16,7 @@ function getClientLocale(config) {
|
|
|
16
16
|
// Locale from browser preference
|
|
17
17
|
const browserLocale = detectBrowserLocale();
|
|
18
18
|
const localeCandidate = cookieLocale || browserLocale;
|
|
19
|
-
return
|
|
19
|
+
return matchLocale(localeCandidate, supportedLocales) || defaultLocale;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export { getClientLocale };
|
|
@@ -26,7 +26,9 @@ function createIntorStore({ config, locale: initialLocale, messages, handlers, p
|
|
|
26
26
|
// ---------------------------------------------------------------------------
|
|
27
27
|
// Effective state
|
|
28
28
|
// ---------------------------------------------------------------------------
|
|
29
|
-
const externalIsLoadingStore =
|
|
29
|
+
const externalIsLoadingStore = typeof externalIsLoading === "object" && "subscribe" in externalIsLoading
|
|
30
|
+
? externalIsLoading
|
|
31
|
+
: readable(!!externalIsLoading);
|
|
30
32
|
// external > internal
|
|
31
33
|
const effectiveIsLoading = derived([externalIsLoadingStore, internalIsLoading], ([$external, $internal]) => $external || $internal);
|
|
32
34
|
// runtime (client refetch) > initial > config (static)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonicalizes a BCP 47 locale string.
|
|
3
|
+
*
|
|
4
|
+
* - Uses `Intl.getCanonicalLocales` when available.
|
|
5
|
+
* - Returns the original input if `Intl` is unavailable.
|
|
6
|
+
* - Returns `undefined` for invalid locale input.
|
|
7
|
+
*
|
|
8
|
+
* This function performs normalization only.
|
|
9
|
+
* It does not perform matching or fallback.
|
|
10
|
+
*/
|
|
11
|
+
function canonicalizeLocale(input) {
|
|
12
|
+
try {
|
|
13
|
+
if (typeof Intl === "undefined" || !Intl.getCanonicalLocales) {
|
|
14
|
+
return input;
|
|
15
|
+
}
|
|
16
|
+
return Intl.getCanonicalLocales(input)[0];
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export { canonicalizeLocale };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { canonicalizeLocale } from './canonicalize-locale.js';
|
|
2
|
+
import { parseLocale } from './parse-locale.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Matches a locale candidate against a list of supported locales.
|
|
6
|
+
*
|
|
7
|
+
* Resolution order:
|
|
8
|
+
* 1. Exact canonical match
|
|
9
|
+
* 2. Same language + same script
|
|
10
|
+
* 3. Same language only (only if candidate has no script)
|
|
11
|
+
*
|
|
12
|
+
* Returns `undefined` if no match is found.
|
|
13
|
+
*
|
|
14
|
+
* Notes:
|
|
15
|
+
* - Matching is deterministic and order-sensitive.
|
|
16
|
+
* - Does not perform automatic fallback to default locale.
|
|
17
|
+
* - Does not cross script boundaries.
|
|
18
|
+
*/
|
|
19
|
+
function matchLocale(locale, supportedLocales = []) {
|
|
20
|
+
if (!locale || supportedLocales.length === 0)
|
|
21
|
+
return;
|
|
22
|
+
const canonicalCandidate = canonicalizeLocale(locale);
|
|
23
|
+
if (!canonicalCandidate)
|
|
24
|
+
return;
|
|
25
|
+
const candidateParts = parseLocale(canonicalCandidate);
|
|
26
|
+
const supportedCanonical = supportedLocales.flatMap((original) => {
|
|
27
|
+
const canonical = canonicalizeLocale(original);
|
|
28
|
+
if (!canonical)
|
|
29
|
+
return [];
|
|
30
|
+
return [{ original, canonical, parts: parseLocale(canonical) }];
|
|
31
|
+
});
|
|
32
|
+
// 1. Exact match
|
|
33
|
+
for (const s of supportedCanonical) {
|
|
34
|
+
if (s.canonical === canonicalCandidate) {
|
|
35
|
+
return s.original;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// 2. Same language + same script
|
|
39
|
+
if (candidateParts.script) {
|
|
40
|
+
for (const s of supportedCanonical) {
|
|
41
|
+
if (s.parts.language === candidateParts.language &&
|
|
42
|
+
s.parts.script === candidateParts.script) {
|
|
43
|
+
return s.original;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
// 3. Same language only
|
|
49
|
+
for (const s of supportedCanonical) {
|
|
50
|
+
if (s.parts.language === candidateParts.language) {
|
|
51
|
+
return s.original;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export { matchLocale };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parses a canonical locale tag into structural components.
|
|
3
|
+
*
|
|
4
|
+
* - Uses `Intl.Locale` when available.
|
|
5
|
+
* - Falls back to minimal parsing (language only) if unavailable.
|
|
6
|
+
*
|
|
7
|
+
* This function does not validate or match locales.
|
|
8
|
+
*/
|
|
9
|
+
function parseLocale(tag) {
|
|
10
|
+
try {
|
|
11
|
+
if (typeof Intl !== "undefined" && typeof Intl.Locale === "function") {
|
|
12
|
+
const locale = new Intl.Locale(tag);
|
|
13
|
+
return {
|
|
14
|
+
language: locale.language,
|
|
15
|
+
script: locale.script,
|
|
16
|
+
region: locale.region,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// fallback below
|
|
22
|
+
}
|
|
23
|
+
const parts = tag.split("-");
|
|
24
|
+
return { language: parts[0].toLowerCase() };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export { parseLocale };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonicalizes a BCP 47 locale string.
|
|
3
|
+
*
|
|
4
|
+
* - Uses `Intl.getCanonicalLocales` when available.
|
|
5
|
+
* - Returns the original input if `Intl` is unavailable.
|
|
6
|
+
* - Returns `undefined` for invalid locale input.
|
|
7
|
+
*
|
|
8
|
+
* This function performs normalization only.
|
|
9
|
+
* It does not perform matching or fallback.
|
|
10
|
+
*/
|
|
11
|
+
function canonicalizeLocale(input) {
|
|
12
|
+
try {
|
|
13
|
+
if (typeof Intl === "undefined" || !Intl.getCanonicalLocales) {
|
|
14
|
+
return input;
|
|
15
|
+
}
|
|
16
|
+
return Intl.getCanonicalLocales(input)[0];
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export { canonicalizeLocale };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { canonicalizeLocale } from './canonicalize-locale.js';
|
|
2
|
+
import { parseLocale } from './parse-locale.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Matches a locale candidate against a list of supported locales.
|
|
6
|
+
*
|
|
7
|
+
* Resolution order:
|
|
8
|
+
* 1. Exact canonical match
|
|
9
|
+
* 2. Same language + same script
|
|
10
|
+
* 3. Same language only (only if candidate has no script)
|
|
11
|
+
*
|
|
12
|
+
* Returns `undefined` if no match is found.
|
|
13
|
+
*
|
|
14
|
+
* Notes:
|
|
15
|
+
* - Matching is deterministic and order-sensitive.
|
|
16
|
+
* - Does not perform automatic fallback to default locale.
|
|
17
|
+
* - Does not cross script boundaries.
|
|
18
|
+
*/
|
|
19
|
+
function matchLocale(locale, supportedLocales = []) {
|
|
20
|
+
if (!locale || supportedLocales.length === 0)
|
|
21
|
+
return;
|
|
22
|
+
const canonicalCandidate = canonicalizeLocale(locale);
|
|
23
|
+
if (!canonicalCandidate)
|
|
24
|
+
return;
|
|
25
|
+
const candidateParts = parseLocale(canonicalCandidate);
|
|
26
|
+
const supportedCanonical = supportedLocales.flatMap((original) => {
|
|
27
|
+
const canonical = canonicalizeLocale(original);
|
|
28
|
+
if (!canonical)
|
|
29
|
+
return [];
|
|
30
|
+
return [{ original, canonical, parts: parseLocale(canonical) }];
|
|
31
|
+
});
|
|
32
|
+
// 1. Exact match
|
|
33
|
+
for (const s of supportedCanonical) {
|
|
34
|
+
if (s.canonical === canonicalCandidate) {
|
|
35
|
+
return s.original;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// 2. Same language + same script
|
|
39
|
+
if (candidateParts.script) {
|
|
40
|
+
for (const s of supportedCanonical) {
|
|
41
|
+
if (s.parts.language === candidateParts.language &&
|
|
42
|
+
s.parts.script === candidateParts.script) {
|
|
43
|
+
return s.original;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
// 3. Same language only
|
|
49
|
+
for (const s of supportedCanonical) {
|
|
50
|
+
if (s.parts.language === candidateParts.language) {
|
|
51
|
+
return s.original;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export { matchLocale };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parses a canonical locale tag into structural components.
|
|
3
|
+
*
|
|
4
|
+
* - Uses `Intl.Locale` when available.
|
|
5
|
+
* - Falls back to minimal parsing (language only) if unavailable.
|
|
6
|
+
*
|
|
7
|
+
* This function does not validate or match locales.
|
|
8
|
+
*/
|
|
9
|
+
function parseLocale(tag) {
|
|
10
|
+
try {
|
|
11
|
+
if (typeof Intl !== "undefined" && typeof Intl.Locale === "function") {
|
|
12
|
+
const locale = new Intl.Locale(tag);
|
|
13
|
+
return {
|
|
14
|
+
language: locale.language,
|
|
15
|
+
script: locale.script,
|
|
16
|
+
region: locale.region,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// fallback below
|
|
22
|
+
}
|
|
23
|
+
const parts = tag.split("-");
|
|
24
|
+
return { language: parts[0].toLowerCase() };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export { parseLocale };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import '../../../core/error/intor-error.js';
|
|
2
|
-
import {
|
|
2
|
+
import { matchLocale } from '../../../core/locale/match-locale.js';
|
|
3
3
|
import 'logry';
|
|
4
4
|
import 'p-limit';
|
|
5
5
|
import 'intor-translator';
|
|
@@ -15,7 +15,7 @@ function resolveLocale(config, context) {
|
|
|
15
15
|
const { localeSources } = config.routing.inbound;
|
|
16
16
|
for (const source of localeSources) {
|
|
17
17
|
const locale = context[source]?.locale;
|
|
18
|
-
const normalized =
|
|
18
|
+
const normalized = matchLocale(locale, config.supportedLocales);
|
|
19
19
|
if (!normalized)
|
|
20
20
|
continue;
|
|
21
21
|
return {
|
|
@@ -24,7 +24,7 @@ function resolveLocale(config, context) {
|
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
26
|
// Fallback: detected is always available
|
|
27
|
-
const fallback =
|
|
27
|
+
const fallback = matchLocale(context.detected.locale, config.supportedLocales) ||
|
|
28
28
|
config.defaultLocale;
|
|
29
29
|
return {
|
|
30
30
|
locale: fallback,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
export { LOCALE_PLACEHOLDER, IntorError, IntorErrorCode, clearLoggerPool, mergeMessages, type MessagesReader, type MessagesReaders, type GenLocale as Locale, } from "../src/core";
|
|
1
|
+
export { LOCALE_PLACEHOLDER, IntorError, IntorErrorCode, matchLocale, clearLoggerPool, mergeMessages, type MessagesReader, type MessagesReaders, type GenLocale as Locale, } from "../src/core";
|
|
2
2
|
export { defineIntorConfig, type IntorRawConfig, type IntorResolvedConfig, } from "../src/config";
|
|
3
3
|
export { localizePathname, type InboundContext } from "../src/routing";
|
|
4
|
+
export { Translator, type TranslatorPlugin, type TranslateContext, type TranslateHook, type TranslateHandlers, type HandlerContext, type FormatHandler, type LoadingHandler, type MissingHandler, type LocaleMessages, type MessageObject, type MessageValue, } from "intor-translator";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { LOCALE_PLACEHOLDER, INTOR_HEADERS } from "./constants";
|
|
2
2
|
export { IntorError, IntorErrorCode } from "./error";
|
|
3
|
-
export { deepMerge, type PlainObject, type DeepMergeOverrideEvent, resolveLoaderOptions, parseCookieHeader, normalizePathname, normalizeCacheKey,
|
|
3
|
+
export { deepMerge, type PlainObject, type DeepMergeOverrideEvent, resolveLoaderOptions, parseCookieHeader, normalizePathname, normalizeCacheKey, normalizeQuery, type NormalizedQuery, } from "./utils";
|
|
4
|
+
export { matchLocale } from "./locale";
|
|
4
5
|
export { getLogger, clearLoggerPool } from "./logger";
|
|
5
6
|
export { loadRemoteMessages, mergeMessages, isValidMessages, nestObjectFromPath, type MessagesReader, type MessagesReaders, INTOR_PREFIX, INTOR_MESSAGES_KIND_KEY, INTOR_MESSAGES_KIND, getMessagesKind, type IntorMessagesKind, } from "./messages";
|
|
6
7
|
export { createHtmlRenderer, type TagRenderers, type HtmlTagRenderers, } from "./render";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonicalizes a BCP 47 locale string.
|
|
3
|
+
*
|
|
4
|
+
* - Uses `Intl.getCanonicalLocales` when available.
|
|
5
|
+
* - Returns the original input if `Intl` is unavailable.
|
|
6
|
+
* - Returns `undefined` for invalid locale input.
|
|
7
|
+
*
|
|
8
|
+
* This function performs normalization only.
|
|
9
|
+
* It does not perform matching or fallback.
|
|
10
|
+
*/
|
|
11
|
+
export declare function canonicalizeLocale(input: string): string | undefined;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { matchLocale } from "./match-locale";
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Matches a locale candidate against a list of supported locales.
|
|
3
|
+
*
|
|
4
|
+
* Resolution order:
|
|
5
|
+
* 1. Exact canonical match
|
|
6
|
+
* 2. Same language + same script
|
|
7
|
+
* 3. Same language only (only if candidate has no script)
|
|
8
|
+
*
|
|
9
|
+
* Returns `undefined` if no match is found.
|
|
10
|
+
*
|
|
11
|
+
* Notes:
|
|
12
|
+
* - Matching is deterministic and order-sensitive.
|
|
13
|
+
* - Does not perform automatic fallback to default locale.
|
|
14
|
+
* - Does not cross script boundaries.
|
|
15
|
+
*/
|
|
16
|
+
export declare function matchLocale(locale: string | undefined, supportedLocales?: readonly string[]): string | undefined;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type LocaleParts = {
|
|
2
|
+
language: string;
|
|
3
|
+
script?: string;
|
|
4
|
+
region?: string;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Parses a canonical locale tag into structural components.
|
|
8
|
+
*
|
|
9
|
+
* - Uses `Intl.Locale` when available.
|
|
10
|
+
* - Falls back to minimal parsing (language only) if unavailable.
|
|
11
|
+
*
|
|
12
|
+
* This function does not validate or match locales.
|
|
13
|
+
*/
|
|
14
|
+
export declare function parseLocale(tag: string): LocaleParts;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { deepMerge, type PlainObject, type DeepMergeOverrideEvent, } from "./deep-merge";
|
|
2
2
|
export { resolveLoaderOptions } from "./resolve-loader-options";
|
|
3
3
|
export { parseCookieHeader } from "./parse-cookie-header";
|
|
4
|
-
export { normalizeCacheKey,
|
|
4
|
+
export { normalizeCacheKey, normalizePathname, normalizeQuery, type NormalizedQuery, } from "./normalizers";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import '../../../core/error/intor-error.js';
|
|
2
|
-
import {
|
|
2
|
+
import { matchLocale } from '../../../core/locale/match-locale.js';
|
|
3
3
|
import 'logry';
|
|
4
4
|
import 'p-limit';
|
|
5
5
|
import 'intor-translator';
|
|
@@ -16,7 +16,7 @@ function getClientLocale(config) {
|
|
|
16
16
|
// Locale from browser preference
|
|
17
17
|
const browserLocale = detectBrowserLocale();
|
|
18
18
|
const localeCandidate = cookieLocale || browserLocale;
|
|
19
|
-
return
|
|
19
|
+
return matchLocale(localeCandidate, supportedLocales) || defaultLocale;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export { getClientLocale };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Translator } from 'intor-translator';
|
|
2
|
-
import { defineComponent, ref, computed, provide } from 'vue';
|
|
2
|
+
import { defineComponent, ref, computed, watch, provide } from 'vue';
|
|
3
3
|
import { useLocaleEffects } from './effects/use-locale-effects.js';
|
|
4
4
|
import { useMessagesEffects } from './effects/use-messages-effects.js';
|
|
5
5
|
|
|
@@ -8,11 +8,10 @@ const IntorProvider = defineComponent({
|
|
|
8
8
|
name: "IntorProvider",
|
|
9
9
|
props: { value: { type: Object, required: true } },
|
|
10
10
|
setup(props, { slots }) {
|
|
11
|
-
const { config, locale: initialLocale, messages, handlers, plugins, onLocaleChange, isLoading: externalIsLoading, } = props.value;
|
|
12
11
|
// ---------------------------------------------------------------------------
|
|
13
12
|
// Internal state
|
|
14
13
|
// ---------------------------------------------------------------------------
|
|
15
|
-
const locale = ref(
|
|
14
|
+
const locale = ref(props.value.locale);
|
|
16
15
|
const runtimeMessages = ref(null);
|
|
17
16
|
const internalIsLoading = ref(false);
|
|
18
17
|
// ---------------------------------------------------------------------------
|
|
@@ -23,15 +22,18 @@ const IntorProvider = defineComponent({
|
|
|
23
22
|
if (newLocale === locale.value)
|
|
24
23
|
return;
|
|
25
24
|
locale.value = newLocale;
|
|
26
|
-
onLocaleChange?.(newLocale); // Notify external listener (fire-and-forget)
|
|
25
|
+
props.value.onLocaleChange?.(newLocale); // Notify external listener (fire-and-forget)
|
|
27
26
|
};
|
|
28
27
|
// ---------------------------------------------------------------------------
|
|
29
28
|
// Effective state
|
|
30
29
|
// ---------------------------------------------------------------------------
|
|
31
30
|
// external > internal
|
|
32
|
-
const effectiveIsLoading = computed(() => !!
|
|
31
|
+
const effectiveIsLoading = computed(() => !!props.value.isLoading?.value || internalIsLoading.value);
|
|
33
32
|
// runtime (client refetch) > initial > config (static)
|
|
34
|
-
const effectiveMessages = computed(() => runtimeMessages.value ||
|
|
33
|
+
const effectiveMessages = computed(() => runtimeMessages.value ||
|
|
34
|
+
props.value.messages?.value ||
|
|
35
|
+
props.value.config.messages ||
|
|
36
|
+
{});
|
|
35
37
|
// ---------------------------------------------------------------------------
|
|
36
38
|
// Translator
|
|
37
39
|
// ---------------------------------------------------------------------------
|
|
@@ -40,20 +42,25 @@ const IntorProvider = defineComponent({
|
|
|
40
42
|
messages: effectiveMessages.value,
|
|
41
43
|
locale: locale.value,
|
|
42
44
|
isLoading: effectiveIsLoading.value,
|
|
43
|
-
fallbackLocales: config.fallbackLocales,
|
|
44
|
-
loadingMessage: config.translator?.loadingMessage,
|
|
45
|
-
missingMessage: config.translator?.missingMessage,
|
|
46
|
-
handlers: handlers,
|
|
47
|
-
plugins: plugins,
|
|
45
|
+
fallbackLocales: props.value.config.fallbackLocales,
|
|
46
|
+
loadingMessage: props.value.config.translator?.loadingMessage,
|
|
47
|
+
missingMessage: props.value.config.translator?.missingMessage,
|
|
48
|
+
handlers: props.value.handlers,
|
|
49
|
+
plugins: props.value.plugins,
|
|
48
50
|
});
|
|
49
51
|
});
|
|
50
52
|
// -------------------------------------------------------------------------
|
|
51
53
|
// Side effects
|
|
52
54
|
// -------------------------------------------------------------------------
|
|
53
|
-
useLocaleEffects(config, locale);
|
|
54
|
-
useMessagesEffects(config, locale, runtimeMessages, internalIsLoading);
|
|
55
|
+
useLocaleEffects(props.value.config, locale);
|
|
56
|
+
useMessagesEffects(props.value.config, locale, runtimeMessages, internalIsLoading);
|
|
57
|
+
// Sync internal locale with external prop
|
|
58
|
+
watch(() => props.value.locale, (newLocale) => {
|
|
59
|
+
if (newLocale !== locale.value)
|
|
60
|
+
locale.value = newLocale;
|
|
61
|
+
});
|
|
55
62
|
const contextValue = computed(() => ({
|
|
56
|
-
config,
|
|
63
|
+
config: props.value.config,
|
|
57
64
|
locale,
|
|
58
65
|
setLocale,
|
|
59
66
|
translator,
|