@i18n-micro/core 1.0.27 → 1.0.28

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@i18n-micro/core",
3
- "version": "1.0.27",
3
+ "version": "1.0.28",
4
4
  "description": "",
5
5
  "repository": "s00d/nuxt-i18n-micro",
6
6
  "license": "MIT",
@@ -22,11 +22,11 @@
22
22
  },
23
23
  "keywords": [],
24
24
  "dependencies": {
25
- "@i18n-micro/types": "1.0.15"
25
+ "@i18n-micro/types": "1.0.16"
26
26
  },
27
27
  "devDependencies": {
28
- "vite": "^5.0.0",
29
- "vite-plugin-dts": "^4.3.0"
28
+ "vite": "^7.2.7",
29
+ "vite-plugin-dts": "^4.5.4"
30
30
  },
31
31
  "scripts": {
32
32
  "build": "vite build",
package/src/base.ts ADDED
@@ -0,0 +1,250 @@
1
+ import { useTranslationHelper, type TranslationCache } from './translation'
2
+ import { FormatService } from './format-service'
3
+ import { interpolate, defaultPlural } from './helpers'
4
+ import type {
5
+ Translations,
6
+ Params,
7
+ PluralFunc,
8
+ Getter,
9
+ CleanTranslation,
10
+ TranslationKey,
11
+ MissingHandler,
12
+ } from '@i18n-micro/types'
13
+
14
+ export interface BaseI18nOptions {
15
+ cache?: TranslationCache
16
+ plural?: PluralFunc
17
+ missingWarn?: boolean
18
+ missingHandler?: (locale: string, key: string, routeName: string) => void
19
+ // Optional hooks for Nuxt runtime specific features
20
+ getPreviousPageInfo?: () => { locale: string, routeName: string } | null
21
+ getCustomMissingHandler?: () => MissingHandler | null
22
+ enablePreviousPageFallback?: boolean
23
+ }
24
+
25
+ /**
26
+ * Abstract base class for i18n adapters
27
+ *
28
+ * Contains all common translation logic (t, ts, tc, tn, td, tdr, has).
29
+ * Adapters must implement abstract methods to provide current state (locale, fallbackLocale, route).
30
+ */
31
+ export abstract class BaseI18n {
32
+ // Public fields (made public to allow type export in Nuxt plugins)
33
+ public helper: ReturnType<typeof useTranslationHelper>
34
+ public formatter = new FormatService()
35
+ public pluralFunc: PluralFunc
36
+ public missingWarn: boolean
37
+ public missingHandler?: (locale: string, key: string, routeName: string) => void
38
+ // Optional hooks for Nuxt runtime specific features
39
+ public getPreviousPageInfo?: () => { locale: string, routeName: string } | null
40
+ public getCustomMissingHandler?: () => MissingHandler | null
41
+ public enablePreviousPageFallback: boolean
42
+
43
+ constructor(options: BaseI18nOptions = {}) {
44
+ this.helper = useTranslationHelper(options.cache)
45
+ this.formatter = new FormatService()
46
+ this.pluralFunc = options.plural || defaultPlural
47
+ this.missingWarn = options.missingWarn ?? true
48
+ this.missingHandler = options.missingHandler
49
+ this.getPreviousPageInfo = options.getPreviousPageInfo
50
+ this.getCustomMissingHandler = options.getCustomMissingHandler
51
+ this.enablePreviousPageFallback = options.enablePreviousPageFallback ?? false
52
+ }
53
+
54
+ // --- Abstract methods (must be implemented by subclasses) ---
55
+
56
+ /**
57
+ * Get current locale
58
+ */
59
+ public abstract getLocale(): string
60
+
61
+ /**
62
+ * Get fallback locale
63
+ */
64
+ public abstract getFallbackLocale(): string
65
+
66
+ /**
67
+ * Get current route name
68
+ */
69
+ public abstract getRoute(): string
70
+
71
+ // --- Public methods (implemented in base class) ---
72
+
73
+ /**
74
+ * Get translation for a key
75
+ * Based on logic from src/runtime/plugins/01.plugin.ts
76
+ */
77
+ public t(
78
+ key: TranslationKey,
79
+ params?: Params,
80
+ defaultValue?: string | null,
81
+ routeName?: string,
82
+ ): CleanTranslation {
83
+ if (!key) return ''
84
+
85
+ // Use abstract getters to get current state
86
+ const locale = this.getLocale()
87
+ const route = routeName || this.getRoute()
88
+
89
+ // 1. Try to find translation in current locale
90
+ // Note: In Nuxt runtime, server already merges global translations, so we don't need explicit fallback
91
+ let value = this.helper.getTranslation<string>(locale, route, key)
92
+
93
+ // 2. If translation not found and there are saved previous translations, use them (only if enabled)
94
+ if (!value && this.enablePreviousPageFallback && this.getPreviousPageInfo) {
95
+ const prev = this.getPreviousPageInfo()
96
+ if (prev) {
97
+ const prevValue = this.helper.getTranslation<string>(prev.locale, prev.routeName, key)
98
+ if (prevValue) {
99
+ value = prevValue
100
+ if (process.env.NODE_ENV !== 'production') {
101
+ console.log(`Using fallback translation from previous route: ${prev.routeName} -> ${key}`)
102
+ }
103
+ }
104
+ }
105
+ }
106
+
107
+ // 3. Fallback to fallbackLocale if not found and different (for non-Nuxt adapters)
108
+ if (!value) {
109
+ const fallbackLocale = this.getFallbackLocale()
110
+ if (locale !== fallbackLocale) {
111
+ value = this.helper.getTranslation<string>(fallbackLocale, route, key)
112
+ }
113
+ }
114
+
115
+ // 4. Handle missing
116
+ if (!value) {
117
+ // Call custom handler if set (Nuxt runtime), otherwise use instance handler
118
+ const customHandler = this.getCustomMissingHandler?.()
119
+ if (customHandler) {
120
+ customHandler(locale, key, route)
121
+ }
122
+ else if (this.missingHandler) {
123
+ this.missingHandler(locale, key as string, route)
124
+ }
125
+ else if (this.missingWarn) {
126
+ const isDev = process.env.NODE_ENV !== 'production'
127
+ const isClient = typeof window !== 'undefined'
128
+ if (isDev && isClient) {
129
+ console.warn(`Not found '${key}' key in '${locale}' locale messages for route '${route}'.`)
130
+ }
131
+ }
132
+ value = defaultValue === undefined ? key : (defaultValue || key)
133
+ }
134
+
135
+ // 5. Interpolate
136
+ return typeof value === 'string' && params ? interpolate(value, params) : value as CleanTranslation
137
+ }
138
+
139
+ /**
140
+ * Get translation as string
141
+ */
142
+ public ts(
143
+ key: TranslationKey,
144
+ params?: Params,
145
+ defaultValue?: string,
146
+ routeName?: string,
147
+ ): string {
148
+ const value = this.t(key, params, defaultValue, routeName)
149
+ return value?.toString() ?? defaultValue ?? key
150
+ }
151
+
152
+ /**
153
+ * Plural translation
154
+ */
155
+ public tc(key: TranslationKey, count: number | Params, defaultValue?: string): string {
156
+ const { count: countValue, ...params } = typeof count === 'number' ? { count } : count
157
+
158
+ if (countValue === undefined) {
159
+ return defaultValue ?? key
160
+ }
161
+
162
+ // Getter passed to plural function
163
+ const getter: Getter = (k: TranslationKey, p?: Params, dv?: string) => {
164
+ return this.t(k, p, dv)
165
+ }
166
+
167
+ const result = this.pluralFunc(
168
+ key,
169
+ Number.parseInt(countValue.toString()),
170
+ params,
171
+ this.getLocale(),
172
+ getter,
173
+ )
174
+
175
+ return result ?? defaultValue ?? key
176
+ }
177
+
178
+ /**
179
+ * Format number
180
+ */
181
+ public tn(value: number, options?: Intl.NumberFormatOptions): string {
182
+ return this.formatter.formatNumber(value, this.getLocale(), options)
183
+ }
184
+
185
+ /**
186
+ * Format date
187
+ */
188
+ public td(value: Date | number | string, options?: Intl.DateTimeFormatOptions): string {
189
+ return this.formatter.formatDate(value, this.getLocale(), options)
190
+ }
191
+
192
+ /**
193
+ * Format relative time
194
+ */
195
+ public tdr(value: Date | number | string, options?: Intl.RelativeTimeFormatOptions): string {
196
+ return this.formatter.formatRelativeTime(value, this.getLocale(), options)
197
+ }
198
+
199
+ /**
200
+ * Check if translation exists
201
+ * Based on logic from src/runtime/plugins/01.plugin.ts
202
+ */
203
+ public has(key: TranslationKey, routeName?: string): boolean {
204
+ const route = routeName || this.getRoute()
205
+ const locale = this.getLocale()
206
+
207
+ // Check only through getTranslation (as in plugin)
208
+ return !!this.helper.getTranslation(locale, route, key)
209
+ }
210
+
211
+ /**
212
+ * Clear cache
213
+ */
214
+ public clearCache(): void {
215
+ this.helper.clearCache()
216
+ }
217
+
218
+ // --- Public methods (for subclasses to use) ---
219
+
220
+ /**
221
+ * Core translation loading logic (without reactivity)
222
+ * Subclasses can override addTranslations/addRouteTranslations to add reactivity
223
+ */
224
+ public loadTranslationsCore(locale: string, translations: Translations, merge: boolean): void {
225
+ if (merge) {
226
+ this.helper.mergeGlobalTranslation(locale, translations, true)
227
+ }
228
+ else {
229
+ this.helper.loadTranslations(locale, translations)
230
+ }
231
+ }
232
+
233
+ /**
234
+ * Core route translation loading logic (without reactivity)
235
+ * Subclasses can override addRouteTranslations to add reactivity
236
+ */
237
+ public loadRouteTranslationsCore(
238
+ locale: string,
239
+ routeName: string,
240
+ translations: Translations,
241
+ merge: boolean,
242
+ ): void {
243
+ if (merge) {
244
+ this.helper.mergeTranslation(locale, routeName, translations, true)
245
+ }
246
+ else {
247
+ this.helper.loadPageTranslations(locale, routeName, translations)
248
+ }
249
+ }
250
+ }
package/src/index.ts CHANGED
@@ -2,6 +2,7 @@ import { useTranslationHelper, type TranslationCache } from './translation'
2
2
  import { RouteService } from './route-service'
3
3
  import { FormatService } from './format-service'
4
4
  import { interpolate, withPrefixStrategy, isNoPrefixStrategy, isPrefixStrategy, isPrefixExceptDefaultStrategy, isPrefixAndDefaultStrategy, defaultPlural } from './helpers'
5
+ import { BaseI18n, type BaseI18nOptions } from './base'
5
6
 
6
7
  export {
7
8
  useTranslationHelper,
@@ -14,5 +15,7 @@ export {
14
15
  defaultPlural,
15
16
  RouteService,
16
17
  FormatService,
18
+ BaseI18n,
17
19
  type TranslationCache,
20
+ type BaseI18nOptions,
18
21
  }
@@ -374,19 +374,20 @@ export class RouteService {
374
374
  }
375
375
 
376
376
  updateCookies(toLocale: string): void {
377
+ const cookieLocaleName = this.cookieLocaleName || this.i18nConfig.localeCookie || 'user-locale'
377
378
  if (this.i18nConfig.hashMode) {
378
379
  this.setCookie('hash-locale', toLocale)
379
380
  // useCookie('hash-locale').value = toLocale
380
381
  this.hashLocaleDefault = toLocale
381
382
  }
382
383
  if (isNoPrefixStrategy(this.i18nConfig.strategy!)) {
383
- this.setCookie('no-prefix-locale', toLocale)
384
- // useCookie('no-prefix-locale').value = toLocale
384
+ this.setCookie(cookieLocaleName, toLocale)
385
+ // useCookie(cookieLocaleName).value = toLocale
385
386
  this.noPrefixDefault = toLocale
386
387
  }
387
388
  // Update cookie for regular strategy (prefix or prefix_except_default)
388
- if (!this.i18nConfig.hashMode && !isNoPrefixStrategy(this.i18nConfig.strategy!) && this.cookieLocaleName) {
389
- this.setCookie(this.cookieLocaleName, toLocale)
389
+ if (!this.i18nConfig.hashMode && !isNoPrefixStrategy(this.i18nConfig.strategy!) && cookieLocaleName) {
390
+ this.setCookie(cookieLocaleName, toLocale)
390
391
  this.cookieLocaleDefault = toLocale
391
392
  }
392
393
  }