@rimelight/i18n 0.0.4 → 0.0.5

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/README.md ADDED
@@ -0,0 +1,19 @@
1
+ # Rimelight Entertainment Workspace
2
+
3
+ ## Structure
4
+
5
+ ### Apps (`/packages`)
6
+
7
+ - **`rimelight.com`**: The main company website.
8
+ - **`starter.rimelight.com`**: Our standardized starter template for Astro websites.
9
+
10
+ ### Packages (`/packages`)
11
+
12
+ - **`@rimelight/auth`**: Authentication and authorization utilities for the Rimelight ecosystem.
13
+ - **`@rimelight/cli`**: The command line interface for managing Rimelight projects.
14
+ - **`@rimelight/cms`**: Enterprise content management, block rendering, and wiki engine.
15
+ - **`@rimelight/docs`**: Documentation components and utilities.
16
+ - **`@rimelight/i18n`**: Internationalization and localization tools.
17
+ - **`@rimelight/security`**: Astro security integration (CSP, SRI, and more).
18
+ - **`@rimelight/seo`**: SEO utilities including sitemap, robots, and meta components.
19
+ - **`@rimelight/ui`**: Our component library used in all our web projects.
package/package.json CHANGED
@@ -1,8 +1,20 @@
1
1
  {
2
2
  "name": "@rimelight/i18n",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
4
4
  "private": false,
5
- "description": "Rimelight i18n unified translation loader with optional KV backing",
5
+ "description": "Rimelight Entertainment's Internationalization Package",
6
+ "homepage": "https://rimelight.com/docs",
7
+ "bugs": {
8
+ "url": "https://github.com/Rimelight-Entertainment/rimelight/issues"
9
+ },
10
+ "license": "MIT",
11
+ "author": {
12
+ "name": "Rimelight Entertainment"
13
+ },
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/Rimelight-Entertainment/rimelight.git"
17
+ },
6
18
  "files": [
7
19
  "src"
8
20
  ],
@@ -15,19 +27,31 @@
15
27
  "./types": "./src/types.ts",
16
28
  "./utils": "./src/utils.ts"
17
29
  },
30
+ "publishConfig": {
31
+ "access": "public"
32
+ },
18
33
  "scripts": {
19
- "check": "node -e \"process.exit(0)\""
34
+ "check": "pnpm audit --audit-level=moderate && vp check --fix"
20
35
  },
21
36
  "dependencies": {
22
37
  "@nanostores/i18n": "1.3.3",
23
- "nanostores": "1.4.0"
38
+ "nanostores": "1.5.1"
39
+ },
40
+ "devDependencies": {
41
+ "@rimelight/config": "workspace:*",
42
+ "astro": "7.2.2",
43
+ "typescript": "6.0.3"
24
44
  },
25
45
  "peerDependencies": {
26
- "astro": ">=6.0.0"
46
+ "astro": ">=7.0.0"
27
47
  },
28
48
  "peerDependenciesMeta": {
29
49
  "astro": {
30
50
  "optional": true
31
51
  }
32
- }
52
+ },
53
+ "engines": {
54
+ "node": ">=26.7.0"
55
+ },
56
+ "packageManager": "pnpm@11.22.0"
33
57
  }
package/src/env.d.ts CHANGED
@@ -1 +1,13 @@
1
- /// <reference types="astro/client" />
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 CHANGED
@@ -3,6 +3,7 @@ import type { ComponentsJSON, KVNamespaceBinding } from "./types"
3
3
  import {
4
4
  useI18n as baseUseI18n,
5
5
  useI18nAsync as baseUseI18nAsync,
6
+ t,
6
7
  currentLocale,
7
8
  useFormat,
8
9
  clearCache,
@@ -11,55 +12,77 @@ import {
11
12
  } from "@rimelight/i18n:runtime"
12
13
  import type { Translations } from "@nanostores/i18n"
13
14
 
14
- export { currentLocale, useFormat, clearCache, getI18nInstance, getFormatterInstance }
15
+ export { t, currentLocale, useFormat, clearCache, getI18nInstance, getFormatterInstance }
15
16
 
17
+ export interface ComponentMessages {
18
+ [key: string]: string
19
+ }
20
+
21
+ export function useI18n(componentName: string): ComponentMessages
16
22
  export function useI18n<Body extends Translations>(
17
23
  componentName: string,
18
24
  baseTranslations: Body
19
- ): Body
25
+ ): ComponentMessages & Body
26
+ export function useI18n(
27
+ astro: { currentLocale?: string | undefined; params?: Record<string, any> } | undefined | null,
28
+ componentName: string
29
+ ): ComponentMessages
20
30
  export function useI18n<Body extends Translations>(
21
- astro: { currentLocale?: string | undefined } | undefined | null,
31
+ astro: { currentLocale?: string | undefined; params?: Record<string, any> } | undefined | null,
22
32
  componentName: string,
23
33
  baseTranslations: Body
24
- ): Body
25
- export function useI18n(arg1: any, arg2: any, arg3?: any): any {
34
+ ): ComponentMessages & Body
35
+ export function useI18n(arg1: any, arg2?: any, arg3?: any): any {
26
36
  if (typeof arg1 === "string") {
27
37
  return baseUseI18n(arg1, arg2)
28
38
  } else {
29
- const activeLocale = arg1?.currentLocale ?? "en"
30
- currentLocale.set(activeLocale)
39
+ const locale = arg1?.currentLocale || arg1?.params?.locale
40
+ if (locale) {
41
+ currentLocale.set(locale)
42
+ }
31
43
  return baseUseI18n(arg2, arg3)
32
44
  }
33
45
  }
