diff --git a/package.json b/package.json index 2532f3c1..4be52717 100644 --- a/package.json +++ b/package.json @@ -49,11 +49,13 @@ "dockview-react": "8.2.0", "emoji-mart": "5.6.0", "flexlayout-react": "0.10.8", + "i18next": "^26.4.2", "lucide-react": "1.34.0", "mdast-util-from-markdown": "2.0.3", "nostr-tools": "2.25.0", "react": "19.2.8", "react-dom": "19.2.8", + "react-i18next": "^17.0.13", "react-markdown": "10.1.0", "remark-breaks": "4.0.0", "remark-gfm": "4.0.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d64a91f9..cdcafed6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -53,6 +53,9 @@ importers: flexlayout-react: specifier: 0.10.8 version: 0.10.8(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + i18next: + specifier: ^26.4.2 + version: 26.4.2(typescript@7.0.2) lucide-react: specifier: 1.34.0 version: 1.34.0(react@19.2.8) @@ -68,6 +71,9 @@ importers: react-dom: specifier: 19.2.8 version: 19.2.8(react@19.2.8) + react-i18next: + specifier: ^17.0.13 + version: 17.0.13(i18next@26.4.2(typescript@7.0.2))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(typescript@7.0.2) react-markdown: specifier: 10.1.0 version: 10.1.0(@types/react@19.2.18)(react@19.2.8) @@ -961,9 +967,20 @@ packages: hast-util-whitespace@3.0.0: resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + html-parse-stringify@4.0.1: + resolution: {integrity: sha512-0zHsZJrK7S3K2aucXWL6ycoYJ/iNtIcFHC/nYQgFklPtrv5LpJctIiSCroWZWeuoXvuyFdzp6KzjJQ+OT5MfFw==} + html-url-attributes@3.0.1: resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==} + i18next@26.4.2: + resolution: {integrity: sha512-RX+R0VLg13IbvRuJSxnqykUFS9vQZTl8wYpWPCIUDWVrSGjsQywB5Y+pjzrkboxGAuYfJZVH1InFTdgBdxq6ug==} + peerDependencies: + typescript: ^5 || ^6 || ^7 + peerDependenciesMeta: + typescript: + optional: true + inline-style-parser@0.2.7: resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} @@ -1343,6 +1360,22 @@ packages: peerDependencies: react: ^19.2.8 + react-i18next@17.0.13: + resolution: {integrity: sha512-Cc1PscmblIHA1kljTqDwrcVMI21ydgmUzw0UAeQBe7pAOgfuRLfzXze4EUBQoeDiICzFIXXhHFoZxuetNg5D0Q==} + peerDependencies: + i18next: '>= 26.2.0' + react: '>= 16.8.0' + react-dom: '*' + react-native: '*' + typescript: ^5 || ^6 || ^7 + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + typescript: + optional: true + react-markdown@10.1.0: resolution: {integrity: sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==} peerDependencies: @@ -2215,8 +2248,14 @@ snapshots: dependencies: '@types/hast': 3.0.5 + html-parse-stringify@4.0.1: {} + html-url-attributes@3.0.1: {} + i18next@26.4.2(typescript@7.0.2): + optionalDependencies: + typescript: 7.0.2 + inline-style-parser@0.2.7: {} is-alphabetical@2.0.1: {} @@ -2752,6 +2791,17 @@ snapshots: react: 19.2.8 scheduler: 0.27.0 + react-i18next@17.0.13(i18next@26.4.2(typescript@7.0.2))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(typescript@7.0.2): + dependencies: + '@babel/runtime': 7.29.7 + html-parse-stringify: 4.0.1 + i18next: 26.4.2(typescript@7.0.2) + react: 19.2.8 + use-sync-external-store: 1.6.0(react@19.2.8) + optionalDependencies: + react-dom: 19.2.8(react@19.2.8) + typescript: 7.0.2 + react-markdown@10.1.0(@types/react@19.2.18)(react@19.2.8): dependencies: '@types/hast': 3.0.5 diff --git a/src/app/LanguageSettings.tsx b/src/app/LanguageSettings.tsx new file mode 100644 index 00000000..f20743bd --- /dev/null +++ b/src/app/LanguageSettings.tsx @@ -0,0 +1,49 @@ +import { useTranslation } from "react-i18next"; +import { + appLocales, + currentLocale, + setAppLocale, + type AppLocale, +} from "../features/localization/service"; + +export function LanguageSettings() { + const { t } = useTranslation(); + return ( +
+

+ {t("language.title")} +

+
+ +

+ {t("language.description")} +

+ +
+
+ ); +} diff --git a/src/app/Settings.tsx b/src/app/Settings.tsx index 04a899d5..1850747a 100644 --- a/src/app/Settings.tsx +++ b/src/app/Settings.tsx @@ -1,6 +1,13 @@ import { useEffect, useState, useSyncExternalStore } from "react"; import { RecoveryScreen } from "./RecoveryScreen"; -import { Blocks, Settings2, UserRound, Palette, Bell } from "lucide-react"; +import { + Blocks, + Settings2, + UserRound, + Palette, + Bell, + Languages, +} from "lucide-react"; import type { PluginManager } from "../plugins/manager"; import type { Communities } from "../features/communities/service"; import { PluginImport } from "./PluginImport"; @@ -8,14 +15,17 @@ import { ProfileSettings } from "./ProfileSettings"; import type { Appearance } from "../shared/theme/service"; import { AppearanceSettings } from "./AppearanceSettings"; +import { LanguageSettings } from "./LanguageSettings"; +import { useTranslation } from "react-i18next"; import { NotificationSettings } from "./NotificationSettings"; import type { NotificationsService } from "../features/notifications/service"; const sections = [ - { id: "profile", label: "Profile", icon: UserRound }, - { id: "plugins", label: "Plugins", icon: Blocks }, - { id: "appearance", label: "Appearance", icon: Palette }, - { id: "notifications", label: "Notifications", icon: Bell }, + { id: "profile", label: "settings.profile", icon: UserRound }, + { id: "plugins", label: "settings.plugins", icon: Blocks }, + { id: "appearance", label: "settings.appearance", icon: Palette }, + { id: "notifications", label: "settings.notifications", icon: Bell }, + { id: "language", label: "settings.language", icon: Languages }, ] as const; export function Settings({ @@ -35,6 +45,7 @@ export function Settings({ | undefined; onSection?: (section: string) => void; }) { + const { t } = useTranslation(); const [selected, setSelected] = useState<(typeof sections)[number]["id"]>("profile"); const requestedSection = @@ -68,13 +79,13 @@ export function Settings({ id="settings-title" className="m-0 text-3xl font-medium tracking-tight" > - Settings + {t("settings.title")}
@@ -104,6 +115,9 @@ export function Settings({ + -