diff --git a/App.js b/App.js
index 47bbd91..d27a3e1 100644
--- a/App.js
+++ b/App.js
@@ -1,6 +1,6 @@
import "react-native-gesture-handler";
import { StatusBar } from "expo-status-bar";
-import { Appearance, StyleSheet } from "react-native";
+import { Appearance, StyleSheet, Text, View } from "react-native";
import { createDrawerNavigator } from "@react-navigation/drawer";
import { NavigationContainer } from "@react-navigation/native";
import SideBar from "./src/Components/SideBar";
@@ -16,8 +16,9 @@ import {
} from "./src/utils";
import DeviceScreen from "./src/Screens/Device";
import SettingsScreen from "./src/Screens/Settings";
-import { useContext, useEffect } from "react";
+import { Suspense, useContext, useEffect } from "react";
import { SafeAreaView } from "react-native-safe-area-context";
+import "./i18n";
const Drawer = createDrawerNavigator();
@@ -42,7 +43,7 @@ export function MyApp() {
appLanguage === null ? Constants.defaultLanguage : appLanguage
);
appContext.setAppColorScheme(
- appColorScheme === null ? Appearance.getColorScheme() : appColorScheme
+ appColorScheme === null ? "auto" : appColorScheme
);
appContext.setIsUserExpertModeEnabled(
@@ -88,17 +89,18 @@ export function MyApp() {
export default function App() {
return (
-
-
-
+
+ Loading...
+
+ }
+ >
+
+
+
+
);
}
-
-const styles = StyleSheet.create({
- container: {
- flex: 1,
- backgroundColor: "#fff",
- alignItems: "center",
- justifyContent: "center",
- },
-});
diff --git a/README.md b/README.md
index 01fbac5..4a855d5 100644
--- a/README.md
+++ b/README.md
@@ -9,3 +9,7 @@ https://pictogrammers.com/library/mdi/
# RNUILib
https://wix.github.io/react-native-ui-lib/docs/getting-started/setup
+
+https://stackoverflow.com/questions/68243384/dark-mode-usecolorscheme-always-returns-light-on-android
+
+https://stackoverflow.com/questions/70493788/i18nextpluralresolver-your-environment-seems-not-to-be-intl-api-compatible-u
diff --git a/app.json b/app.json
index 73de1cf..aed4eb3 100644
--- a/app.json
+++ b/app.json
@@ -5,23 +5,23 @@
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
- "userInterfaceStyle": "light",
+ "userInterfaceStyle": "automatic",
"splash": {
"image": "./assets/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
- "assetBundlePatterns": [
- "**/*"
- ],
+ "assetBundlePatterns": ["**/*"],
"ios": {
- "supportsTablet": true
+ "supportsTablet": true,
+ "userInterfaceStyle": "automatic"
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#ffffff"
- }
+ },
+ "userInterfaceStyle": "automatic"
},
"web": {
"favicon": "./assets/favicon.png"
diff --git a/i18n.js b/i18n.js
new file mode 100644
index 0000000..67c8767
--- /dev/null
+++ b/i18n.js
@@ -0,0 +1,29 @@
+import i18n from "i18next";
+import { initReactI18next } from "react-i18next";
+
+import de from "./locales/de.json";
+import en from "./locales/en.json";
+
+const resources = {
+ de: {
+ translation: de,
+ },
+ en: {
+ translation: en,
+ },
+};
+
+i18n
+ .use(initReactI18next) // passes i18n down to react-i18next
+ .init({
+ compatibilityJSON: "v3",
+ resources,
+ lng: "en", // language to use, more information here: https://www.i18next.com/overview/configuration-options#languages-namespaces-resources
+ // you can use the i18n.changeLanguage function to change the language manually: https://www.i18next.com/overview/api#changelanguage
+ // if you're using a language detector, do not define the lng option
+ interpolation: {
+ escapeValue: false, // react already safes from xss
+ },
+ });
+
+export default i18n;
diff --git a/locales/de.json b/locales/de.json
new file mode 100644
index 0000000..6ac2a92
--- /dev/null
+++ b/locales/de.json
@@ -0,0 +1,34 @@
+{
+ "test": "Einstellungen",
+ "sideBar": {
+ "devicesTitle": "Geräte",
+ "settings": "Einstellungen",
+ "faq": "FAQ",
+ "feedback": "Feedback geben"
+ },
+ "screens": {
+ "device": {
+ "settings": {
+ "settingsTitle": "Einstellungen",
+ "wifiStandByTitle": "WLAN im Standby",
+ "wifiStandByDescription": "Die WLAN-Verbindung bleibt bestehen, auch wenn das Gerät ausgeschaltet ist. Bitte beachten Sie, dass dies zu einem erhöhten Stromverbrauch führen kann.",
+ "deviceInformationTitle": "Geräteinformationen",
+ "deviceModelTitle": "Gerätemodell",
+ "deviceFirmwareVersionTitle": "Firmware Version",
+ "deviceLastUpdated": "Letzte Aktualisierung"
+ }
+ },
+ "settings": {
+ "settingsCardTitle": "Einstellungen",
+ "languageText": "Sprache",
+ "appColorSchemeText": "Anzeigemodus",
+ "appColorSchemePicker": {
+ "auto": "Systemvoreinstellung",
+ "dark": "Dunkel",
+ "light": "Hell"
+ },
+ "expertModeTitle": "Experten Modus",
+ "expertModeDescription": "Durch das Einschalten werden zusätzliche Funktionen in der App freigeschaltet, wie beispielsweise die Möglichkeit, benutzerdefinierte Farbcodes anzugeben."
+ }
+ }
+}
diff --git a/locales/en.json b/locales/en.json
new file mode 100644
index 0000000..3660791
--- /dev/null
+++ b/locales/en.json
@@ -0,0 +1,34 @@
+{
+ "test": "Settings",
+ "sideBar": {
+ "devicesTitle": "Devices",
+ "settings": "Settings",
+ "faq": "FAQ",
+ "feedback": "Give feedback"
+ },
+ "screens": {
+ "device": {
+ "settings": {
+ "settingsTitle": "Settings",
+ "wifiStandByTitle": "WLAN in standby",
+ "wifiStandByDescription": "The WLAN connection remains established even if the device is switched off. Please note that this can lead to increased power consumption.",
+ "deviceInformationTitle": "Device information",
+ "deviceModelTitle": "Device model",
+ "deviceFirmwareVersionTitle": "Firmware Version",
+ "deviceLastUpdated": "Last updated"
+ }
+ },
+ "settings": {
+ "settingsCardTitle": "Settings",
+ "languageText": "Language",
+ "appColorSchemeText": "Appearance",
+ "appColorSchemePicker": {
+ "auto": "System default",
+ "dark": "Dark",
+ "light": "Light"
+ },
+ "expertModeTitle": "Expert mode",
+ "expertModeDescription": "Turning it on unlocks additional features in the app, such as the ability to specify custom color codes."
+ }
+ }
+}
diff --git a/package-lock.json b/package-lock.json
index ea5cc2a..a97367d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14,7 +14,10 @@
"@react-navigation/native-stack": "^6.9.13",
"expo": "~48.0.18",
"expo-status-bar": "~1.4.4",
+ "i18next": "^23.2.11",
+ "i18next-browser-languagedetector": "^7.1.0",
"react": "18.2.0",
+ "react-i18next": "^13.0.2",
"react-native": "0.71.8",
"react-native-gesture-handler": "~2.9.0",
"react-native-pager-view": "6.1.2",
@@ -8091,6 +8094,14 @@
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
+ "node_modules/html-parse-stringify": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
+ "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
+ "dependencies": {
+ "void-elements": "3.1.0"
+ }
+ },
"node_modules/http-errors": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
@@ -8126,6 +8137,36 @@
"node": ">= 6"
}
},
+ "node_modules/i18next": {
+ "version": "23.2.11",
+ "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.2.11.tgz",
+ "integrity": "sha512-MA4FsxOjyCaOZtRDB4yuwjCvqYEioD4G4LlXOn7SO3rnQUlxTufyLsOqfL9MKakeLRBkefe8bqcs0D6Z/xFk1w==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://locize.com"
+ },
+ {
+ "type": "individual",
+ "url": "https://locize.com/i18next.html"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
+ }
+ ],
+ "dependencies": {
+ "@babel/runtime": "^7.22.5"
+ }
+ },
+ "node_modules/i18next-browser-languagedetector": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-7.1.0.tgz",
+ "integrity": "sha512-cr2k7u1XJJ4HTOjM9GyOMtbOA47RtUoWRAtt52z43r3AoMs2StYKyjS3URPhzHaf+mn10hY9dZWamga5WPQjhA==",
+ "dependencies": {
+ "@babel/runtime": "^7.19.4"
+ }
+ },
"node_modules/iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@@ -12105,6 +12146,27 @@
"react": ">=17.0.0"
}
},
+ "node_modules/react-i18next": {
+ "version": "13.0.2",
+ "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-13.0.2.tgz",
+ "integrity": "sha512-NEVxC32v0oR4egwYM0QM0WE93AiJG5r0NTXTL8mhQfAhsMfDS2fSO6jpluyfsfypP988KzUQrAXncspcJ7+GHA==",
+ "dependencies": {
+ "@babel/runtime": "^7.22.5",
+ "html-parse-stringify": "^3.0.1"
+ },
+ "peerDependencies": {
+ "i18next": ">= 23.2.3",
+ "react": ">= 16.8.0"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ },
+ "react-native": {
+ "optional": true
+ }
+ }
+ },
"node_modules/react-is": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
@@ -14351,6 +14413,14 @@
"resolved": "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz",
"integrity": "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w=="
},
+ "node_modules/void-elements": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
+ "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/walker": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",
diff --git a/package.json b/package.json
index 41c94a0..6b76188 100644
--- a/package.json
+++ b/package.json
@@ -15,7 +15,10 @@
"@react-navigation/native-stack": "^6.9.13",
"expo": "~48.0.18",
"expo-status-bar": "~1.4.4",
+ "i18next": "^23.2.11",
+ "i18next-browser-languagedetector": "^7.1.0",
"react": "18.2.0",
+ "react-i18next": "^13.0.2",
"react-native": "0.71.8",
"react-native-gesture-handler": "~2.9.0",
"react-native-pager-view": "6.1.2",
diff --git a/src/Components/PickerModal/index.js b/src/Components/PickerModal/index.js
new file mode 100644
index 0000000..3c3cac0
--- /dev/null
+++ b/src/Components/PickerModal/index.js
@@ -0,0 +1,90 @@
+import { useContext } from "react";
+import { AppContext, AppStyles } from "../../utils";
+import Icon from "@expo/vector-icons/MaterialCommunityIcons";
+import { Modal, Text, TouchableOpacity, View } from "react-native";
+import { Divider } from "../Divider";
+
+export default function PickerModal({ isOpen, setIsOpen, items }) {
+ const appContext = useContext(AppContext);
+ const closeModal = () => setIsOpen(false);
+
+ return (
+ closeModal()}
+ >
+
+ {
+ console.log("press");
+ setIsOpen(false);
+ }}
+ >
+
+
+
+ {items.map((item, i) => {
+ return item.selected ? (
+
+ closeModal()}>
+
+
+ {item.label}
+
+
+
+
+
+
+ ) : (
+
+ {
+ closeModal();
+ item.onPress();
+ }}
+ >
+
+ {item.label}
+
+
+
+
+ );
+ })}
+
+
+ );
+}
diff --git a/src/Components/SideBar/index.js b/src/Components/SideBar/index.js
index 7ba2cf6..9f6c7de 100644
--- a/src/Components/SideBar/index.js
+++ b/src/Components/SideBar/index.js
@@ -4,10 +4,11 @@ import { Image, Text, TouchableOpacity, View } from "react-native";
import { AppContext, AppStyles } from "../../utils";
import { Divider } from "../Divider";
import Icon from "@expo/vector-icons/MaterialCommunityIcons";
-import { useIsFocused } from "@react-navigation/native";
+import { useTranslation } from "react-i18next";
export default function Sidebar(props) {
const appContext = useContext(AppContext);
+ const { t } = useTranslation();
const MyDrawerItem = ({
label,
@@ -53,7 +54,7 @@ export default function Sidebar(props) {
console.log("Pressed")}
+ onPress={() => console.log("Pressed power")}
style={{ right: -30 }}
>
- Geräte
+ {t("sideBar.devicesTitle")}
{["Turtle"].map((item, i) => (
@@ -137,19 +138,19 @@ export default function Sidebar(props) {
props.navigation.navigate("FAQ")}
iconName="frequently-asked-questions"
routeName="FAQ"
/>
props.navigation.navigate("Feedback")}
iconName="comment-quote"
routeName="Feedback"
/>
props.navigation.navigate("Settings")}
iconName="cog"
routeName="Settings"
diff --git a/src/Screens/Device/settings.js b/src/Screens/Device/settings.js
index f061328..0d51d30 100644
--- a/src/Screens/Device/settings.js
+++ b/src/Screens/Device/settings.js
@@ -1,11 +1,15 @@
import { Text, View } from "react-native";
import Card from "../../Components/Card";
import { Incubator, Switch } from "react-native-ui-lib";
-import { useState } from "react";
-import { AppStyles } from "../../utils";
+import { useContext, useState } from "react";
+import { AppContext, AppStyles } from "../../utils";
import { Divider } from "../../Components/Divider";
+import { useTranslation } from "react-i18next";
export default function SettingsView() {
+ const appContext = useContext(AppContext);
+ const { t } = useTranslation();
+
const [switchState, setSwitchState] = useState(false);
const [sliderValue, setSliderValue] = useState(0);
@@ -16,12 +20,12 @@ export default function SettingsView() {
style={[
AppStyles.typography20,
{
- color: "#fff",
+ color: appContext.appTheme.text,
marginBottom: 10,
},
]}
>
- Einstellungen
+ {t("screens.device.settings.settingsTitle")}
- WLAN im Standby
+ {t("screens.device.settings.wifiStandByTitle")}
- Die WLAN-Verbindung bleibt bestehen, auch wenn das Gerät
- ausgeschaltet ist. Bitte beachten Sie, dass dies zu einem erhöhten
- Stromverbrauch führen kann.
+ {t("screens.device.settings.wifiStandByDescription")}
- Geräteinformationen
+ {t("screens.device.settings.deviceInformationTitle")}
- Gerätemodell
+ {t("screens.device.settings.deviceModelTitle")}
Shimmex Aurora
- Firmware Version
+ {t("screens.device.settings.deviceFirmwareVersionTitle")}
1.0.1
- Letzte Aktualisierung
+ {t("screens.device.settings.deviceLastUpdated")}
11.07.2023 um 20:33 Uhr
diff --git a/src/Screens/Settings/index.js b/src/Screens/Settings/index.js
index 19cd112..df65501 100644
--- a/src/Screens/Settings/index.js
+++ b/src/Screens/Settings/index.js
@@ -2,13 +2,17 @@ import { useContext, useState } from "react";
import { Modal, Text, TouchableOpacity, View } from "react-native";
import { Picker, Switch } from "react-native-ui-lib";
import Card from "../../Components/Card";
-import { AppContext, AppStyles } from "../../utils";
+import { AppContext, AppStyles, Constants } from "../../utils";
import { Divider } from "../../Components/Divider";
-import Icon from "@expo/vector-icons/MaterialCommunityIcons";
+import PickerModal from "../../Components/PickerModal";
+import { useTranslation } from "react-i18next";
export default function SettingsScreen() {
const appContext = useContext(AppContext);
- const [modalVisible, setModalVisible] = useState(false);
+ const { t } = useTranslation();
+ const [modalAppColorSchemeVisible, setAppColorSchemeModalVisible] =
+ useState(false);
+ const [modalAppLanguageVisible, setModalAppLanguageVisible] = useState(false);
return (
- Einstellungen
+ {t("screens.settings.settingsCardTitle")}
- appContext.setAppLanguage(v)}
- fieldType={Picker.fieldTypes.settings}
- showSearch
- searchPlaceholder="Search a language"
- searchStyle={{ color: "#fff", placeholderTextColor: "#fff" }}
+ setModalAppLanguageVisible(true)}
+ style={{ marginBottom: 6 }}
>
-
-
-
-
+
+
+ {t("screens.settings.languageText")}
+
+
+ {
+ Constants.languages.find(
+ (language) => language.name === appContext.appLanguage
+ ).label
+ }
+
+
+
+
+ setAppColorSchemeModalVisible(true)}>
+
+
+ {t("screens.settings.appColorSchemeText")}
+
+
+ {appContext.appColorScheme === "auto"
+ ? t("screens.settings.appColorSchemePicker.auto")
+ : appContext.appColorScheme === "dark"
+ ? t("screens.settings.appColorSchemePicker.dark")
+ : t("screens.settings.appColorSchemePicker.light")}
+
+
+
- appContext.setAppColorScheme(v)}
- fieldType={Picker.fieldTypes.settings}
- >
-
-
-
-
- Experten Modus
+ {t("screens.settings.expertModeTitle")}
- Durch das Einschalten werden zusätzliche Funktionen in der App
- freigeschaltet, wie beispielsweise die Möglichkeit,
- benutzerdefinierte Farbcodes anzugeben.
+ {t("screens.settings.expertModeDescription")}
- setModalVisible(true)}>
-
-
- Anzeigemodus
-
-
- {appContext.appColorScheme}
-
-
-
+ appContext.setAppColorScheme("auto"),
+ },
+ {
+ label: t("screens.settings.appColorSchemePicker.dark"),
+ onPress: () => appContext.setAppColorScheme("dark"),
+ selected: appContext.appColorScheme === "dark",
+ },
+ {
+ label: t("screens.settings.appColorSchemePicker.light"),
+ onPress: () => appContext.setAppColorScheme("light"),
+ selected: appContext.appColorScheme === "light",
+ },
+ ]}
+ />
- {
- setModalVisible(!modalVisible);
- }}
- >
- setModalVisible(false)}>
-
-
-
-
- System default
-
-
-
-
- {appContext.appColorScheme === "dark" ? (
- setModalVisible(false)}>
-
- Dark
-
-
-
- ) : (
- {
- setModalVisible(false);
- appContext.setAppColorScheme("dark");
- }}
- >
-
- Dark
-
-
- )}
-
-
-
- {appContext.appColorScheme === "light" ? (
- setModalVisible(false)}>
-
- Light
-
-
-
- ) : (
- {
- setModalVisible(false);
- appContext.setAppColorScheme("light");
- }}
- >
-
- Light
-
-
- )}
-
+ {
+ return {
+ label: language.label,
+ onPress: () => appContext.setAppLanguage(language.name),
+ selected: appContext.appLanguage === language.name,
+ };
+ })}
+ />
);
diff --git a/src/utils.js b/src/utils.js
index 3016ac2..1a93f5e 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -1,10 +1,20 @@
import AsyncStorage from "@react-native-async-storage/async-storage";
import { createContext, useState } from "react";
-import { StyleSheet } from "react-native";
-import { Colors } from "react-native-ui-lib";
+import { useTranslation } from "react-i18next";
+import { Appearance, StyleSheet } from "react-native";
export const Constants = {
defaultLanguage: "de",
+ languages: [
+ {
+ name: "de",
+ label: "Deutsch",
+ },
+ {
+ name: "en",
+ label: "English",
+ },
+ ],
};
export const AppStyles = StyleSheet.create({
@@ -45,7 +55,7 @@ const DarkAppTheme = {
},
},
divider: "#ddd",
- icon: "#fff",
+ icon: "#ddd",
};
const LightAppTheme = {
@@ -122,23 +132,23 @@ export function AppProvider({ children }) {
// TODO: only while development
const [isUserDeveloperModeEnabled, setIsUserDeveloperModeEnabled] =
useState(false);
+ const { i18n } = useTranslation();
const saveAppColorScheme = async (value) => {
StoreData("appColorScheme", value);
setAppColorScheme(value);
- if (value === "dark") {
- setAppTheme(DarkAppTheme);
- console.log("dark");
- } else {
- setAppTheme(LightAppTheme);
- console.log("light");
- }
+ let colorScheme;
+
+ colorScheme = value === "auto" ? Appearance.getColorScheme() : value;
+
+ setAppTheme(colorScheme === "light" ? LightAppTheme : DarkAppTheme);
};
const saveAppLanguage = async (value) => {
StoreData("appLanguage", value);
setAppLanguage(value);
+ i18n.changeLanguage(value);
};
const saveUserExpertMode = async (value) => {