34
46
 
47
+ export function useI18nAsync(componentName: string): Promise<Record<string, string>>
35
48
  export function useI18nAsync<Body extends Translations>(
36
49
  componentName: string,
37
50
  baseTranslations: Body
38
- ): Promise<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>>
39
56
  export function useI18nAsync<Body extends Translations>(
40
- astro: { currentLocale?: string | undefined } | undefined | null,
57
+ astro: { currentLocale?: string | undefined; params?: Record<string, any> } | undefined | null,
41
58
  componentName: string,
42
59
  baseTranslations: Body
43
- ): Promise<Body>
44
- export function useI18nAsync(arg1: any, arg2: any, arg3?: any): any {
60
+ ): Promise<Record<string, string> & Body>
61
+ export function useI18nAsync(arg1: any, arg2?: any, arg3?: any): any {
45
62
  if (typeof arg1 === "string") {
46
63
  return baseUseI18nAsync(arg1, arg2)
47
64
  } else {
48
- const activeLocale = arg1?.currentLocale ?? "en"
49
- currentLocale.set(activeLocale)
65
+ const locale = arg1?.currentLocale || arg1?.params?.locale
66
+ if (locale) {
67
+ currentLocale.set(locale)
68
+ }
50
69
  return baseUseI18nAsync(arg2, arg3)
51
70
  }
52
71
  }
53
72
 
54
73
  import { getRelativeLocaleUrl as astroGetRelativeLocaleUrl } from "astro:i18n"
55
74
 
75
+ export function getLocale(): string {
76
+ return currentLocale.get() || "en"
77
+ }
78
+
56
79
  export function getRelativeLocaleUrl(path: string): string
57
80
  export function getRelativeLocaleUrl(locale: string, path: string): string
58
81
  export function getRelativeLocaleUrl(arg1: string, arg2?: string): string {
59
82
  if (arg2 !== undefined) {
60
83
  return astroGetRelativeLocaleUrl(arg1, arg2)
61
84
  } else {
62
- const locale = currentLocale.get() || "en"
85
+ const locale = getLocale()
63
86
  return astroGetRelativeLocaleUrl(locale, arg1)
64
87
  }
65
88
  }
