diff --git a/mobile/app.json b/mobile/app.json index 8c51125..a209442 100644 --- a/mobile/app.json +++ b/mobile/app.json @@ -25,7 +25,8 @@ }, "plugins": [ "expo-router", - "expo-sqlite" + "expo-sqlite", + "expo-localization" ] } } diff --git a/mobile/app/_layout.tsx b/mobile/app/_layout.tsx index e9ab433..1054d8b 100644 --- a/mobile/app/_layout.tsx +++ b/mobile/app/_layout.tsx @@ -1,6 +1,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { Stack } from 'expo-router'; import { AuthProvider } from '../context/AuthContext'; +import '../i18n'; const queryClient = new QueryClient({ defaultOptions: { diff --git a/mobile/app/folder/[id].tsx b/mobile/app/folder/[id].tsx index 59a835e..9323ca7 100644 --- a/mobile/app/folder/[id].tsx +++ b/mobile/app/folder/[id].tsx @@ -3,6 +3,7 @@ import { useCallback, useState } from 'react'; import { FlatList, Pressable, StyleSheet, Text, View } from 'react-native'; import { getFolder, getFolderFolders, getFiles } from '../../services/localStorage'; import type { StoredFile, StoredFolder } from '../../services/db/types'; +import i18n from '../../i18n'; type Row = { key: string; @@ -13,9 +14,9 @@ type Row = { }; function formatSize(bytes: number): string { - if ( bytes < 1024 ) return `${bytes} o`; - if ( bytes < 1024 * 1024 ) return `${(bytes / 1024).toFixed(1)} Ko`; - return `${(bytes / (1024 * 1024)).toFixed(1)} Mo`; + if ( bytes < 1024 ) return `${bytes} ${i18n.t('bytes')}`; + if ( bytes < 1024 * 1024 ) return `${(bytes / 1024).toFixed(1)} ${i18n.t('kilobytes')}`; + return `${(bytes / (1024 * 1024)).toFixed(1)} ${i18n.t('megabytes')}`; } export default function FolderScreen() { @@ -48,7 +49,7 @@ export default function FolderScreen() { key: f.resource_id, kind: 'folder' as const, label: f.name, - meta: 'Dossier', + meta: i18n.t('folder'), resourceId: f.resource_id, })), ...files.map((f) => ({ @@ -62,7 +63,7 @@ export default function FolderScreen() { return ( - + item.key} @@ -79,7 +80,7 @@ export default function FolderScreen() { )} ListEmptyComponent={ - Dossier vide — le prochain syncDevice l'actualisera. + {i18n.t('empty_folder')} } /> diff --git a/mobile/app/index.tsx b/mobile/app/index.tsx index cfb233a..5f2d7d7 100644 --- a/mobile/app/index.tsx +++ b/mobile/app/index.tsx @@ -6,6 +6,7 @@ import { syncRoot } from '../features/syncDevice'; import { pickDirectory } from '../services/safDirectory'; import { getFolders, saveDirectory } from '../services/localStorage'; import type { StoredFolder } from '../services/db/types'; +import i18n from '../i18n'; export default function Index() { @@ -40,7 +41,7 @@ export default function Index() { - Ajouter un dossier + {i18n.t('add_folder')} - Aucun dossier pour le moment. Appuie sur « Ajouter un dossier » pour synchroniser un dossier. + {i18n.t('no_folders_yet')} } /> diff --git a/mobile/i18n/en.json b/mobile/i18n/en.json new file mode 100644 index 0000000..9e6eb1b --- /dev/null +++ b/mobile/i18n/en.json @@ -0,0 +1,12 @@ +{ + "files": "Files", + "search": "Search", + "configuration": "Settings", + "add_folder": "Add a folder", + "no_folders_yet": "No folders yet. Tap \"Add a folder\" to sync a folder.", + "folder": "Folder", + "empty_folder": "Empty folder — next syncDevice will refresh it.", + "bytes": "B", + "kilobytes": "KB", + "megabytes": "MB" +} diff --git a/mobile/i18n/fr.json b/mobile/i18n/fr.json new file mode 100644 index 0000000..cbb80bc --- /dev/null +++ b/mobile/i18n/fr.json @@ -0,0 +1,12 @@ +{ + "files": "Fichiers", + "search": "Recherche", + "configuration": "Configuration", + "add_folder": "Ajouter un dossier", + "no_folders_yet": "Aucun dossier pour le moment. Appuie sur « Ajouter un dossier » pour synchroniser un dossier.", + "folder": "Dossier", + "empty_folder": "Dossier vide — le prochain syncDevice l'actualisera.", + "bytes": "o", + "kilobytes": "Ko", + "megabytes": "Mo" +} diff --git a/mobile/i18n/index.ts b/mobile/i18n/index.ts new file mode 100644 index 0000000..1e6ffc7 --- /dev/null +++ b/mobile/i18n/index.ts @@ -0,0 +1,12 @@ +import { I18n } from 'i18n-js'; +import { getLocales } from 'expo-localization'; +import fr from './fr.json'; +import en from './en.json'; + +const i18n = new I18n({ fr, en }); + +i18n.defaultLocale = 'fr'; +i18n.enableFallback = true; +i18n.locale = getLocales()[0]?.languageCode ?? 'fr'; + +export default i18n; diff --git a/mobile/package-lock.json b/mobile/package-lock.json index 1c4b7b6..e31253b 100644 --- a/mobile/package-lock.json +++ b/mobile/package-lock.json @@ -13,9 +13,11 @@ "expo-constants": "~57.0.17", "expo-file-system": "~57.0.6", "expo-linking": "~57.0.9", + "expo-localization": "~57.0.1", "expo-router": "~57.0.20", "expo-sqlite": "~57.0.2", "expo-status-bar": "~57.0.1", + "i18n-js": "^4.5.3", "react": "19.2.3", "react-dom": "19.2.3", "react-native": "0.86.0", @@ -3318,6 +3320,12 @@ "node": ">=0.6" } }, + "node_modules/bignumber.js": { + "version": "11.1.5", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-11.1.5.tgz", + "integrity": "sha512-6WmzCNtUnfKpbozq+hOgWaZMMzORmYBwF1xZScyoIX3QRYWeKTtxxwDOW5tIz7C9BdjkIYHGTcelCLkXg0mndw==", + "license": "MIT" + }, "node_modules/bplist-creator": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/bplist-creator/-/bplist-creator-0.1.0.tgz", @@ -4090,6 +4098,19 @@ "react-native": "*" } }, + "node_modules/expo-localization": { + "version": "57.0.1", + "resolved": "https://registry.npmjs.org/expo-localization/-/expo-localization-57.0.1.tgz", + "integrity": "sha512-8Ffl4UTbOsQeGT0v5fxMbyPHyPMPnhSPDFQJa8p9rjJrthFoAtNi+fL6Ssmrvf1/7dmPq1mVY52MEt0TMEfgjA==", + "license": "MIT", + "dependencies": { + "rtl-detect": "^1.0.2" + }, + "peerDependencies": { + "expo": "*", + "react": "*" + } + }, "node_modules/expo-modules-autolinking": { "version": "57.0.12", "resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-57.0.12.tgz", @@ -5015,6 +5036,17 @@ "node": ">= 14" } }, + "node_modules/i18n-js": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/i18n-js/-/i18n-js-4.5.3.tgz", + "integrity": "sha512-5/tT6R9t9qlYqGhxGq9I9Ap3WKUaAMq5aRuO1gqAcUqm6xGbL0jwTAjSFjgbx935BAV8QbEzvQOzE796dUlEfA==", + "license": "MIT", + "dependencies": { + "bignumber.js": "*", + "lodash": "*", + "make-plural": "7.5.0" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -5564,6 +5596,12 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -5680,6 +5718,12 @@ "yallist": "^3.0.2" } }, + "node_modules/make-plural": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/make-plural/-/make-plural-7.5.0.tgz", + "integrity": "sha512-0booA+aVYyVFoR67JBHdfVk0U08HmrBH2FrtmBqBa+NldlqXv/G2Z9VQuQq6Wgp2jDWdybEWGfBkk1cq5264WA==", + "license": "Unicode-DFS-2016" + }, "node_modules/makeerror": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", @@ -7121,6 +7165,12 @@ "node": ">=4" } }, + "node_modules/rtl-detect": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/rtl-detect/-/rtl-detect-1.1.2.tgz", + "integrity": "sha512-PGMBq03+TTG/p/cRB7HCLKJ1MgDIi07+QU1faSjiYRfmY5UsAttV9Hs08jDAHVwcOwmVLcSJkpwyfXszVjWfIQ==", + "license": "BSD-3-Clause" + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", diff --git a/mobile/package.json b/mobile/package.json index dad5b65..8841cc6 100644 --- a/mobile/package.json +++ b/mobile/package.json @@ -8,9 +8,11 @@ "expo-constants": "~57.0.17", "expo-file-system": "~57.0.6", "expo-linking": "~57.0.9", + "expo-localization": "~57.0.1", "expo-router": "~57.0.20", "expo-sqlite": "~57.0.2", "expo-status-bar": "~57.0.1", + "i18n-js": "^4.5.3", "react": "19.2.3", "react-dom": "19.2.3", "react-native": "0.86.0",