From 11dc68e3ac7f7d127df9073507ad3171b444e809 Mon Sep 17 00:00:00 2001 From: Gabriel Hofman <1540619+tsyirvo@users.noreply.github.com> Date: Thu, 7 Nov 2024 22:08:03 +0100 Subject: [PATCH] feat: simplify i18n setup and add more type safety (#396) --- .vscode/settings.json | 7 +- src/app/blogPost/[id].tsx | 4 +- src/app/dummyForm.tsx | 4 +- src/app/index.tsx | 8 +- src/app/other.tsx | 12 +- src/core/i18n/i18n.ts | 2 +- src/core/i18n/resources/en/appConfig.json | 9 -- src/core/i18n/resources/en/common.json | 5 - src/core/i18n/resources/en/homeScreen.json | 19 --- src/core/i18n/resources/en/index.ts | 1 + src/core/i18n/resources/en/messages.ts | 133 +++++++++++++++++ src/core/i18n/resources/en/miscScreens.json | 56 -------- src/core/i18n/resources/en/otherScreen.json | 13 -- src/core/i18n/resources/en/settings.json | 22 --- src/core/i18n/resources/fr/appConfig.json | 9 -- src/core/i18n/resources/fr/common.json | 5 - src/core/i18n/resources/fr/homeScreen.json | 19 --- src/core/i18n/resources/fr/index.ts | 1 + src/core/i18n/resources/fr/messages.ts | 134 ++++++++++++++++++ src/core/i18n/resources/fr/miscScreens.json | 56 -------- src/core/i18n/resources/fr/otherScreen.json | 13 -- src/core/i18n/resources/fr/settings.json | 22 --- src/core/i18n/resources/index.ts | 28 +--- src/core/i18n/types/i18next.d.ts | 18 +-- src/core/i18n/utils/languageSwitcher.ts | 4 +- src/features/blogPost/BlogPost.tsx | 4 +- src/features/dummyForm/DummyFormExample.tsx | 24 +++- .../dummyForm/utils/dummyForm.schema.ts | 10 +- src/features/home/components/Informations.tsx | 14 +- src/features/home/components/Version.tsx | 6 +- src/features/notifications/Notifications.tsx | 8 +- src/shared/components/AppUpdateNeeded.tsx | 8 +- .../components/FullscreenErrorBoundary.tsx | 8 +- src/shared/components/MaintenanceMode.tsx | 8 +- src/shared/components/StoreUpdateBanner.tsx | 18 ++- .../hooks/useCheckNetworkStateOnMount.ts | 6 +- src/shared/hooks/useRequestPermission.ts | 12 +- 37 files changed, 366 insertions(+), 364 deletions(-) delete mode 100644 src/core/i18n/resources/en/appConfig.json delete mode 100644 src/core/i18n/resources/en/common.json delete mode 100644 src/core/i18n/resources/en/homeScreen.json create mode 100644 src/core/i18n/resources/en/index.ts create mode 100644 src/core/i18n/resources/en/messages.ts delete mode 100644 src/core/i18n/resources/en/miscScreens.json delete mode 100644 src/core/i18n/resources/en/otherScreen.json delete mode 100644 src/core/i18n/resources/en/settings.json delete mode 100644 src/core/i18n/resources/fr/appConfig.json delete mode 100644 src/core/i18n/resources/fr/common.json delete mode 100644 src/core/i18n/resources/fr/homeScreen.json create mode 100644 src/core/i18n/resources/fr/index.ts create mode 100644 src/core/i18n/resources/fr/messages.ts delete mode 100644 src/core/i18n/resources/fr/miscScreens.json delete mode 100644 src/core/i18n/resources/fr/otherScreen.json delete mode 100644 src/core/i18n/resources/fr/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json index 7b31714c..0964c22f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,10 +1,9 @@ { "i18n-ally.localesPaths": ["src/core/i18n/resources"], "i18n-ally.enabledFrameworks": ["react-i18next"], - "i18n-ally.namespace": true, - "i18n-ally.sortKeys": true, - "i18n-ally.defaultNamespace": "common", - "i18n-ally.pathMatcher": "{locale}/{namespaces}.json", + "i18n-ally.defaultNamespace": "messages", + "i18n-ally.enabledParsers": ["ts", "json"], + "i18n-ally.pathMatcher": "{locale}/messages.ts", "i18n-ally.keystyle": "nested", "i18n-ally.sourceLanguage": "en", "search.exclude": { diff --git a/src/app/blogPost/[id].tsx b/src/app/blogPost/[id].tsx index 962bd776..69a01a02 100644 --- a/src/app/blogPost/[id].tsx +++ b/src/app/blogPost/[id].tsx @@ -6,13 +6,13 @@ import { Screen } from '$shared/uiKit/Screen'; const BlogPostScreen = () => { const { id } = useLocalSearchParams<{ id: string }>(); - const { t } = useTranslation('miscScreens'); + const { t } = useTranslation(); return ( diff --git a/src/app/dummyForm.tsx b/src/app/dummyForm.tsx index 3db802df..d3f313a4 100644 --- a/src/app/dummyForm.tsx +++ b/src/app/dummyForm.tsx @@ -6,13 +6,13 @@ import { DummyForm as DummyFormComponent } from '$features/dummyForm'; import { Screen } from '$shared/uiKit/Screen'; const DummyFormScreen = () => { - const { t } = useTranslation('miscScreens'); + const { t } = useTranslation(); return ( diff --git a/src/app/index.tsx b/src/app/index.tsx index 7f547c95..df91e78f 100644 --- a/src/app/index.tsx +++ b/src/app/index.tsx @@ -8,7 +8,7 @@ import { Screen } from '$shared/uiKit/Screen'; const HomeScreen = () => { const router = useRouter(); - const { t } = useTranslation('homeScreen'); + const { t } = useTranslation(); const goToOtherScreen = () => { router.push('/other'); @@ -19,7 +19,7 @@ const HomeScreen = () => { @@ -27,12 +27,12 @@ const HomeScreen = () => { - {t('navigation.title')} + {t('homeScreen.navigation.title')} - {t('navigation.content')} + {t('homeScreen.navigation.content')} diff --git a/src/app/other.tsx b/src/app/other.tsx index e286a01d..41babc0b 100644 --- a/src/app/other.tsx +++ b/src/app/other.tsx @@ -8,7 +8,7 @@ import { Screen } from '$shared/uiKit/Screen'; const OtherScreen = () => { const router = useRouter(); - const { t } = useTranslation('otherScreen'); + const { t } = useTranslation(); const goToBlogPost = () => { router.push('/blogPost/1'); @@ -20,11 +20,11 @@ const OtherScreen = () => { return ( - + - {t('graphql.title')} + {t('otherScreen.graphql.title')} @@ -32,20 +32,20 @@ const OtherScreen = () => { testID="otherScreen-blogPost-navigateCta" onPress={goToBlogPost} > - {t('graphql.cta')} + {t('otherScreen.graphql.cta')} - {t('form.title')} + {t('otherScreen.form.title')} - {t('form.cta')} + {t('otherScreen.form.cta')} diff --git a/src/core/i18n/i18n.ts b/src/core/i18n/i18n.ts index 4e0aa41e..e52e18b8 100644 --- a/src/core/i18n/i18n.ts +++ b/src/core/i18n/i18n.ts @@ -10,7 +10,7 @@ export const i18n = i18next.use(initReactI18next).use(languageDetector).init({ fallbackLng: config.defaultLocale, supportedLngs: config.supportedLocales, nonExplicitSupportedLngs: true, - defaultNS: 'common', + defaultNS: 'messages', resources, load: 'languageOnly', compatibilityJSON: 'v3', diff --git a/src/core/i18n/resources/en/appConfig.json b/src/core/i18n/resources/en/appConfig.json deleted file mode 100644 index eb79b89d..00000000 --- a/src/core/i18n/resources/en/appConfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "networkStateCheck": { - "message": "Please check your internet connection. The app might not work properly without it.", - "title": "No internet connection" - }, - "updateCheck": { - "isEmbeddedLaunch": "The app is running from an OTA update" - } -} diff --git a/src/core/i18n/resources/en/common.json b/src/core/i18n/resources/en/common.json deleted file mode 100644 index 14d1ddfa..00000000 --- a/src/core/i18n/resources/en/common.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "back": "Back", - "cancel": "Cancel", - "next": "Next" -} diff --git a/src/core/i18n/resources/en/homeScreen.json b/src/core/i18n/resources/en/homeScreen.json deleted file mode 100644 index 894d1a99..00000000 --- a/src/core/i18n/resources/en/homeScreen.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "formatting": { - "content": "The code is checked with ESLint, Prettier and TypeScript", - "title": "Formatting & type checking" - }, - "navigation": { - "content": "Press to navigate", - "screenTitle": "Home", - "title": "Navigate to another page" - }, - "sandbox": { - "content": "Access the Sandbox form the Dev Menu with {{command}}", - "title": "Sandbox" - }, - "tests": { - "content": "You can run tests with Jest or Maestro", - "title": "Tests" - } -} diff --git a/src/core/i18n/resources/en/index.ts b/src/core/i18n/resources/en/index.ts new file mode 100644 index 00000000..fc26805f --- /dev/null +++ b/src/core/i18n/resources/en/index.ts @@ -0,0 +1 @@ +export { messages } from './messages'; diff --git a/src/core/i18n/resources/en/messages.ts b/src/core/i18n/resources/en/messages.ts new file mode 100644 index 00000000..3ecdbbf7 --- /dev/null +++ b/src/core/i18n/resources/en/messages.ts @@ -0,0 +1,133 @@ +export const messages = { + common: { + back: 'Back', + cancel: 'Cancel', + next: 'Next', + }, + appConfig: { + networkStateCheck: { + message: + 'Please check your internet connection. The app might not work properly without it.', + title: 'No internet connection', + }, + }, + homeScreen: { + formatting: { + content: 'The code is checked with ESLint, Prettier and TypeScript', + title: 'Formatting & type checking', + }, + navigation: { + content: 'Press to navigate', + screenTitle: 'Home', + title: 'Navigate to another page', + }, + sandbox: { + content: 'Access the Sandbox form the Dev Menu with {{command}}', + title: 'Sandbox', + }, + tests: { + content: 'You can run tests with Jest or Maestro', + title: 'Tests', + }, + updateCheck: { + isEmbeddedLaunch: 'The app is running from an OTA update', + }, + }, + miscScreens: { + appUpdate: { + description: + 'Please go update the application to access the latest features', + title: 'Your app is outdated', + }, + blogPost: { + screenTitle: 'Blog post', + title: 'Blog post fetched with GraphQL', + }, + codepush: { + cta: 'Install now', + description: + 'An app update is mandatory to be able to use the application.', + title: 'Update is required', + }, + dummyForm: { + form: { + email: { + label: 'Email', + placeholder: 'Enter your email', + validation: { + email: 'Please enter a valid email', + }, + }, + firstName: { + label: 'First name', + placeholder: 'John', + validation: { + maxLength: 'First name must be at most 20 characters long', + minLength: 'First name must be at least 2 characters long', + }, + }, + lastName: { + label: 'Last name', + placeholder: 'Doe', + validation: { + maxLength: 'Last name must be at most 30 characters long', + minLength: 'Last name must be at least 2 characters long', + }, + }, + }, + screenTitle: 'Dummy form', + }, + errorBoundary: { + cta: 'Relaunch the app', + description: + 'An unknown error occured. If the error persist, contact an administrator.', + title: 'Error', + }, + maintenanceMode: { + description: 'It will be available online as soon as possible', + title: 'The app is in maintenance', + }, + notifications: { + cta: 'Request', + title: 'Request Notification permission', + }, + }, + otherScreen: { + form: { + cta: 'Navigate', + title: 'Form example', + }, + graphql: { + cta: 'Navigate', + title: 'API call example', + }, + navigation: { + title: 'Other screen', + }, + }, + settings: { + changeLocale: { + failure: 'The language could not be changed', + success: 'The language has been changed', + }, + permissions: { + notAvailable: 'This permission is not available on this device', + notGranted: 'You rejected this permission request', + }, + updateAvailable: { + banner: { + compareVersions: + 'Version {{storeVersion}} of the app is now available. You are currently on version {{currentVersion}}.', + defaultTitle: 'A new version of the app is available.', + updateCta: 'Update now', + }, + nativePrompt: { + message: 'A new version is available. Do you want to update now?', + title: 'Update available', + updateCta: 'Update', + }, + }, + }, +}; + +export type MessagesTypes = typeof messages; diff --git a/src/core/i18n/resources/en/miscScreens.json b/src/core/i18n/resources/en/miscScreens.json deleted file mode 100644 index cc39fda6..00000000 --- a/src/core/i18n/resources/en/miscScreens.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "appUpdate": { - "description": "Please go update the application to access the latest features", - "title": "Your app is outdated" - }, - "blogPost": { - "screenTitle": "Blog post", - "title": "Blog post fetched with GraphQL" - }, - "codepush": { - "cta": "Install now", - "description": "An app update is mandatory to be able to use the application.", - "title": "Update is required" - }, - "dummyForm": { - "form": { - "email": { - "label": "Email", - "placeholder": "Enter your email", - "validation": { - "email": "Please enter a valid email" - } - }, - "firstName": { - "label": "First name", - "placeholder": "John", - "validation": { - "maxLength": "First name must be at most 20 characters long", - "minLength": "First name must be at least 2 characters long" - } - }, - "lastName": { - "label": "Last name", - "placeholder": "Doe", - "validation": { - "maxLength": "Last name must be at most 30 characters long", - "minLength": "Last name must be at least 2 characters long" - } - } - }, - "screenTitle": "Dummy form" - }, - "errorBoundary": { - "cta": "Relaunch the app", - "description": "An unknown error occured. If the error persist, contact an administrator.", - "title": "Error" - }, - "maintenanceMode": { - "description": "It will be available online as soon as possible", - "title": "The app is in maintenance" - }, - "notifications": { - "cta": "Request", - "title": "Request Notification permission" - } -} diff --git a/src/core/i18n/resources/en/otherScreen.json b/src/core/i18n/resources/en/otherScreen.json deleted file mode 100644 index af1c0a20..00000000 --- a/src/core/i18n/resources/en/otherScreen.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "form": { - "cta": "Navigate", - "title": "Form example" - }, - "graphql": { - "cta": "Navigate", - "title": "API call example" - }, - "navigation": { - "title": "Other screen" - } -} diff --git a/src/core/i18n/resources/en/settings.json b/src/core/i18n/resources/en/settings.json deleted file mode 100644 index 0ed98798..00000000 --- a/src/core/i18n/resources/en/settings.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "changeLocale": { - "failure": "The language could not be changed", - "success": "The language has been changed" - }, - "permissions": { - "notAvailable": "This permission is not available on this device", - "notGranted": "You rejected this permission request" - }, - "updateAvailable": { - "banner": { - "compareVersions": "Version {{storeVersion}} of the app is now available. You are currently on version {{currentVersion}}.", - "defaultTitle": "A new version of the app is available.", - "updateCta": "Update now" - }, - "nativePrompt": { - "message": "A new version is available. Do you want to update now?", - "title": "Update available", - "updateCta": "Update" - } - } -} diff --git a/src/core/i18n/resources/fr/appConfig.json b/src/core/i18n/resources/fr/appConfig.json deleted file mode 100644 index 038307ed..00000000 --- a/src/core/i18n/resources/fr/appConfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "networkStateCheck": { - "message": "Vérifiez la connexion internet. L'app peut ne pas fonctionner correctement sans connexion internet.", - "title": "Pas de connexion internet" - }, - "updateCheck": { - "isEmbeddedLaunch": "L'app tourne depuis une MAJ OTA" - } -} diff --git a/src/core/i18n/resources/fr/common.json b/src/core/i18n/resources/fr/common.json deleted file mode 100644 index e7923cea..00000000 --- a/src/core/i18n/resources/fr/common.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "back": "Retour", - "cancel": "Annuler", - "next": "Suivant" -} diff --git a/src/core/i18n/resources/fr/homeScreen.json b/src/core/i18n/resources/fr/homeScreen.json deleted file mode 100644 index 56a98263..00000000 --- a/src/core/i18n/resources/fr/homeScreen.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "formatting": { - "content": "Le code est vérifié avec ESLint, Prettier and TypeScript", - "title": "Formatage & type checking" - }, - "navigation": { - "content": "Touchez pour naviguer", - "screenTitle": "Accueil", - "title": "Naviguer vers une autre page" - }, - "sandbox": { - "content": "Accéder à la Sandbox depuis le menu de dev avec {{command}}", - "title": "Sandbox" - }, - "tests": { - "content": "Vous pouvez faire tourner les tests avec Jest ou Maestro", - "title": "Tests" - } -} diff --git a/src/core/i18n/resources/fr/index.ts b/src/core/i18n/resources/fr/index.ts new file mode 100644 index 00000000..fc26805f --- /dev/null +++ b/src/core/i18n/resources/fr/index.ts @@ -0,0 +1 @@ +export { messages } from './messages'; diff --git a/src/core/i18n/resources/fr/messages.ts b/src/core/i18n/resources/fr/messages.ts new file mode 100644 index 00000000..e8e7221a --- /dev/null +++ b/src/core/i18n/resources/fr/messages.ts @@ -0,0 +1,134 @@ +import type { MessagesTypes } from '../en/messages'; + +export const messages: MessagesTypes = { + common: { + back: 'Retour', + cancel: 'Annuler', + next: 'Suivant', + }, + appConfig: { + networkStateCheck: { + message: + "Vérifiez la connexion internet. L'app peut ne pas fonctionner correctement sans connexion internet.", + title: 'Pas de connexion internet', + }, + }, + homeScreen: { + formatting: { + content: 'Le code est vérifié avec ESLint, Prettier and TypeScript', + title: 'Formatage & type checking', + }, + navigation: { + content: 'Touchez pour naviguer', + screenTitle: 'Accueil', + title: 'Naviguer vers une autre page', + }, + sandbox: { + content: 'Accéder à la Sandbox depuis le menu de dev avec {{command}}', + title: 'Sandbox', + }, + tests: { + content: 'Vous pouvez faire tourner les tests avec Jest ou Maestro', + title: 'Tests', + }, + updateCheck: { + isEmbeddedLaunch: "L'app tourne depuis une MAJ OTA", + }, + }, + miscScreens: { + appUpdate: { + description: + 'Merci de la mettre à jour pour utiliser les dernières fonctionnalités', + title: 'Votre app est trop vieille', + }, + blogPost: { + screenTitle: 'Article de blog', + title: 'Article récupéré avec GraphQL', + }, + codepush: { + cta: 'Installer maintenant', + description: + "Une mise à jour de l'application est requise pour fonctionner.", + title: 'Mise à jour requise', + }, + dummyForm: { + form: { + email: { + label: 'Email', + placeholder: 'Saisir un email', + validation: { + email: 'Il faut un email valide', + }, + }, + firstName: { + label: 'Prénom', + placeholder: 'Martin', + validation: { + maxLength: 'Le prénom doit faire au plus 20 caractères', + minLength: 'Le prénom doit faire au moins 2 caractères', + }, + }, + lastName: { + label: 'Nom', + placeholder: 'Dupont', + validation: { + maxLength: 'Le nom doit faire au plus 30 caractères', + minLength: 'Le nom doit faire au moins 2 caractères', + }, + }, + }, + screenTitle: 'Dummy form', + }, + errorBoundary: { + cta: "Relancer l'app", + description: + "Une erreur est survenue. Si l'erreur persiste, contacter un administrateur.", + title: 'Erreur', + }, + maintenanceMode: { + description: 'Elle sera de nouveau fonctionnelle au plus vite', + title: "L'app est en maintenance", + }, + notifications: { + cta: 'Demander', + title: 'Demander les permissions de Notification', + }, + }, + otherScreen: { + form: { + cta: 'Naviguer', + title: 'Formulaire', + }, + graphql: { + cta: 'Naviguer', + title: "Example d'appel API", + }, + navigation: { + title: 'Autre écran', + }, + }, + settings: { + changeLocale: { + failure: "La langue n'a pas pu être changée", + success: 'La langue a bien été changée', + }, + permissions: { + notAvailable: "Cette permission n'est pas disponible sur cet appareil", + notGranted: 'Vous avez refusé cette demande de permission', + }, + updateAvailable: { + banner: { + compareVersions: + "La version {{storeVersion}} de l'app est maintenant disponible. Vous êtes actuellement sur la version {{currentVersion}}.", + defaultTitle: "Une nouvelle version de l'app est disponible.", + updateCta: 'Mettre à jour', + }, + nativePrompt: { + message: + 'Une nouvelle version est disponible. Voulez-vous mettre à jour maintenant?', + title: 'Mise à jour disponible', + updateCta: 'Mettre à jour', + }, + }, + }, +}; diff --git a/src/core/i18n/resources/fr/miscScreens.json b/src/core/i18n/resources/fr/miscScreens.json deleted file mode 100644 index 24b955b4..00000000 --- a/src/core/i18n/resources/fr/miscScreens.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "appUpdate": { - "description": "Merci de la mettre à jour pour utiliser les dernières fonctionnalités", - "title": "Votre app est trop vieille" - }, - "blogPost": { - "screenTitle": "Article de blog", - "title": "Article récupéré avec GraphQL" - }, - "codepush": { - "cta": "Installer maintenant", - "description": "Une mise à jour de l'application est requise pour fonctionner.", - "title": "Mise à jour requise" - }, - "dummyForm": { - "form": { - "email": { - "label": "Email", - "placeholder": "Saisir un email", - "validation": { - "email": "Il faut un email valide" - } - }, - "firstName": { - "label": "Prénom", - "placeholder": "Martin", - "validation": { - "maxLength": "Le prénom doit faire au plus 20 caractères", - "minLength": "Le prénom doit faire au moins 2 caractères" - } - }, - "lastName": { - "label": "Nom", - "placeholder": "Dupont", - "validation": { - "maxLength": "Le nom doit faire au plus 30 caractères", - "minLength": "Le nom doit faire au moins 2 caractères" - } - } - }, - "screenTitle": "Dummy form" - }, - "errorBoundary": { - "cta": "Relancer l'app", - "description": "Une erreur est survenue. Si l'erreur persiste, contacter un administrateur.", - "title": "Erreur" - }, - "maintenanceMode": { - "description": "Elle sera de nouveau fonctionnelle au plus vite", - "title": "L'app est en maintenance" - }, - "notifications": { - "cta": "Demander", - "title": "Demander les permissions de Notification" - } -} diff --git a/src/core/i18n/resources/fr/otherScreen.json b/src/core/i18n/resources/fr/otherScreen.json deleted file mode 100644 index b65ccca1..00000000 --- a/src/core/i18n/resources/fr/otherScreen.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "form": { - "cta": "Naviguer", - "title": "Formulaire" - }, - "graphql": { - "cta": "Naviguer", - "title": "Example d'appel API" - }, - "navigation": { - "title": "Autre écran" - } -} diff --git a/src/core/i18n/resources/fr/settings.json b/src/core/i18n/resources/fr/settings.json deleted file mode 100644 index c80a0c0b..00000000 --- a/src/core/i18n/resources/fr/settings.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "changeLocale": { - "failure": "La langue n'a pas pu être changée", - "success": "La langue a bien été changée" - }, - "permissions": { - "notAvailable": "Cette permission n'est pas disponible sur cet appareil", - "notGranted": "Vous avez refusé cette demande de permission" - }, - "updateAvailable": { - "banner": { - "compareVersions": "La version {{storeVersion}} de l'app est maintenant disponible. Vous êtes actuellement sur la version {{currentVersion}}.", - "defaultTitle": "Une nouvelle version de l'app est disponible.", - "updateCta": "Mettre à jour" - }, - "nativePrompt": { - "message": "Une nouvelle version est disponible. Voulez-vous mettre à jour maintenant?", - "title": "Mise à jour disponible", - "updateCta": "Mettre à jour" - } - } -} diff --git a/src/core/i18n/resources/index.ts b/src/core/i18n/resources/index.ts index 4e7977d3..b00413aa 100644 --- a/src/core/i18n/resources/index.ts +++ b/src/core/i18n/resources/index.ts @@ -1,31 +1,11 @@ -import appConfigEN from './en/appConfig.json'; -import commonEN from './en/common.json'; -import homeScreenEN from './en/homeScreen.json'; -import miscScreensEN from './en/miscScreens.json'; -import otherScreenEN from './en/otherScreen.json'; -import settingsEN from './en/settings.json'; -import appConfigFR from './fr/appConfig.json'; -import commonFR from './fr/common.json'; -import homeScreenFR from './fr/homeScreen.json'; -import miscScreensFR from './fr/miscScreens.json'; -import otherScreenFR from './fr/otherScreen.json'; -import settingsFR from './fr/settings.json'; +import { messages as messagesEN } from './en'; +import { messages as messagesFR } from './fr'; export const resources = { fr: { - common: commonFR, - homeScreen: homeScreenFR, - otherScreen: otherScreenFR, - miscScreens: miscScreensFR, - settings: settingsFR, - appConfig: appConfigFR, + messages: messagesFR, }, en: { - common: commonEN, - homeScreen: homeScreenEN, - otherScreen: otherScreenEN, - miscScreens: miscScreensEN, - settings: settingsEN, - appConfig: appConfigEN, + messages: messagesEN, }, }; diff --git a/src/core/i18n/types/i18next.d.ts b/src/core/i18n/types/i18next.d.ts index 560bd92b..74049636 100644 --- a/src/core/i18n/types/i18next.d.ts +++ b/src/core/i18n/types/i18next.d.ts @@ -1,24 +1,12 @@ import 'react-i18next'; -import type appConfigEN from '../resources/en/appConfig.json'; -import type commonEN from '../resources/en/common.json'; -import type homeScreenEN from '../resources/en/homeScreen.json'; -import type miscScreensEN from '../resources/en/miscScreens.json'; -import type otherScreenEN from '../resources/en/otherScreen.json'; -import type settingsEN from '../resources/en/settings.json'; - -// https://www.geodev.me/blog/type-check-translation-keys/ +import type { MessagesTypes } from '../resources/en/messages'; declare module 'i18next' { interface CustomTypeOptions { - defaultNS: 'common'; + defaultNS: 'messages'; resources: { - common: typeof commonEN; - homeScreen: typeof homeScreenEN; - otherScreen: typeof otherScreenEN; - miscScreens: typeof miscScreensEN; - settings: typeof settingsEN; - appConfig: typeof appConfigEN; + messages: MessagesTypes; }; } } diff --git a/src/core/i18n/utils/languageSwitcher.ts b/src/core/i18n/utils/languageSwitcher.ts index d0ff214c..fb33cb09 100644 --- a/src/core/i18n/utils/languageSwitcher.ts +++ b/src/core/i18n/utils/languageSwitcher.ts @@ -10,7 +10,7 @@ export const changeLanguage = async (language: SupportedLanguages) => { await i18next.changeLanguage(language, (error, t) => { if (error) { Toaster.show({ - text1: t('changeLocale.failure', { ns: 'settings' }), + text1: t('settings.changeLocale.failure'), }); return; @@ -19,7 +19,7 @@ export const changeLanguage = async (language: SupportedLanguages) => { setSavedAppLocale(language); Toaster.show({ - text1: t('changeLocale.success', { ns: 'settings' }), + text1: t('settings.changeLocale.success'), }); }); }; diff --git a/src/features/blogPost/BlogPost.tsx b/src/features/blogPost/BlogPost.tsx index f64da602..5cc646ee 100644 --- a/src/features/blogPost/BlogPost.tsx +++ b/src/features/blogPost/BlogPost.tsx @@ -13,7 +13,7 @@ type BlogPostProps = { }; export const BlogPost = ({ id }: BlogPostProps) => { - const { t } = useTranslation('miscScreens'); + const { t } = useTranslation(); const { data, isLoading } = useGetPostQuery({ id, }); @@ -22,7 +22,7 @@ export const BlogPost = ({ id }: BlogPostProps) => { return ( <> - {t('blogPost.title')} + {t('miscScreens.blogPost.title')} {data?.post?.title} diff --git a/src/features/dummyForm/DummyFormExample.tsx b/src/features/dummyForm/DummyFormExample.tsx index 74ed0746..108603f6 100644 --- a/src/features/dummyForm/DummyFormExample.tsx +++ b/src/features/dummyForm/DummyFormExample.tsx @@ -7,6 +7,7 @@ import { Controller, useForm } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; import type { TextInput } from 'react-native'; +import { changeLanguage } from '$core/i18n'; import { DummyFormSchema, type DummyFormSchemaType, @@ -19,7 +20,7 @@ export const DummyFormExample = () => { const firstNameInputRef = useRef(null); const lastNameInputRef = useRef(null); - const { t } = useTranslation('miscScreens'); + const { t } = useTranslation(); const { control, handleSubmit } = useForm({ resolver: zodResolver(DummyFormSchema), @@ -33,6 +34,13 @@ export const DummyFormExample = () => { return ( <> + changeLanguage('en')} + > + Set locale to FR + + { autoCorrect={false} error={fieldState.error?.message} keyboardType="email-address" - label={t('dummyForm.form.email.label')} - placeholder={t('dummyForm.form.email.placeholder')} + label={t('miscScreens.dummyForm.form.email.label')} + placeholder={t('miscScreens.dummyForm.form.email.placeholder')} returnKeyType="next" testID="dummyForm-input-email" value={value} @@ -68,10 +76,12 @@ export const DummyFormExample = () => { ref={firstNameInputRef} autoComplete="name-given" error={fieldState.error?.message} - label={t('dummyForm.form.firstName.label')} - placeholder={t('dummyForm.form.firstName.placeholder')} + label={t('miscScreens.dummyForm.form.firstName.label')} returnKeyType="next" value={value} + placeholder={t( + 'miscScreens.dummyForm.form.firstName.placeholder', + )} onBlur={onBlur} onChangeText={onChange} onSubmitEditing={() => { @@ -91,8 +101,8 @@ export const DummyFormExample = () => { ref={lastNameInputRef} autoComplete="name-family" error={fieldState.error?.message} - label={t('dummyForm.form.lastName.label')} - placeholder={t('dummyForm.form.lastName.placeholder')} + label={t('miscScreens.dummyForm.form.lastName.label')} + placeholder={t('miscScreens.dummyForm.form.lastName.placeholder')} returnKeyType="done" value={value} onBlur={onBlur} diff --git a/src/features/dummyForm/utils/dummyForm.schema.ts b/src/features/dummyForm/utils/dummyForm.schema.ts index 66073da6..4038d75c 100644 --- a/src/features/dummyForm/utils/dummyForm.schema.ts +++ b/src/features/dummyForm/utils/dummyForm.schema.ts @@ -8,30 +8,30 @@ const LAST_NAME_MAX_LENGTH = 30; export const DummyFormSchema = z.object({ email: z.string().email({ - message: i18next.t('miscScreens:dummyForm.form.email.validation.email'), + message: i18next.t('miscScreens.dummyForm.form.email.validation.email'), }), firstName: z .string() .min(FIRST_NAME_MIN_LENGTH, { message: i18next.t( - 'miscScreens:dummyForm.form.firstName.validation.minLength', + 'miscScreens.dummyForm.form.firstName.validation.minLength', ), }) .max(FIRST_NAME_MAX_LENGTH, { message: i18next.t( - 'miscScreens:dummyForm.form.firstName.validation.maxLength', + 'miscScreens.dummyForm.form.firstName.validation.maxLength', ), }), lastName: z .string() .min(LAST_NAME_MIN_LENGTH, { message: i18next.t( - 'miscScreens:dummyForm.form.lastName.validation.minLength', + 'miscScreens.dummyForm.form.lastName.validation.minLength', ), }) .max(LAST_NAME_MAX_LENGTH, { message: i18next.t( - 'miscScreens:dummyForm.form.lastName.validation.minLength', + 'miscScreens.dummyForm.form.lastName.validation.minLength', ), }), }); diff --git a/src/features/home/components/Informations.tsx b/src/features/home/components/Informations.tsx index 471ed022..b765947b 100644 --- a/src/features/home/components/Informations.tsx +++ b/src/features/home/components/Informations.tsx @@ -4,31 +4,31 @@ import { IS_IOS } from '$core/constants'; import { Text } from '$shared/uiKit/primitives'; export const Informations = () => { - const { t } = useTranslation('homeScreen'); + const { t } = useTranslation(); return ( <> - {t('sandbox.title')} + {t('homeScreen.sandbox.title')} - {t('sandbox.content', { + {t('homeScreen.sandbox.content', { command: IS_IOS ? 'Cmd+R' : 'Cmd+M', })} - {t('tests.title')} + {t('homeScreen.tests.title')} - {t('tests.content')} + {t('homeScreen.tests.content')} - {t('formatting.title')} + {t('homeScreen.formatting.title')} - {t('formatting.content')} + {t('homeScreen.formatting.content')} ); }; diff --git a/src/features/home/components/Version.tsx b/src/features/home/components/Version.tsx index 70578ce5..2bef45c4 100644 --- a/src/features/home/components/Version.tsx +++ b/src/features/home/components/Version.tsx @@ -8,7 +8,7 @@ import { Box, Text } from '$shared/uiKit/primitives'; export const Version = () => { const insets = useSafeAreaInsets(); - const { t } = useTranslation('appConfig'); + const { t } = useTranslation(); const { currentlyRunning } = Updates.useUpdates(); @@ -42,7 +42,9 @@ export const Version = () => { )} {!isRunningBuiltInCode && ( - {t('updateCheck.isEmbeddedLaunch')} + + {t('homeScreen.updateCheck.isEmbeddedLaunch')} + )} diff --git a/src/features/notifications/Notifications.tsx b/src/features/notifications/Notifications.tsx index c0d9ab8b..03d82cbb 100644 --- a/src/features/notifications/Notifications.tsx +++ b/src/features/notifications/Notifications.tsx @@ -7,7 +7,7 @@ import { Button } from '$shared/uiKit/button'; import { Box, Text } from '$shared/uiKit/primitives'; export const Notifications = () => { - const { t } = useTranslation('miscScreens'); + const { t } = useTranslation(); const { requestNotificationPermission } = useRequestPermission(); @@ -25,10 +25,12 @@ export const Notifications = () => { return ( <> - {t('notifications.title')} + {t('miscScreens.notifications.title')} - {t('notifications.cta')} + + {t('miscScreens.notifications.cta')} + ); diff --git a/src/shared/components/AppUpdateNeeded.tsx b/src/shared/components/AppUpdateNeeded.tsx index 4bf95a61..5569bc0d 100644 --- a/src/shared/components/AppUpdateNeeded.tsx +++ b/src/shared/components/AppUpdateNeeded.tsx @@ -13,7 +13,7 @@ import { Box, Text } from '$shared/uiKit/primitives'; export const AppUpdateNeeded = () => { const [isAppUnsupported, setIsAppUnsupported] = useState(false); - const { t } = useTranslation('miscScreens'); + const { t } = useTranslation(); const { getFlagValueSync } = useGetFlagValueSync(); @@ -66,15 +66,15 @@ export const AppUpdateNeeded = () => { width="100%" > - {t('appUpdate.title')} + {t('miscScreens.appUpdate.title')} - {t('appUpdate.description')} + {t('miscScreens.appUpdate.description')} - {t('updateAvailable.banner.updateCta', { ns: 'settings' })} + {t('settings.updateAvailable.banner.updateCta')} ); diff --git a/src/shared/components/FullscreenErrorBoundary.tsx b/src/shared/components/FullscreenErrorBoundary.tsx index 68f63eff..3ff07950 100644 --- a/src/shared/components/FullscreenErrorBoundary.tsx +++ b/src/shared/components/FullscreenErrorBoundary.tsx @@ -7,7 +7,7 @@ import { Box, Text } from '$shared/uiKit/primitives'; import { SafeView } from '$shared/uiKit/SafeView'; const FullscreenErrorBoundary = () => { - const { t } = useTranslation('miscScreens'); + const { t } = useTranslation(); const reloadApp = async () => Updates.reloadAsync().catch((error: unknown) => { @@ -24,16 +24,16 @@ const FullscreenErrorBoundary = () => { width="80%" > - {t('errorBoundary.title')} + {t('miscScreens.errorBoundary.title')} - {t('errorBoundary.description')} + {t('miscScreens.errorBoundary.description')} - {t('errorBoundary.cta')} + {t('miscScreens.errorBoundary.cta')} diff --git a/src/shared/components/MaintenanceMode.tsx b/src/shared/components/MaintenanceMode.tsx index 4bbba80c..c9e25573 100644 --- a/src/shared/components/MaintenanceMode.tsx +++ b/src/shared/components/MaintenanceMode.tsx @@ -5,7 +5,7 @@ import { useIsFeatureFlagEnabled } from '$core/featureFlags'; import { Box, Text } from '$shared/uiKit/primitives'; export const MaintenanceMode = () => { - const { t } = useTranslation('miscScreens'); + const { t } = useTranslation(); const isMaintenanceModeEnabled = useIsFeatureFlagEnabled( 'is-maintenance-mode', @@ -25,10 +25,12 @@ export const MaintenanceMode = () => { width="100%" > - {t('maintenanceMode.title')} + {t('miscScreens.maintenanceMode.title')} - {t('maintenanceMode.description')} + + {t('miscScreens.maintenanceMode.description')} + ); }; diff --git a/src/shared/components/StoreUpdateBanner.tsx b/src/shared/components/StoreUpdateBanner.tsx index 48f25042..36cf571f 100644 --- a/src/shared/components/StoreUpdateBanner.tsx +++ b/src/shared/components/StoreUpdateBanner.tsx @@ -17,16 +17,14 @@ type UpdateStatus = { export const StoreUpdateBanner = () => { const [updateStatus, setUpdateStatus] = useState(); - const { t } = useTranslation('settings'); + const { t } = useTranslation(); useRunOnMount(() => { checkForNativeUpdate({ - title: t('updateAvailable.nativePrompt.title'), - message: t('updateAvailable.nativePrompt.message'), - buttonUpgradeText: t('updateAvailable.nativePrompt.updateCta'), - buttonCancelText: t('cancel', { - ns: 'common', - }), + title: t('settings.updateAvailable.nativePrompt.title'), + message: t('settings.updateAvailable.nativePrompt.message'), + buttonUpgradeText: t('settings.updateAvailable.nativePrompt.updateCta'), + buttonCancelText: t('common.cancel'), }) .then((status) => { setUpdateStatus(status); @@ -46,16 +44,16 @@ export const StoreUpdateBanner = () => { {updateStatus.storeVersion - ? t('updateAvailable.banner.compareVersions', { + ? t('settings.updateAvailable.banner.compareVersions', { currentVersion: updateStatus.currentVersion, storeVersion: updateStatus.storeVersion, }) - : t('updateAvailable.banner.defaultTitle')} + : t('settings.updateAvailable.banner.defaultTitle')} - {t('updateAvailable.banner.updateCta')} + {t('settings.updateAvailable.banner.updateCta')} diff --git a/src/shared/hooks/useCheckNetworkStateOnMount.ts b/src/shared/hooks/useCheckNetworkStateOnMount.ts index 22ade432..7cef6732 100644 --- a/src/shared/hooks/useCheckNetworkStateOnMount.ts +++ b/src/shared/hooks/useCheckNetworkStateOnMount.ts @@ -10,7 +10,7 @@ import { useRunOnMount } from './useRunOnMount'; const ONE_SECOND = 1000; export const useCheckNetworkStateOnMount = () => { - const { t } = useTranslation('appConfig'); + const { t } = useTranslation(); const checkNetworkState = async () => { await sleep(ONE_SECOND); @@ -19,8 +19,8 @@ export const useCheckNetworkStateOnMount = () => { if (!isInternetReachable) { Toaster.show({ type: 'info', - text1: t('networkStateCheck.title'), - text2: t('networkStateCheck.message'), + text1: t('appConfig.networkStateCheck.title'), + text2: t('appConfig.networkStateCheck.message'), }); } }; diff --git a/src/shared/hooks/useRequestPermission.ts b/src/shared/hooks/useRequestPermission.ts index f0e167af..e299c76e 100644 --- a/src/shared/hooks/useRequestPermission.ts +++ b/src/shared/hooks/useRequestPermission.ts @@ -17,7 +17,7 @@ export const useRequestPermission = () => { Logger.dev('Permission is not available'); Toaster.show({ type: 'error', - text1: t('permissions.notAvailable', { ns: 'settings' }), + text1: t('settings.permissions.notAvailable'), }); return; @@ -35,7 +35,7 @@ export const useRequestPermission = () => { Logger.dev('Permission refused'); Toaster.show({ type: 'error', - text1: t('permissions.notGranted', { ns: 'settings' }), + text1: t('settings.permissions.notGranted'), }); return; @@ -50,7 +50,7 @@ export const useRequestPermission = () => { Logger.dev('Notifications not granted'); Toaster.show({ type: 'error', - text1: t('permissions.notGranted', { ns: 'settings' }), + text1: t('settings.permissions.notGranted'), }); }; @@ -65,7 +65,7 @@ export const useRequestPermission = () => { Logger.dev('Notifications are not available'); Toaster.show({ type: 'error', - text1: t('permissions.notAvailable', { ns: 'settings' }), + text1: t('settings.permissions.notAvailable'), }); return; @@ -83,7 +83,7 @@ export const useRequestPermission = () => { Logger.dev('Notifications refused'); Toaster.show({ type: 'error', - text1: t('permissions.notGranted', { ns: 'settings' }), + text1: t('settings.permissions.notGranted'), }); return; @@ -98,7 +98,7 @@ export const useRequestPermission = () => { Logger.dev('Notifications not granted'); Toaster.show({ type: 'error', - text1: t('permissions.notGranted', { ns: 'settings' }), + text1: t('settings.permissions.notGranted'), }); };