Skip to content

Commit 89f222a

Browse files
feat: add type to locales and remove automatic mapping (#2112)
* feat: add type to locales and remove automatic mapping from browser type to basic locale * docs: add changeset * fix: test i18n major update * fix: namespaceTranslation * fix: rename check locale function name * fix: rename T param in more meaningful name * Update packages/use-i18n/src/usei18n.tsx Co-authored-by: philibeaux <[email protected]> --------- Co-authored-by: philibeaux <[email protected]>
1 parent c3ef8e0 commit 89f222a

File tree

4 files changed

+139
-111
lines changed

4 files changed

+139
-111
lines changed

.changeset/fuzzy-emus-repeat.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@scaleway/use-i18n": major
3+
---
4+
5+
Add typing on locales and remove auto mapping from browser locale to simple locale

packages/use-i18n/src/__tests__/usei18n.test.tsx

Lines changed: 35 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@ import fr from './locales/fr'
1010

1111
const LOCALE_ITEM_STORAGE = 'locales'
1212

13-
type LocaleEN = typeof en
14-
type Locale = LocaleEN
13+
const ListLocales = ['es', 'en', 'fr', 'fr-FR', 'en-GB'] as const
14+
type Locales = (typeof ListLocales)[number]
15+
16+
type Locale = typeof en
1517
type NamespaceLocale = {
1618
name: 'Name'
1719
lastName: 'Last Name'
@@ -20,7 +22,8 @@ type NamespaceLocale = {
2022

2123
type OnTranslateError = ComponentProps<typeof I18n>['onTranslateError']
2224

23-
const defaultSupportedLocales = ['en', 'fr', 'es']
25+
const isDefaultLocalesSupported = (locale: string): locale is Locales =>
26+
ListLocales.includes(locale as Locales)
2427

2528
const defaultOnTranslateError: OnTranslateError = () => {}
2629

@@ -57,7 +60,7 @@ const wrapper =
5760
enableDebugKey = false,
5861
enableDefaultLocale = false,
5962
localeItemStorage = LOCALE_ITEM_STORAGE,
60-
supportedLocales = defaultSupportedLocales,
63+
isLocaleSupported = isDefaultLocalesSupported,
6164
onTranslateError = defaultOnTranslateError,
6265
} = {}) =>
6366
({ children }: { children: ReactNode }) => (
@@ -70,7 +73,7 @@ const wrapper =
7073
enableDebugKey={enableDebugKey}
7174
enableDefaultLocale={enableDefaultLocale}
7275
localeItemStorage={localeItemStorage}
73-
supportedLocales={supportedLocales}
76+
isLocaleSupported={isLocaleSupported}
7477
onTranslateError={onTranslateError}
7578
>
7679
{children}
@@ -114,7 +117,7 @@ describe('i18n hook', () => {
114117
})
115118

116119
it('should use defaultLoad, useTranslation, switch local and translate', async () => {
117-
const { result } = renderHook(() => useTranslation<Locale>([]), {
120+
const { result } = renderHook(() => useTranslation<Locale, Locales>([]), {
118121
wrapper: wrapper({ defaultLocale: 'en' }),
119122
})
120123
// first render there is no load
@@ -152,11 +155,11 @@ describe('i18n hook', () => {
152155
}) => import(`./locales/namespaces/${locale}/${namespace}.json`)
153156

154157
const { result } = renderHook(
155-
() => useTranslation<NamespaceLocale>(['user', 'profile'], load),
158+
() => useTranslation<NamespaceLocale, Locales>(['user', 'profile'], load),
156159
{
157160
wrapper: wrapper({
158161
defaultLocale: 'en',
159-
supportedLocales: ['en', 'fr'],
162+
isLocaleSupported: isDefaultLocalesSupported,
160163
}),
161164
},
162165
)
@@ -208,12 +211,12 @@ describe('i18n hook', () => {
208211
}) => import(`./locales/namespaces/${locale}/${namespace}.json`)
209212

210213
const { result } = renderHook(
211-
() => useTranslation<NamespaceLocale>(['user'], load),
214+
() => useTranslation<NamespaceLocale, Locales>(['user'], load),
212215
{
213216
wrapper: wrapper({
214217
defaultLocale: 'fr',
215218
enableDefaultLocale: true,
216-
supportedLocales: ['en', 'fr'],
219+
isLocaleSupported: isDefaultLocalesSupported,
217220
}),
218221
},
219222
)
@@ -247,7 +250,7 @@ describe('i18n hook', () => {
247250
const { result } = renderHook(() => useI18n(), {
248251
wrapper: wrapper({
249252
defaultLocale: 'fr',
250-
supportedLocales: ['en', 'fr', 'es'],
253+
isLocaleSupported: isDefaultLocalesSupported,
251254
}),
252255
})
253256

@@ -303,7 +306,7 @@ describe('i18n hook', () => {
303306
const { result } = renderHook(() => useI18n(), {
304307
wrapper: wrapper({
305308
defaultLocale: 'es',
306-
supportedLocales: ['en', 'fr', 'es'],
309+
isLocaleSupported: isDefaultLocalesSupported,
307310
}),
308311
})
309312

@@ -334,7 +337,7 @@ describe('i18n hook', () => {
334337
const { result } = renderHook(() => useI18n(), {
335338
wrapper: wrapper({
336339
defaultLocale: 'en',
337-
supportedLocales: ['en'],
340+
isLocaleSupported: isDefaultLocalesSupported,
338341
}),
339342
})
340343

@@ -365,7 +368,7 @@ describe('i18n hook', () => {
365368
const { result } = renderHook(() => useI18n(), {
366369
wrapper: wrapper({
367370
defaultLocale: 'es',
368-
supportedLocales: ['en', 'fr', 'es'],
371+
isLocaleSupported: isDefaultLocalesSupported,
369372
}),
370373
})
371374

@@ -394,7 +397,7 @@ describe('i18n hook', () => {
394397
const { result } = renderHook(() => useI18n(), {
395398
wrapper: wrapper({
396399
defaultLocale: 'es',
397-
supportedLocales: ['en', 'fr', 'es'],
400+
isLocaleSupported: isDefaultLocalesSupported,
398401
}),
399402
})
400403

@@ -406,10 +409,10 @@ describe('i18n hook', () => {
406409
})
407410

408411
it('should switch locale', async () => {
409-
const { result } = renderHook(() => useI18n(), {
412+
const { result } = renderHook(() => useI18n<Locale, Locales>(), {
410413
wrapper: wrapper({
411414
defaultLocale: 'en',
412-
supportedLocales: ['en', 'fr', 'es'],
415+
isLocaleSupported: isDefaultLocalesSupported,
413416
}),
414417
})
415418
expect(result.current.currentLocale).toEqual('en')
@@ -434,6 +437,9 @@ describe('i18n hook', () => {
434437
expect(localStorage.getItem(LOCALE_ITEM_STORAGE)).toBe('es')
435438

436439
await act(async () => {
440+
// we test even if an incorrect typescript value is being passed to the function
441+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
442+
// @ts-expect-error
437443
await result.current.switchLocale('test')
438444
})
439445

@@ -449,7 +455,7 @@ describe('i18n hook', () => {
449455
defaultLocale: 'en',
450456
defaultTranslations: { en },
451457
enableDebugKey: true,
452-
supportedLocales: ['en', 'fr'],
458+
isLocaleSupported: isDefaultLocalesSupported,
453459
}),
454460
})
455461

@@ -468,11 +474,11 @@ describe('i18n hook', () => {
468474
it('should call onTranslateError when there is a sync issue to remove/add variable in one traduction of a language', async () => {
469475
const mockOnTranslateError = vi.fn()
470476

471-
const { result } = renderHook(() => useI18n<Locale>(), {
477+
const { result } = renderHook(() => useI18n<Locale, Locales>(), {
472478
wrapper: wrapper({
473479
defaultLocale: 'en',
474480
defaultTranslations: { en, fr },
475-
supportedLocales: ['en', 'fr'],
481+
isLocaleSupported: isDefaultLocalesSupported,
476482
onTranslateError: mockOnTranslateError,
477483
}),
478484
})
@@ -534,7 +540,7 @@ describe('i18n hook', () => {
534540
})
535541

536542
it('should use formatNumber', async () => {
537-
const { result } = renderHook(() => useI18n(), {
543+
const { result } = renderHook(() => useI18n<Locale, Locales>(), {
538544
wrapper: wrapper({
539545
defaultLocale: 'en',
540546
}),
@@ -578,7 +584,7 @@ describe('i18n hook', () => {
578584
})
579585

580586
it('should use formatList', async () => {
581-
const { result } = renderHook(() => useI18n(), {
587+
const { result } = renderHook(() => useI18n<Locale, Locales>(), {
582588
wrapper: wrapper({
583589
defaultLocale: 'en',
584590
}),
@@ -635,7 +641,7 @@ describe('i18n hook', () => {
635641
})
636642

637643
it('should use datetime', async () => {
638-
const { result } = renderHook(() => useI18n(), {
644+
const { result } = renderHook(() => useI18n<Locale, Locales>(), {
639645
wrapper: wrapper({
640646
defaultLocale: 'en',
641647
}),
@@ -707,7 +713,7 @@ describe('i18n hook', () => {
707713
})
708714

709715
it('should relativeTime', async () => {
710-
const { result } = renderHook(() => useI18n(), {
716+
const { result } = renderHook(() => useI18n<Locale, Locales>(), {
711717
wrapper: wrapper({
712718
defaultLocale: 'en',
713719
}),
@@ -728,7 +734,7 @@ describe('i18n hook', () => {
728734
})
729735

730736
it('should relativeTimeStrict', async () => {
731-
const { result } = renderHook(() => useI18n(), {
737+
const { result } = renderHook(() => useI18n<Locale, Locales>(), {
732738
wrapper: wrapper({
733739
defaultLocale: 'en',
734740
}),
@@ -749,7 +755,7 @@ describe('i18n hook', () => {
749755
})
750756

751757
it('should formatUnit', async () => {
752-
const { result } = renderHook(() => useI18n(), {
758+
const { result } = renderHook(() => useI18n<Locale, Locales>(), {
753759
wrapper: wrapper({
754760
defaultLocale: 'en',
755761
}),
@@ -770,7 +776,7 @@ describe('i18n hook', () => {
770776
})
771777

772778
it('should formatDate', async () => {
773-
const { result } = renderHook(() => useI18n(), {
779+
const { result } = renderHook(() => useI18n<Locale, Locales>(), {
774780
wrapper: wrapper({
775781
defaultLocale: 'en',
776782
}),
@@ -795,7 +801,7 @@ describe('i18n hook', () => {
795801
const { result } = renderHook(() => useI18n(), {
796802
wrapper: wrapper({
797803
defaultLocale: 'test',
798-
supportedLocales: ['test'],
804+
isLocaleSupported: isDefaultLocalesSupported,
799805
}),
800806
})
801807

@@ -823,7 +829,7 @@ describe('i18n hook', () => {
823829
const { result } = renderHook(() => useI18n(), {
824830
wrapper: wrapper({
825831
defaultLocale: 'es',
826-
supportedLocales: ['en', 'fr', 'es'],
832+
isLocaleSupported: isDefaultLocalesSupported,
827833
}),
828834
})
829835

packages/use-i18n/src/__typetests__/namespaceTranslation.test.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,18 @@
22
import { expect, test } from 'tstyche'
33
import { useI18n } from '../usei18n'
44

5+
const ListLocales = ['es', 'en', 'fr', 'fr-FR', 'en-GB'] as const
6+
type Locales = (typeof ListLocales)[number]
7+
type Locale = {
8+
hello: 'world'
9+
'doe.john': 'John Doe'
10+
'doe.jane': 'Jane Doe'
11+
'doe.child': 'Child is {name}'
12+
'describe.john': '{name} is {age} years old'
13+
}
14+
515
test('i18n - namespaceTranslation', () => {
6-
const { namespaceTranslation } = useI18n<{
7-
hello: 'world'
8-
'doe.john': 'John Doe'
9-
'doe.jane': 'Jane Doe'
10-
'doe.child': 'Child is {name}'
11-
'describe.john': '{name} is {age} years old'
12-
}>()
16+
const { namespaceTranslation } = useI18n<Locale, Locales>()
1317

1418
// Single key
1519
expect(namespaceTranslation('hello')).type.toRaiseError(
@@ -62,7 +66,7 @@ test('i18n - namespaceTranslation', () => {
6266
expect(namespaceTranslation('describe')('john')).type.toRaiseError()
6367

6468
// Required generic
65-
const { namespaceTranslation: namespaceTranslation2 } = useI18n()
69+
const { namespaceTranslation: namespaceTranslation2 } = useI18n<Locale>()
6670
expect(namespaceTranslation2('test')).type.toRaiseError(
6771
`Argument of type '"test"' is not assignable to parameter of type`,
6872
)

0 commit comments

Comments
 (0)