@i18n-micro/core 1.0.27

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.
@@ -0,0 +1,377 @@
1
+ import type {
2
+ RouteLocationAsPathGeneric,
3
+ RouteLocationNamedRaw,
4
+ RouteLocationNormalizedLoaded,
5
+ RouteLocationRaw,
6
+ RouteLocationResolvedGeneric,
7
+ Router,
8
+ } from 'vue-router'
9
+ import type { ModuleOptionsExtend } from '@i18n-micro/types'
10
+ import { RouteService } from '../src'
11
+
12
+ describe('RouteService', () => {
13
+ let routeService: RouteService
14
+ let mockRouter: jest.Mocked<Router>
15
+ let mockI18nConfig: ModuleOptionsExtend
16
+ let setCookieMock: jest.Mock
17
+ let navigateToMock: jest.Mock
18
+
19
+ beforeEach(() => {
20
+ // Mock Router
21
+ mockRouter = {
22
+ currentRoute: { value: { params: { locale: 'en' } } },
23
+ resolve: jest.fn((to: RouteLocationRaw | string) => ({
24
+ fullPath: typeof to === 'string' ? to : to.path || '',
25
+ path: typeof to === 'string' ? to : to.path || '',
26
+ query: {},
27
+ hash: '',
28
+ params: {},
29
+ })) as unknown as jest.MockedFunction<Router['resolve']>,
30
+ hasRoute: jest.fn((_name: string) => false) as jest.MockedFunction<Router['hasRoute']>,
31
+ push: jest.fn(() => Promise.resolve()) as unknown as jest.MockedFunction<Router['push']>,
32
+ } as unknown as jest.Mocked<Router>
33
+
34
+ // Mock ModuleOptionsExtend
35
+ mockI18nConfig = {
36
+ apiBaseUrl: '',
37
+ dateBuild: 0,
38
+ disablePageLocales: false,
39
+ isSSG: false,
40
+ defaultLocale: 'en',
41
+ strategy: 'prefix_except_default',
42
+ locales: [
43
+ { code: 'en', iso: 'en-US' },
44
+ { code: 'de', iso: 'de-DE' },
45
+ { code: 'ru', iso: 'ru-RU' },
46
+ ],
47
+ hashMode: false,
48
+ }
49
+
50
+ // Mock setCookie and navigateTo
51
+ setCookieMock = jest.fn()
52
+ navigateToMock = jest.fn()
53
+
54
+ // Initialize RouteService
55
+ routeService = new RouteService(
56
+ mockI18nConfig,
57
+ mockRouter,
58
+ null,
59
+ null,
60
+ navigateToMock,
61
+ setCookieMock,
62
+ null,
63
+ null,
64
+ )
65
+ })
66
+
67
+ test('getCurrentLocale should return the correct locale', () => {
68
+ const locale = routeService.getCurrentLocale()
69
+ expect(locale).toBe('en')
70
+ })
71
+
72
+ test('getCurrentLocale should return the hash locale if hashMode is enabled', () => {
73
+ const mockI18nConfigWithHashMode = { ...mockI18nConfig, hashMode: true }
74
+ routeService = new RouteService(mockI18nConfigWithHashMode, mockRouter, 'de', null, navigateToMock, setCookieMock, null, null)
75
+ const locale = routeService.getCurrentLocale()
76
+ expect(locale).toBe('de')
77
+ })
78
+
79
+ test('getCurrentLocale should return the noPrefix locale if noPrefix strategy is enabled', () => {
80
+ const mockI18nConfigWithNoPrefix: ModuleOptionsExtend = { ...mockI18nConfig, strategy: 'no_prefix' }
81
+ routeService = new RouteService(mockI18nConfigWithNoPrefix, mockRouter, null, 'ru', navigateToMock, setCookieMock, null, null)
82
+ const locale = routeService.getCurrentLocale()
83
+ expect(locale).toBe('ru')
84
+ })
85
+
86
+ test('getCurrentName should return the display name of the current locale', () => {
87
+ const mockI18nConfigWithDisplayName = {
88
+ ...mockI18nConfig,
89
+ locales: [
90
+ { code: 'en', iso: 'en-US', displayName: 'English' },
91
+ { code: 'de', iso: 'de-DE', displayName: 'German' },
92
+ { code: 'ru', iso: 'ru-RU', displayName: 'Russian' },
93
+ ],
94
+ }
95
+ routeService = new RouteService(mockI18nConfigWithDisplayName, mockRouter, null, null, navigateToMock, setCookieMock, null, null)
96
+ const route = { name: 'localized-about-en' } as RouteLocationNormalizedLoaded
97
+ const displayName = routeService.getCurrentName(route)
98
+ expect(displayName).toBe('English')
99
+ })
100
+
101
+ test('getRouteName should return the correct route name without locale suffix', () => {
102
+ const route = { name: 'localized-about-en' } as RouteLocationNamedRaw
103
+ const routeName = routeService.getRouteName(route, 'en')
104
+ expect(routeName).toBe('about')
105
+ })
106
+
107
+ test('switchLocaleRoute should return the correct route for the new locale', () => {
108
+ const route = { name: 'localized-about-en', params: {} } as RouteLocationNamedRaw
109
+ const i18nRouteParams = { de: { param1: 'value1' } }
110
+ const newRoute = routeService.switchLocaleRoute('en', 'de', route, i18nRouteParams)
111
+ expect(newRoute).toEqual({
112
+ name: 'localized-about',
113
+ hash: undefined,
114
+ query: undefined,
115
+ params: { param1: 'value1', locale: 'de' },
116
+ })
117
+ })
118
+
119
+ test('getLocalizedRoute should return the correct localized route', () => {
120
+ const route = { name: 'about', params: {} } as RouteLocationNormalizedLoaded
121
+ const to = { path: '/about' } as RouteLocationResolvedGeneric
122
+ mockRouter.resolve.mockReturnValueOnce({
123
+ fullPath: '/en/about',
124
+ path: '/about',
125
+ query: {},
126
+ hash: '',
127
+ params: {},
128
+ } as RouteLocationResolvedGeneric)
129
+ const localizedRoute = routeService.getLocalizedRoute(to, route, 'en')
130
+ expect(localizedRoute).toEqual({
131
+ path: '/about',
132
+ fullPath: '/about',
133
+ query: {},
134
+ params: {},
135
+ hash: '',
136
+ })
137
+ })
138
+
139
+ test('switchLocaleLogic should switch the locale and navigate to the new route', async () => {
140
+ const route = { name: 'about-en', params: {} } as RouteLocationResolvedGeneric
141
+ const i18nRouteParams = { de: { param1: 'value1' } }
142
+ await routeService.switchLocaleLogic('de', i18nRouteParams, route)
143
+
144
+ // Check that push was called with correct arguments
145
+ expect(mockRouter.push).toHaveBeenCalledWith({
146
+ name: 'localized-about',
147
+ params: { param1: 'value1', locale: 'de' },
148
+ hash: undefined,
149
+ query: undefined,
150
+ })
151
+ })
152
+
153
+ test('getFullPathWithBaseUrl should return the correct full path with base URL', () => {
154
+ const currentLocale = { code: 'de', iso: 'de-DE', baseUrl: 'https://example.com/de' }
155
+ const route = { path: '/about' } as RouteLocationRaw
156
+ const fullPath = routeService.getFullPathWithBaseUrl(currentLocale, route)
157
+ expect(fullPath).toBe('https://example.com/de/about')
158
+ })
159
+
160
+ test('updateCookies should update cookies for hashMode and noPrefixStrategy', () => {
161
+ const mockI18nConfigWithHashMode: ModuleOptionsExtend = {
162
+ ...mockI18nConfig,
163
+ hashMode: true,
164
+ strategy: 'prefix_except_default',
165
+ }
166
+ routeService = new RouteService(mockI18nConfigWithHashMode, mockRouter, 'de', null, navigateToMock, setCookieMock, null, null)
167
+ routeService.updateCookies('de')
168
+ expect(setCookieMock).toHaveBeenCalledWith('hash-locale', 'de')
169
+
170
+ const mockI18nConfigWithNoPrefix: ModuleOptionsExtend = {
171
+ ...mockI18nConfig,
172
+ strategy: 'no_prefix',
173
+ }
174
+ routeService = new RouteService(mockI18nConfigWithNoPrefix, mockRouter, null, 'ru', navigateToMock, setCookieMock, null, null)
175
+ routeService.updateCookies('ru')
176
+ expect(setCookieMock).toHaveBeenCalledWith('no-prefix-locale', 'ru')
177
+ })
178
+
179
+ test('resolveLocalizedRoute should return the correct localized route', () => {
180
+ const to = { path: '/about' } as RouteLocationAsPathGeneric
181
+ mockRouter.resolve.mockReturnValueOnce({
182
+ fullPath: '/en/about',
183
+ path: '/about',
184
+ query: {},
185
+ hash: '',
186
+ params: {},
187
+ } as RouteLocationResolvedGeneric)
188
+ const localizedRoute = routeService.resolveLocalizedRoute(to, 'en')
189
+ expect(localizedRoute).toEqual({
190
+ path: '/about',
191
+ fullPath: '/about',
192
+ query: {},
193
+ params: {},
194
+ hash: '',
195
+ })
196
+ })
197
+
198
+ test('getCurrentLocale should return defaultLocale if locale is missing in route params', () => {
199
+ mockRouter.currentRoute.value.params = {} // Remove locale from params
200
+ mockRouter.currentRoute.value.path = '/' // No locale in path
201
+ const locale = routeService.getCurrentLocale()
202
+ expect(locale).toBe('en') // defaultLocale
203
+ })
204
+
205
+ test('getCurrentLocale should extract locale from URL path when route params are missing', () => {
206
+ mockRouter.currentRoute.value.params = {} // Remove locale from params
207
+ mockRouter.currentRoute.value.path = '/ru/sdfsdf' // URL with locale prefix
208
+ const locale = routeService.getCurrentLocale()
209
+ expect(locale).toBe('ru') // Should extract from URL
210
+ })
211
+
212
+ test('getCurrentLocale should use cookie locale when route params and URL path are missing', () => {
213
+ routeService = new RouteService(
214
+ mockI18nConfig,
215
+ mockRouter,
216
+ null,
217
+ null,
218
+ navigateToMock,
219
+ setCookieMock,
220
+ 'de', // cookie locale
221
+ 'user-locale', // cookie name
222
+ )
223
+ mockRouter.currentRoute.value.params = {} // Remove locale from params
224
+ mockRouter.currentRoute.value.path = '/' // No locale in path
225
+ const locale = routeService.getCurrentLocale()
226
+ expect(locale).toBe('de') // Should use cookie
227
+ })
228
+
229
+ test('getCurrentLocale should prioritize route params over URL path', () => {
230
+ mockRouter.currentRoute.value.params = { locale: 'de' } // Route params have locale
231
+ mockRouter.currentRoute.value.path = '/ru/sdfsdf' // URL has different locale
232
+ const locale = routeService.getCurrentLocale()
233
+ expect(locale).toBe('de') // Should use route params
234
+ })
235
+
236
+ test('getCurrentLocale should prioritize URL path over cookie', () => {
237
+ routeService = new RouteService(
238
+ mockI18nConfig,
239
+ mockRouter,
240
+ null,
241
+ null,
242
+ navigateToMock,
243
+ setCookieMock,
244
+ 'de', // cookie locale
245
+ 'user-locale', // cookie name
246
+ )
247
+ mockRouter.currentRoute.value.params = {} // No locale in params
248
+ mockRouter.currentRoute.value.path = '/ru/sdfsdf' // URL has locale
249
+ const locale = routeService.getCurrentLocale()
250
+ expect(locale).toBe('ru') // Should use URL path, not cookie
251
+ })
252
+
253
+ test('updateCookies should update cookie for regular strategy', () => {
254
+ routeService = new RouteService(
255
+ mockI18nConfig,
256
+ mockRouter,
257
+ null,
258
+ null,
259
+ navigateToMock,
260
+ setCookieMock,
261
+ null,
262
+ 'user-locale', // cookie name
263
+ )
264
+ routeService.updateCookies('ru')
265
+ expect(setCookieMock).toHaveBeenCalledWith('user-locale', 'ru')
266
+ })
267
+
268
+ test('getCurrentName should return null if displayName is missing', () => {
269
+ const mockI18nConfigWithoutDisplayName = {
270
+ ...mockI18nConfig,
271
+ locales: [
272
+ { code: 'en', iso: 'en-US' }, // displayName is missing
273
+ { code: 'de', iso: 'de-DE' },
274
+ { code: 'ru', iso: 'ru-RU' },
275
+ ],
276
+ }
277
+ routeService = new RouteService(mockI18nConfigWithoutDisplayName, mockRouter, null, null, navigateToMock, setCookieMock, null, null)
278
+ const route = { name: 'localized-about-en' } as RouteLocationNormalizedLoaded
279
+ const displayName = routeService.getCurrentName(route)
280
+ expect(displayName).toBeNull()
281
+ })
282
+
283
+ test('switchLocaleRoute should handle missing i18nRouteParams', () => {
284
+ const route = { name: 'localized-about-en', params: {} } as RouteLocationResolvedGeneric
285
+ const newRoute = routeService.switchLocaleRoute('en', 'de', route, {})
286
+ expect(newRoute).toEqual({
287
+ name: 'localized-about',
288
+ hash: undefined,
289
+ query: undefined,
290
+ params: { locale: 'de' },
291
+ })
292
+ })
293
+
294
+ test('getFullPathWithBaseUrl should handle missing baseUrl', () => {
295
+ const currentLocale = { code: 'de', iso: 'de-DE' } // baseUrl is missing
296
+ const route = { path: '/about' } as RouteLocationRaw
297
+ const fullPath = routeService.getFullPathWithBaseUrl(currentLocale, route)
298
+ expect(fullPath).toBe('/about')
299
+ })
300
+
301
+ test('handlePrefixStrategy should handle invalid route', () => {
302
+ const to = '/invalid-route'
303
+ mockRouter.resolve.mockReturnValueOnce({
304
+ fullPath: '/invalid-route',
305
+ path: '/invalid-route',
306
+ query: {},
307
+ hash: '',
308
+ params: {},
309
+ } as RouteLocationResolvedGeneric)
310
+ const processedTo = routeService['handlePrefixStrategy'](to)
311
+ expect(processedTo).toEqual(to)
312
+ })
313
+
314
+ test('createLocalizedRoute should handle invalid route', () => {
315
+ const to = '/invalid-route'
316
+ const route = { name: 'about', params: {} } as RouteLocationNormalizedLoaded
317
+ mockRouter.resolve.mockReturnValueOnce({
318
+ fullPath: '/invalid-route',
319
+ path: '/invalid-route',
320
+ query: {},
321
+ hash: '',
322
+ params: {},
323
+ } as RouteLocationResolvedGeneric)
324
+ const localizedRoute = routeService['createLocalizedRoute'](to, route, 'en')
325
+ expect(localizedRoute).toEqual({
326
+ path: '/invalid-route',
327
+ fullPath: '/invalid-route',
328
+ query: {},
329
+ params: {},
330
+ hash: '',
331
+ })
332
+ })
333
+
334
+ test('resolveLocalizedRoute should handle invalid route', () => {
335
+ const to = '/invalid-route'
336
+ mockRouter.resolve.mockReturnValueOnce({
337
+ fullPath: '/invalid-route',
338
+ path: '/invalid-route',
339
+ query: {},
340
+ hash: '',
341
+ params: {},
342
+ } as RouteLocationResolvedGeneric)
343
+ const localizedRoute = routeService.resolveLocalizedRoute(to, 'en')
344
+ expect(localizedRoute).toEqual({
345
+ path: '/invalid-route',
346
+ fullPath: '/invalid-route',
347
+ query: {},
348
+ params: {},
349
+ hash: '',
350
+ })
351
+ })
352
+
353
+ test('updateCookies should not update cookies if hashMode and noPrefixStrategy are disabled', () => {
354
+ routeService.updateCookies('de')
355
+ expect(setCookieMock).not.toHaveBeenCalled()
356
+ })
357
+
358
+ test('getCurrentLocale should extract locale from fullPath with query params when route.path is missing', () => {
359
+ mockRouter.currentRoute.value.params = {}
360
+ mockRouter.currentRoute.value.path = '' // Simulate missing path (e.g. error state)
361
+ mockRouter.currentRoute.value.fullPath = '/de?foo=bar&baz=qux' // Fallback uses fullPath
362
+
363
+ // Ensure 'de' is in the config locales for this test
364
+ // (It is already present in mockI18nConfig in beforeEach)
365
+
366
+ const locale = routeService.getCurrentLocale()
367
+ expect(locale).toBe('de')
368
+ })
369
+
370
+ test('getCurrentLocale should extract locale from path with hash', () => {
371
+ mockRouter.currentRoute.value.params = {}
372
+ mockRouter.currentRoute.value.path = ''
373
+ mockRouter.currentRoute.value.fullPath = '/de#section'
374
+ const locale = routeService.getCurrentLocale()
375
+ expect(locale).toBe('de')
376
+ })
377
+ })
package/tsconfig.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2018",
4
+ "module": "esnext",
5
+ "moduleResolution": "node",
6
+
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true,
10
+ "forceConsistentCasingInFileNames": true,
11
+ "outDir": "./dist",
12
+ "declaration": true,
13
+ "declarationDir": "./dist",
14
+ "sourceMap": true,
15
+ "rootDir": "./src",
16
+ "baseUrl": "./",
17
+ "paths": {
18
+ "*": ["node_modules/*", "src/types/*"]
19
+ },
20
+ "types": ["jest", "node"]
21
+ },
22
+ "include": ["src/**/*"],
23
+ "exclude": ["node_modules", "dist", "tests"]
24
+ }
@@ -0,0 +1,25 @@
1
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
2
+ // @ts-nocheck
3
+ import { resolve } from 'node:path'
4
+ import { defineConfig } from 'vite'
5
+ import dts from 'vite-plugin-dts'
6
+
7
+ export default defineConfig({
8
+ build: {
9
+ lib: {
10
+ entry: resolve(__dirname, 'src/index.ts'),
11
+ name: '@i18n-micro/core',
12
+ formats: ['cjs', 'es'],
13
+ fileName: format => `index.${format === 'cjs' ? 'cjs' : 'mjs'}`,
14
+ },
15
+ rollupOptions: {
16
+ external: [],
17
+ output: {
18
+ exports: 'named',
19
+ },
20
+ },
21
+ },
22
+ plugins: [
23
+ dts(),
24
+ ],
25
+ })