@i18n-micro/astro 1.0.0 → 1.1.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/dist/client/core.d.ts +23 -0
- package/dist/client/index.d.ts +6 -0
- package/dist/client/index.js +17 -0
- package/dist/client/preact.d.ts +30 -0
- package/dist/client/preact.js +51 -0
- package/dist/client/react.d.ts +26 -0
- package/dist/client/react.js +51 -0
- package/dist/client/svelte.d.ts +28 -0
- package/dist/client/svelte.js +70 -0
- package/dist/client/vue.d.ts +27 -0
- package/dist/client/vue.js +1558 -0
- package/dist/composer.d.ts +21 -15
- package/dist/core-Bx9n-eFD.cjs +1 -0
- package/dist/core-D32Y48CN.js +42 -0
- package/dist/env.d.ts +2 -0
- package/dist/index.cjs +17 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.mjs +442 -282
- package/dist/integration.d.ts +4 -0
- package/dist/load-translations.d.ts +44 -0
- package/dist/middleware.d.ts +2 -0
- package/dist/router/adapter.d.ts +8 -0
- package/dist/router/types.d.ts +65 -0
- package/dist/utils.d.ts +17 -1
- package/package.json +57 -13
- package/src/client/core.ts +121 -0
- package/src/client/index.ts +15 -0
- package/src/client/preact.tsx +114 -0
- package/src/client/react.tsx +111 -0
- package/src/client/svelte.ts +124 -0
- package/src/client/vue.ts +128 -0
- package/src/components/i18n-link.astro +37 -4
- package/src/components/i18n-switcher.astro +209 -17
- package/src/components/index.ts +8 -2
- package/src/composer.ts +210 -0
- package/src/env.d.ts +20 -0
- package/src/index.ts +59 -0
- package/src/integration.ts +120 -0
- package/src/load-translations.ts +130 -0
- package/src/middleware.ts +203 -0
- package/src/router/adapter.ts +184 -0
- package/src/router/types.ts +66 -0
- package/src/routing.ts +108 -0
- package/src/utils.ts +397 -0
- package/dist/bridge/astro-bridge.d.ts +0 -13
- package/dist/index-C-UMdqSG.cjs +0 -1
- package/dist/index-CVhedN6W.js +0 -146
- package/dist/toolbar-app.d.ts +0 -2
package/dist/integration.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AstroIntegration } from 'astro';
|
|
2
2
|
import { AstroI18n, AstroI18nOptions } from './composer';
|
|
3
3
|
import { Locale, ModuleOptions, PluralFunc } from '@i18n-micro/types';
|
|
4
|
+
import { I18nRoutingStrategy } from './router/types';
|
|
4
5
|
export interface I18nIntegrationOptions extends Omit<ModuleOptions, 'plural'> {
|
|
5
6
|
locale: string;
|
|
6
7
|
fallbackLocale?: string;
|
|
@@ -13,7 +14,10 @@ export interface I18nIntegrationOptions extends Omit<ModuleOptions, 'plural'> {
|
|
|
13
14
|
autoDetect?: boolean;
|
|
14
15
|
redirectToDefault?: boolean;
|
|
15
16
|
translationDir?: string;
|
|
17
|
+
routingStrategy?: I18nRoutingStrategy;
|
|
16
18
|
}
|
|
19
|
+
export declare function getGlobalRoutingStrategy(): I18nRoutingStrategy | null;
|
|
20
|
+
export declare function setGlobalRoutingStrategy(strategy: I18nRoutingStrategy | null): void;
|
|
17
21
|
/**
|
|
18
22
|
* Astro Integration for i18n-micro
|
|
19
23
|
*/
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Translations } from '@i18n-micro/types';
|
|
2
|
+
/**
|
|
3
|
+
* WARNING: Node.js-only functions
|
|
4
|
+
*
|
|
5
|
+
* The functions in this file use Node.js filesystem APIs (node:fs) and will NOT work
|
|
6
|
+
* in Edge runtime environments (Cloudflare Workers, Vercel Edge, Deno Deploy, etc.).
|
|
7
|
+
*
|
|
8
|
+
* If you import this module in an Edge environment, the bundler will fail at build time
|
|
9
|
+
* because node:fs is not available. This is the intended behavior (Fail Fast).
|
|
10
|
+
*
|
|
11
|
+
* For Edge-compatible translation loading, use import.meta.glob in your middleware:
|
|
12
|
+
* const translations = import.meta.glob('/src/locales/*.json', { eager: true })
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Load translations from a directory structure
|
|
16
|
+
* Supports both flat structure (en.json, fr.json) and nested structure (pages/home/en.json)
|
|
17
|
+
*/
|
|
18
|
+
export interface LoadTranslationsOptions {
|
|
19
|
+
translationDir: string;
|
|
20
|
+
rootDir?: string;
|
|
21
|
+
disablePageLocales?: boolean;
|
|
22
|
+
}
|
|
23
|
+
export interface LoadedTranslations {
|
|
24
|
+
general: Record<string, Translations>;
|
|
25
|
+
routes: Record<string, Record<string, Translations>>;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Load all translations from a directory
|
|
29
|
+
*
|
|
30
|
+
* WARNING: Node.js-only - This function uses node:fs and will NOT work in Edge runtime.
|
|
31
|
+
* If imported in Edge environment, bundler will fail at build time (Fail Fast).
|
|
32
|
+
* Use import.meta.glob for Edge-compatible loading.
|
|
33
|
+
*/
|
|
34
|
+
export declare function loadTranslationsFromDir(options: LoadTranslationsOptions): LoadedTranslations;
|
|
35
|
+
/**
|
|
36
|
+
* Load translations and add them to an AstroI18n instance
|
|
37
|
+
*
|
|
38
|
+
* WARNING: Node.js-only - This function uses node:fs and will NOT work in Edge runtime.
|
|
39
|
+
* For Edge environments, use import.meta.glob to load translations at build time.
|
|
40
|
+
*/
|
|
41
|
+
export declare function loadTranslationsIntoI18n(i18n: {
|
|
42
|
+
addTranslations: (locale: string, translations: Translations, merge?: boolean) => void;
|
|
43
|
+
addRouteTranslations: (locale: string, routeName: string, translations: Translations, merge?: boolean) => void;
|
|
44
|
+
}, options: LoadTranslationsOptions): void;
|
package/dist/middleware.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AstroI18n } from './composer';
|
|
2
2
|
import { MiddlewareHandler } from 'astro';
|
|
3
3
|
import { Locale } from '@i18n-micro/types';
|
|
4
|
+
import { I18nRoutingStrategy } from './router/types';
|
|
4
5
|
export interface I18nMiddlewareOptions {
|
|
5
6
|
i18n: AstroI18n;
|
|
6
7
|
defaultLocale: string;
|
|
@@ -8,6 +9,7 @@ export interface I18nMiddlewareOptions {
|
|
|
8
9
|
localeObjects?: Locale[];
|
|
9
10
|
autoDetect?: boolean;
|
|
10
11
|
redirectToDefault?: boolean;
|
|
12
|
+
routingStrategy?: I18nRoutingStrategy;
|
|
11
13
|
}
|
|
12
14
|
/**
|
|
13
15
|
* Create Astro middleware for i18n locale detection
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { I18nRoutingStrategy } from './types';
|
|
2
|
+
import { Locale } from '@i18n-micro/types';
|
|
3
|
+
/**
|
|
4
|
+
* Factory for Astro router adapter
|
|
5
|
+
* Implements routing utilities for Astro file-based routing
|
|
6
|
+
* Uses standard Astro APIs: Astro.url, context.url
|
|
7
|
+
*/
|
|
8
|
+
export declare function createAstroRouterAdapter(locales: Locale[], defaultLocale: string, getCurrentUrl?: () => URL): I18nRoutingStrategy;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Routing strategy interface for i18n in Astro
|
|
3
|
+
* Implement this interface to integrate with any routing logic
|
|
4
|
+
* Matches Vue/Solid/React package interface for consistency
|
|
5
|
+
* Adapted for Astro SSR context (push/replace are optional)
|
|
6
|
+
*/
|
|
7
|
+
export interface I18nRoutingStrategy {
|
|
8
|
+
/**
|
|
9
|
+
* Returns current path (without locale prefix if needed, or full path)
|
|
10
|
+
* Used for determining active classes in links
|
|
11
|
+
*/
|
|
12
|
+
getCurrentPath: () => string;
|
|
13
|
+
/**
|
|
14
|
+
* Generate path for specific locale
|
|
15
|
+
*/
|
|
16
|
+
resolvePath?: (to: string | {
|
|
17
|
+
path?: string;
|
|
18
|
+
}, locale: string) => string | {
|
|
19
|
+
path?: string;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Get route name from path (e.g., /en/about -> about)
|
|
23
|
+
* Used in middleware to set route name
|
|
24
|
+
*/
|
|
25
|
+
getRouteName?: (path: string, locales: string[]) => string;
|
|
26
|
+
/**
|
|
27
|
+
* Get locale from path
|
|
28
|
+
* Checks if first segment is a locale code
|
|
29
|
+
*/
|
|
30
|
+
getLocaleFromPath?: (path: string, defaultLocale: string, locales: string[]) => string;
|
|
31
|
+
/**
|
|
32
|
+
* Switch locale in path
|
|
33
|
+
* Replaces or adds locale prefix to path
|
|
34
|
+
*/
|
|
35
|
+
switchLocalePath?: (path: string, newLocale: string, locales: string[], defaultLocale?: string) => string;
|
|
36
|
+
/**
|
|
37
|
+
* Localize path with locale prefix
|
|
38
|
+
*/
|
|
39
|
+
localizePath?: (path: string, locale: string, locales: string[], defaultLocale?: string) => string;
|
|
40
|
+
/**
|
|
41
|
+
* Remove locale from path
|
|
42
|
+
*/
|
|
43
|
+
removeLocaleFromPath?: (path: string, locales: string[]) => string;
|
|
44
|
+
/**
|
|
45
|
+
* (Optional) Function to navigate to another route/locale
|
|
46
|
+
* Not used in SSR, but can be used in client-side islands
|
|
47
|
+
*/
|
|
48
|
+
push?: (target: {
|
|
49
|
+
path: string;
|
|
50
|
+
}) => void;
|
|
51
|
+
/**
|
|
52
|
+
* (Optional) Function to replace current route
|
|
53
|
+
* Not used in SSR, but can be used in client-side islands
|
|
54
|
+
*/
|
|
55
|
+
replace?: (target: {
|
|
56
|
+
path: string;
|
|
57
|
+
}) => void;
|
|
58
|
+
/**
|
|
59
|
+
* (Optional) Get current route object for SEO/Meta tags
|
|
60
|
+
*/
|
|
61
|
+
getRoute?: () => {
|
|
62
|
+
fullPath: string;
|
|
63
|
+
query: Record<string, unknown>;
|
|
64
|
+
};
|
|
65
|
+
}
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AstroI18n } from './composer';
|
|
2
|
-
import { Params, Locale, CleanTranslation, TranslationKey } from '@i18n-micro/types';
|
|
2
|
+
import { Params, Locale, CleanTranslation, TranslationKey, Translations } from '@i18n-micro/types';
|
|
3
3
|
import { AstroGlobal } from 'astro';
|
|
4
4
|
/**
|
|
5
5
|
* Get i18n instance from Astro context
|
|
@@ -69,3 +69,19 @@ export interface LocaleHeadResult {
|
|
|
69
69
|
}>;
|
|
70
70
|
}
|
|
71
71
|
export declare function useLocaleHead(astro: AstroGlobal, options?: LocaleHeadOptions): LocaleHeadResult;
|
|
72
|
+
/**
|
|
73
|
+
* Props для передачи в клиентские острова (Vue, React, Svelte, Preact)
|
|
74
|
+
*/
|
|
75
|
+
export interface I18nClientProps {
|
|
76
|
+
locale: string;
|
|
77
|
+
fallbackLocale: string;
|
|
78
|
+
translations: Record<string, Translations>;
|
|
79
|
+
currentRoute: string;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Подготавливает пропсы для передачи в клиентский остров.
|
|
83
|
+
* Принимает список ключей, которые нужно передать в остров.
|
|
84
|
+
* Использует методы i18n для правильной работы с routesLocaleLinks.
|
|
85
|
+
* currentRoute уже нормализован через middleware (getRouteName), поэтому используем его напрямую.
|
|
86
|
+
*/
|
|
87
|
+
export declare function getI18nProps(astro: AstroGlobal, keys?: string[]): I18nClientProps;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@i18n-micro/astro",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.cjs",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -18,31 +18,75 @@
|
|
|
18
18
|
"./components/i18n-link.astro": "./src/components/i18n-link.astro",
|
|
19
19
|
"./components/i18n-switcher.astro": "./src/components/i18n-switcher.astro",
|
|
20
20
|
"./components/i18n-group.astro": "./src/components/i18n-group.astro",
|
|
21
|
-
"./
|
|
21
|
+
"./i18n-t": "./src/components/i18n-t.astro",
|
|
22
|
+
"./i18n-link": "./src/components/i18n-link.astro",
|
|
23
|
+
"./i18n-switcher": "./src/components/i18n-switcher.astro",
|
|
24
|
+
"./i18n-group": "./src/components/i18n-group.astro",
|
|
25
|
+
"./client": {
|
|
26
|
+
"types": "./dist/client/index.d.ts",
|
|
27
|
+
"import": "./dist/client/index.js"
|
|
28
|
+
},
|
|
29
|
+
"./client/vue": {
|
|
30
|
+
"types": "./dist/client/vue.d.ts",
|
|
31
|
+
"import": "./dist/client/vue.js"
|
|
32
|
+
},
|
|
33
|
+
"./client/react": {
|
|
34
|
+
"types": "./dist/client/react.d.ts",
|
|
35
|
+
"import": "./dist/client/react.js"
|
|
36
|
+
},
|
|
37
|
+
"./client/preact": {
|
|
38
|
+
"types": "./dist/client/preact.d.ts",
|
|
39
|
+
"import": "./dist/client/preact.js"
|
|
40
|
+
},
|
|
41
|
+
"./client/svelte": {
|
|
42
|
+
"types": "./dist/client/svelte.d.ts",
|
|
43
|
+
"import": "./dist/client/svelte.js"
|
|
44
|
+
}
|
|
22
45
|
},
|
|
23
46
|
"files": [
|
|
24
47
|
"dist",
|
|
25
|
-
"src
|
|
48
|
+
"src"
|
|
26
49
|
],
|
|
27
50
|
"publishConfig": {
|
|
28
51
|
"access": "public"
|
|
29
52
|
},
|
|
30
53
|
"dependencies": {
|
|
31
|
-
"@i18n-micro/
|
|
32
|
-
"@i18n-micro/
|
|
33
|
-
"@i18n-micro/
|
|
34
|
-
"@i18n-micro/node": "1.0.0"
|
|
54
|
+
"@i18n-micro/core": "1.0.28",
|
|
55
|
+
"@i18n-micro/types": "1.0.16",
|
|
56
|
+
"@i18n-micro/node": "1.0.1"
|
|
35
57
|
},
|
|
36
58
|
"peerDependencies": {
|
|
37
|
-
"astro": "^5.0.0"
|
|
59
|
+
"astro": "^5.0.0",
|
|
60
|
+
"svelte": "^4.0.0 || ^5.0.0",
|
|
61
|
+
"vue": "^3.0.0",
|
|
62
|
+
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
|
63
|
+
"preact": "^10.0.0"
|
|
64
|
+
},
|
|
65
|
+
"peerDependenciesMeta": {
|
|
66
|
+
"svelte": {
|
|
67
|
+
"optional": true
|
|
68
|
+
},
|
|
69
|
+
"vue": {
|
|
70
|
+
"optional": true
|
|
71
|
+
},
|
|
72
|
+
"react": {
|
|
73
|
+
"optional": true
|
|
74
|
+
},
|
|
75
|
+
"preact": {
|
|
76
|
+
"optional": true
|
|
77
|
+
}
|
|
38
78
|
},
|
|
39
79
|
"devDependencies": {
|
|
40
|
-
"astro": "^5.
|
|
41
|
-
"vite": "^
|
|
42
|
-
"vite-plugin-dts": "^4.
|
|
80
|
+
"astro": "^5.16.5",
|
|
81
|
+
"vite": "^7.2.7",
|
|
82
|
+
"vite-plugin-dts": "^4.5.4",
|
|
43
83
|
"jest": "^29.7.0",
|
|
44
|
-
"ts-jest": "^29.
|
|
45
|
-
"@types/jest": "^29.5.
|
|
84
|
+
"ts-jest": "^29.4.6",
|
|
85
|
+
"@types/jest": "^29.5.14",
|
|
86
|
+
"svelte": "^5.0.0",
|
|
87
|
+
"vue": "^3.4.0",
|
|
88
|
+
"react": "^18.3.0",
|
|
89
|
+
"preact": "^10.0.0"
|
|
46
90
|
},
|
|
47
91
|
"scripts": {
|
|
48
92
|
"build": "vite build",
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import type { Translations, Params } from '@i18n-micro/types'
|
|
2
|
+
import { interpolate } from '@i18n-micro/core'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Состояние i18n для клиентских островов
|
|
6
|
+
*/
|
|
7
|
+
export interface I18nState {
|
|
8
|
+
locale: string
|
|
9
|
+
fallbackLocale: string
|
|
10
|
+
translations: Record<string, Translations> // routeName -> translations
|
|
11
|
+
currentRoute: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Вспомогательная функция для поиска перевода в объекте Translations
|
|
15
|
+
// Returns the value as-is, including objects (for nested translations)
|
|
16
|
+
function findTranslation<T = unknown>(translations: Translations | null, key: string): T | null {
|
|
17
|
+
if (translations === null || typeof key !== 'string') {
|
|
18
|
+
return null
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let value: string | number | boolean | Translations | unknown | null = translations
|
|
22
|
+
|
|
23
|
+
// Прямой доступ к ключу
|
|
24
|
+
if (translations[key]) {
|
|
25
|
+
value = translations[key]
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
// Поиск по вложенным ключам (например, "nested.message")
|
|
29
|
+
const parts = key.toString().split('.')
|
|
30
|
+
for (const part of parts) {
|
|
31
|
+
if (value && typeof value === 'object' && value !== null && part in value) {
|
|
32
|
+
value = (value as Translations)[part]
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
return null
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Return value as-is (can be string, number, boolean, object, or null)
|
|
41
|
+
// This matches CleanTranslation type which allows objects
|
|
42
|
+
return (value as T) ?? null
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Чистая функция для получения перевода из состояния
|
|
47
|
+
*
|
|
48
|
+
* Note: This is a simplified version optimized for client-side islands.
|
|
49
|
+
* It supports basic translation lookup, interpolation, and fallback to general translations.
|
|
50
|
+
* Returns CleanTranslation which can be string, number, boolean, object, or null.
|
|
51
|
+
* For advanced features like Linked Messages (@:path.to.key), use the server-side i18n instance.
|
|
52
|
+
*/
|
|
53
|
+
export function translate(
|
|
54
|
+
state: I18nState,
|
|
55
|
+
key: string,
|
|
56
|
+
params?: Params,
|
|
57
|
+
defaultValue?: string | null,
|
|
58
|
+
routeName?: string,
|
|
59
|
+
): string | number | boolean | Translations | null {
|
|
60
|
+
if (!key) {
|
|
61
|
+
return defaultValue || key || ''
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const route = routeName || state.currentRoute
|
|
65
|
+
let value: string | number | boolean | Translations | null = null
|
|
66
|
+
|
|
67
|
+
// 1. Ищем в route-specific переводах
|
|
68
|
+
if (state.translations[route]) {
|
|
69
|
+
value = findTranslation<string | number | boolean | Translations>(state.translations[route], key)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 2. Fallback на general переводы
|
|
73
|
+
if (!value && state.translations.general) {
|
|
74
|
+
value = findTranslation<string | number | boolean | Translations>(state.translations.general, key)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 3. Если не найдено, используем defaultValue или key
|
|
78
|
+
if (!value) {
|
|
79
|
+
value = defaultValue === undefined ? key : (defaultValue || key)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// 4. Интерполяция параметров (только для строк)
|
|
83
|
+
if (typeof value === 'string' && params) {
|
|
84
|
+
return interpolate(value, params)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// 5. Возвращаем value как есть (может быть string, number, boolean, object, или null)
|
|
88
|
+
// Это соответствует типу CleanTranslation
|
|
89
|
+
return value
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Проверяет наличие перевода в состоянии
|
|
94
|
+
*/
|
|
95
|
+
export function hasTranslation(
|
|
96
|
+
state: I18nState,
|
|
97
|
+
key: string,
|
|
98
|
+
routeName?: string,
|
|
99
|
+
): boolean {
|
|
100
|
+
const route = routeName || state.currentRoute
|
|
101
|
+
const routeTranslations = state.translations[route]
|
|
102
|
+
const generalTranslations = state.translations.general
|
|
103
|
+
|
|
104
|
+
// Проверяем в route-specific переводах
|
|
105
|
+
if (routeTranslations) {
|
|
106
|
+
const value = findTranslation(routeTranslations, key)
|
|
107
|
+
if (value !== null && typeof value !== 'object') {
|
|
108
|
+
return true
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Проверяем в general переводах
|
|
113
|
+
if (generalTranslations) {
|
|
114
|
+
const value = findTranslation(generalTranslations, key)
|
|
115
|
+
if (value !== null && typeof value !== 'object') {
|
|
116
|
+
return true
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return false
|
|
121
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Core utilities (чистые функции)
|
|
2
|
+
export { translate, hasTranslation } from './core'
|
|
3
|
+
export type { I18nState } from './core'
|
|
4
|
+
|
|
5
|
+
// Vue adapter
|
|
6
|
+
export { provideI18n, useAstroI18n as useAstroI18nVue } from './vue'
|
|
7
|
+
|
|
8
|
+
// React adapter
|
|
9
|
+
export { I18nProvider, useAstroI18n as useAstroI18nReact } from './react'
|
|
10
|
+
|
|
11
|
+
// Preact adapter
|
|
12
|
+
export { I18nProvider as I18nProviderPreact, useAstroI18n as useAstroI18nPreact } from './preact'
|
|
13
|
+
|
|
14
|
+
// Svelte adapter
|
|
15
|
+
export { createI18nStore, useAstroI18n as useAstroI18nSvelte } from './svelte'
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// Preact может использовать React Context API, так как Preact совместим с React
|
|
2
|
+
import type { ComponentChildren } from 'preact'
|
|
3
|
+
import { createContext, createElement } from 'preact'
|
|
4
|
+
import { useContext, useState, useMemo } from 'preact/hooks'
|
|
5
|
+
import type { I18nClientProps } from '../utils'
|
|
6
|
+
import { translate, hasTranslation, type I18nState } from './core'
|
|
7
|
+
import { defaultPlural, FormatService } from '@i18n-micro/core'
|
|
8
|
+
import type { Params, TranslationKey, CleanTranslation } from '@i18n-micro/types'
|
|
9
|
+
|
|
10
|
+
const formatter = new FormatService()
|
|
11
|
+
|
|
12
|
+
const I18nContext = createContext<I18nState | null>(null)
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Провайдер для i18n в Preact островах
|
|
16
|
+
*/
|
|
17
|
+
export const I18nProvider = ({ children, value }: { children: ComponentChildren, value: I18nClientProps }) => {
|
|
18
|
+
const [state] = useState<I18nState>(() => ({
|
|
19
|
+
locale: value.locale,
|
|
20
|
+
fallbackLocale: value.fallbackLocale,
|
|
21
|
+
translations: value.translations,
|
|
22
|
+
currentRoute: value.currentRoute,
|
|
23
|
+
}))
|
|
24
|
+
|
|
25
|
+
return createElement(I18nContext.Provider, { value: state }, children)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Хук для использования i18n в Preact компонентах
|
|
30
|
+
*/
|
|
31
|
+
export function useAstroI18n() {
|
|
32
|
+
const state = useContext(I18nContext)
|
|
33
|
+
if (!state) {
|
|
34
|
+
throw new Error('useAstroI18n must be used within an I18nProvider')
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const t = useMemo(() => (
|
|
38
|
+
key: TranslationKey,
|
|
39
|
+
params?: Params,
|
|
40
|
+
defaultValue?: string | null,
|
|
41
|
+
routeName?: string,
|
|
42
|
+
): CleanTranslation => {
|
|
43
|
+
return translate(state, key as string, params, defaultValue, routeName)
|
|
44
|
+
}, [state])
|
|
45
|
+
|
|
46
|
+
const ts = useMemo(() => (
|
|
47
|
+
key: TranslationKey,
|
|
48
|
+
params?: Params,
|
|
49
|
+
defaultValue?: string,
|
|
50
|
+
routeName?: string,
|
|
51
|
+
): string => {
|
|
52
|
+
const value = t(key, params, defaultValue, routeName)
|
|
53
|
+
return value?.toString() ?? defaultValue ?? (key as string)
|
|
54
|
+
}, [t])
|
|
55
|
+
|
|
56
|
+
const tc = useMemo(() => (key: TranslationKey, count: number | Params, defaultValue?: string): string => {
|
|
57
|
+
const { count: countValue, ...params } = typeof count === 'number' ? { count } : count
|
|
58
|
+
|
|
59
|
+
if (countValue === undefined) {
|
|
60
|
+
return defaultValue ?? (key as string)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const getter = (k: TranslationKey, p?: Params, dv?: string) => {
|
|
64
|
+
return t(k, p, dv)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const result = defaultPlural(
|
|
68
|
+
key,
|
|
69
|
+
Number.parseInt(countValue.toString(), 10),
|
|
70
|
+
params,
|
|
71
|
+
state.locale,
|
|
72
|
+
getter,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
return result ?? defaultValue ?? (key as string)
|
|
76
|
+
}, [t, state])
|
|
77
|
+
|
|
78
|
+
const tn = useMemo(() => (value: number, options?: Intl.NumberFormatOptions): string => {
|
|
79
|
+
return formatter.formatNumber(value, state.locale, options)
|
|
80
|
+
}, [state.locale])
|
|
81
|
+
|
|
82
|
+
const td = useMemo(() => (value: Date | number | string, options?: Intl.DateTimeFormatOptions): string => {
|
|
83
|
+
return formatter.formatDate(value, state.locale, options)
|
|
84
|
+
}, [state.locale])
|
|
85
|
+
|
|
86
|
+
const tdr = useMemo(() => (value: Date | number | string, options?: Intl.RelativeTimeFormatOptions): string => {
|
|
87
|
+
return formatter.formatRelativeTime(value, state.locale, options)
|
|
88
|
+
}, [state.locale])
|
|
89
|
+
|
|
90
|
+
const has = useMemo(() => (key: TranslationKey, routeName?: string): boolean => {
|
|
91
|
+
return hasTranslation(state, key as string, routeName)
|
|
92
|
+
}, [state])
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
// Translation methods
|
|
96
|
+
t,
|
|
97
|
+
ts,
|
|
98
|
+
tc,
|
|
99
|
+
tn,
|
|
100
|
+
td,
|
|
101
|
+
tdr,
|
|
102
|
+
has,
|
|
103
|
+
|
|
104
|
+
// Locale state
|
|
105
|
+
locale: state.locale,
|
|
106
|
+
fallbackLocale: state.fallbackLocale,
|
|
107
|
+
currentRoute: state.currentRoute,
|
|
108
|
+
|
|
109
|
+
// Route management (read-only в клиентских островах)
|
|
110
|
+
getRoute: (): string => {
|
|
111
|
+
return state.currentRoute
|
|
112
|
+
},
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import React, { createContext, useContext, useState, useMemo } from 'react'
|
|
2
|
+
import type { I18nClientProps } from '../utils'
|
|
3
|
+
import { translate, hasTranslation, type I18nState } from './core'
|
|
4
|
+
import { defaultPlural, FormatService } from '@i18n-micro/core'
|
|
5
|
+
import type { Params, TranslationKey, CleanTranslation } from '@i18n-micro/types'
|
|
6
|
+
|
|
7
|
+
const formatter = new FormatService()
|
|
8
|
+
|
|
9
|
+
const I18nContext = createContext<I18nState | null>(null)
|
|
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
|
+
key: TranslationKey,
|
|
36
|
+
params?: Params,
|
|
37
|
+
defaultValue?: string | null,
|
|
38
|
+
routeName?: string,
|
|
39
|
+
): CleanTranslation => {
|
|
40
|
+
return translate(state, key as string, params, defaultValue, routeName)
|
|
41
|
+
}, [state])
|
|
42
|
+
|
|
43
|
+
const ts = useMemo(() => (
|
|
44
|
+
key: TranslationKey,
|
|
45
|
+
params?: Params,
|
|
46
|
+
defaultValue?: string,
|
|
47
|
+
routeName?: string,
|
|
48
|
+
): string => {
|
|
49
|
+
const value = t(key, params, defaultValue, routeName)
|
|
50
|
+
return value?.toString() ?? defaultValue ?? (key as string)
|
|
51
|
+
}, [t])
|
|
52
|
+
|
|
53
|
+
const tc = useMemo(() => (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(
|
|
65
|
+
key,
|
|
66
|
+
Number.parseInt(countValue.toString(), 10),
|
|
67
|
+
params,
|
|
68
|
+
state.locale,
|
|
69
|
+
getter,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
return result ?? defaultValue ?? (key as string)
|
|
73
|
+
}, [t, state])
|
|
74
|
+
|
|
75
|
+
const tn = useMemo(() => (value: number, options?: Intl.NumberFormatOptions): string => {
|
|
76
|
+
return formatter.formatNumber(value, state.locale, options)
|
|
77
|
+
}, [state.locale])
|
|
78
|
+
|
|
79
|
+
const td = useMemo(() => (value: Date | number | string, options?: Intl.DateTimeFormatOptions): string => {
|
|
80
|
+
return formatter.formatDate(value, state.locale, options)
|
|
81
|
+
}, [state.locale])
|
|
82
|
+
|
|
83
|
+
const tdr = useMemo(() => (value: Date | number | string, options?: Intl.RelativeTimeFormatOptions): string => {
|
|
84
|
+
return formatter.formatRelativeTime(value, state.locale, options)
|
|
85
|
+
}, [state.locale])
|
|
86
|
+
|
|
87
|
+
const has = useMemo(() => (key: TranslationKey, routeName?: string): boolean => {
|
|
88
|
+
return hasTranslation(state, key as string, routeName)
|
|
89
|
+
}, [state])
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
// Translation methods
|
|
93
|
+
t,
|
|
94
|
+
ts,
|
|
95
|
+
tc,
|
|
96
|
+
tn,
|
|
97
|
+
td,
|
|
98
|
+
tdr,
|
|
99
|
+
has,
|
|
100
|
+
|
|
101
|
+
// Locale state
|
|
102
|
+
locale: state.locale,
|
|
103
|
+
fallbackLocale: state.fallbackLocale,
|
|
104
|
+
currentRoute: state.currentRoute,
|
|
105
|
+
|
|
106
|
+
// Route management (read-only в клиентских островах)
|
|
107
|
+
getRoute: (): string => {
|
|
108
|
+
return state.currentRoute
|
|
109
|
+
},
|
|
110
|
+
}
|
|
111
|
+
}
|