diff --git a/app/_layout.tsx b/app/_layout.tsx index 8096a100..b12630a7 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -273,9 +273,9 @@ function Layout() { useEffect(() => { i18n.changeLanguage( - settings?.preferedLanguage || getLocales()[0].languageCode || "en" + settings?.preferedLanguage ?? getLocales()[0].languageCode ?? "en" ); - }, [settings]); + }, [settings?.preferedLanguage, i18n]); const appState = useRef(AppState.currentState); diff --git a/bun.lockb b/bun.lockb index 3615fd38..a93e42e7 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/components/settings/AppLanguageSelector.tsx b/components/settings/AppLanguageSelector.tsx new file mode 100644 index 00000000..a160735d --- /dev/null +++ b/components/settings/AppLanguageSelector.tsx @@ -0,0 +1,80 @@ +import * as DropdownMenu from "zeego/dropdown-menu"; +import { TouchableOpacity, View } from "react-native"; +import { Text } from "../common/Text"; +import { useSettings } from "@/utils/atoms/settings"; +import { t } from "i18next"; +import { APP_LANGUAGES } from "@/i18n"; + +export const AppLanguageSelector = () => { + const [settings, updateSettings] = useSettings(); + + return ( + + + {t("home.settings.languages.title")} + + + + + {t("home.settings.languages.app_language")} + + + {t("home.settings.languages.app_language_description")} + + + + + + + {APP_LANGUAGES.find( + (l) => l.value === settings?.preferedLanguage + )?.label || t("home.settings.languages.system")} + + + + + + {t("home.settings.languages.title")} + + { + updateSettings({ + preferedLanguage: undefined, + }); + }} + > + + {t("home.settings.languages.system")} + + + {APP_LANGUAGES?.map((l) => ( + { + updateSettings({ + preferedLanguage: l.value, + }); + }} + > + {l.label} + + ))} + + + + + ); +}; diff --git a/components/settings/SettingToggles.tsx b/components/settings/SettingToggles.tsx index ef3ae116..d32b3b24 100644 --- a/components/settings/SettingToggles.tsx +++ b/components/settings/SettingToggles.tsx @@ -44,6 +44,7 @@ import { JellyseerrApi, useJellyseerr } from "@/hooks/useJellyseerr"; import { ListItem } from "@/components/ListItem"; import { JellyseerrSettings } from "./Jellyseerr"; import { useTranslation } from "react-i18next"; +import { AppLanguageSelector } from "./AppLanguageSelector"; interface Props extends ViewProps {} @@ -133,6 +134,8 @@ export const SettingToggles: React.FC = ({ ...props }) => { */} + + @@ -140,12 +143,16 @@ export const SettingToggles: React.FC = ({ ...props }) => { - {t("home.settings.other.other_title")} + + {t("home.settings.other.other_title")} + - {t("home.settings.other.auto_rotate")} + + {t("home.settings.other.auto_rotate")} + {t("home.settings.other.auto_rotate_hint")} @@ -168,7 +175,9 @@ export const SettingToggles: React.FC = ({ ...props }) => { `} > - {t("home.settings.other.video_orientation")} + + {t("home.settings.other.video_orientation")} + {t("home.settings.other.video_orientation_hint")} @@ -265,7 +274,9 @@ export const SettingToggles: React.FC = ({ ...props }) => { - {t("home.settings.other.safe_area_in_controls")} + + {t("home.settings.other.safe_area_in_controls")} + {t("home.settings.other.safe_area_in_controls_hint")} @@ -281,8 +292,12 @@ export const SettingToggles: React.FC = ({ ...props }) => { - {t("home.settings.other.use_popular_lists_plugin")} - {t("home.settings.other.use_popular_lists_plugin_hint")} + + {t("home.settings.other.use_popular_lists_plugin")} + + + {t("home.settings.other.use_popular_lists_plugin_hint")} + { Linking.openURL( @@ -290,7 +305,9 @@ export const SettingToggles: React.FC = ({ ...props }) => { ); }} > - {t("home.settings.other.more_info")} + + {t("home.settings.other.more_info")} + = ({ ...props }) => { `} > - {t("home.settings.other.search_engine")} + + {t("home.settings.other.search_engine")} + {t("home.settings.other.search_engine_hint")} @@ -438,7 +457,9 @@ export const SettingToggles: React.FC = ({ ...props }) => { - {t("home.settings.other.show_custom_menu_links")} + + {t("home.settings.other.show_custom_menu_links")} + {t("home.settings.other.show_custom_menu_links_hint")} @@ -449,7 +470,9 @@ export const SettingToggles: React.FC = ({ ...props }) => { ) } > - {t("home.settings.other.more_info")} + + {t("home.settings.other.more_info")} + = ({ ...props }) => { - {t("home.settings.downloads.downloads_title")} + + {t("home.settings.downloads.downloads_title")} + = ({ ...props }) => { `} > - {t("home.settings.downloads.download_method")} + + {t("home.settings.downloads.download_method")} + {t("home.settings.downloads.download_method_hint")} @@ -531,7 +558,9 @@ export const SettingToggles: React.FC = ({ ...props }) => { }`} > - {t("home.settings.downloads.remux_max_download")} + + {t("home.settings.downloads.remux_max_download")} + {t("home.settings.downloads.remux_max_download_hint")} @@ -562,7 +591,9 @@ export const SettingToggles: React.FC = ({ ...props }) => { }`} > - {t("home.settings.downloads.auto_download")} + + {t("home.settings.downloads.auto_download")} + {t("home.settings.downloads.auto_download_hint")} diff --git a/i18n.ts b/i18n.ts index 841150e9..edfe7202 100644 --- a/i18n.ts +++ b/i18n.ts @@ -6,8 +6,14 @@ import fr from "./translations/fr.json"; import sv from "./translations/sv.json"; import { getLocales } from "expo-localization"; +export const APP_LANGUAGES = [ + { label: "English", value: "en" }, + { label: "Français", value: "fr" }, + { label: "Svenska", value: "sv" }, +]; + i18n.use(initReactI18next).init({ - compatibilityJSON: "v3", + compatibilityJSON: "v4", resources: { en: { translation: en }, fr: { translation: fr }, diff --git a/translations/en.json b/translations/en.json index 337cf1fe..50f4b1bf 100644 --- a/translations/en.json +++ b/translations/en.json @@ -33,18 +33,19 @@ "user_info_title": "User Info", "user": "User", "server": "Server", - "log_out_button": "Log out" + "log_out_button": "Log out", + "token": "Token" }, "quick_connect": { "quick_connect_title": "Quick connect", "authorize_button": "Authorize" }, - "media":{ + "media": { "media_title": "Media", "forward_skip_length": "Forward skip length", "forward_skip_length_hint": "Choose length in seconds when skipping in video playback.", "rewind_length": "Rewind length", - "rewind_length_hint": "Choose length in seconds when skipping in video playback." + "rewind_length_hint": "Choose length in seconds when skipping in video playback." }, "audio": { "audio_title": "Audio", @@ -62,7 +63,7 @@ "subtitle_mode": "Subtitle Mode", "subtitle_mode_hint": "Subtitles are loaded based on the default and forced flags in the\nembedded metadata. Language preferences are considered when\nmultiple options are available.", "set_subtitle_track": "Set Subtitle Track From Previous Item", - "set_subtitle_track_hint" :"Try to set the subtitle track to the closest match to the last\nvideo.", + "set_subtitle_track_hint": "Try to set the subtitle track to the closest match to the last\nvideo.", "subtitle_size": "Subtitle Size", "subtitle_size_hint": "Choose a default subtitle size for direct play (only works for\nsome subtitle formats)." }, @@ -116,6 +117,12 @@ "logs": { "logs_title": "Logs", "no_logs_available": "No logs available" + }, + "languages": { + "title": "Languages", + "app_language": "App language", + "app_language_description": "Select the language for the app.", + "system": "System" } }, "downloads": { diff --git a/utils/atoms/settings.ts b/utils/atoms/settings.ts index 74d133b5..2e6675ba 100644 --- a/utils/atoms/settings.ts +++ b/utils/atoms/settings.ts @@ -99,7 +99,7 @@ const loadSettings = (): Settings => { usePopularPlugin: false, deviceProfile: "Expo", mediaListCollectionIds: [], - preferedLanguage: getLocales()[0].languageCode || "en", + preferedLanguage: undefined, searchEngine: "Jellyfin", marlinServerUrl: "", openInVLC: false,