Chore: Migrate i18n to Typescript (#3988)
* Chore: Migrate i18n to Typescript and fix the Left and Right actions in RoomItem * remove fix to roomItem * update storyshot * Chore: Migrate i18n to Typescript and fix the Left and Right actions in RoomItem * remove fix to roomItem * update storyshot * fix removed itens * fix changes requested * interface for i18n, added resolveJsonModule to tsconfig.json * tweak at error alert * fix storyshot * refactor comments * create function isTranslated * fix accessibilityLabel Co-authored-by: Diego Mello <diegolmello@gmail.com>
This commit is contained in:
parent
397cd3d9b8
commit
25c37c1a60
|
@ -91,7 +91,7 @@ const Content = React.memo(
|
||||||
<View style={styles.textContainer}>
|
<View style={styles.textContainer}>
|
||||||
<View style={styles.textAlertContainer}>
|
<View style={styles.textAlertContainer}>
|
||||||
<Text style={[styles.title, { color: color || themes[theme].titleText }]} numberOfLines={1}>
|
<Text style={[styles.title, { color: color || themes[theme].titleText }]} numberOfLines={1}>
|
||||||
{translateTitle ? I18n.t(title) : title}
|
{translateTitle && title ? I18n.t(title) : title}
|
||||||
</Text>
|
</Text>
|
||||||
{alert ? (
|
{alert ? (
|
||||||
<CustomIcon style={[styles.alertIcon, { color: themes[theme].dangerColor }]} size={ICON_SIZE} name='info' />
|
<CustomIcon style={[styles.alertIcon, { color: themes[theme].dangerColor }]} size={ICON_SIZE} name='info' />
|
||||||
|
|
|
@ -20,7 +20,10 @@ const BaseButton = ({ accessibilityLabel, icon, color, ...props }: Partial<IBase
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
return (
|
return (
|
||||||
<BorderlessButton {...props} style={styles.actionButton}>
|
<BorderlessButton {...props} style={styles.actionButton}>
|
||||||
<View accessible accessibilityLabel={i18n.t(accessibilityLabel)} accessibilityRole='button'>
|
<View
|
||||||
|
accessible
|
||||||
|
accessibilityLabel={accessibilityLabel ? i18n.t(accessibilityLabel) : accessibilityLabel}
|
||||||
|
accessibilityRole='button'>
|
||||||
<CustomIcon name={icon} size={24} color={color || themes[theme].auxiliaryTintColor} />
|
<CustomIcon name={icon} size={24} color={color || themes[theme].auxiliaryTintColor} />
|
||||||
</View>
|
</View>
|
||||||
</BorderlessButton>
|
</BorderlessButton>
|
||||||
|
|
|
@ -680,7 +680,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
Alert.alert(I18n.t('Error_uploading'), I18n.t(result.error));
|
Alert.alert(I18n.t('Error_uploading'), result.error && I18n.isTranslated(result.error) ? I18n.t(result.error) : result.error);
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ interface IPasscodeBase {
|
||||||
type: string;
|
type: string;
|
||||||
previousPasscode?: string;
|
previousPasscode?: string;
|
||||||
title: string;
|
title: string;
|
||||||
subtitle?: string;
|
subtitle?: string | null;
|
||||||
showBiometry?: boolean;
|
showBiometry?: boolean;
|
||||||
onEndProcess: Function;
|
onEndProcess: Function;
|
||||||
onError?: Function;
|
onError?: Function;
|
||||||
|
|
|
@ -14,7 +14,7 @@ interface IPasscodeChoose {
|
||||||
const PasscodeChoose = ({ finishProcess, force = false }: IPasscodeChoose) => {
|
const PasscodeChoose = ({ finishProcess, force = false }: IPasscodeChoose) => {
|
||||||
const chooseRef = useRef<IBase>(null);
|
const chooseRef = useRef<IBase>(null);
|
||||||
const confirmRef = useRef<IBase>(null);
|
const confirmRef = useRef<IBase>(null);
|
||||||
const [subtitle, setSubtitle] = useState(null);
|
const [subtitle, setSubtitle] = useState<string | null>(null);
|
||||||
const [status, setStatus] = useState(TYPE.CHOOSE);
|
const [status, setStatus] = useState(TYPE.CHOOSE);
|
||||||
const [previousPasscode, setPreviouPasscode] = useState('');
|
const [previousPasscode, setPreviouPasscode] = useState('');
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@ import { themes } from '../../lib/constants';
|
||||||
export { default as MarkdownPreview } from './Preview';
|
export { default as MarkdownPreview } from './Preview';
|
||||||
|
|
||||||
interface IMarkdownProps {
|
interface IMarkdownProps {
|
||||||
msg?: string;
|
msg?: string | null;
|
||||||
theme: TSupportedThemes;
|
theme: TSupportedThemes;
|
||||||
md?: MarkdownAST;
|
md?: MarkdownAST;
|
||||||
mentions?: IUserMention[];
|
mentions?: IUserMention[];
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { TMessageModel } from '../../definitions/IMessage';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import { DISCUSSION } from './constants';
|
import { DISCUSSION } from './constants';
|
||||||
|
|
||||||
export const formatMessageCount = (count?: number, type?: string): string => {
|
export const formatMessageCount = (count?: number, type?: string): string | null => {
|
||||||
const discussion = type === DISCUSSION;
|
const discussion = type === DISCUSSION;
|
||||||
let text = discussion ? I18n.t('No_messages_yet') : null;
|
let text = discussion ? I18n.t('No_messages_yet') : null;
|
||||||
if (!count) {
|
if (!count) {
|
||||||
|
|
|
@ -6,10 +6,19 @@ import 'moment/min/locales';
|
||||||
|
|
||||||
import { toMomentLocale } from '../utils/moment';
|
import { toMomentLocale } from '../utils/moment';
|
||||||
import { isRTL } from './isRTL';
|
import { isRTL } from './isRTL';
|
||||||
|
import englishJson from './locales/en.json';
|
||||||
|
|
||||||
|
type TTranslatedKeys = keyof typeof englishJson;
|
||||||
|
|
||||||
export { isRTL };
|
export { isRTL };
|
||||||
|
|
||||||
export const LANGUAGES = [
|
interface ILanguage {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
file: () => any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LANGUAGES: ILanguage[] = [
|
||||||
{
|
{
|
||||||
label: 'English',
|
label: 'English',
|
||||||
value: 'en',
|
value: 'en',
|
||||||
|
@ -82,12 +91,16 @@ export const LANGUAGES = [
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
interface ITranslations {
|
||||||
|
[language: string]: () => typeof englishJson;
|
||||||
|
}
|
||||||
|
|
||||||
const translations = LANGUAGES.reduce((ret, item) => {
|
const translations = LANGUAGES.reduce((ret, item) => {
|
||||||
ret[item.value] = item.file;
|
ret[item.value] = item.file;
|
||||||
return ret;
|
return ret;
|
||||||
}, {});
|
}, {} as ITranslations);
|
||||||
|
|
||||||
export const setLanguage = l => {
|
export const setLanguage = (l: string) => {
|
||||||
if (!l) {
|
if (!l) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -104,6 +117,8 @@ export const setLanguage = l => {
|
||||||
i18n.translations = { ...i18n.translations, [locale]: translations[locale]?.() };
|
i18n.translations = { ...i18n.translations, [locale]: translations[locale]?.() };
|
||||||
I18nManager.forceRTL(isRTL(locale));
|
I18nManager.forceRTL(isRTL(locale));
|
||||||
I18nManager.swapLeftAndRightInRTL(isRTL(locale));
|
I18nManager.swapLeftAndRightInRTL(isRTL(locale));
|
||||||
|
// TODO: Review this logic
|
||||||
|
// @ts-ignore
|
||||||
i18n.isRTL = I18nManager.isRTL;
|
i18n.isRTL = I18nManager.isRTL;
|
||||||
moment.locale(toMomentLocale(locale));
|
moment.locale(toMomentLocale(locale));
|
||||||
};
|
};
|
||||||
|
@ -113,7 +128,16 @@ const defaultLanguage = { languageTag: 'en', isRTL: false };
|
||||||
const availableLanguages = Object.keys(translations);
|
const availableLanguages = Object.keys(translations);
|
||||||
const { languageTag } = RNLocalize.findBestAvailableLanguage(availableLanguages) || defaultLanguage;
|
const { languageTag } = RNLocalize.findBestAvailableLanguage(availableLanguages) || defaultLanguage;
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
i18n.isTranslated = (text?: string) => text in englishJson;
|
||||||
|
|
||||||
setLanguage(languageTag);
|
setLanguage(languageTag);
|
||||||
i18n.fallbacks = true;
|
i18n.fallbacks = true;
|
||||||
|
|
||||||
export default i18n;
|
type Ti18n = {
|
||||||
|
isRTL: boolean;
|
||||||
|
t(scope: TTranslatedKeys, options?: any): string;
|
||||||
|
isTranslated: (text?: string) => boolean;
|
||||||
|
} & typeof i18n;
|
||||||
|
|
||||||
|
export default i18n as Ti18n;
|
|
@ -1,4 +1,4 @@
|
||||||
// https://github.com/zoontek/react-native-localize/blob/master/src/constants.ts#L5
|
// https://github.com/zoontek/react-native-localize/blob/master/src/constants.ts#L5
|
||||||
const USES_RTL_LAYOUT = ['ar', 'ckb', 'fa', 'he', 'ks', 'lrc', 'mzn', 'ps', 'ug', 'ur', 'yi'];
|
const USES_RTL_LAYOUT = ['ar', 'ckb', 'fa', 'he', 'ks', 'lrc', 'mzn', 'ps', 'ug', 'ur', 'yi'];
|
||||||
|
|
||||||
export const isRTL = locale => USES_RTL_LAYOUT.includes(locale);
|
export const isRTL = (locale?: string) => (locale ? USES_RTL_LAYOUT.includes(locale) : false);
|
|
@ -16,7 +16,7 @@ import protectedFunction from '../../lib/methods/helpers/protectedFunction';
|
||||||
import SafeAreaView from '../../containers/SafeAreaView';
|
import SafeAreaView from '../../containers/SafeAreaView';
|
||||||
import log, { events, logEvent } from '../../utils/log';
|
import log, { events, logEvent } from '../../utils/log';
|
||||||
import sharedStyles from '../Styles';
|
import sharedStyles from '../Styles';
|
||||||
import { OPTIONS } from './options';
|
import { IOptionsField, OPTIONS } from './options';
|
||||||
import { ChatsStackParamList } from '../../stacks/types';
|
import { ChatsStackParamList } from '../../stacks/types';
|
||||||
import { IRoomNotifications } from '../../definitions';
|
import { IRoomNotifications } from '../../definitions';
|
||||||
|
|
||||||
|
@ -131,10 +131,10 @@ class NotificationPreferencesView extends React.Component<INotificationPreferenc
|
||||||
renderPickerOption = (key: string) => {
|
renderPickerOption = (key: string) => {
|
||||||
const { room } = this.state;
|
const { room } = this.state;
|
||||||
const { theme } = this.props;
|
const { theme } = this.props;
|
||||||
const text = room[key] ? OPTIONS[key].find((option: any) => option.value === room[key]) : OPTIONS[key][0];
|
const text = room[key] ? OPTIONS[key].find(option => option.value === room[key]) : (OPTIONS[key][0] as IOptionsField);
|
||||||
return (
|
return (
|
||||||
<Text style={[styles.pickerText, { color: themes[theme].actionTintColor }]}>
|
<Text style={[styles.pickerText, { color: themes[theme].actionTintColor }]}>
|
||||||
{I18n.t(text?.label, { defaultValue: text?.label, second: text?.second })}
|
{text?.label ? I18n.t(text?.label, { defaultValue: text?.label, second: text?.second }) : text?.label}
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -25,7 +25,7 @@ const Channel = ({ room }: { room: ISubscription }) => {
|
||||||
/>
|
/>
|
||||||
<Item
|
<Item
|
||||||
label={I18n.t('Broadcast_Channel')}
|
label={I18n.t('Broadcast_Channel')}
|
||||||
content={room.broadcast && I18n.t('Broadcast_channel_Description')}
|
content={room.broadcast ? I18n.t('Broadcast_channel_Description') : ''}
|
||||||
testID='room-info-view-broadcast'
|
testID='room-info-view-broadcast'
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -72,7 +72,11 @@ class UserNotificationPreferencesView extends React.Component<
|
||||||
renderPickerOption = (key: TKey) => {
|
renderPickerOption = (key: TKey) => {
|
||||||
const { theme } = this.props;
|
const { theme } = this.props;
|
||||||
const text = this.findDefaultOption(key);
|
const text = this.findDefaultOption(key);
|
||||||
return <Text style={[styles.pickerText, { color: themes[theme].actionTintColor }]}>{I18n.t(text?.label)}</Text>;
|
return (
|
||||||
|
<Text style={[styles.pickerText, { color: themes[theme].actionTintColor }]}>
|
||||||
|
{text?.label ? I18n.t(text?.label) : text?.label}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
pickerSelection = (title: string, key: TKey) => {
|
pickerSelection = (title: string, key: TKey) => {
|
||||||
|
@ -82,7 +86,9 @@ class UserNotificationPreferencesView extends React.Component<
|
||||||
|
|
||||||
const defaultOption = this.findDefaultOption(key);
|
const defaultOption = this.findDefaultOption(key);
|
||||||
if (OPTIONS[key][0]?.value !== 'default') {
|
if (OPTIONS[key][0]?.value !== 'default') {
|
||||||
const defaultValue = { label: `${I18n.t('Default')} (${I18n.t(defaultOption?.label)})` } as {
|
const defaultValue = {
|
||||||
|
label: `${I18n.t('Default')} (${defaultOption?.label ? I18n.t(defaultOption?.label) : defaultOption?.label})`
|
||||||
|
} as {
|
||||||
label: string;
|
label: string;
|
||||||
value: string;
|
value: string;
|
||||||
};
|
};
|
||||||
|
|
|
@ -148,6 +148,7 @@
|
||||||
"@testing-library/react-native": "^9.0.0",
|
"@testing-library/react-native": "^9.0.0",
|
||||||
"@types/bytebuffer": "^5.0.43",
|
"@types/bytebuffer": "^5.0.43",
|
||||||
"@types/ejson": "^2.1.3",
|
"@types/ejson": "^2.1.3",
|
||||||
|
"@types/i18n-js": "^3.8.2",
|
||||||
"@types/jest": "^26.0.24",
|
"@types/jest": "^26.0.24",
|
||||||
"@types/lodash": "^4.14.171",
|
"@types/lodash": "^4.14.171",
|
||||||
"@types/react": "^17.0.14",
|
"@types/react": "^17.0.14",
|
||||||
|
|
|
@ -67,7 +67,8 @@
|
||||||
|
|
||||||
/* Advanced Options */
|
/* Advanced Options */
|
||||||
"skipLibCheck": true /* Skip type checking of declaration files. */,
|
"skipLibCheck": true /* Skip type checking of declaration files. */,
|
||||||
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
|
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */,
|
||||||
|
"resolveJsonModule": true
|
||||||
},
|
},
|
||||||
"exclude": ["node_modules", "e2e/docker", "__mocks__"]
|
"exclude": ["node_modules", "e2e/docker", "__mocks__"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -4514,6 +4514,11 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-5.1.0.tgz#551a4589b6ee2cc9c1dff08056128aec29b94880"
|
resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-5.1.0.tgz#551a4589b6ee2cc9c1dff08056128aec29b94880"
|
||||||
integrity sha512-iYCgjm1dGPRuo12+BStjd1HiVQqhlRhWDOQigNxn023HcjnhsiFz9pc6CzJj4HwDCSQca9bxTL4PxJDbkdm3PA==
|
integrity sha512-iYCgjm1dGPRuo12+BStjd1HiVQqhlRhWDOQigNxn023HcjnhsiFz9pc6CzJj4HwDCSQca9bxTL4PxJDbkdm3PA==
|
||||||
|
|
||||||
|
"@types/i18n-js@^3.8.2":
|
||||||
|
version "3.8.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/i18n-js/-/i18n-js-3.8.2.tgz#957a3fa268124d09e3b3b34695f0184118f4bc4f"
|
||||||
|
integrity sha512-F+AuFCjllE1A0W/YUxJB13q2t7cWITMqXOTXQ/InfXxxT8nXrrqL7s/8Pv6XThGjFPemukElwk6QlMOKCEg7eQ==
|
||||||
|
|
||||||
"@types/is-function@^1.0.0":
|
"@types/is-function@^1.0.0":
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/is-function/-/is-function-1.0.0.tgz#1b0b819b1636c7baf0d6785d030d12edf70c3e83"
|
resolved "https://registry.yarnpkg.com/@types/is-function/-/is-function-1.0.0.tgz#1b0b819b1636c7baf0d6785d030d12edf70c3e83"
|
||||||
|
|
Loading…
Reference in New Issue