@i18n-micro/astro 1.3.24 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +8 -64
- package/dist/auto-middleware.d.ts +1 -0
- package/dist/client/core.d.cts +86 -0
- package/dist/client/core.d.ts +71 -8
- package/dist/client/index.d.cts +2 -6
- package/dist/client/index.d.ts +2 -6
- package/dist/client/index.js +1 -6
- package/dist/client/preact.d.ts +12 -23
- package/dist/client/preact.js +13 -37
- package/dist/client/react.d.ts +14 -21
- package/dist/client/react.js +12 -37
- package/dist/client/svelte.d.ts +14 -14
- package/dist/client/svelte.js +17 -47
- package/dist/client/vue.d.ts +15 -15
- package/dist/client/vue.js +37 -2
- package/dist/composer.d.cts +16 -0
- package/dist/composer.d.ts +10 -30
- package/dist/core-CgrgdmGy.cjs +1 -0
- package/dist/core-DTAcn2yF.js +72 -0
- package/dist/env.d.ts +8 -2
- package/dist/index.cjs +11 -3
- package/dist/index.d.cts +10 -13
- package/dist/index.d.ts +10 -13
- package/dist/index.mjs +171 -482
- package/dist/integration.d.cts +26 -0
- package/dist/integration.d.ts +14 -15
- package/dist/middleware-BT3OA5YL.cjs +1 -0
- package/dist/middleware-Ch8E0iV_.js +257 -0
- package/dist/middleware.d.cts +34 -0
- package/dist/middleware.d.ts +17 -8
- package/dist/middleware.js +23 -0
- package/dist/node.d.ts +26 -0
- package/dist/node.js +22 -0
- package/dist/request.d.ts +69 -0
- package/dist/router/adapter.d.cts +9 -0
- package/dist/router/adapter.d.ts +5 -4
- package/dist/router/types.d.cts +66 -0
- package/dist/router/types.d.ts +3 -2
- package/dist/utils.d.cts +112 -0
- package/dist/utils.d.ts +39 -35
- package/dist/virtual.d.ts +21 -0
- package/package.json +28 -15
- package/src/auto-middleware.ts +26 -0
- package/src/client/core.ts +124 -59
- package/src/client/index.ts +2 -11
- package/src/client/preact.tsx +12 -127
- package/src/client/react.tsx +11 -123
- package/src/client/svelte.ts +6 -69
- package/src/client/vue.ts +22 -77
- package/src/components/i18n-link.astro +2 -59
- package/src/components/i18n-switcher.astro +46 -149
- package/src/components/i18n-t.astro +6 -54
- package/src/composer.ts +25 -115
- package/src/env.d.ts +7 -0
- package/src/index.ts +1 -6
- package/src/integration.ts +113 -52
- package/src/middleware.ts +174 -120
- package/src/node.ts +51 -0
- package/src/request.ts +177 -0
- package/src/router/adapter.ts +47 -149
- package/src/router/types.ts +3 -2
- package/src/utils.ts +149 -323
- package/src/virtual.d.ts +21 -0
- package/dist/components/index.d.cts +0 -0
- package/dist/components/index.d.ts +0 -0
- package/dist/core-DwkpOV-H.cjs +0 -1
- package/dist/core-VSnkGRWR.js +0 -28
- package/dist/load-translations.d.ts +0 -43
- package/dist/routing-context.d.ts +0 -4
- package/dist/vue-BNiEwZr2.cjs +0 -5
- package/dist/vue-CWHjIOiS.js +0 -1724
- package/src/components/index.ts +0 -11
- package/src/load-translations.ts +0 -106
- package/src/routing-context.ts +0 -23
package/src/client/preact.tsx
CHANGED
|
@@ -1,127 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const I18nContext = createContext<I18nState | null>(null)
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Провайдер для i18n в Preact островах
|
|
17
|
-
*/
|
|
18
|
-
export const I18nProvider = ({ children, value }: { children: ComponentChildren; value: I18nClientProps }) => {
|
|
19
|
-
const [state] = useState<I18nState>(() => ({
|
|
20
|
-
locale: value.locale,
|
|
21
|
-
fallbackLocale: value.fallbackLocale,
|
|
22
|
-
translations: value.translations,
|
|
23
|
-
currentRoute: value.currentRoute,
|
|
24
|
-
}))
|
|
25
|
-
|
|
26
|
-
return createElement(I18nContext.Provider, { value: state }, children)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Хук для использования i18n в Preact компонентах
|
|
31
|
-
*/
|
|
32
|
-
export function useAstroI18n() {
|
|
33
|
-
const state = useContext(I18nContext)
|
|
34
|
-
if (!state) {
|
|
35
|
-
throw new Error('useAstroI18n must be used within an I18nProvider')
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const t = useMemo(
|
|
39
|
-
() =>
|
|
40
|
-
(key: TranslationKey, params?: Params, defaultValue?: string | null, routeName?: string): CleanTranslation => {
|
|
41
|
-
return translate(state, key as string, params, defaultValue, routeName)
|
|
42
|
-
},
|
|
43
|
-
[state],
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
const ts = useMemo(
|
|
47
|
-
() =>
|
|
48
|
-
(key: TranslationKey, params?: Params, defaultValue?: string, routeName?: string): string => {
|
|
49
|
-
const value = t(key, params, defaultValue, routeName)
|
|
50
|
-
return value?.toString() ?? defaultValue ?? (key as string)
|
|
51
|
-
},
|
|
52
|
-
[t],
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
const tc = useMemo(
|
|
56
|
-
() =>
|
|
57
|
-
(key: TranslationKey, count: number | Params, defaultValue?: string): string => {
|
|
58
|
-
const { count: countValue, ...params } = typeof count === 'number' ? { count } : count
|
|
59
|
-
|
|
60
|
-
if (countValue === undefined) {
|
|
61
|
-
return defaultValue ?? (key as string)
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const getter = (k: TranslationKey, p?: Params, dv?: string) => {
|
|
65
|
-
return t(k, p, dv)
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const result = defaultPlural(key, Number.parseInt(countValue.toString(), 10), params, state.locale, getter)
|
|
69
|
-
|
|
70
|
-
return result ?? defaultValue ?? (key as string)
|
|
71
|
-
},
|
|
72
|
-
[t, state],
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
const tn = useMemo(
|
|
76
|
-
() =>
|
|
77
|
-
(value: number, options?: Intl.NumberFormatOptions): string => {
|
|
78
|
-
return formatter.formatNumber(value, state.locale, options)
|
|
79
|
-
},
|
|
80
|
-
[state.locale],
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
const td = useMemo(
|
|
84
|
-
() =>
|
|
85
|
-
(value: Date | number | string, options?: Intl.DateTimeFormatOptions): string => {
|
|
86
|
-
return formatter.formatDate(value, state.locale, options)
|
|
87
|
-
},
|
|
88
|
-
[state.locale],
|
|
89
|
-
)
|
|
90
|
-
|
|
91
|
-
const tdr = useMemo(
|
|
92
|
-
() =>
|
|
93
|
-
(value: Date | number | string, options?: Intl.RelativeTimeFormatOptions): string => {
|
|
94
|
-
return formatter.formatRelativeTime(value, state.locale, options)
|
|
95
|
-
},
|
|
96
|
-
[state.locale],
|
|
97
|
-
)
|
|
98
|
-
|
|
99
|
-
const has = useMemo(
|
|
100
|
-
() =>
|
|
101
|
-
(key: TranslationKey, routeName?: string): boolean => {
|
|
102
|
-
return hasTranslation(state, key as string, routeName)
|
|
103
|
-
},
|
|
104
|
-
[state],
|
|
105
|
-
)
|
|
106
|
-
|
|
107
|
-
return {
|
|
108
|
-
// Translation methods
|
|
109
|
-
t,
|
|
110
|
-
ts,
|
|
111
|
-
tc,
|
|
112
|
-
tn,
|
|
113
|
-
td,
|
|
114
|
-
tdr,
|
|
115
|
-
has,
|
|
116
|
-
|
|
117
|
-
// Locale state
|
|
118
|
-
locale: state.locale,
|
|
119
|
-
fallbackLocale: state.fallbackLocale,
|
|
120
|
-
currentRoute: state.currentRoute,
|
|
121
|
-
|
|
122
|
-
// Route management (read-only в клиентских островах)
|
|
123
|
-
getRoute: (): string => {
|
|
124
|
-
return state.currentRoute
|
|
125
|
-
},
|
|
126
|
-
}
|
|
127
|
-
}
|
|
1
|
+
import { type ComponentChildren, createContext, createElement, type PreactContext, type VNode } from 'preact'
|
|
2
|
+
import { useCallback, useContext, useMemo, useState } from 'preact/hooks'
|
|
3
|
+
import { createIslandBinding, type I18nContextValue } from './core'
|
|
4
|
+
|
|
5
|
+
export const { I18nProvider, useAstroI18n } = /*#__PURE__*/ createIslandBinding<ComponentChildren, VNode, PreactContext<I18nContextValue | null>>({
|
|
6
|
+
createContext,
|
|
7
|
+
createElement,
|
|
8
|
+
useCallback,
|
|
9
|
+
useContext,
|
|
10
|
+
useMemo,
|
|
11
|
+
useState,
|
|
12
|
+
})
|
package/src/client/react.tsx
CHANGED
|
@@ -1,123 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
* Провайдер для i18n в React островах
|
|
13
|
-
*/
|
|
14
|
-
export function I18nProvider({ children, value }: { children: React.ReactNode; value: I18nClientProps }): React.ReactElement {
|
|
15
|
-
const [state] = useState<I18nState>(() => ({
|
|
16
|
-
locale: value.locale,
|
|
17
|
-
fallbackLocale: value.fallbackLocale,
|
|
18
|
-
translations: value.translations,
|
|
19
|
-
currentRoute: value.currentRoute,
|
|
20
|
-
}))
|
|
21
|
-
|
|
22
|
-
return React.createElement(I18nContext.Provider, { value: state }, children)
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Хук для использования i18n в React компонентах
|
|
27
|
-
*/
|
|
28
|
-
export function useAstroI18n() {
|
|
29
|
-
const state = useContext(I18nContext)
|
|
30
|
-
if (!state) {
|
|
31
|
-
throw new Error('useAstroI18n must be used within an I18nProvider')
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const t = useMemo(
|
|
35
|
-
() =>
|
|
36
|
-
(key: TranslationKey, params?: Params, defaultValue?: string | null, routeName?: string): CleanTranslation => {
|
|
37
|
-
return translate(state, key as string, params, defaultValue, routeName)
|
|
38
|
-
},
|
|
39
|
-
[state],
|
|
40
|
-
)
|
|
41
|
-
|
|
42
|
-
const ts = useMemo(
|
|
43
|
-
() =>
|
|
44
|
-
(key: TranslationKey, params?: Params, defaultValue?: string, routeName?: string): string => {
|
|
45
|
-
const value = t(key, params, defaultValue, routeName)
|
|
46
|
-
return value?.toString() ?? defaultValue ?? (key as string)
|
|
47
|
-
},
|
|
48
|
-
[t],
|
|
49
|
-
)
|
|
50
|
-
|
|
51
|
-
const tc = useMemo(
|
|
52
|
-
() =>
|
|
53
|
-
(key: TranslationKey, count: number | Params, defaultValue?: string): string => {
|
|
54
|
-
const { count: countValue, ...params } = typeof count === 'number' ? { count } : count
|
|
55
|
-
|
|
56
|
-
if (countValue === undefined) {
|
|
57
|
-
return defaultValue ?? (key as string)
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const getter = (k: TranslationKey, p?: Params, dv?: string) => {
|
|
61
|
-
return t(k, p, dv)
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const result = defaultPlural(key, Number.parseInt(countValue.toString(), 10), params, state.locale, getter)
|
|
65
|
-
|
|
66
|
-
return result ?? defaultValue ?? (key as string)
|
|
67
|
-
},
|
|
68
|
-
[t, state],
|
|
69
|
-
)
|
|
70
|
-
|
|
71
|
-
const tn = useMemo(
|
|
72
|
-
() =>
|
|
73
|
-
(value: number, options?: Intl.NumberFormatOptions): string => {
|
|
74
|
-
return formatter.formatNumber(value, state.locale, options)
|
|
75
|
-
},
|
|
76
|
-
[state.locale],
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
const td = useMemo(
|
|
80
|
-
() =>
|
|
81
|
-
(value: Date | number | string, options?: Intl.DateTimeFormatOptions): string => {
|
|
82
|
-
return formatter.formatDate(value, state.locale, options)
|
|
83
|
-
},
|
|
84
|
-
[state.locale],
|
|
85
|
-
)
|
|
86
|
-
|
|
87
|
-
const tdr = useMemo(
|
|
88
|
-
() =>
|
|
89
|
-
(value: Date | number | string, options?: Intl.RelativeTimeFormatOptions): string => {
|
|
90
|
-
return formatter.formatRelativeTime(value, state.locale, options)
|
|
91
|
-
},
|
|
92
|
-
[state.locale],
|
|
93
|
-
)
|
|
94
|
-
|
|
95
|
-
const has = useMemo(
|
|
96
|
-
() =>
|
|
97
|
-
(key: TranslationKey, routeName?: string): boolean => {
|
|
98
|
-
return hasTranslation(state, key as string, routeName)
|
|
99
|
-
},
|
|
100
|
-
[state],
|
|
101
|
-
)
|
|
102
|
-
|
|
103
|
-
return {
|
|
104
|
-
// Translation methods
|
|
105
|
-
t,
|
|
106
|
-
ts,
|
|
107
|
-
tc,
|
|
108
|
-
tn,
|
|
109
|
-
td,
|
|
110
|
-
tdr,
|
|
111
|
-
has,
|
|
112
|
-
|
|
113
|
-
// Locale state
|
|
114
|
-
locale: state.locale,
|
|
115
|
-
fallbackLocale: state.fallbackLocale,
|
|
116
|
-
currentRoute: state.currentRoute,
|
|
117
|
-
|
|
118
|
-
// Route management (read-only в клиентских островах)
|
|
119
|
-
getRoute: (): string => {
|
|
120
|
-
return state.currentRoute
|
|
121
|
-
},
|
|
122
|
-
}
|
|
123
|
-
}
|
|
1
|
+
import { type Context, createContext, createElement, type ReactElement, type ReactNode, useCallback, useContext, useMemo, useState } from 'react'
|
|
2
|
+
import { createIslandBinding, type I18nContextValue } from './core'
|
|
3
|
+
|
|
4
|
+
export const { I18nProvider, useAstroI18n } = /*#__PURE__*/ createIslandBinding<ReactNode, ReactElement, Context<I18nContextValue | null>>({
|
|
5
|
+
createContext,
|
|
6
|
+
createElement,
|
|
7
|
+
useCallback,
|
|
8
|
+
useContext,
|
|
9
|
+
useMemo,
|
|
10
|
+
useState,
|
|
11
|
+
})
|
package/src/client/svelte.ts
CHANGED
|
@@ -1,21 +1,13 @@
|
|
|
1
|
-
import { defaultPlural, FormatService } from '@i18n-micro/core'
|
|
2
|
-
import type { CleanTranslation, Params, TranslationKey } from '@i18n-micro/types'
|
|
3
1
|
import { get, type Writable, writable } from 'svelte/store'
|
|
4
2
|
import type { I18nClientProps } from '../utils'
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
const formatter = new FormatService()
|
|
3
|
+
import { createIslandApi, type I18nState, withLocale } from './core'
|
|
8
4
|
|
|
9
5
|
/**
|
|
10
|
-
* Creates a Svelte store for i18n state
|
|
6
|
+
* Creates a Svelte store for i18n state. An island kept by `transition:persist` follows its new
|
|
7
|
+
* props with `$: store.set(i18n)` (`$effect(() => store.set(i18n))` with runes).
|
|
11
8
|
*/
|
|
12
9
|
export function createI18nStore(props: I18nClientProps): Writable<I18nState> {
|
|
13
|
-
return writable<I18nState>(
|
|
14
|
-
locale: props.locale,
|
|
15
|
-
fallbackLocale: props.fallbackLocale,
|
|
16
|
-
translations: props.translations,
|
|
17
|
-
currentRoute: props.currentRoute,
|
|
18
|
-
})
|
|
10
|
+
return writable<I18nState>(props)
|
|
19
11
|
}
|
|
20
12
|
|
|
21
13
|
/**
|
|
@@ -25,63 +17,11 @@ export function createI18nStore(props: I18nClientProps): Writable<I18nState> {
|
|
|
25
17
|
export function useAstroI18n(store: Writable<I18nState>) {
|
|
26
18
|
const getState = () => get(store)
|
|
27
19
|
|
|
28
|
-
const t = (key: TranslationKey, params?: Params, defaultValue?: string | null, routeName?: string): CleanTranslation => {
|
|
29
|
-
return translate(getState(), key as string, params, defaultValue, routeName)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const ts = (key: TranslationKey, params?: Params, defaultValue?: string, routeName?: string): string => {
|
|
33
|
-
const value = t(key, params, defaultValue, routeName)
|
|
34
|
-
return value?.toString() ?? defaultValue ?? (key as string)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const tc = (key: TranslationKey, count: number | Params, defaultValue?: string): string => {
|
|
38
|
-
const state = getState()
|
|
39
|
-
const { count: countValue, ...params } = typeof count === 'number' ? { count } : count
|
|
40
|
-
|
|
41
|
-
if (countValue === undefined) {
|
|
42
|
-
return defaultValue ?? (key as string)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const getter = (k: TranslationKey, p?: Params, dv?: string) => {
|
|
46
|
-
return t(k, p, dv)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const result = defaultPlural(key, Number.parseInt(countValue.toString(), 10), params, state.locale, getter)
|
|
50
|
-
|
|
51
|
-
return result ?? defaultValue ?? (key as string)
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const tn = (value: number, options?: Intl.NumberFormatOptions): string => {
|
|
55
|
-
const state = getState()
|
|
56
|
-
return formatter.formatNumber(value, state.locale, options)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const td = (value: Date | number | string, options?: Intl.DateTimeFormatOptions): string => {
|
|
60
|
-
const state = getState()
|
|
61
|
-
return formatter.formatDate(value, state.locale, options)
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const tdr = (value: Date | number | string, options?: Intl.RelativeTimeFormatOptions): string => {
|
|
65
|
-
const state = getState()
|
|
66
|
-
return formatter.formatRelativeTime(value, state.locale, options)
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const has = (key: TranslationKey, routeName?: string): boolean => {
|
|
70
|
-
return hasTranslation(getState(), key as string, routeName)
|
|
71
|
-
}
|
|
72
|
-
|
|
73
20
|
return {
|
|
74
21
|
// Store for reactivity in templates (use $i18nStore in templates)
|
|
75
22
|
store,
|
|
76
23
|
|
|
77
|
-
|
|
78
|
-
t,
|
|
79
|
-
ts,
|
|
80
|
-
tc,
|
|
81
|
-
tn,
|
|
82
|
-
td,
|
|
83
|
-
tdr,
|
|
84
|
-
has,
|
|
24
|
+
...createIslandApi(getState),
|
|
85
25
|
|
|
86
26
|
// Getters for current state (for use in scripts)
|
|
87
27
|
get locale() {
|
|
@@ -96,13 +36,10 @@ export function useAstroI18n(store: Writable<I18nState>) {
|
|
|
96
36
|
|
|
97
37
|
// Route management
|
|
98
38
|
setLocale: (locale: string) => {
|
|
99
|
-
store.update((state) => (
|
|
39
|
+
store.update((state) => withLocale(state, locale))
|
|
100
40
|
},
|
|
101
41
|
setRoute: (routeName: string) => {
|
|
102
42
|
store.update((state) => ({ ...state, currentRoute: routeName }))
|
|
103
43
|
},
|
|
104
|
-
getRoute: (): string => {
|
|
105
|
-
return getState().currentRoute
|
|
106
|
-
},
|
|
107
44
|
}
|
|
108
45
|
}
|
package/src/client/vue.ts
CHANGED
|
@@ -1,24 +1,22 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { CleanTranslation, Params, TranslationKey } from '@i18n-micro/types'
|
|
3
|
-
import { computed, type InjectionKey, inject, provide, type Ref, ref } from 'vue'
|
|
1
|
+
import { computed, type InjectionKey, inject, type MaybeRefOrGetter, provide, type Ref, shallowRef, toValue, watch } from 'vue'
|
|
4
2
|
import type { I18nClientProps } from '../utils'
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
const formatter = new FormatService()
|
|
3
|
+
import { createIslandApi, type I18nState, withLocale } from './core'
|
|
8
4
|
|
|
9
5
|
const I18nSymbol = Symbol('i18n-astro') as InjectionKey<Ref<I18nState>>
|
|
10
6
|
|
|
11
7
|
/**
|
|
12
|
-
* Initializes the i18n provider for a Vue island
|
|
13
|
-
*
|
|
8
|
+
* Initializes the i18n provider for a Vue island. Call in the island's root component, with a
|
|
9
|
+
* getter (`() => props.i18n`) so an island kept by `transition:persist` follows its new props.
|
|
10
|
+
* `shallowRef`: translations are never edited in place, and deep tracking costs 11× on every `t`.
|
|
14
11
|
*/
|
|
15
|
-
export function provideI18n(props: I18nClientProps): Ref<I18nState> {
|
|
16
|
-
const state =
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
12
|
+
export function provideI18n(props: MaybeRefOrGetter<I18nClientProps>): Ref<I18nState> {
|
|
13
|
+
const state = shallowRef<I18nState>(toValue(props))
|
|
14
|
+
watch(
|
|
15
|
+
() => toValue(props),
|
|
16
|
+
(next) => {
|
|
17
|
+
state.value = next
|
|
18
|
+
},
|
|
19
|
+
)
|
|
22
20
|
provide(I18nSymbol, state)
|
|
23
21
|
return state
|
|
24
22
|
}
|
|
@@ -31,82 +29,29 @@ export function useAstroI18n() {
|
|
|
31
29
|
if (!state) {
|
|
32
30
|
throw new Error('useAstroI18n must be used within a component that calls provideI18n')
|
|
33
31
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
return translate(state.value, key as string, params, defaultValue, routeName)
|
|
32
|
+
const setLocale = (locale: string) => {
|
|
33
|
+
state.value = withLocale(state.value, locale)
|
|
37
34
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const value = t(key, params, defaultValue, routeName)
|
|
41
|
-
return value?.toString() ?? defaultValue ?? (key as string)
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const tc = (key: TranslationKey, count: number | Params, defaultValue?: string): string => {
|
|
45
|
-
const { count: countValue, ...params } = typeof count === 'number' ? { count } : count
|
|
46
|
-
|
|
47
|
-
if (countValue === undefined) {
|
|
48
|
-
return defaultValue ?? (key as string)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const getter = (k: TranslationKey, p?: Params, dv?: string) => {
|
|
52
|
-
return t(k, p, dv)
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const result = defaultPlural(key, Number.parseInt(countValue.toString(), 10), params, state.value.locale, getter)
|
|
56
|
-
|
|
57
|
-
return result ?? defaultValue ?? (key as string)
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const tn = (value: number, options?: Intl.NumberFormatOptions): string => {
|
|
61
|
-
return formatter.formatNumber(value, state.value.locale, options)
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const td = (value: Date | number | string, options?: Intl.DateTimeFormatOptions): string => {
|
|
65
|
-
return formatter.formatDate(value, state.value.locale, options)
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const tdr = (value: Date | number | string, options?: Intl.RelativeTimeFormatOptions): string => {
|
|
69
|
-
return formatter.formatRelativeTime(value, state.value.locale, options)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const has = (key: TranslationKey, routeName?: string): boolean => {
|
|
73
|
-
return hasTranslation(state.value, key as string, routeName)
|
|
35
|
+
const setRoute = (currentRoute: string) => {
|
|
36
|
+
state.value = { ...state.value, currentRoute }
|
|
74
37
|
}
|
|
75
38
|
|
|
76
39
|
return {
|
|
77
|
-
|
|
78
|
-
t,
|
|
79
|
-
ts,
|
|
80
|
-
tc,
|
|
81
|
-
tn,
|
|
82
|
-
td,
|
|
83
|
-
tdr,
|
|
84
|
-
has,
|
|
40
|
+
...createIslandApi(() => state.value),
|
|
85
41
|
|
|
86
42
|
// Reactive locale state
|
|
87
43
|
locale: computed({
|
|
88
44
|
get: () => state.value.locale,
|
|
89
|
-
set:
|
|
90
|
-
state.value = { ...state.value, locale: val }
|
|
91
|
-
},
|
|
45
|
+
set: setLocale,
|
|
92
46
|
}),
|
|
93
47
|
fallbackLocale: computed(() => state.value.fallbackLocale),
|
|
94
48
|
currentRoute: computed({
|
|
95
49
|
get: () => state.value.currentRoute,
|
|
96
|
-
set:
|
|
97
|
-
state.value = { ...state.value, currentRoute: val }
|
|
98
|
-
},
|
|
50
|
+
set: setRoute,
|
|
99
51
|
}),
|
|
100
52
|
|
|
101
53
|
// Route management
|
|
102
|
-
setLocale
|
|
103
|
-
|
|
104
|
-
},
|
|
105
|
-
setRoute: (routeName: string) => {
|
|
106
|
-
state.value = { ...state.value, currentRoute: routeName }
|
|
107
|
-
},
|
|
108
|
-
getRoute: (): string => {
|
|
109
|
-
return state.value.currentRoute
|
|
110
|
-
},
|
|
54
|
+
setLocale,
|
|
55
|
+
setRoute,
|
|
111
56
|
}
|
|
112
57
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
import
|
|
3
|
-
import { useI18n } from '../utils'
|
|
2
|
+
import { resolveLinkHref } from '../utils'
|
|
4
3
|
|
|
5
4
|
interface Props {
|
|
6
5
|
href: string
|
|
@@ -10,64 +9,8 @@ interface Props {
|
|
|
10
9
|
}
|
|
11
10
|
|
|
12
11
|
const { href, locale, class: className, ...attrs } = Astro.props
|
|
13
|
-
const { locale: currentLocale, defaultLocale, locales } = useI18n(Astro)
|
|
14
|
-
const localeCodes = locales.map((l) => l.code)
|
|
15
|
-
const currentPath = Astro.url.pathname
|
|
16
|
-
|
|
17
|
-
// Get routing strategy from locals
|
|
18
|
-
// @ts-expect-error
|
|
19
|
-
const routingStrategy: I18nRoutingStrategy | null = Astro.locals.routingStrategy || null
|
|
20
|
-
|
|
21
|
-
// Get current locale from path or use detected locale
|
|
22
|
-
const targetLocale = locale || currentLocale
|
|
23
|
-
|
|
24
|
-
// Helper to remove locale from path
|
|
25
|
-
const removeLocale = (path: string, codes: string[]): string => {
|
|
26
|
-
if (routingStrategy?.removeLocaleFromPath) {
|
|
27
|
-
return routingStrategy.removeLocaleFromPath(path, codes)
|
|
28
|
-
}
|
|
29
|
-
// Fallback
|
|
30
|
-
const segments = path.split('/').filter(Boolean)
|
|
31
|
-
const firstSegment = segments[0]
|
|
32
|
-
if (firstSegment && codes.includes(firstSegment)) {
|
|
33
|
-
segments.shift()
|
|
34
|
-
}
|
|
35
|
-
return `/${segments.join('/')}`
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// Remove locale from current path to get base path
|
|
39
|
-
const basePath = removeLocale(currentPath, localeCodes)
|
|
40
|
-
|
|
41
|
-
// If href is relative, resolve it relative to current base path
|
|
42
|
-
let resolvedHref = href
|
|
43
|
-
if (href.startsWith('/')) {
|
|
44
|
-
// Absolute path - remove locale if present and use as base
|
|
45
|
-
resolvedHref = removeLocale(href, localeCodes)
|
|
46
|
-
} else {
|
|
47
|
-
// Relative path - combine with current base path
|
|
48
|
-
resolvedHref = basePath === '/' ? `/${href}` : `${basePath}/${href}`
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Localize the href
|
|
52
|
-
let localizedHref = resolvedHref
|
|
53
|
-
if (routingStrategy?.localizePath) {
|
|
54
|
-
localizedHref = routingStrategy.localizePath(resolvedHref, targetLocale, localeCodes, defaultLocale)
|
|
55
|
-
} else {
|
|
56
|
-
// Fallback: basic localization
|
|
57
|
-
const cleanPath = resolvedHref.replace(/^\//, '').replace(/\/$/, '') || ''
|
|
58
|
-
const segments = cleanPath.split('/').filter(Boolean)
|
|
59
|
-
const firstSegment = segments[0]
|
|
60
|
-
if (firstSegment && localeCodes.includes(firstSegment)) {
|
|
61
|
-
segments.shift()
|
|
62
|
-
}
|
|
63
|
-
if (targetLocale !== defaultLocale) {
|
|
64
|
-
segments.unshift(targetLocale)
|
|
65
|
-
}
|
|
66
|
-
localizedHref = `/${segments.join('/')}`
|
|
67
|
-
}
|
|
68
12
|
---
|
|
69
13
|
|
|
70
|
-
<a href={
|
|
14
|
+
<a href={resolveLinkHref(Astro, href, locale)} class={className} {...attrs}>
|
|
71
15
|
<slot />
|
|
72
16
|
</a>
|
|
73
|
-
|