Skip to content

Commit

Permalink
feat: add basic i18n support
Browse files Browse the repository at this point in the history
fix #7
  • Loading branch information
Rexogamer committed Nov 9, 2023
1 parent d096edf commit 3dc5c30
Show file tree
Hide file tree
Showing 12 changed files with 170 additions and 12 deletions.
6 changes: 5 additions & 1 deletion App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
Dimensions,
StatusBarStyle,
} from 'react-native';
import {withTranslation} from 'react-i18next';
import {ErrorBoundary} from 'react-error-boundary';
import SideMenuBase from '@chakrahq/react-native-side-menu';
import AsyncStorage from '@react-native-async-storage/async-storage';
Expand Down Expand Up @@ -351,6 +352,7 @@ class MainView extends React.Component {
}

render() {
const {t} = this.props;
return (
<>
{this.state.status === 'loggedIn' ? (
Expand Down Expand Up @@ -692,6 +694,8 @@ class MainView extends React.Component {
}
}

const MainViewi18n = withTranslation()(MainView);

function ErrorMessage({
error,
resetErrorBoundary,
Expand Down Expand Up @@ -738,7 +742,7 @@ export const App = () => {
barStyle={(currentTheme.contentType + '-content') as StatusBarStyle}
/>
<ErrorBoundary fallbackRender={ErrorMessage}>
<MainView />
<MainViewi18n />
</ErrorBoundary>
</GestureHandlerRootView>
);
Expand Down
30 changes: 30 additions & 0 deletions i18n/getLanguage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import AsyncStorage from '@react-native-async-storage/async-storage';
import type {ModuleType} from 'i18next';

const STORE_LANGUAGE_KEY = 'app.language';

export const languageDetectorPlugin = {
type: 'languageDetector' as ModuleType,
async: true,
init: () => {},
detect: async function (callback: (lang: string) => void) {
try {
await AsyncStorage.getItem(STORE_LANGUAGE_KEY).then(language => {
if (language) {
return callback(language);
} else {
// TODO: use the device's locale
return 'en';
}
});
} catch (error) {
console.warn(`[APP] Error reading language: ${error}`);
}
},
cacheUserLanguage: async function (language: string) {
try {
//save a user's language choice in Async storage
await AsyncStorage.setItem(STORE_LANGUAGE_KEY, language);
} catch (error) {}
},
};
29 changes: 29 additions & 0 deletions i18n/i18n.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import i18n from 'i18next';
import {initReactI18next} from 'react-i18next';
import 'intl-pluralrules';

import {resources} from './languages';
import {languageDetectorPlugin} from './getLanguage';

i18n
.use(initReactI18next) // passes i18n down to react-i18next
.use(languageDetectorPlugin) // get the user's language if it's stored
.init({
fallbackLng: 'en',

resources: resources,

interpolation: {
escapeValue: false,
},

react: {
useSuspense: false,
},
});

export default i18n;

export function setLanguage(l: string) {
i18n.changeLanguage(l);
}
16 changes: 16 additions & 0 deletions i18n/languages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import {Language} from '../src/lib/types';

// string files
import {default as en} from './strings/en.json';

// resources object passed to i18next
export const resources = {
en: {translation: en},
};

// languages object, used for settings
export const languages = {
en: {name: 'English (Traditional)', emoji: '🇬🇧'} as Language,
de: {name: 'Deutsch (Deutschland)', emoji: '🇩🇪'} as Language,
it: {name: 'Italiano', emoji: '🇮🇹'} as Language,
};
12 changes: 12 additions & 0 deletions i18n/strings/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"app": {
"home": {
"description": "Swipe from the left of the screen or tap the three lines icon to see your servers and messages!",
"join_lounge": "Join the Revolt Lounge",
"open_lounge": "Open the Revolt Lounge",
"join_rvmob": "Join the RVMob server",
"open_rvmob": "Open the RVMob server",
"open_settings": "Open settings"
}
}
}
3 changes: 3 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
*/

import {AppRegistry} from 'react-native';

import {App} from './App';
import './i18n/i18n';

import {name as appName} from './app.json';

AppRegistry.registerComponent(appName, () => App);
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
"domain-browser": "^4.22.0",
"events": "^3.3.0",
"https-browserify": "^1.0.0",
"i18next": "^23.6.0",
"intl-pluralrules": "^2.0.1",
"markdown-it-regexp": "^0.4.0",
"mobx": "^6.10.2",
"mobx-react-lite": "^4.0.5",
Expand All @@ -43,6 +45,7 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-error-boundary": "^4.0.11",
"react-i18next": "^13.3.1",
"react-native": "^0.72.6",
"react-native-crypto": "^2.2.0",
"react-native-device-info": "^10.11.0",
Expand Down
16 changes: 16 additions & 0 deletions src/Generic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import MaterialCommunityIcon from 'react-native-vector-icons/MaterialCommunityIc

import {API, Channel, Client, Message, Server} from 'revolt.js';

import {setLanguage} from '../i18n/i18n';
import {languages} from '../i18n/languages';
import {currentTheme, setTheme, themes} from './Theme';
import {
DEFAULT_API_URL,
Expand Down Expand Up @@ -115,6 +117,20 @@ export const app = {
}
},
list: [
{
key: 'app.language',
name: 'Language',
category: 'functionality',
default: 'en',
type: 'string',
options: Object.keys(languages),
onChange: (v: string) => {
setLanguage(v);
},
onInitialize: (v: string) => {
setLanguage(v);
},
},
{
key: 'ui.theme',
name: 'Theme',
Expand Down
8 changes: 7 additions & 1 deletion src/components/common/settings/atoms/StringNumberSetting.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {TextInput, TouchableOpacity, View} from 'react-native';

import MaterialIcon from 'react-native-vector-icons/MaterialIcons';

import {languages} from '../../../../../i18n/languages';
import {app} from '../../../../Generic';
import {currentTheme, styles} from '../../../../Theme';
import {Setting} from '../../../../lib/types';
Expand Down Expand Up @@ -58,7 +59,12 @@ export const StringNumberSetting = ({
rerender(renderCount + 1);
}
}}>
<Text style={{flex: 1}}>{o}</Text>
<Text style={{flex: 1}}>
{sRaw.key === 'app.language'
? // @ts-expect-error this will always exist
`${languages[o].emoji} ${languages[o].name}`
: o}
</Text>
<View style={{...styles.iconContainer, marginRight: 0}}>
<MaterialIcon
name={`radio-button-${value === o ? 'on' : 'off'}`}
Expand Down
20 changes: 11 additions & 9 deletions src/components/pages/HomePage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react';
import {useTranslation} from 'react-i18next';
import {TouchableOpacity, View} from 'react-native';
import {observer} from 'mobx-react-lite';

Expand All @@ -14,6 +15,8 @@ import {ChannelHeader} from '../navigation/ChannelHeader';
import {Button, Text, Username} from '../common/atoms';

export const HomePage = observer(() => {
const {t} = useTranslation();

// holiday emoji
const rawDate = new Date();
const rawMonth = rawDate.getMonth() + 1;
Expand Down Expand Up @@ -90,16 +93,16 @@ export const HomePage = observer(() => {
<Text
key={'no-channel-selected'}
style={{textAlign: 'center', marginBottom: 10}}>
Swipe from the left of the screen or press the three lines icon to see
your servers and messages!
{t('app.home.description')}
</Text>
<Button
style={{width: '65%'}}
key={'home-revolt-lounge'}
onPress={() => app.openInvite(SPECIAL_SERVERS.lounge.invite)}>
<Text style={styles.buttonText}>
{client.servers.get(SPECIAL_SERVERS.lounge.id) ? 'Open' : 'Join'}{' '}
the Revolt Lounge
{client.servers.get(SPECIAL_SERVERS.lounge.id)
? t('app.home.open_lounge')
: t('app.home.join_lounge')}
</Text>
</Button>
<Button
Expand All @@ -108,16 +111,15 @@ export const HomePage = observer(() => {
onPress={() => app.openInvite(SPECIAL_SERVERS.supportServer.invite)}>
<Text style={styles.buttonText}>
{client.servers.get(SPECIAL_SERVERS.supportServer.id)
? 'Open'
: 'Join'}{' '}
the RVMob server
? t('app.home.open_rvmob')
: t('app.home.join_rvmob')}
</Text>
</Button>
<Button
style={{width: '30%'}}
style={{width: '65%'}}
key={'home-settings-button'}
onPress={() => app.openSettings(true)}>
<Text style={styles.buttonText}>Settings</Text>
<Text style={styles.buttonText}>{t('app.home.open_settings')}</Text>
</Button>
{app.settings.get('ui.home.holidays') ? holidayEmoji : null}
</View>
Expand Down
5 changes: 5 additions & 0 deletions src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,8 @@ interface TypedUser {
export type ReportedObject = TypedMessage | TypedServer | TypedUser;

export type DeletableObject = TypedMessage | TypedServer;

export type Language = {
name: string;
emoji: string;
};
34 changes: 33 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1130,7 +1130,7 @@
resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310"
integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==

"@babel/runtime@^7.0.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.21.0", "@babel/runtime@^7.23.2", "@babel/runtime@^7.8.4":
"@babel/runtime@^7.0.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.21.0", "@babel/runtime@^7.22.5", "@babel/runtime@^7.23.2", "@babel/runtime@^7.8.4":
version "7.23.2"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.2.tgz#062b0ac103261d68a966c4c7baf2ae3e62ec3885"
integrity sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==
Expand Down Expand Up @@ -5226,6 +5226,13 @@ html-escaper@^2.0.0:
resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453"
integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==

html-parse-stringify@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz#dfc1017347ce9f77c8141a507f233040c59c55d2"
integrity sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==
dependencies:
void-elements "3.1.0"

http-cache-semantics@^4.0.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a"
Expand Down Expand Up @@ -5260,6 +5267,13 @@ human-signals@^2.1.0:
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==

i18next@^23.6.0:
version "23.6.0"
resolved "https://registry.yarnpkg.com/i18next/-/i18next-23.6.0.tgz#c6e996cfd3fef0bf60be3b7c581c35338dba5a71"
integrity sha512-z0Cxr0MGkt+kli306WS4nNNM++9cgt2b2VCMprY92j+AIab/oclgPxdwtTZVLP1zn5t5uo8M6uLsZmYrcjr3HA==
dependencies:
"@babel/runtime" "^7.22.5"

ieee754@^1.1.13, ieee754@^1.1.4, ieee754@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
Expand Down Expand Up @@ -5343,6 +5357,11 @@ internal-slot@^1.0.5:
hasown "^2.0.0"
side-channel "^1.0.4"

intl-pluralrules@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/intl-pluralrules/-/intl-pluralrules-2.0.1.tgz#de16c3df1e09437635829725e88ea70c9ad79569"
integrity sha512-astxTLzIdXPeN0K9Rumi6LfMpm3rvNO0iJE+h/k8Kr/is+wPbRe4ikyDjlLr6VTh/mEfNv8RjN+gu3KwDiuhqg==

invariant@*, invariant@^2.2.4:
version "2.2.4"
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
Expand Down Expand Up @@ -8394,6 +8413,14 @@ react-error-boundary@^4.0.11:
dependencies:
"@babel/runtime" "^7.12.5"

react-i18next@^13.3.1:
version "13.3.1"
resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-13.3.1.tgz#9b072bf4dd4cafb028e92315a8a1415f8034bdca"
integrity sha512-JAtYREK879JXaN9GdzfBI4yJeo/XyLeXWUsRABvYXiFUakhZJ40l+kaTo+i+A/3cKIED41kS/HAbZ5BzFtq/Og==
dependencies:
"@babel/runtime" "^7.22.5"
html-parse-stringify "^3.0.1"

"react-is@^16.12.0 || ^17.0.0 || ^18.0.0", react-is@^18.0.0, react-is@^18.2.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b"
Expand Down Expand Up @@ -10093,6 +10120,11 @@ vm-browserify@^1.1.2:
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==

[email protected]:
version "3.1.0"
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09"
integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==

walker@^1.0.7, walker@^1.0.8:
version "1.0.8"
resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f"
Expand Down

0 comments on commit 3dc5c30

Please sign in to comment.