@rimelight/i18n 0.0.6 → 0.0.7
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/index.d.mts +39 -0
- package/dist/index.mjs +63 -0
- package/dist/integration.d.mts +11 -0
- package/dist/integration.mjs +122 -0
- package/dist/middleware.d.mts +7 -0
- package/dist/middleware.mjs +56 -0
- package/dist/runtime.d.mts +71 -0
- package/dist/runtime.mjs +179 -0
- package/dist/types.d.mts +16 -0
- package/dist/types.mjs +1 -0
- package/dist/utils.d.mts +7 -0
- package/dist/utils.mjs +28 -0
- package/package.json +30 -11
- package/src/env.d.ts +0 -13
- package/src/index.ts +0 -143
- package/src/integration.ts +0 -185
- package/src/middleware/kv.ts +0 -10
- package/src/middleware/routing.ts +0 -78
- package/src/middleware.ts +0 -12
- package/src/runtime.ts +0 -316
- package/src/types.ts +0 -18
- package/src/utils.ts +0 -44
- package/src/virtual.d.ts +0 -15
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rimelight/i18n",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Rimelight Entertainment's Internationalization Package",
|
|
6
6
|
"homepage": "https://rimelight.com/docs",
|
|
@@ -16,27 +16,45 @@
|
|
|
16
16
|
"url": "git+https://github.com/Rimelight-Entertainment/rimelight.git"
|
|
17
17
|
},
|
|
18
18
|
"files": [
|
|
19
|
-
"
|
|
19
|
+
"dist"
|
|
20
20
|
],
|
|
21
21
|
"type": "module",
|
|
22
22
|
"exports": {
|
|
23
|
-
".":
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
"./
|
|
28
|
-
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./dist/index.d.mts",
|
|
25
|
+
"import": "./dist/index.mjs"
|
|
26
|
+
},
|
|
27
|
+
"./integration": {
|
|
28
|
+
"types": "./dist/integration.d.mts",
|
|
29
|
+
"import": "./dist/integration.mjs"
|
|
30
|
+
},
|
|
31
|
+
"./middleware": {
|
|
32
|
+
"types": "./dist/middleware.d.mts",
|
|
33
|
+
"import": "./dist/middleware.mjs"
|
|
34
|
+
},
|
|
35
|
+
"./runtime": {
|
|
36
|
+
"types": "./dist/runtime.d.mts",
|
|
37
|
+
"import": "./dist/runtime.mjs"
|
|
38
|
+
},
|
|
39
|
+
"./types": {
|
|
40
|
+
"types": "./dist/types.d.mts",
|
|
41
|
+
"import": "./dist/types.mjs"
|
|
42
|
+
},
|
|
43
|
+
"./utils": {
|
|
44
|
+
"types": "./dist/utils.d.mts",
|
|
45
|
+
"import": "./dist/utils.mjs"
|
|
46
|
+
}
|
|
29
47
|
},
|
|
30
48
|
"publishConfig": {
|
|
31
49
|
"access": "public"
|
|
32
50
|
},
|
|
33
51
|
"dependencies": {
|
|
34
52
|
"@nanostores/i18n": "1.3.3",
|
|
35
|
-
"nanostores": "1.5.
|
|
53
|
+
"nanostores": "1.5.3"
|
|
36
54
|
},
|
|
37
55
|
"devDependencies": {
|
|
38
|
-
"@rimelight/config": "0.0.
|
|
39
|
-
"astro": "7.
|
|
56
|
+
"@rimelight/config": "0.0.4",
|
|
57
|
+
"astro": "7.3.1",
|
|
40
58
|
"typescript": "6.0.3"
|
|
41
59
|
},
|
|
42
60
|
"peerDependencies": {
|
|
@@ -51,6 +69,7 @@
|
|
|
51
69
|
"node": ">=26.7.0"
|
|
52
70
|
},
|
|
53
71
|
"scripts": {
|
|
72
|
+
"build": "vp pack",
|
|
54
73
|
"check": "vp check --fix"
|
|
55
74
|
}
|
|
56
75
|
}
|
package/src/env.d.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
// Enables importing .astro components inside .ts files (e.g., email rendering)
|
|
2
|
-
declare module "*.astro" {
|
|
3
|
-
export default {} as import("astro/runtime/server").AstroComponentFactory
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
declare module "astro:i18n" {
|
|
7
|
-
export function getRelativeLocaleUrl(locale: string, path?: string): string
|
|
8
|
-
export function getAbsoluteLocaleUrl(locale: string, path?: string): string
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
declare module "@rimelight/i18n:runtime" {
|
|
12
|
-
export * from "./runtime"
|
|
13
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
import type { TranslationLoader } from "@nanostores/i18n"
|
|
2
|
-
import type { ComponentsJSON, KVNamespaceBinding } from "./types"
|
|
3
|
-
import {
|
|
4
|
-
useI18n as baseUseI18n,
|
|
5
|
-
useI18nAsync as baseUseI18nAsync,
|
|
6
|
-
t,
|
|
7
|
-
currentLocale,
|
|
8
|
-
useFormat,
|
|
9
|
-
clearCache,
|
|
10
|
-
getI18nInstance,
|
|
11
|
-
getFormatterInstance
|
|
12
|
-
} from "@rimelight/i18n:runtime"
|
|
13
|
-
import type { Translations } from "@nanostores/i18n"
|
|
14
|
-
|
|
15
|
-
export { t, currentLocale, useFormat, clearCache, getI18nInstance, getFormatterInstance }
|
|
16
|
-
|
|
17
|
-
export interface ComponentMessages {
|
|
18
|
-
[key: string]: string
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function useI18n(componentName: string): ComponentMessages
|
|
22
|
-
export function useI18n<Body extends Translations>(
|
|
23
|
-
componentName: string,
|
|
24
|
-
baseTranslations: Body
|
|
25
|
-
): ComponentMessages & Body
|
|
26
|
-
export function useI18n(
|
|
27
|
-
astro: { currentLocale?: string | undefined; params?: Record<string, any> } | undefined | null,
|
|
28
|
-
componentName: string
|
|
29
|
-
): ComponentMessages
|
|
30
|
-
export function useI18n<Body extends Translations>(
|
|
31
|
-
astro: { currentLocale?: string | undefined; params?: Record<string, any> } | undefined | null,
|
|
32
|
-
componentName: string,
|
|
33
|
-
baseTranslations: Body
|
|
34
|
-
): ComponentMessages & Body
|
|
35
|
-
export function useI18n(arg1: any, arg2?: any, arg3?: any): any {
|
|
36
|
-
if (typeof arg1 === "string") {
|
|
37
|
-
return baseUseI18n(arg1, arg2)
|
|
38
|
-
} else {
|
|
39
|
-
const locale = arg1?.currentLocale || arg1?.params?.locale
|
|
40
|
-
if (locale) {
|
|
41
|
-
currentLocale.set(locale)
|
|
42
|
-
}
|
|
43
|
-
return baseUseI18n(arg2, arg3)
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function useI18nAsync(componentName: string): Promise<Record<string, string>>
|
|
48
|
-
export function useI18nAsync<Body extends Translations>(
|
|
49
|
-
componentName: string,
|
|
50
|
-
baseTranslations: Body
|
|
51
|
-
): Promise<Record<string, string> & Body>
|
|
52
|
-
export function useI18nAsync(
|
|
53
|
-
astro: { currentLocale?: string | undefined; params?: Record<string, any> } | undefined | null,
|
|
54
|
-
componentName: string
|
|
55
|
-
): Promise<Record<string, string>>
|
|
56
|
-
export function useI18nAsync<Body extends Translations>(
|
|
57
|
-
astro: { currentLocale?: string | undefined; params?: Record<string, any> } | undefined | null,
|
|
58
|
-
componentName: string,
|
|
59
|
-
baseTranslations: Body
|
|
60
|
-
): Promise<Record<string, string> & Body>
|
|
61
|
-
export function useI18nAsync(arg1: any, arg2?: any, arg3?: any): any {
|
|
62
|
-
if (typeof arg1 === "string") {
|
|
63
|
-
return baseUseI18nAsync(arg1, arg2)
|
|
64
|
-
} else {
|
|
65
|
-
const locale = arg1?.currentLocale || arg1?.params?.locale
|
|
66
|
-
if (locale) {
|
|
67
|
-
currentLocale.set(locale)
|
|
68
|
-
}
|
|
69
|
-
return baseUseI18nAsync(arg2, arg3)
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
import { getRelativeLocaleUrl as astroGetRelativeLocaleUrl } from "astro:i18n"
|
|
74
|
-
|
|
75
|
-
export function getLocale(): string {
|
|
76
|
-
return currentLocale.get() || "en"
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export function getRelativeLocaleUrl(path: string): string
|
|
80
|
-
export function getRelativeLocaleUrl(locale: string, path: string): string
|
|
81
|
-
export function getRelativeLocaleUrl(arg1: string, arg2?: string): string {
|
|
82
|
-
if (arg2 !== undefined) {
|
|
83
|
-
return astroGetRelativeLocaleUrl(arg1, arg2)
|
|
84
|
-
} else {
|
|
85
|
-
const locale = getLocale()
|
|
86
|
-
return astroGetRelativeLocaleUrl(locale, arg1)
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
let kvBinding: KVNamespaceBinding | null = null
|
|
91
|
-
|
|
92
|
-
export function setKVBinding(kv: KVNamespaceBinding) {
|
|
93
|
-
kvBinding = kv
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export function createTranslationLoader(
|
|
97
|
-
translations: Record<string, ComponentsJSON>
|
|
98
|
-
): TranslationLoader {
|
|
99
|
-
return async (locale, components) => {
|
|
100
|
-
const localeTranslations = translations[locale] || {}
|
|
101
|
-
|
|
102
|
-
// If components is not provided, is not an array, or is empty, return the entire locale's translations.
|
|
103
|
-
if (!components || !Array.isArray(components) || components.length === 0) {
|
|
104
|
-
return localeTranslations
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const activeKv = kvBinding
|
|
108
|
-
if (activeKv) {
|
|
109
|
-
try {
|
|
110
|
-
const results = await Promise.all(
|
|
111
|
-
components.map(async (name) => {
|
|
112
|
-
const key = `locale:${locale}:${name}`
|
|
113
|
-
const data = await activeKv.get(key, "json")
|
|
114
|
-
// Fall back to bundled translation for this component if missing in KV
|
|
115
|
-
if (data == null) {
|
|
116
|
-
return { [name]: localeTranslations[name] ?? {} }
|
|
117
|
-
}
|
|
118
|
-
return { [name]: data }
|
|
119
|
-
})
|
|
120
|
-
)
|
|
121
|
-
return Object.assign({}, ...results)
|
|
122
|
-
} catch {
|
|
123
|
-
// KV read failed — fall through to static
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
const results = await Promise.all(
|
|
128
|
-
components.map(async (name) => {
|
|
129
|
-
const data = localeTranslations[name]
|
|
130
|
-
return { [name]: data ?? {} }
|
|
131
|
-
})
|
|
132
|
-
)
|
|
133
|
-
return Object.assign({}, ...results)
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Returns the plural category (e.g. 'one', 'few', 'many', 'other') for a given number and locale
|
|
139
|
-
* using the native browser Intl.PluralRules API.
|
|
140
|
-
*/
|
|
141
|
-
export function getPluralCategory(count: number, locale: string = "en"): Intl.LDMLPluralRule {
|
|
142
|
-
return new Intl.PluralRules(locale).select(count)
|
|
143
|
-
}
|
package/src/integration.ts
DELETED
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
import type { AstroIntegration } from "astro"
|
|
2
|
-
import fs from "node:fs"
|
|
3
|
-
import path from "node:path"
|
|
4
|
-
|
|
5
|
-
export interface RimelightI18nOptions {
|
|
6
|
-
validateExtraction?: boolean
|
|
7
|
-
translations?: Record<string, Record<string, any>>
|
|
8
|
-
kvBinding?: string
|
|
9
|
-
translationLoader?: string
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function rimelightI18n(options?: RimelightI18nOptions): AstroIntegration[] {
|
|
13
|
-
const validateExtraction = options?.validateExtraction ?? true
|
|
14
|
-
const translations = options?.translations
|
|
15
|
-
const kvBinding = options?.kvBinding ?? "TRANSLATIONS_KV"
|
|
16
|
-
const translationLoader = options?.translationLoader
|
|
17
|
-
|
|
18
|
-
// Normalise translations passed in options
|
|
19
|
-
const normalisedTranslations: Record<string, any> = {}
|
|
20
|
-
if (translations) {
|
|
21
|
-
for (const [locale, val] of Object.entries(translations)) {
|
|
22
|
-
normalisedTranslations[locale] = val
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// ---------- Vite virtual module: @rimelight/i18n:runtime ----------
|
|
27
|
-
// This replicates what astro-nanostores-i18n integration.js used to do:
|
|
28
|
-
// it creates a virtual module that calls initializeI18n() once at module
|
|
29
|
-
// evaluation time and re-exports all runtime helpers.
|
|
30
|
-
const VIRTUAL_MODULE_ID = "@rimelight/i18n:runtime"
|
|
31
|
-
const RESOLVED_VIRTUAL_MODULE_ID = "\0" + VIRTUAL_MODULE_ID
|
|
32
|
-
|
|
33
|
-
// Use the package export specifier so Vite resolves it through normal
|
|
34
|
-
// module resolution rather than an absolute filesystem path.
|
|
35
|
-
const runtimeSpecifier = "@rimelight/i18n/runtime"
|
|
36
|
-
|
|
37
|
-
const loaderImport = translationLoader
|
|
38
|
-
? `import translationLoader from ${JSON.stringify(translationLoader)};`
|
|
39
|
-
: ""
|
|
40
|
-
const loaderOption = translationLoader ? ", get: translationLoader" : ""
|
|
41
|
-
|
|
42
|
-
// ----------------------------------------------------------------
|
|
43
|
-
|
|
44
|
-
const rimelightI18nCore: AstroIntegration = {
|
|
45
|
-
name: "@rimelight/i18n",
|
|
46
|
-
hooks: {
|
|
47
|
-
"astro:config:setup": ({ config, addMiddleware, updateConfig, logger }) => {
|
|
48
|
-
const locales = config.i18n?.locales ?? ["en"]
|
|
49
|
-
const defaultLocale = config.i18n?.defaultLocale ?? "en"
|
|
50
|
-
|
|
51
|
-
const virtualModuleContent = `\
|
|
52
|
-
import { initializeI18n, useFormat, useI18n, useI18nAsync, t,
|
|
53
|
-
currentLocale, getI18nInstance, getFormatterInstance, clearCache }
|
|
54
|
-
from ${JSON.stringify(runtimeSpecifier)};
|
|
55
|
-
${loaderImport}
|
|
56
|
-
|
|
57
|
-
initializeI18n({
|
|
58
|
-
defaultLocale: ${JSON.stringify(defaultLocale)},
|
|
59
|
-
translations: ${JSON.stringify(normalisedTranslations)}${loaderOption}
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
export { useFormat, useI18n, useI18nAsync, t, currentLocale,
|
|
63
|
-
getI18nInstance, getFormatterInstance, clearCache };
|
|
64
|
-
`
|
|
65
|
-
|
|
66
|
-
addMiddleware({
|
|
67
|
-
entrypoint: "@rimelight/i18n/middleware",
|
|
68
|
-
order: "pre"
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
updateConfig({
|
|
72
|
-
vite: {
|
|
73
|
-
ssr: {
|
|
74
|
-
noExternal: ["@rimelight/i18n"]
|
|
75
|
-
},
|
|
76
|
-
plugins: [
|
|
77
|
-
// Virtual module: @rimelight/i18n:runtime
|
|
78
|
-
{
|
|
79
|
-
name: "vite-plugin-rimelight-i18n-runtime",
|
|
80
|
-
resolveId(id) {
|
|
81
|
-
if (id === VIRTUAL_MODULE_ID) return RESOLVED_VIRTUAL_MODULE_ID
|
|
82
|
-
return null
|
|
83
|
-
},
|
|
84
|
-
load(id) {
|
|
85
|
-
if (id === RESOLVED_VIRTUAL_MODULE_ID) return virtualModuleContent
|
|
86
|
-
return null
|
|
87
|
-
}
|
|
88
|
-
},
|
|
89
|
-
// Virtual module: virtual:rimelight-i18n-config (locale metadata)
|
|
90
|
-
{
|
|
91
|
-
name: "vite-plugin-rimelight-i18n-config",
|
|
92
|
-
resolveId(id) {
|
|
93
|
-
if (id === "virtual:rimelight-i18n-config") return "\0" + id
|
|
94
|
-
return null
|
|
95
|
-
},
|
|
96
|
-
load(id) {
|
|
97
|
-
if (id === "\0virtual:rimelight-i18n-config") {
|
|
98
|
-
return `
|
|
99
|
-
export const locales = ${JSON.stringify(locales)};
|
|
100
|
-
export const defaultLocale = ${JSON.stringify(defaultLocale)};
|
|
101
|
-
`
|
|
102
|
-
}
|
|
103
|
-
return null
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
]
|
|
107
|
-
}
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
if (validateExtraction) {
|
|
111
|
-
logger.info("i18n extraction validation active")
|
|
112
|
-
}
|
|
113
|
-
},
|
|
114
|
-
|
|
115
|
-
"astro:config:done": ({ injectTypes }) => {
|
|
116
|
-
// Inject ambient type declarations for the virtual module so TypeScript
|
|
117
|
-
// and editor tooling know the shape of @rimelight/i18n:runtime.
|
|
118
|
-
injectTypes({
|
|
119
|
-
filename: "rimelight-i18n.d.ts",
|
|
120
|
-
content: `\
|
|
121
|
-
declare module "@rimelight/i18n:runtime" {
|
|
122
|
-
import type { Translations } from '@nanostores/i18n';
|
|
123
|
-
export type { InitializeI18nOptions } from '@rimelight/i18n/runtime';
|
|
124
|
-
export const currentLocale: import('nanostores').PreinitializedWritableAtom<string> & object;
|
|
125
|
-
export declare function initializeI18n(options: import('@rimelight/i18n/runtime').InitializeI18nOptions): void;
|
|
126
|
-
export declare function useFormat(): import('@nanostores/i18n').Formatter;
|
|
127
|
-
export type ComponentMessages = Record<string, string>;
|
|
128
|
-
export declare function useI18n(componentName: string): ComponentMessages;
|
|
129
|
-
export declare function useI18n<Body extends Translations>(componentName: string, baseTranslations: Body): ComponentMessages & Body;
|
|
130
|
-
export declare function useI18nAsync(componentName: string): Promise<Record<string, string>>;
|
|
131
|
-
export declare function useI18nAsync<Body extends Translations>(componentName: string, baseTranslations: Body): Promise<Record<string, string> & Body>;
|
|
132
|
-
export declare function t(key: string, params?: Record<string, any>): string;
|
|
133
|
-
export declare function t(astro: { currentLocale?: string | undefined; params?: Record<string, any> } | undefined | null, key: string, params?: Record<string, any>): string;
|
|
134
|
-
export declare function getLocale(): string;
|
|
135
|
-
export declare function getI18nInstance(): ReturnType<typeof import('@nanostores/i18n').createI18n>;
|
|
136
|
-
export declare function getFormatterInstance(): ReturnType<typeof import('@nanostores/i18n').formatter>;
|
|
137
|
-
export declare function clearCache(locale?: string): void;
|
|
138
|
-
}
|
|
139
|
-
`
|
|
140
|
-
})
|
|
141
|
-
},
|
|
142
|
-
|
|
143
|
-
"astro:build:done": async ({ logger }) => {
|
|
144
|
-
if (translations) {
|
|
145
|
-
const seedData: { keys: { key: string; value: string }[] } = { keys: [] }
|
|
146
|
-
for (const [locale, localeData] of Object.entries(translations)) {
|
|
147
|
-
for (const [component, componentData] of Object.entries(localeData)) {
|
|
148
|
-
seedData.keys.push({
|
|
149
|
-
key: `locale:${locale}:${component}`,
|
|
150
|
-
value: JSON.stringify(componentData)
|
|
151
|
-
})
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
const outDir = process.env.WRANGLER_OUT_DIR ?? "dist"
|
|
155
|
-
const seedFile = path.join(outDir, "_translations-seed.json")
|
|
156
|
-
fs.mkdirSync(path.dirname(seedFile), { recursive: true })
|
|
157
|
-
fs.writeFileSync(seedFile, JSON.stringify(seedData, null, 2))
|
|
158
|
-
logger.info(`translations seed file written to ${seedFile}`)
|
|
159
|
-
|
|
160
|
-
const autoSeed = process.env.WRANGLER_SEED_KV === "true" || process.env.CI === "true"
|
|
161
|
-
if (autoSeed) {
|
|
162
|
-
try {
|
|
163
|
-
const { execSync } = await import("node:child_process")
|
|
164
|
-
execSync(`npx wrangler kv:bulk put --binding "${kvBinding}" "${seedFile}"`, {
|
|
165
|
-
stdio: "pipe",
|
|
166
|
-
timeout: 30_000
|
|
167
|
-
})
|
|
168
|
-
logger.info(`KV "${kvBinding}" seeded with translations`)
|
|
169
|
-
} catch {
|
|
170
|
-
logger.warn(
|
|
171
|
-
`could not seed KV automatically — run ` +
|
|
172
|
-
`"npx wrangler kv:bulk put --binding "${kvBinding}" "${seedFile}"" ` +
|
|
173
|
-
`to seed translations into KV`
|
|
174
|
-
)
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
} else {
|
|
178
|
-
logger.info("i18n build complete (no translations to seed)")
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
return [rimelightI18nCore]
|
|
185
|
-
}
|
package/src/middleware/kv.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { setKVBinding } from "../index"
|
|
2
|
-
import { env } from "cloudflare:workers"
|
|
3
|
-
|
|
4
|
-
export const onRequest = async (_context: unknown, next: () => Promise<Response>) => {
|
|
5
|
-
const kv = Reflect.get(env, "TRANSLATIONS_KV")
|
|
6
|
-
if (kv) {
|
|
7
|
-
setKVBinding(kv)
|
|
8
|
-
}
|
|
9
|
-
return next()
|
|
10
|
-
}
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { locales, defaultLocale } from "virtual:rimelight-i18n-config"
|
|
2
|
-
import { currentLocale } from "../runtime"
|
|
3
|
-
|
|
4
|
-
const SUPPORTED_LOCALES = new Set(locales)
|
|
5
|
-
const DEFAULT_LOCALE = defaultLocale
|
|
6
|
-
|
|
7
|
-
function getPreferredLocale(acceptLanguage: string | null): string {
|
|
8
|
-
if (!acceptLanguage) return DEFAULT_LOCALE
|
|
9
|
-
|
|
10
|
-
const parsed = acceptLanguage
|
|
11
|
-
.split(",")
|
|
12
|
-
.map((lang) => {
|
|
13
|
-
const parts = lang.split(";")
|
|
14
|
-
const code = (parts[0] ?? "").trim().toLowerCase()
|
|
15
|
-
const base = code.split("-")[0] ?? ""
|
|
16
|
-
let q = 1.0
|
|
17
|
-
const qualityPart = parts[1]
|
|
18
|
-
if (qualityPart) {
|
|
19
|
-
const qMatch = qualityPart.match(/q=([0-9.]+)/)
|
|
20
|
-
if (qMatch) {
|
|
21
|
-
q = parseFloat(qMatch[1] ?? "1")
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
return { code, base, q }
|
|
25
|
-
})
|
|
26
|
-
.toSorted((a, b) => b.q - a.q)
|
|
27
|
-
|
|
28
|
-
for (const item of parsed) {
|
|
29
|
-
if (SUPPORTED_LOCALES.has(item.code)) {
|
|
30
|
-
return item.code
|
|
31
|
-
}
|
|
32
|
-
const base: string = item.base
|
|
33
|
-
if (SUPPORTED_LOCALES.has(base)) {
|
|
34
|
-
return base
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
return DEFAULT_LOCALE
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export const i18n = async (context: any, next: any) => {
|
|
42
|
-
const url = new URL(context.request.url)
|
|
43
|
-
const pathname = url.pathname
|
|
44
|
-
|
|
45
|
-
// Extract active locale from request context
|
|
46
|
-
const paramLocale = context.params?.locale
|
|
47
|
-
const firstSegment = pathname.split("/").find(Boolean)
|
|
48
|
-
const activeLocale =
|
|
49
|
-
(paramLocale && SUPPORTED_LOCALES.has(paramLocale) ? paramLocale : null) ||
|
|
50
|
-
(firstSegment && SUPPORTED_LOCALES.has(firstSegment) ? firstSegment : null) ||
|
|
51
|
-
context.currentLocale ||
|
|
52
|
-
DEFAULT_LOCALE
|
|
53
|
-
|
|
54
|
-
currentLocale.set(activeLocale)
|
|
55
|
-
|
|
56
|
-
// Only handle GET/HEAD requests
|
|
57
|
-
if (context.request.method !== "GET" && context.request.method !== "HEAD") {
|
|
58
|
-
return next()
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Skip API routes, internal Astro routes, and files with extensions
|
|
62
|
-
if (pathname.startsWith("/api") || pathname.startsWith("/_") || pathname.includes(".")) {
|
|
63
|
-
return next()
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Check if pathname starts with a supported locale prefix
|
|
67
|
-
if (firstSegment && SUPPORTED_LOCALES.has(firstSegment)) {
|
|
68
|
-
return next()
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Redirect to the preferred locale
|
|
72
|
-
const acceptLanguage = context.request.headers.get("accept-language")
|
|
73
|
-
const locale = getPreferredLocale(acceptLanguage)
|
|
74
|
-
currentLocale.set(locale)
|
|
75
|
-
const targetPath = `/${locale}${pathname}${url.search}`
|
|
76
|
-
|
|
77
|
-
return context.redirect(targetPath, 302)
|
|
78
|
-
}
|
package/src/middleware.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { env } from "cloudflare:workers"
|
|
2
|
-
import { setKVBinding } from "./index"
|
|
3
|
-
|
|
4
|
-
export const onRequest = async (_context: unknown, next: () => Promise<Response>) => {
|
|
5
|
-
const kv = Reflect.get(env, "TRANSLATIONS_KV")
|
|
6
|
-
if (kv) {
|
|
7
|
-
setKVBinding(kv)
|
|
8
|
-
}
|
|
9
|
-
return next()
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export { i18n } from "./middleware/routing"
|