@i18n-micro/core 1.1.0 → 1.1.2

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.
@@ -1,469 +0,0 @@
1
- import type {
2
- NavigationFailure,
3
- RouteLocationAsPath, RouteLocationAsPathGeneric,
4
- RouteLocationAsRelative,
5
- RouteLocationAsString,
6
- RouteLocationNamedRaw,
7
- RouteLocationNormalizedLoaded,
8
- RouteLocationOptions,
9
- RouteLocationRaw,
10
- RouteLocationResolved,
11
- RouteLocationResolvedGeneric,
12
- RouteParamsRawGeneric,
13
- Router,
14
- } from 'vue-router'
15
- import type { I18nRouteParams, Locale, ModuleOptionsExtend } from '@i18n-micro/types'
16
- import { isNoPrefixStrategy, withPrefixStrategy, isPrefixAndDefaultStrategy, isPrefixExceptDefaultStrategy } from './helpers'
17
-
18
- interface NavigateToInterface {
19
- replace?: boolean
20
- redirectCode?: number
21
- external?: boolean
22
- }
23
-
24
- export type GetDefaultLocale = () => string | null | undefined
25
-
26
- export class RouteService {
27
- constructor(
28
- private i18nConfig: ModuleOptionsExtend,
29
- private router: Router,
30
- private hashLocaleDefault: string | null | undefined,
31
- private noPrefixDefault: string | null | undefined,
32
- private navigateTo: (to: RouteLocationRaw | undefined | null, options?: NavigateToInterface) => Promise<void | NavigationFailure | false> | false | void | RouteLocationRaw,
33
- /** Optional getter for current locale (used by hash/noPrefix when state changes). Plugin provides () => localeState.value */
34
- private getDefaultLocale?: GetDefaultLocale,
35
- ) {}
36
-
37
- /**
38
- * Extracts locale from URL path by checking the first path segment
39
- * @param path - URL path (e.g., '/ru/sdfsdf' or '/en/about')
40
- * @returns Locale code or null if not found
41
- */
42
- private extractLocaleFromPath(path: string): string | null {
43
- if (!path) {
44
- return null
45
- }
46
-
47
- // Remove query params and hash to ensure clean path comparison
48
- // This is important when falling back to fullPath which might contain these
49
- const querySplit = path.split('?')
50
- const cleanPath = querySplit[0]?.split('#')[0]
51
-
52
- if (!cleanPath || cleanPath === '/') {
53
- return null
54
- }
55
-
56
- const pathSegments = cleanPath.split('/').filter(Boolean)
57
- if (pathSegments.length === 0) {
58
- return null
59
- }
60
-
61
- const firstSegment = pathSegments[0]
62
- if (!firstSegment) {
63
- return null
64
- }
65
-
66
- const availableLocales = this.i18nConfig.locales?.map(l => l.code) || []
67
-
68
- // Check if the first segment is a valid locale
69
- if (availableLocales.includes(firstSegment)) {
70
- return firstSegment
71
- }
72
-
73
- return null
74
- }
75
-
76
- getCurrentLocale(route?: RouteLocationNormalizedLoaded | RouteLocationResolvedGeneric): string {
77
- route = route ?? this.router.currentRoute.value
78
-
79
- // 1. Check hashMode (getter overrides initial value when state changes)
80
- if (this.i18nConfig.hashMode) {
81
- const fromGetter = this.getDefaultLocale?.()
82
- if (fromGetter) return fromGetter
83
- if (this.hashLocaleDefault) return this.hashLocaleDefault
84
- }
85
-
86
- // 2. Check noPrefix strategy
87
- if (isNoPrefixStrategy(this.i18nConfig.strategy!)) {
88
- const fromGetter = this.getDefaultLocale?.()
89
- if (fromGetter) return fromGetter
90
- if (this.noPrefixDefault) return this.noPrefixDefault
91
- }
92
-
93
- const path = route.path || route.fullPath || ''
94
- // 2b. prefix_and_default at /: useState/cookie can override — / is valid for any locale
95
- if (isPrefixAndDefaultStrategy(this.i18nConfig.strategy!) && (path === '/' || path === '')) {
96
- const fromGetter = this.getDefaultLocale?.()
97
- if (fromGetter) return fromGetter
98
- }
99
-
100
- // 3. Check route.params.locale (for existing routes)
101
- if (route.params?.locale) {
102
- return route.params.locale.toString()
103
- }
104
-
105
- // 4. Extract locale from URL path (for non-existent routes)
106
- const localeFromPath = this.extractLocaleFromPath(path)
107
- if (localeFromPath) {
108
- return localeFromPath
109
- }
110
-
111
- // 5. For prefix_except_default: URL without locale prefix = defaultLocale
112
- if (isPrefixExceptDefaultStrategy(this.i18nConfig.strategy!)) {
113
- return (this.i18nConfig.defaultLocale || 'en').toString()
114
- }
115
-
116
- // 6. Check getter (for no_prefix and other strategies)
117
- const fromGetter = this.getDefaultLocale?.()
118
- if (fromGetter) return fromGetter
119
-
120
- // 7. Return defaultLocale as fallback
121
- return (this.i18nConfig.defaultLocale || 'en').toString()
122
- }
123
-
124
- getCurrentName(route: RouteLocationNormalizedLoaded | RouteLocationResolvedGeneric): string | null {
125
- const currentLocaleCode = this.getCurrentLocale(route)
126
- const checkLocale = this.i18nConfig.locales?.find(l => l.code === currentLocaleCode)
127
- return checkLocale?.displayName ?? null
128
- }
129
-
130
- getRouteName(route: RouteLocationResolvedGeneric | RouteLocationNamedRaw, locale: string): string {
131
- const name = (route.name ?? '').toString()
132
- return name
133
- .toString()
134
- .replace('localized-', '')
135
- .replace(new RegExp(`-${locale}$`), '')
136
- }
137
-
138
- getPluginRouteName(route: RouteLocationResolvedGeneric | RouteLocationNamedRaw, locale: string): string {
139
- if (this.i18nConfig.disablePageLocales) {
140
- return 'general'
141
- }
142
- return this.getRouteName(route, locale)
143
- }
144
-
145
- getFullPathWithBaseUrl(currentLocale: Locale, route: RouteLocationRaw): string {
146
- const resolvedRoute = this.router.resolve(route)
147
- let fullPath = resolvedRoute.fullPath
148
-
149
- if (currentLocale?.baseDefault) {
150
- fullPath = fullPath.replace(new RegExp(`^/${currentLocale!.code}`), '')
151
- }
152
-
153
- let baseUrl = currentLocale!.baseUrl
154
- if (!baseUrl) baseUrl = ''
155
- if (baseUrl?.endsWith('/')) {
156
- baseUrl = baseUrl.slice(0, -1)
157
- }
158
-
159
- return baseUrl + fullPath
160
- }
161
-
162
- switchLocaleRoute(
163
- fromLocale: string,
164
- toLocale: string,
165
- route: RouteLocationResolvedGeneric | RouteLocationNamedRaw,
166
- i18nRouteParams: I18nRouteParams,
167
- ): RouteLocationRaw {
168
- const currentLocale = this.i18nConfig.locales?.find(l => l.code === toLocale)
169
-
170
- const routeName = this.getRouteName(route, fromLocale)
171
- if (this.router.hasRoute(`localized-${routeName}-${toLocale}`)) {
172
- // If i18nRouteParams exist for target locale, use them as base, otherwise use route.params
173
- const baseParams = i18nRouteParams?.[toLocale]
174
- ? { ...i18nRouteParams[toLocale] }
175
- : { ...route.params ?? {} }
176
- // Merge remaining route.params that are not in i18nRouteParams
177
- const newParams = { ...baseParams }
178
- // Remove locale from params if it exists, we'll add it explicitly
179
- delete newParams.locale
180
- if (!isNoPrefixStrategy(this.i18nConfig.strategy!)) newParams.locale = toLocale
181
-
182
- const newRoute = {
183
- name: `localized-${routeName}-${toLocale}`,
184
- params: newParams,
185
- query: route.query,
186
- hash: route.hash,
187
- }
188
-
189
- if (currentLocale?.baseUrl) {
190
- return this.getFullPathWithBaseUrl(currentLocale, newRoute)
191
- }
192
-
193
- return newRoute
194
- }
195
-
196
- let newRouteName = routeName
197
- // If i18nRouteParams exist for target locale, use them as base, otherwise use route.params
198
- const baseParams = i18nRouteParams?.[toLocale]
199
- ? { ...i18nRouteParams[toLocale] }
200
- : { ...route.params ?? {} }
201
- // Merge remaining route.params that are not in i18nRouteParams
202
- const newParams = { ...baseParams }
203
- delete newParams.locale
204
-
205
- if (!isNoPrefixStrategy(this.i18nConfig.strategy!)) {
206
- if (routeName === 'custom-fallback-route') {
207
- newRouteName = routeName
208
- }
209
- else {
210
- newRouteName
211
- = toLocale !== this.i18nConfig.defaultLocale || withPrefixStrategy(this.i18nConfig.strategy!)
212
- ? `localized-${routeName}`
213
- : routeName
214
- }
215
-
216
- if (!isNoPrefixStrategy(this.i18nConfig.strategy!)) {
217
- if (toLocale !== this.i18nConfig.defaultLocale || withPrefixStrategy(this.i18nConfig.strategy!)) {
218
- newParams.locale = toLocale
219
- }
220
- }
221
- }
222
-
223
- const newRoute = {
224
- name: newRouteName,
225
- params: newParams,
226
- query: route.query,
227
- hash: route.hash,
228
- }
229
-
230
- if (isNoPrefixStrategy(this.i18nConfig.strategy!)) {
231
- this.i18nConfig.locales?.forEach((locale, _index) => {
232
- if (newRoute.name.endsWith(`-${locale.code}`)) {
233
- newRoute.name = newRoute.name.slice(0, -locale.code - 1)
234
- }
235
- })
236
- }
237
-
238
- if (currentLocale?.baseUrl) {
239
- return this.getFullPathWithBaseUrl(currentLocale, newRoute)
240
- }
241
-
242
- return newRoute
243
- }
244
-
245
- private resolveParams(to: RouteLocationAsString | RouteLocationAsRelative | RouteLocationAsPath): RouteParamsRawGeneric {
246
- const params
247
- = typeof to === 'object' && 'params' in to && typeof to.params === 'object'
248
- ? { ...to.params }
249
- : {}
250
-
251
- if (typeof to === 'string') {
252
- const resolved = this.router.resolve(to)
253
- if (resolved && resolved.params) {
254
- Object.assign(params, resolved.params)
255
- }
256
- }
257
-
258
- return params
259
- }
260
-
261
- private handlePrefixStrategy(
262
- to: RouteLocationResolvedGeneric | RouteLocationAsPathGeneric | RouteLocationNamedRaw | string,
263
- ): RouteLocationResolvedGeneric | RouteLocationAsPathGeneric | RouteLocationNamedRaw | string {
264
- if (!withPrefixStrategy(this.i18nConfig.strategy!)) {
265
- return to
266
- }
267
-
268
- const defaultLocale = this.i18nConfig.defaultLocale!
269
- let resolvedTo = to
270
-
271
- if (typeof to === 'string') {
272
- resolvedTo = this.router.resolve('/' + defaultLocale + to)
273
- }
274
-
275
- const defaultRouteName = this.getRouteName(resolvedTo as RouteLocationResolvedGeneric, defaultLocale)
276
- const newParams = this.resolveParams(resolvedTo)
277
-
278
- if (!isNoPrefixStrategy(this.i18nConfig.strategy!)) {
279
- newParams.locale = defaultLocale
280
- }
281
-
282
- if (this.router.hasRoute(`localized-${defaultRouteName}`)) {
283
- return this.router.resolve({
284
- name: `localized-${defaultRouteName}`,
285
- query: (resolvedTo as RouteLocationNormalizedLoaded).query,
286
- hash: (resolvedTo as RouteLocationNormalizedLoaded).hash,
287
- params: newParams,
288
- })
289
- }
290
- else if (this.router.hasRoute(`localized-${defaultRouteName}-${defaultLocale}`)) {
291
- return this.router.resolve({
292
- name: `localized-${defaultRouteName}-${defaultLocale}`,
293
- query: (resolvedTo as RouteLocationNormalizedLoaded).query,
294
- hash: (resolvedTo as RouteLocationNormalizedLoaded).hash,
295
- params: newParams,
296
- })
297
- }
298
-
299
- return to
300
- }
301
-
302
- private createLocalizedRoute(
303
- to: RouteLocationAsString | RouteLocationAsRelative | RouteLocationAsPath,
304
- route: RouteLocationNormalizedLoaded,
305
- locale: string,
306
- ): RouteLocationResolved {
307
- const selectRoute = this.router.resolve(to)
308
- const routeName = this.getRouteName(selectRoute, locale)
309
- .replace(new RegExp(`-${this.i18nConfig.defaultLocale!}$`), '')
310
-
311
- if (!isNoPrefixStrategy(this.i18nConfig.strategy!)) {
312
- if (!routeName || routeName === '') {
313
- const resolved = this.router.resolve(to)
314
- let url = resolved.path.replace(new RegExp(`^/${locale}/`), '/')
315
- if (locale !== this.i18nConfig.defaultLocale || withPrefixStrategy(this.i18nConfig.strategy!)) {
316
- url = '/' + locale + '' + url
317
- }
318
-
319
- return this.router.resolve({
320
- path: url,
321
- query: selectRoute.query,
322
- hash: selectRoute.hash,
323
- })
324
- }
325
- }
326
-
327
- if (this.router.hasRoute(`localized-${routeName}-${locale}`)) {
328
- const newParams = this.resolveParams(selectRoute)
329
- if (!isNoPrefixStrategy(this.i18nConfig.strategy!)) newParams.locale = locale
330
-
331
- return this.router.resolve({
332
- name: `localized-${routeName}-${locale}`,
333
- params: newParams,
334
- query: selectRoute.query,
335
- hash: selectRoute.hash,
336
- })
337
- }
338
-
339
- const newRouteName
340
- = locale !== this.i18nConfig.defaultLocale || withPrefixStrategy(this.i18nConfig.strategy!)
341
- ? `localized-${routeName}`
342
- : routeName
343
-
344
- if (!this.router.hasRoute(newRouteName)) {
345
- const newParams = this.resolveParams(to)
346
- delete newParams.locale
347
-
348
- if (!this.router.hasRoute(routeName)) {
349
- return this.router.resolve('/')
350
- }
351
-
352
- return this.router.resolve({
353
- name: routeName,
354
- params: newParams,
355
- query: selectRoute.query,
356
- hash: selectRoute.hash,
357
- })
358
- }
359
-
360
- const newParams = this.resolveParams(to)
361
- delete newParams.locale
362
-
363
- if (!isNoPrefixStrategy(this.i18nConfig.strategy!)) {
364
- if (locale !== this.i18nConfig.defaultLocale || withPrefixStrategy(this.i18nConfig.strategy!)) {
365
- newParams.locale = locale
366
- }
367
- }
368
-
369
- return this.router.resolve({
370
- name: newRouteName,
371
- params: newParams,
372
- query: selectRoute.query,
373
- hash: selectRoute.hash,
374
- })
375
- }
376
-
377
- getLocalizedRoute(
378
- to: RouteLocationResolvedGeneric | RouteLocationAsPathGeneric | RouteLocationNamedRaw | string,
379
- route: RouteLocationNormalizedLoaded,
380
- locale?: string,
381
- ): RouteLocationResolved {
382
- const currentLocale = locale || this.getCurrentLocale(route)
383
-
384
- // Handle prefix strategy
385
- const processedTo = this.handlePrefixStrategy(to)
386
-
387
- // Create localized route
388
- return this.createLocalizedRoute(processedTo, route, currentLocale)
389
- }
390
-
391
- getCurrentRoute(): RouteLocationNormalizedLoaded {
392
- return this.router.currentRoute.value
393
- }
394
-
395
- private resolveRouteWithStrategy(
396
- route: string,
397
- currentLocale: string,
398
- fromLocale: string,
399
- ): RouteLocationResolved {
400
- if (isNoPrefixStrategy(this.i18nConfig.strategy!)) {
401
- return this.router.resolve(route)
402
- }
403
- else if (currentLocale !== this.i18nConfig.defaultLocale || withPrefixStrategy(this.i18nConfig.strategy!)) {
404
- return this.router.resolve(`/${fromLocale}${route}`)
405
- }
406
- else {
407
- return this.router.resolve(route)
408
- }
409
- }
410
-
411
- switchLocaleLogic(toLocale: string, i18nRouteParams: I18nRouteParams, to?: RouteLocationNamedRaw | RouteLocationResolvedGeneric | string) {
412
- const fromLocale = this.getCurrentLocale()
413
-
414
- let current: RouteLocationResolved | RouteLocationNamedRaw
415
- if (typeof to === 'string') {
416
- current = this.resolveRouteWithStrategy(to, toLocale, fromLocale)
417
- }
418
- else {
419
- current = to ?? this.getCurrentRoute() as RouteLocationResolved
420
- }
421
-
422
- const switchedRoute = this.switchLocaleRoute(fromLocale, toLocale, current, i18nRouteParams)
423
-
424
- if (typeof switchedRoute === 'string' && switchedRoute.startsWith('http')) {
425
- return this.navigateTo(switchedRoute, { redirectCode: 200, external: true })
426
- }
427
-
428
- if (isNoPrefixStrategy(this.i18nConfig.strategy!)) {
429
- (switchedRoute as RouteLocationRaw & RouteLocationOptions).force = true
430
- }
431
-
432
- return this.router.push(switchedRoute)
433
- }
434
-
435
- resolveLocalizedRoute(
436
- to: RouteLocationNamedRaw | RouteLocationAsPathGeneric | string,
437
- locale?: string,
438
- ): RouteLocationResolved {
439
- const currentRoute = this.getCurrentRoute()
440
- const fromLocale = this.getCurrentLocale()
441
- const currentLocale = locale ?? fromLocale
442
-
443
- let current: RouteLocationResolved | RouteLocationNamedRaw | RouteLocationAsPathGeneric
444
- if (typeof to === 'string') {
445
- // Try to resolve as route name first (if it doesn't start with / and exists as route name)
446
- if (!to.startsWith('/')) {
447
- const routeName = to
448
- // Check if this is a route name (not a path)
449
- if (this.router.hasRoute(routeName)) {
450
- // Resolve by name to get the correct route object
451
- current = this.router.resolve({ name: routeName })
452
- }
453
- else {
454
- // Treat as path
455
- to = `/${to}`
456
- current = this.resolveRouteWithStrategy(to, currentLocale, fromLocale)
457
- }
458
- }
459
- else {
460
- current = this.resolveRouteWithStrategy(to, currentLocale, fromLocale)
461
- }
462
- }
463
- else {
464
- current = to
465
- }
466
-
467
- return this.getLocalizedRoute(current, currentRoute, currentLocale)
468
- }
469
- }