From 4076ac31ce21a88a5aab387cca043a1914e842d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=85=B8=EC=A3=BC=EC=B0=AC?= Date: Wed, 29 Mar 2023 23:33:45 +0900 Subject: [PATCH] feat: support i18n (ko, en) --- App.tsx | 12 +++++++--- src/components/Card.tsx | 7 ++++-- src/locales/en.json | 41 ++++++++++++++++++++++++++++++++++ src/locales/i18n.ts | 29 ++++++++++++++++++++++++ src/locales/i18next.d.ts | 17 ++++++++++++++ src/locales/ko.json | 41 ++++++++++++++++++++++++++++++++++ src/screens/CardEditScreen.tsx | 22 +++++++++++------- src/screens/MainScreen.tsx | 29 +++++++++++++++--------- 8 files changed, 174 insertions(+), 24 deletions(-) create mode 100644 src/locales/en.json create mode 100644 src/locales/i18n.ts create mode 100644 src/locales/i18next.d.ts create mode 100644 src/locales/ko.json diff --git a/App.tsx b/App.tsx index 6da9e98..ba39314 100644 --- a/App.tsx +++ b/App.tsx @@ -13,6 +13,9 @@ import { ThemeProvider } from 'styled-components/native'; import LightTheme from './src/themes/lightTheme'; import DarkTheme from './src/themes/darkTheme'; +import './src/locales/i18n'; +import { useTranslation } from 'react-i18next'; + export type RootStackParams = { Main: undefined; Add: undefined; @@ -23,6 +26,7 @@ const queryClient = new QueryClient(); const Stack = createNativeStackNavigator(); const App = () => { + const { t } = useTranslation(); const colorScheme = useColorScheme(); const theme = useMemo( () => (colorScheme === 'dark' ? DarkTheme : LightTheme), @@ -54,18 +58,20 @@ const App = () => { name={'Main'} component={MainScreen} options={{ - title: '홈', + title: t('title.home'), }} /> diff --git a/src/components/Card.tsx b/src/components/Card.tsx index 09e0575..cba611d 100644 --- a/src/components/Card.tsx +++ b/src/components/Card.tsx @@ -8,6 +8,7 @@ import styled from 'styled-components/native'; import CardConv from '../modules/CardConv'; import { Card } from '../types'; +import { useTranslation } from 'react-i18next'; interface CardViewProps { card: Card; @@ -63,6 +64,8 @@ const BottomButtonText = styled.Text` const CardView = (props: CardViewProps) => { const { card, index, onPress: onPressFromProps, onEdit, onDelete } = props; + const { t } = useTranslation(); + const onPress = useCallback(() => { onPressFromProps?.(index); }, [onPressFromProps, index]); @@ -126,13 +129,13 @@ const CardView = (props: CardViewProps) => { style={styles.bottomMenuButton} onPress={onPressEdit} > - 편집 + {t('card.edit')} - 삭제 + {t('card.remove')} )} diff --git a/src/locales/en.json b/src/locales/en.json new file mode 100644 index 0000000..7e89c79 --- /dev/null +++ b/src/locales/en.json @@ -0,0 +1,41 @@ +{ + "title": { + "home": "Home", + "card_add": "Add", + "card_edit": "Edit" + }, + "alert": { + "title": { + "error": "Error", + "card_remove": "Remove" + }, + "body": { + "hcef_not_support": "This device does not support HCE-F. Please try again with another device.", + "hcef_init_fail": "Failed to initialize HCE-F.\nAfter activating NFC, please restart the app\n", + "card_remove": "Do you want to remove the card \"{{cardName}}\"?" + }, + "button": { + "confirm": "Confirm", + "remove": "Remove", + "cancel": "Cancel" + } + }, + "main": { + "please_add_a_card": "Please add a card." + }, + "card": { + "touch_to_activate": "Tap to activate", + "touch_to_deactivate": "Tap to deactivate", + "edit": "Edit", + "remove": "Remove" + }, + "card_edit": { + "loading_card_number": "Loading card number...", + "invalid_card_number": "Invalid card number", + "card_preview": "Card Preview", + "card_name": "Card Name", + "card_number": "Card Number", + "change_card_number": "Change card number", + "save": "Save" + } +} diff --git a/src/locales/i18n.ts b/src/locales/i18n.ts new file mode 100644 index 0000000..37fa709 --- /dev/null +++ b/src/locales/i18n.ts @@ -0,0 +1,29 @@ +import i18n from 'i18next'; +import { initReactI18next } from 'react-i18next'; +import RNLanguageDetector from '@os-team/i18next-react-native-language-detector'; + +import { default as ko } from './ko.json'; +import { default as en } from './en.json'; + +const resources = { + ko: { + translation: ko, + }, + en: { + translation: en, + }, +}; + +i18n + .use(initReactI18next) + .use(RNLanguageDetector) + .init({ + resources, + compatibilityJSON: 'v3', + fallbackLng: 'en', + interpolation: { + escapeValue: false, + }, + }); + +export default i18n; diff --git a/src/locales/i18next.d.ts b/src/locales/i18next.d.ts new file mode 100644 index 0000000..ddfcdad --- /dev/null +++ b/src/locales/i18next.d.ts @@ -0,0 +1,17 @@ +// import the original type declarations +import 'i18next'; +// import all namespaces (for the default language, only) +import { default as ko } from './ko.json'; + +declare module 'i18next' { + // Extend CustomTypeOptions + interface CustomTypeOptions { + // custom namespace type, if you changed it + defaultNS: 'translation'; + // custom resources type + resources: { + translation: typeof ko; + }; + // other + } +} diff --git a/src/locales/ko.json b/src/locales/ko.json new file mode 100644 index 0000000..fa52821 --- /dev/null +++ b/src/locales/ko.json @@ -0,0 +1,41 @@ +{ + "title": { + "home": "홈", + "card_add": "카드 추가", + "card_edit": "카드 편집" + }, + "alert": { + "title": { + "error": "오류", + "card_remove": "카드 삭제" + }, + "body": { + "hcef_not_support": "이 기기는 HCE-F를 지원하지 않습니다. 다른 기기로 다시 시도해 주세요.", + "hcef_init_fail": "HCE-F 초기 설정에 실패했습니다.\n앱을 종료한 뒤, NFC를 활성화하고 다시 실행해 주세요.", + "card_remove": "\"{{cardName}}\" 카드를 삭제하시겠습니까?" + }, + "button": { + "confirm": "확인", + "remove": "삭제", + "cancel": "취소" + } + }, + "main": { + "please_add_a_card": "카드를 추가해 주세요." + }, + "card": { + "touch_to_activate": "터치해서 활성화", + "touch_to_deactivate": "터치해서 비활성화", + "edit": "편집", + "remove": "삭제" + }, + "card_edit": { + "loading_card_number": "카드 번호를 불러오는 중...", + "invalid_card_number": "잘못된 카드 번호입니다.", + "card_preview": "카드 미리보기", + "card_name": "카드 이름", + "card_number": "카드 번호", + "change_card_number": "카드 번호 변경", + "save": "저장" + } +} diff --git a/src/screens/CardEditScreen.tsx b/src/screens/CardEditScreen.tsx index 1a5dd7e..096bb5f 100644 --- a/src/screens/CardEditScreen.tsx +++ b/src/screens/CardEditScreen.tsx @@ -20,6 +20,7 @@ import { RootStackParams } from '../../App'; import CardView from '../components/Card'; import { addCard, updateCard } from '../data/cards'; import { Card } from '../types'; +import { useTranslation } from 'react-i18next'; type TextFieldProps = TextInputProps & { title: string; @@ -140,6 +141,7 @@ type CardAddScreenProps = NativeStackScreenProps; type CardEditScreenProps = NativeStackScreenProps; const CardEditScreen = (props: CardAddScreenProps | CardEditScreenProps) => { + const { t } = useTranslation(); const initialData = props.route.params?.card ?? undefined; const [mode] = useState<'add' | 'edit'>(() => { @@ -156,14 +158,14 @@ const CardEditScreen = (props: CardAddScreenProps | CardEditScreenProps) => { const styledUid = useMemo(() => { if (!uid.isSuccess) { - return '카드번호를 불러오는 중...'; + return t('card_edit.loading_card_number'); } return ( uid.data.match(/[A-Za-z0-9]{4}/g)?.join(' - ') ?? - '잘못된 카드 번호입니다.' + t('card_edit.invalid_card_number') ); - }, [uid]); + }, [t, uid]); const onChangeCardName = useCallback((s: string) => { setCardName(s); @@ -228,7 +230,7 @@ const CardEditScreen = (props: CardAddScreenProps | CardEditScreenProps) => { sid: cardNumber, name: cardName, }} - mainText={'카드 미리보기'} + mainText={t('card_edit.card_preview')} index={0 /* dummy index */} disabledMainButton={true} hideBottomMenu={true} @@ -236,26 +238,30 @@ const CardEditScreen = (props: CardAddScreenProps | CardEditScreenProps) => { - +