From b3a0bfe772cfb5f17e23423b9fc75ead1a49cb0f Mon Sep 17 00:00:00 2001 From: Shusui MOYATANI Date: Sat, 17 Jun 2023 23:42:54 +0900 Subject: [PATCH] feat: i18n --- package-lock.json | 68 ++++++++-- package.json | 2 + src/App.tsx | 21 ++- src/components/column/BookmarkColumn.tsx | 4 +- src/components/column/ChannelColumn.tsx | 5 +- src/components/column/ColumnSettings.tsx | 30 ++-- src/components/column/FollwingColumn.tsx | 4 +- src/components/column/NotificationColumn.tsx | 4 +- src/components/column/PostsColumn.tsx | 4 +- src/components/column/ReactionsColumn.tsx | 4 +- src/components/column/RelaysColumn.tsx | 4 +- src/components/event/Reaction.tsx | 4 +- src/components/event/Repost.tsx | 4 +- src/components/event/ZapReceipt.tsx | 3 +- .../event/textNote/TextNoteDisplay.tsx | 23 ++-- src/components/modal/AddColumn.tsx | 14 +- src/components/modal/Config.tsx | 128 ++++++++++-------- src/components/modal/ProfileDisplay.tsx | 27 ++-- src/i18n/i18n.ts | 22 +++ src/i18n/useTranslation.tsx | 39 ++++++ src/locales/en.ts | 125 +++++++++++++++++ src/locales/ja.ts | 124 +++++++++++++++++ src/nostr/useReposts.ts | 2 +- src/pages/Hello.tsx | 8 +- src/types/i18next.d.ts | 12 ++ tsconfig.json | 1 + 26 files changed, 555 insertions(+), 131 deletions(-) create mode 100644 src/i18n/i18n.ts create mode 100644 src/i18n/useTranslation.tsx create mode 100644 src/locales/en.ts create mode 100644 src/locales/ja.ts create mode 100644 src/types/i18next.d.ts diff --git a/package-lock.json b/package-lock.json index 8f9bbb1..f498522 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,8 @@ "@types/lodash": "^4.14.195", "emoji-mart": "^5.5.2", "heroicons": "^2.0.18", + "i18next": "^23.1.0", + "i18next-browser-languagedetector": "^7.0.2", "lodash": "^4.17.21", "nostr-tools": "^1.11.2", "solid-js": "^1.7.5", @@ -507,10 +509,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz", - "integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==", - "dev": true, + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.5.tgz", + "integrity": "sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==", "dependencies": { "regenerator-runtime": "^0.13.11" }, @@ -3999,6 +4000,36 @@ "url": "https://github.com/sponsors/typicode" } }, + "node_modules/i18next": { + "version": "23.1.0", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.1.0.tgz", + "integrity": "sha512-CObNPofJpw7zGVGYLd58mtMZUF+NZQl9czYMihbJkStjX+Nlu9kC3PHiC6uE1niP3qxP/3ocLXIBc2zqbAb1dg==", + "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.0.2", + "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-7.0.2.tgz", + "integrity": "sha512-5ViaK+gikxfqZ9M3jJ7gJkUzzu/p3HwiqfLoL1bdiL7CUb0IylcTyVLdPaTU3pH5VFWFCiGFuJDg3VkLUikWgg==", + "dependencies": { + "@babel/runtime": "^7.19.4" + } + }, "node_modules/ignore": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", @@ -5920,8 +5951,7 @@ "node_modules/regenerator-runtime": { "version": "0.13.11", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", - "dev": true + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" }, "node_modules/regexp.prototype.flags": { "version": "1.4.3", @@ -7726,10 +7756,9 @@ } }, "@babel/runtime": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz", - "integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==", - "dev": true, + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.5.tgz", + "integrity": "sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==", "requires": { "regenerator-runtime": "^0.13.11" } @@ -10140,6 +10169,22 @@ "integrity": "sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==", "dev": true }, + "i18next": { + "version": "23.1.0", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.1.0.tgz", + "integrity": "sha512-CObNPofJpw7zGVGYLd58mtMZUF+NZQl9czYMihbJkStjX+Nlu9kC3PHiC6uE1niP3qxP/3ocLXIBc2zqbAb1dg==", + "requires": { + "@babel/runtime": "^7.22.5" + } + }, + "i18next-browser-languagedetector": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-7.0.2.tgz", + "integrity": "sha512-5ViaK+gikxfqZ9M3jJ7gJkUzzu/p3HwiqfLoL1bdiL7CUb0IylcTyVLdPaTU3pH5VFWFCiGFuJDg3VkLUikWgg==", + "requires": { + "@babel/runtime": "^7.19.4" + } + }, "ignore": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", @@ -11499,8 +11544,7 @@ "regenerator-runtime": { "version": "0.13.11", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", - "dev": true + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" }, "regexp.prototype.flags": { "version": "1.4.3", diff --git a/package.json b/package.json index 5f424e9..76bc97e 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,8 @@ "@types/lodash": "^4.14.195", "emoji-mart": "^5.5.2", "heroicons": "^2.0.18", + "i18next": "^23.1.0", + "i18next-browser-languagedetector": "^7.0.2", "lodash": "^4.17.21", "nostr-tools": "^1.11.2", "solid-js": "^1.7.5", diff --git a/src/App.tsx b/src/App.tsx index 09dec90..47ce437 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -5,12 +5,17 @@ import { persistQueryClient } from '@tanstack/query-persist-client-core'; import { createSyncStoragePersister } from '@tanstack/query-sync-storage-persister'; import { QueryClient, QueryClientProvider } from '@tanstack/solid-query'; +import i18nextInstance from '@/i18n/i18n'; +import { I18NextProvider } from '@/i18n/useTranslation'; + const Home = lazy(() => import('@/pages/Home')); const Hello = lazy(() => import('@/pages/Hello')); const NotFound = lazy(() => import('@/pages/NotFound')); const queryClient = new QueryClient({}); +const i18next = i18nextInstance(); + const localStoragePersister = createSyncStoragePersister({ storage: window.localStorage, }); @@ -25,13 +30,15 @@ const App: Component = () => { }); return ( - - - } /> - } /> - } /> - - + + + + } /> + } /> + } /> + + + ); }; diff --git a/src/components/column/BookmarkColumn.tsx b/src/components/column/BookmarkColumn.tsx index c7126e5..399402f 100644 --- a/src/components/column/BookmarkColumn.tsx +++ b/src/components/column/BookmarkColumn.tsx @@ -8,6 +8,7 @@ import ColumnSettings from '@/components/column/ColumnSettings'; import Bookmark from '@/components/timeline/Bookmark'; import { BookmarkColumnType } from '@/core/column'; import useConfig from '@/core/useConfig'; +import { useTranslation } from '@/i18n/useTranslation'; import useDecrypt from '@/nostr/useDecrypt'; import useParameterizedReplaceableEvent from '@/nostr/useParameterizedReplaceableEvent'; @@ -18,6 +19,7 @@ type BookmarkColumnDisplayProps = { }; const BookmarkColumn: Component = (props) => { + const i18n = useTranslation(); const { removeColumn } = useConfig(); const { event } = useParameterizedReplaceableEvent(() => ({ @@ -30,7 +32,7 @@ const BookmarkColumn: Component = (props) => { } settings={() => } onClose={() => removeColumn(props.column.id)} diff --git a/src/components/column/ChannelColumn.tsx b/src/components/column/ChannelColumn.tsx index ea8cff2..0319495 100644 --- a/src/components/column/ChannelColumn.tsx +++ b/src/components/column/ChannelColumn.tsx @@ -11,7 +11,7 @@ import Timeline from '@/components/timeline/Timeline'; import { ChannelColumnType, FollowingColumnType } from '@/core/column'; import { applyContentFilter } from '@/core/contentFilter'; import useConfig from '@/core/useConfig'; -import useFollowings from '@/nostr/useFollowings'; +import { useTranslation } from '@/i18n/useTranslation'; import useSubscription from '@/nostr/useSubscription'; import epoch from '@/utils/epoch'; @@ -22,6 +22,7 @@ export type ChannelColumnProps = { }; const ChannelColumn: Component = (props) => { + const i18n = useTranslation(); const { config, removeColumn } = useConfig(); const { events } = useSubscription(() => ({ @@ -44,7 +45,7 @@ const ChannelColumn: Component = (props) => { } settings={() => } onClose={() => removeColumn(props.column.id)} diff --git a/src/components/column/ColumnSettings.tsx b/src/components/column/ColumnSettings.tsx index 6168fdb..98d8e9f 100644 --- a/src/components/column/ColumnSettings.tsx +++ b/src/components/column/ColumnSettings.tsx @@ -7,6 +7,7 @@ import Trash from 'heroicons/24/outline/trash.svg'; import { ColumnType } from '@/core/column'; import useConfig from '@/core/useConfig'; import { useRequestCommand } from '@/hooks/useCommandBus'; +import { useTranslation } from '@/i18n/useTranslation'; type ColumnSettingsProps = { column: ColumnType; @@ -28,6 +29,7 @@ const ColumnSettingsSection: Component = (props) => }; const ColumnSettings: Component = (props) => { + const i18n = useTranslation(); const { saveColumn, removeColumn, moveColumn } = useConfig(); const request = useRequestCommand(); @@ -42,41 +44,49 @@ const ColumnSettings: Component = (props) => { return (
- -
+ +
- - diff --git a/src/components/column/FollwingColumn.tsx b/src/components/column/FollwingColumn.tsx index dfcc495..1432267 100644 --- a/src/components/column/FollwingColumn.tsx +++ b/src/components/column/FollwingColumn.tsx @@ -10,6 +10,7 @@ import Timeline from '@/components/timeline/Timeline'; import { FollowingColumnType } from '@/core/column'; import { applyContentFilter } from '@/core/contentFilter'; import useConfig from '@/core/useConfig'; +import { useTranslation } from '@/i18n/useTranslation'; import useFollowings from '@/nostr/useFollowings'; import useSubscription from '@/nostr/useSubscription'; import epoch from '@/utils/epoch'; @@ -21,6 +22,7 @@ type FollowingColumnDisplayProps = { }; const FollowingColumn: Component = (props) => { + const i18n = useTranslation(); const { config, removeColumn } = useConfig(); const { followingPubkeys } = useFollowings(() => ({ pubkey: props.column.pubkey })); @@ -57,7 +59,7 @@ const FollowingColumn: Component = (props) => { } settings={() => } onClose={() => removeColumn(props.column.id)} diff --git a/src/components/column/NotificationColumn.tsx b/src/components/column/NotificationColumn.tsx index f9413c9..4b8a36d 100644 --- a/src/components/column/NotificationColumn.tsx +++ b/src/components/column/NotificationColumn.tsx @@ -9,6 +9,7 @@ import Notification from '@/components/timeline/Notification'; import { NotificationColumnType } from '@/core/column'; import { applyContentFilter } from '@/core/contentFilter'; import useConfig from '@/core/useConfig'; +import { useTranslation } from '@/i18n/useTranslation'; import useSubscription from '@/nostr/useSubscription'; type NotificationColumnDisplayProps = { @@ -18,6 +19,7 @@ type NotificationColumnDisplayProps = { }; const NotificationColumn: Component = (props) => { + const i18n = useTranslation(); const { config, removeColumn } = useConfig(); const { events: notifications } = useSubscription(() => ({ @@ -39,7 +41,7 @@ const NotificationColumn: Component = (props) => } settings={() => } onClose={() => removeColumn(props.column.id)} diff --git a/src/components/column/PostsColumn.tsx b/src/components/column/PostsColumn.tsx index 6e4af7f..4682867 100644 --- a/src/components/column/PostsColumn.tsx +++ b/src/components/column/PostsColumn.tsx @@ -9,6 +9,7 @@ import Timeline from '@/components/timeline/Timeline'; import { PostsColumnType } from '@/core/column'; import { applyContentFilter } from '@/core/contentFilter'; import useConfig from '@/core/useConfig'; +import { useTranslation } from '@/i18n/useTranslation'; import useSubscription from '@/nostr/useSubscription'; type PostsColumnDisplayProps = { @@ -18,6 +19,7 @@ type PostsColumnDisplayProps = { }; const PostsColumn: Component = (props) => { + const i18n = useTranslation(); const { config, removeColumn } = useConfig(); const { events } = useSubscription(() => ({ @@ -39,7 +41,7 @@ const PostsColumn: Component = (props) => { } settings={() => } onClose={() => removeColumn(props.column.id)} diff --git a/src/components/column/ReactionsColumn.tsx b/src/components/column/ReactionsColumn.tsx index 93f54c5..f53065f 100644 --- a/src/components/column/ReactionsColumn.tsx +++ b/src/components/column/ReactionsColumn.tsx @@ -9,6 +9,7 @@ import Notification from '@/components/timeline/Notification'; import { ReactionsColumnType } from '@/core/column'; import { applyContentFilter } from '@/core/contentFilter'; import useConfig from '@/core/useConfig'; +import { useTranslation } from '@/i18n/useTranslation'; import useSubscription from '@/nostr/useSubscription'; type ReactionsColumnDisplayProps = { @@ -18,6 +19,7 @@ type ReactionsColumnDisplayProps = { }; const ReactionsColumn: Component = (props) => { + const i18n = useTranslation(); const { config, removeColumn } = useConfig(); const { events: reactions } = useSubscription(() => ({ @@ -39,7 +41,7 @@ const ReactionsColumn: Component = (props) => { } settings={() => } onClose={() => removeColumn(props.column.id)} diff --git a/src/components/column/RelaysColumn.tsx b/src/components/column/RelaysColumn.tsx index 8fc4a0b..d9fd8c2 100644 --- a/src/components/column/RelaysColumn.tsx +++ b/src/components/column/RelaysColumn.tsx @@ -9,6 +9,7 @@ import Timeline from '@/components/timeline/Timeline'; import { RelaysColumnType } from '@/core/column'; import { applyContentFilter } from '@/core/contentFilter'; import useConfig from '@/core/useConfig'; +import { useTranslation } from '@/i18n/useTranslation'; import useSubscription from '@/nostr/useSubscription'; import epoch from '@/utils/epoch'; @@ -19,6 +20,7 @@ type RelaysColumnDisplayProps = { }; const RelaysColumn: Component = (props) => { + const i18n = useTranslation(); const { removeColumn } = useConfig(); const { events } = useSubscription(() => ({ @@ -40,7 +42,7 @@ const RelaysColumn: Component = (props) => { } settings={() => } onClose={() => removeColumn(props.column.id)} diff --git a/src/components/event/Reaction.tsx b/src/components/event/Reaction.tsx index c15e2ef..bd47a83 100644 --- a/src/components/event/Reaction.tsx +++ b/src/components/event/Reaction.tsx @@ -7,6 +7,7 @@ import TextNoteDisplay from '@/components/event/textNote/TextNoteDisplay'; import UserDisplayName from '@/components/UserDisplayName'; import useConfig from '@/core/useConfig'; import useModalState from '@/hooks/useModalState'; +import { useTranslation } from '@/i18n/useTranslation'; import { genericEvent } from '@/nostr/event'; import useEvent from '@/nostr/useEvent'; import useProfile from '@/nostr/useProfile'; @@ -17,6 +18,7 @@ type ReactionProps = { }; const Reaction: Component = (props) => { + const i18n = useTranslation(); const { shouldMuteEvent } = useConfig(); const { showProfile } = useModalState(); const event = () => genericEvent(props.event); @@ -65,7 +67,7 @@ const Reaction: Component = (props) => { > - {' がリアクション'} + {i18n()('notification.reacted')}
diff --git a/src/components/event/Repost.tsx b/src/components/event/Repost.tsx index ff80f9c..84a753a 100644 --- a/src/components/event/Repost.tsx +++ b/src/components/event/Repost.tsx @@ -9,6 +9,7 @@ import EventDisplayById from '@/components/event/EventDisplayById'; import UserDisplayName from '@/components/UserDisplayName'; import useFormatDate from '@/hooks/useFormatDate'; import useModalState from '@/hooks/useModalState'; +import { useTranslation } from '@/i18n/useTranslation'; import { genericEvent } from '@/nostr/event'; export type RepostProps = { @@ -16,6 +17,7 @@ export type RepostProps = { }; const Repost: Component = (props) => { + const i18n = useTranslation(); const { showProfile } = useModalState(); const formatDate = useFormatDate(); const event = createMemo(() => genericEvent(props.event)); @@ -34,7 +36,7 @@ const Repost: Component = (props) => { > - {' がリポスト'} + {i18n()('notification.reposted')}
{formatDate(event().createdAtAsDate())}
diff --git a/src/components/event/ZapReceipt.tsx b/src/components/event/ZapReceipt.tsx index 3cc8a0a..722510c 100644 --- a/src/components/event/ZapReceipt.tsx +++ b/src/components/event/ZapReceipt.tsx @@ -35,8 +35,7 @@ const ZapReceipt: Component = (props) => { return ( - ⚡ - + ⚡{/* */}
{JSON.stringify(props.event, null, 2)}
); diff --git a/src/components/event/textNote/TextNoteDisplay.tsx b/src/components/event/textNote/TextNoteDisplay.tsx index b03db3b..2565445 100644 --- a/src/components/event/textNote/TextNoteDisplay.tsx +++ b/src/components/event/textNote/TextNoteDisplay.tsx @@ -32,6 +32,7 @@ import Post from '@/components/Post'; import { useTimelineContext } from '@/components/timeline/TimelineContext'; import useConfig from '@/core/useConfig'; import useModalState from '@/hooks/useModalState'; +import { useTranslation } from '@/i18n/useTranslation'; import { textNote } from '@/nostr/event'; import useCommands from '@/nostr/useCommands'; import usePubkey from '@/nostr/usePubkey'; @@ -97,6 +98,7 @@ const EmojiReactions: Component = (props) => { }; const TextNoteDisplay: Component = (props) => { + const i18n = useTranslation(); const { config } = useConfig(); const pubkey = usePubkey(); const { showProfile } = useModalState(); @@ -180,11 +182,11 @@ const TextNoteDisplay: Component = (props) => { const succeeded = results.filter((res) => res.status === 'fulfilled').length; const failed = results.length - succeeded; if (succeeded === results.length) { - window.alert('削除しました(画面の反映にはリロード)'); + window.alert(i18n()('post.deletedSuccessfully')); } else if (succeeded > 0) { - window.alert(`${failed}個のリレーで削除に失敗しました`); + window.alert(i18n()('post.failedToDeletePartially', { count: failed })); } else { - window.alert('すべてのリレーで削除に失敗しました'); + window.alert(i18n()('post.failedToDelete')); } }, onError: (err) => { @@ -194,37 +196,37 @@ const TextNoteDisplay: Component = (props) => { const menu: MenuItem[] = [ { - content: () => 'IDをコピー', + content: () => i18n()('post.copyEventId'), onSelect: () => { navigator.clipboard.writeText(noteEncode(props.event.id)).catch((err) => window.alert(err)); }, }, { - content: () => 'JSONを確認', + content: () => i18n()('post.showJSON'), onSelect: () => { setModal('EventDebugModal'); }, }, { - content: () => 'リポスト一覧', + content: () => i18n()('post.showReposts'), onSelect: () => { setModal('Reposts'); }, }, { - content: () => 'リアクション一覧', + content: () => i18n()('post.showReactions'), onSelect: () => { setModal('Reactions'); }, }, { when: () => event().pubkey === pubkey(), - content: () => 削除, + content: () => {i18n()('post.deletePost')}, onSelect: () => { const p = pubkey(); if (p == null) return; - if (!window.confirm('本当に削除しますか?')) return; + if (!window.confirm(i18n()('post.confirmDelete'))) return; deleteMutation.mutate({ relayUrls: config().relayUrls, pubkey: p, @@ -326,6 +328,7 @@ const TextNoteDisplay: Component = (props) => { 0}>
+ {i18n()('post.replyToPre')} {(replyToPubkey: string) => ( )} - {'への返信'} + {i18n()('post.replyToPost')}
diff --git a/src/components/modal/AddColumn.tsx b/src/components/modal/AddColumn.tsx index 86c670b..8943470 100644 --- a/src/components/modal/AddColumn.tsx +++ b/src/components/modal/AddColumn.tsx @@ -20,6 +20,7 @@ import { } from '@/core/column'; import useConfig from '@/core/useConfig'; import { useRequestCommand } from '@/hooks/useCommandBus'; +import { useTranslation } from '@/i18n/useTranslation'; import usePubkey from '@/nostr/usePubkey'; import ensureNonNull from '@/utils/ensureNonNull'; @@ -28,6 +29,7 @@ type AddColumnProps = { }; const AddColumn: Component = (props) => { + const i18n = useTranslation(); const pubkey = usePubkey(); const { saveColumn } = useConfig(); const request = useRequestCommand(); @@ -85,7 +87,7 @@ const AddColumn: Component = (props) => { - ホーム + {i18n()('column.home')} {/* diff --git a/src/components/modal/Config.tsx b/src/components/modal/Config.tsx index 61b6ef2..3b92c93 100644 --- a/src/components/modal/Config.tsx +++ b/src/components/modal/Config.tsx @@ -12,6 +12,7 @@ import BasicModal from '@/components/modal/BasicModal'; import UserNameDisplay from '@/components/UserDisplayName'; import useConfig, { type Config } from '@/core/useConfig'; import useModalState from '@/hooks/useModalState'; +import { useTranslation } from '@/i18n/useTranslation'; import usePubkey from '@/nostr/usePubkey'; import { simpleEmojiPackSchema, convertToEmojiConfig } from '@/utils/emojipack'; import ensureNonNull from '@/utils/ensureNonNull'; @@ -26,12 +27,13 @@ const HttpUrlRegex = BaseUrlRegex('https?'); const RelayUrlRegex = BaseUrlRegex('wss?'); const ProfileSection = () => { + const i18n = useTranslation(); const pubkey = usePubkey(); const { showProfile, showProfileEdit } = useModalState(); return (
-

プロフィール

+

{i18n()('config.profile.profile')}

@@ -55,6 +57,7 @@ const ProfileSection = () => { }; const RelayConfig = () => { + const i18n = useTranslation(); const { config, addRelay, removeRelay } = useConfig(); const [relayUrlInput, setRelayUrlInput] = createSignal(''); @@ -73,11 +76,11 @@ const RelayConfig = () => { const relayUrls = importedRelays.map(([relayUrl]) => relayUrl).join('\n'); if (importedRelays.length === 0) { - window.alert('リレーが設定されていません'); + window.alert(i18n()('config.relays.notConfigured')); return; } - if (!window.confirm(`これらのリレーをインポートしますか:\n${relayUrls}`)) { + if (!window.confirm(`${i18n()('config.relays.askImport')}\n\n${relayUrls}`)) { return; } @@ -89,14 +92,16 @@ const RelayConfig = () => { }); const currentCount = config().relayUrls.length; const importedCount = currentCount - lastCount; - window.alert(`${importedCount} 個のリレーをインポートしました`); + window.alert(i18n()('config.relays.imported', { count: importedCount })); }; return ( <>
-

リレー

-

{config().relayUrls.length} 個のリレーが設定されています

+

{i18n()('config.relays.relays')}

+

+ {i18n()('config.relays.numOfRelays', { count: config().relayUrls.length })} +

    {(relayUrl: string) => { @@ -121,61 +126,62 @@ const RelayConfig = () => { onChange={(ev) => setRelayUrlInput(ev.currentTarget.value)} />
-

インポート

+

{i18n()('config.relays.importRelays')}

); }; -const dateFormats: { - id: Config['dateFormat']; - name: string; - example: string; -}[] = [ - { - id: 'relative', - name: '相対表記', - example: '7秒前', - }, - { - id: 'absolute-short', - name: '絶対表記 (短形式)', - example: '昨日 23:55', - }, - { - id: 'absolute-long', - name: '絶対表記 (長形式)', - example: '2020/11/8 21:02:53', - }, -]; - const DateFormatConfig = () => { + const i18n = useTranslation(); const { config, setConfig } = useConfig(); + const dateFormats: { + id: Config['dateFormat']; + name: string; + example: string; + }[] = [ + { + id: 'relative', + name: i18n()('config.display.relativeTimeNotation'), + example: i18n()('config.display.relativeTimeNotationExample'), + }, + { + id: 'absolute-short', + name: i18n()('config.display.absoluteTimeNotationShort'), + example: i18n()('config.display.absoluteTimeNotationShortExample'), + }, + { + id: 'absolute-long', + name: i18n()('config.display.absoluteTimeNotationLong'), + example: i18n()('config.display.absoluteTimeNotationLongExample'), + }, + ]; + const updateDateFormat = (dateFormat: Config['dateFormat']) => { setConfig((current) => ({ ...current, dateFormat })); }; return (
-

時刻の表記

+

{i18n()('config.display.timeNotation')}

{({ id, name, example }) => ( @@ -224,6 +230,7 @@ const ToggleButton = (props: { }; const ReactionConfig = () => { + const i18n = useTranslation(); const { config, setConfig } = useConfig(); const toggleUseEmojiReaction = () => { @@ -242,17 +249,17 @@ const ReactionConfig = () => { return (
-

リアクション

+

{i18n()('config.display.reaction')}

-
絵文字を選べるようにする
+
{i18n()('config.display.enableEmojiReaction')}
toggleUseEmojiReaction()} />
-
投稿にリアクションされた絵文字を表示する
+
{i18n()('config.display.showEmojiReaction')}
toggleShowEmojiReaction()} @@ -264,6 +271,7 @@ const ReactionConfig = () => { }; const EmojiConfig = () => { + const i18n = useTranslation(); const { config, saveEmoji, removeEmoji } = useConfig(); const [shortcodeInput, setShortcodeInput] = createSignal(''); @@ -279,7 +287,7 @@ const EmojiConfig = () => { return (
-

カスタム絵文字

+

{i18n()('config.customEmoji.customEmoji')}

    {({ shortcode, url }) => ( @@ -295,7 +303,7 @@ const EmojiConfig = () => {
@@ -329,6 +337,7 @@ const EmojiConfig = () => { }; const EmojiImport = () => { + const i18n = useTranslation(); const { saveEmojis } = useConfig(); const [jsonInput, setJSONInput] = createSignal(''); @@ -350,8 +359,8 @@ const EmojiImport = () => { return (
-

絵文字のインポート

-

絵文字の名前をキー、画像のURLを値とするJSONを読み込むことができます。

+

{i18n()('config.customEmoji.emojiImport')}

+

{i18n()('config.customEmoji.emojiImportDescription')}