@i18n-micro/core 1.0.28 → 1.1.1

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