@@ -15,11 +15,11 @@ export function rimelightI18n(options?: RimelightI18nOptions): AstroIntegration[
15
15
  const kvBinding = options?.kvBinding ?? "TRANSLATIONS_KV"
16
16
  const translationLoader = options?.translationLoader
17
17
 
18
- // Normalise locale keys: "pt" → "pt-br" for nanostores compatibility
18
+ // Normalise translations passed in options
19
19
  const normalisedTranslations: Record<string, any> = {}
20
20
  if (translations) {
21
21
  for (const [locale, val] of Object.entries(translations)) {
22
- normalisedTranslations[locale === "pt" ? "pt-br" : locale] = val
22
+ normalisedTranslations[locale] = val
23
23
  }
24
24
  }
25
25
 
@@ -49,7 +49,7 @@ export function rimelightI18n(options?: RimelightI18nOptions): AstroIntegration[
49
49
  const defaultLocale = config.i18n?.defaultLocale ?? "en"
50
50
 
51
51
  const virtualModuleContent = `\
52
- import { initializeI18n, useFormat, useI18n, useI18nAsync,
52
+ import { initializeI18n, useFormat, useI18n, useI18nAsync, t,
53
53
  currentLocale, getI18nInstance, getFormatterInstance, clearCache }
54
54
  from ${JSON.stringify(runtimeSpecifier)};
55
55
  ${loaderImport}
@@ -59,7 +59,7 @@ initializeI18n({
59
59
  translations: ${JSON.stringify(normalisedTranslations)}${loaderOption}
60
60
  });
61
61
 
62
- export { useFormat, useI18n, useI18nAsync, currentLocale,
62
+ export { useFormat, useI18n, useI18nAsync, t, currentLocale,
63
63
  getI18nInstance, getFormatterInstance, clearCache };
64
64
  `
65
65
 
@@ -124,8 +124,14 @@ declare module "@rimelight/i18n:runtime" {
124
124
  export const currentLocale: import('nanostores').PreinitializedWritableAtom<string> & object;
125
125
  export declare function initializeI18n(options: import('@rimelight/i18n/runtime').InitializeI18nOptions): void;
126
126
  export declare function useFormat(): import('@nanostores/i18n').Formatter;
127
- export declare function useI18n<Body extends Translations>(componentName: string, baseTranslations: Body): Body;
128
- export declare function useI18nAsync<Body extends Translations>(componentName: string, baseTranslations: Body): Promise<Body>;
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;
129
135
  export declare function getI18nInstance(): ReturnType<typeof import('@nanostores/i18n').createI18n>;
130
136
  export declare function getFormatterInstance(): ReturnType<typeof import('@nanostores/i18n').formatter>;
131
137
  export declare function clearCache(locale?: string): void;
@@ -151,19 +157,22 @@ declare module "@rimelight/i18n:runtime" {
151
157
  fs.writeFileSync(seedFile, JSON.stringify(seedData, null, 2))
152
158
  logger.info(`translations seed file written to ${seedFile}`)
153
159
 
154
- try {
155
- const { execSync } = await import("node:child_process")
156
- execSync(`npx wrangler kv:bulk put --binding "${kvBinding}" "${seedFile}"`, {
157
- stdio: "pipe",
158
- timeout: 30_000
159
- })
160
- logger.info(`KV "${kvBinding}" seeded with translations`)
161
- } catch {
162
- logger.warn(
163
- `could not seed KV automatically — run ` +
164
- `"npx wrangler kv:bulk put --binding "${kvBinding}" "${seedFile}"" ` +
165
- `to seed translations into KV`
166
- )
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
+ }
167
176
  }
168
177
  } else {
169
178
  logger.info("i18n build complete (no translations to seed)")
@@ -1,4 +1,5 @@
1
1
  import { locales, defaultLocale } from "virtual:rimelight-i18n-config"
2
+ import { currentLocale } from "../runtime"
2
3
 
3
4
  const SUPPORTED_LOCALES = new Set(locales)
4
5
  const DEFAULT_LOCALE = defaultLocale
@@ -41,6 +42,17 @@ export const i18n = async (context: any, next: any) => {
41
42
  const url = new URL(context.request.url)
42
43
  const pathname = url.pathname
43
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
+
44
56
  // Only handle GET/HEAD requests
45
57
  if (context.request.method !== "GET" && context.request.method !== "HEAD") {
46
58
  return next()
@@ -52,8 +64,6 @@ export const i18n = async (context: any, next: any) => {
52
64
  }
53
65
 
54
66
  // Check if pathname starts with a supported locale prefix
55
- const firstSegment = pathname.split("/").find(Boolean)
56
-
57
67
  if (firstSegment && SUPPORTED_LOCALES.has(firstSegment)) {
58
68
  return next()
59
69
  }
@@ -61,6 +71,7 @@ export const i18n = async (context: any, next: any) => {
61
71
  // Redirect to the preferred locale
62
72
  const acceptLanguage = context.request.headers.get("accept-language")
63
73
  const locale = getPreferredLocale(acceptLanguage)
74
+ currentLocale.set(locale)
64
75
  const targetPath = `/${locale}${pathname}${url.search}`
65
76
 
66
77
  return context.redirect(targetPath, 302)
package/src/runtime.ts CHANGED
@@ -19,6 +19,8 @@ type FormatterInstance = ReturnType<typeof formatter>
19
19
 
20
20
  let i18nInstance: I18nInstance | undefined
21
21
  let formatterInstance: FormatterInstance | undefined
22
+ let baseLocaleDefault: string = "en"
23
+ let rawTranslationsDict: Record<string, Components> = {}
22
24
 
23
25
  function throwNotInitialized(): never {
24
26
  throw new Error("i18n not initialized. Call initializeI18n first.")
@@ -70,12 +72,34 @@ export interface InitializeI18nOptions {
70
72
  */
71
73
  export function initializeI18n(options: InitializeI18nOptions): void {
72
74
  const { defaultLocale, translations, get } = options
75
+ baseLocaleDefault = defaultLocale
76
+ rawTranslationsDict = translations || {}
77
+
73
78
  if (!i18nInstance) {
74
79
  currentLocale.set(defaultLocale)
80
+
81
+ // Convert plain JSON dictionaries to nanostores atoms expected by nanostores/i18n cache
82
+ const formattedCache: Record<string, Components> = {}
83
+ for (const [locale, components] of Object.entries(translations)) {
84
+ formattedCache[locale] = {}
85
+ for (const [compName, compBody] of Object.entries(components)) {
86
+ let bodyObj: Record<string, string> = {}
87
+ if (isStringRecord(compBody)) {
88
+ bodyObj = compBody
89
+ } else if (isStoreWithGet(compBody)) {
90
+ const res: unknown = compBody.get()
91
+ if (isStringRecord(res)) {
92
+ bodyObj = res
93
+ }
94
+ }
95
+ formattedCache[locale][compName] = atom(bodyObj)
96
+ }
97
+ }
98
+
75
99
  i18nInstance = createI18n(currentLocale, {
76
100
  baseLocale: defaultLocale,
77
101
  get: wrapLoader(get ?? (async (): Promise<ComponentsJSON> => ({}))),
78
- cache: translations,
102
+ cache: formattedCache,
79
103
  isSSR: true
80
104
  })
81
105
  }
@@ -107,32 +131,175 @@ export function useFormat(): ReturnType<FormatterInstance["get"]> {
107
131
  }
108
132
 
109
133
  /**
110
- * Returns the translated strings for a component in the current locale. Falls back to
111
- * `baseTranslations` for any key not present in the loaded locale.
134
+ * Helper to safely extract raw translation JSON object from cache regardless of whether it is an
135
+ * atom or object. Checks exact locale code (e.g. 'zh-CN'), base language code (e.g. 'zh'), and
136
+ * default locale fallback.
137
+ */
138
+ interface StoreWithGet {
139
+ get: () => unknown
140
+ }
141
+
142
+ function isStoreWithGet(obj: unknown): obj is StoreWithGet {
143
+ if (typeof obj !== "object" || obj === null) return false
144
+ const getFn = Reflect.get(obj, "get")
145
+ return typeof getFn === "function"
146
+ }
147
+
148
+ function isStringRecord(obj: unknown): obj is Record<string, string> {
149
+ return typeof obj === "object" && obj !== null
150
+ }
151
+
152
+ function getCachedComponent(
153
+ locale: string,
154
+ componentName: string
155
+ ): Record<string, string> | undefined {
156
+ const normalizedLocale = locale.toLowerCase()
157
+ const baseLanguage = normalizedLocale.split("-")[0] ?? normalizedLocale
158
+
159
+ const candidates = [locale, normalizedLocale, baseLanguage, baseLocaleDefault]
160
+
161
+ for (const code of candidates) {
162
+ const rawComp: unknown = rawTranslationsDict[code]?.[componentName]
163
+ if (isStoreWithGet(rawComp)) {
164
+ const res: unknown = rawComp.get()
165
+ if (isStringRecord(res)) return res
166
+ } else if (isStringRecord(rawComp)) {
167
+ return rawComp
168
+ }
169
+ }
170
+
171
+ if (!i18nInstance) return undefined
172
+ for (const code of candidates) {
173
+ const rawComp: unknown = i18nInstance.cache[code]?.[componentName]
174
+ if (isStoreWithGet(rawComp)) {
175
+ const res: unknown = rawComp.get()
176
+ if (isStringRecord(res)) return res
177
+ } else if (isStringRecord(rawComp)) {
178
+ return rawComp
179
+ }
180
+ }
181
+
182
+ return undefined
183
+ }
184
+
185
+ /**
186
+ * Returns the translated strings for a component in the current locale. Falls back to defaultLocale
187
+ * loaded translations or `baseTranslations` if provided.
112
188
  */
189
+ export interface ComponentMessages {
190
+ [key: string]: string
191
+ }
192
+
193
+ export function useI18n(componentName: string): ComponentMessages
113
194
  export function useI18n<Body extends Translations>(
114
195
  componentName: string,
115
196
  baseTranslations: Body
116
- ): Body {
117
- return getI18nInstance()(componentName, baseTranslations).get()
197
+ ): ComponentMessages & Body
198
+ export function useI18n(componentName: string, baseTranslations?: Translations): ComponentMessages {
199
+ const i18n = getI18nInstance()
200
+ const activeLocale = currentLocale.get() || baseLocaleDefault
201
+ const fallbackDict: Record<string, string> = isStringRecord(baseTranslations)
202
+ ? baseTranslations
203
+ : {}
204
+ const baseDict =
205
+ getCachedComponent(activeLocale, componentName) ??
206
+ getCachedComponent(baseLocaleDefault, componentName) ??
207
+ fallbackDict
208
+
209
+ const store = i18n(componentName, baseDict)
210
+ const targetObj = store.get()
211
+
212
+ return new Proxy(targetObj, {
213
+ get(target: Record<string, string>, prop: string | symbol): string {
214
+ if (typeof prop === "symbol" || prop in Object.prototype) {
215
+ const val: unknown = Reflect.get(target, prop)
216
+ return typeof val === "string" ? val : ""
217
+ }
218
+ const val = target[prop] ?? baseDict[prop]
219
+ return val ?? prop
220
+ }
221
+ })
118
222
  }
119
223
 
120
224
  /**
121
- * Async version of useI18n that waits for translations to finish loading. Required when using a
122
- * dynamic translationLoader (e.g. KV-backed loading).
225
+ * Async version of useI18n that waits for translations to finish loading.
123
226
  */
227
+ export async function useI18nAsync(componentName: string): Promise<ComponentMessages>
124
228
  export async function useI18nAsync<Body extends Translations>(
125
229
  componentName: string,
126
230
  baseTranslations: Body
127
- ): Promise<Body> {
231
+ ): Promise<ComponentMessages & Body>
232
+ export async function useI18nAsync(
233
+ componentName: string,
234
+ baseTranslations?: Translations
235
+ ): Promise<ComponentMessages> {
128
236
  const i18n = getI18nInstance()
129
- const store = i18n(componentName, baseTranslations)
237
+ const activeLocale = currentLocale.get() || baseLocaleDefault
238
+ const cachedActive = getCachedComponent(activeLocale, componentName)
239
+ const cachedDefault = getCachedComponent(baseLocaleDefault, componentName)
240
+ let baseDict: Record<string, string> = cachedActive ?? cachedDefault ?? {}
241
+ if (!cachedActive && !cachedDefault && isStringRecord(baseTranslations)) {
242
+ baseDict = baseTranslations
243
+ }
244
+
245
+ const store = i18n(componentName, baseDict)
130
246
  const unsubscribe = store.listen(() => {})
131
247
  await translationsLoading(i18n)
132
248
  unsubscribe()
133
249
  return store.get()
134
250
  }
135
251
 
252
+ /**
253
+ * Translates a single string key using dot-notation (e.g. `t("page_home.section_hero_title")`).
254
+ */
255
+ export function t(key: string, params?: Record<string, any>): string
256
+ export function t(
257
+ astro: { currentLocale?: string | undefined; params?: Record<string, any> } | undefined | null,
258
+ key: string,
259
+ params?: Record<string, any>
260
+ ): string
261
+ export function t(arg1: any, arg2?: any, arg3?: any): string {
262
+ let key: string
263
+ let params: Record<string, any> | undefined
264
+
265
+ if (typeof arg1 === "string") {
266
+ key = arg1
267
+ params = arg2
268
+ } else {
269
+ const locale = arg1?.currentLocale || arg1?.params?.locale
270
+ if (locale) {
271
+ currentLocale.set(locale)
272
+ }
273
+ key = arg2
274
+ params = arg3
275
+ }
276
+
277
+ const dotIndex = key.indexOf(".")
278
+ if (dotIndex === -1) {
279
+ return key
280
+ }
281
+ const componentName = key.slice(0, dotIndex)
282
+ const keyName = key.slice(dotIndex + 1)
283
+
284
+ const activeLocale = currentLocale.get() || baseLocaleDefault
285
+ const componentDict =
286
+ getCachedComponent(activeLocale, componentName) ??
287
+ getCachedComponent(baseLocaleDefault, componentName) ??
288
+ {}
289
+
290
+ const value = componentDict[keyName] ?? key
291
+
292
+ if (typeof value === "function") {
293
+ return (value as Function)(params)
294
+ }
295
+
296
+ if (params && typeof value === "string") {
297
+ return value.replace(/\{(\w+)\}/g, (_, k) => String(params[k] ?? `{${k}}`))
298
+ }
299
+
300
+ return value
301
+ }
302
+
136
303
  /**
137
304
  * Clears the translation cache.
138
305
  *
package/src/virtual.d.ts CHANGED
@@ -11,6 +11,5 @@ declare module "cloudflare:workers" {
11
11
  /**
12
12
  * Ambient Cloudflare Workers env — populated at runtime by the CF runtime.
13
13
  */
14
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
15
14
  export const env: Record<string, any>
16
15
  }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Rimelight Entertainment
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.