@@ -23,7 +25,7 @@ export default function PrimaryPageSidebar() {
)}
diff --git a/src/renderer/src/components/ThemeToggle/index.tsx b/src/renderer/src/components/ThemeToggle/index.tsx
index 9cca61c..36aa3e7 100644
--- a/src/renderer/src/components/ThemeToggle/index.tsx
+++ b/src/renderer/src/components/ThemeToggle/index.tsx
@@ -1,8 +1,10 @@
import { Button } from '@renderer/components/ui/button'
import { useTheme } from '@renderer/providers/ThemeProvider'
import { Moon, Sun, SunMoon } from 'lucide-react'
+import { useTranslation } from 'react-i18next'
export default function ThemeToggle() {
+ const { t } = useTranslation()
const { themeSetting, setThemeSetting } = useTheme()
return (
@@ -12,7 +14,7 @@ export default function ThemeToggle() {
variant="titlebar"
size="titlebar"
onClick={() => setThemeSetting('light')}
- title="switch to light theme"
+ title={t('switch to light theme')}
>
@@ -21,7 +23,7 @@ export default function ThemeToggle() {
variant="titlebar"
size="titlebar"
onClick={() => setThemeSetting('dark')}
- title="switch to dark theme"
+ title={t('switch to dark theme')}
>
@@ -30,7 +32,7 @@ export default function ThemeToggle() {
variant="titlebar"
size="titlebar"
onClick={() => setThemeSetting('system')}
- title="switch to system theme"
+ title={t('switch to system theme')}
>
diff --git a/src/renderer/src/i18n/en.ts b/src/renderer/src/i18n/en.ts
new file mode 100644
index 0000000..e9d5162
--- /dev/null
+++ b/src/renderer/src/i18n/en.ts
@@ -0,0 +1,49 @@
+export default {
+ translation: {
+ 'Welcome! 🥳': 'Welcome! 🥳',
+ About: 'About',
+ 'New post': 'New post',
+ Post: 'Post',
+ 'Relay settings': 'Relay settings',
+ SidebarRelays: 'Relays',
+ Refresh: 'Refresh',
+ Profile: 'Profile',
+ Logout: 'Logout',
+ Following: 'Following',
+ reposted: 'reposted',
+ 'just now': 'just now',
+ 'n minutes ago': '{{n}} minutes ago',
+ 'n hours ago': '{{n}} hours ago',
+ 'n days ago': '{{n}} days ago',
+ date: '{{timestamp, date}}',
+ Follow: 'Follow',
+ Unfollow: 'Unfollow',
+ 'Follow failed': 'Follow failed',
+ 'Unfollow failed': 'Unfollow failed',
+ 'show new notes': 'show new notes',
+ 'loading...': 'loading...',
+ 'no more notes': 'no more notes',
+ 'reply to': 'reply to',
+ reply: 'reply',
+ Reply: 'Reply',
+ 'load more older replies': 'load more older replies',
+ 'Write something...': 'Write something...',
+ Cancel: 'Cancel',
+ Mentions: 'Mentions',
+ 'Failed to post': 'Failed to post',
+ 'Post successful': 'Post successful',
+ 'Your post has been published': 'Your post has been published',
+ Repost: 'Repost',
+ Quote: 'Quote',
+ 'copy embedded code': 'copy embedded code',
+ 'raw event': 'raw event',
+ Like: 'Like',
+ 'switch to light theme': 'switch to light theme',
+ 'switch to dark theme': 'switch to dark theme',
+ 'switch to system theme': 'switch to system theme',
+ note: 'note',
+ "username's following": "{{username}}'s following",
+ following: 'following',
+ Login: 'Login'
+ }
+}
diff --git a/src/renderer/src/i18n/index.ts b/src/renderer/src/i18n/index.ts
new file mode 100644
index 0000000..c0737f1
--- /dev/null
+++ b/src/renderer/src/i18n/index.ts
@@ -0,0 +1,32 @@
+import i18n from 'i18next'
+import { initReactI18next } from 'react-i18next'
+import LanguageDetector from 'i18next-browser-languagedetector'
+import en from './en'
+import zh from './zh'
+import dayjs from 'dayjs'
+
+const resources = {
+ en,
+ zh
+}
+
+i18n
+ .use(LanguageDetector)
+ .use(initReactI18next)
+ .init({
+ fallbackLng: 'en',
+ resources,
+ interpolation: {
+ escapeValue: false // react already safes from xss
+ }
+ })
+
+i18n.services.formatter?.add('date', (value, lng) => {
+ console.log('lng', lng)
+ if (lng?.startsWith('zh')) {
+ return dayjs(value).format('YYYY-MM-DD')
+ }
+ return dayjs(value).format('MMM D, YYYY')
+})
+
+export default i18n
diff --git a/src/renderer/src/i18n/zh.ts b/src/renderer/src/i18n/zh.ts
new file mode 100644
index 0000000..e3e8b9f
--- /dev/null
+++ b/src/renderer/src/i18n/zh.ts
@@ -0,0 +1,49 @@
+export default {
+ translation: {
+ 'Welcome! 🥳': '欢迎!🥳',
+ About: '关于',
+ 'New post': '发布新笔记',
+ Post: '发布笔记',
+ 'Relay settings': '中继设置',
+ SidebarRelays: '中继设置',
+ Refresh: '刷新列表',
+ Profile: '个人资料',
+ Logout: '退出登录',
+ Following: '关注',
+ reposted: '转发',
+ 'just now': '刚刚',
+ 'n minutes ago': '{{n}} 分钟前',
+ 'n hours ago': '{{n}} 小时前',
+ 'n days ago': '{{n}} 天前',
+ date: '{{timestamp, date}}',
+ Follow: '关注',
+ Unfollow: '取消关注',
+ 'Follow failed': '关注失败',
+ 'Unfollow failed': '取消关注失败',
+ 'show new notes': '显示新笔记',
+ 'loading...': '加载中...',
+ 'no more notes': '到底了',
+ 'reply to': '回复',
+ reply: '回复',
+ Reply: '回复',
+ 'load more older replies': '加载更多早期回复',
+ 'Write something...': '写点什么...',
+ Cancel: '取消',
+ Mentions: '提及',
+ 'Failed to post': '发布失败',
+ 'Post successful': '发布成功',
+ 'Your post has been published': '您的笔记已发布',
+ Repost: '转发',
+ Quote: '引用',
+ 'copy embedded code': '复制嵌入代码',
+ 'raw event': '原始事件',
+ Like: '点赞',
+ 'switch to light theme': '切换到浅色主题',
+ 'switch to dark theme': '切换到深色主题',
+ 'switch to system theme': '切换到系统主题',
+ note: '笔记',
+ "username's following": '{{username}} 的关注',
+ following: '关注',
+ Login: '登录'
+ }
+}
diff --git a/src/renderer/src/lib/timestamp.ts b/src/renderer/src/lib/timestamp.tsx
similarity index 59%
rename from src/renderer/src/lib/timestamp.ts
rename to src/renderer/src/lib/timestamp.tsx
index 799a129..d3a9e8b 100644
--- a/src/renderer/src/lib/timestamp.ts
+++ b/src/renderer/src/lib/timestamp.tsx
@@ -1,28 +1,30 @@
import dayjs from 'dayjs'
+import { useTranslation } from 'react-i18next'
export function formatTimestamp(timestamp: number) {
+ const { t } = useTranslation()
const time = dayjs(timestamp * 1000)
const now = dayjs()
const diffMonth = now.diff(time, 'month')
if (diffMonth >= 1) {
- return time.format('MMM D, YYYY')
+ return t('date', { timestamp: time.valueOf() })
}
const diffDay = now.diff(time, 'day')
if (diffDay >= 1) {
- return `${diffDay} days ago`
+ return t('n days ago', { n: diffDay })
}
const diffHour = now.diff(time, 'hour')
if (diffHour >= 1) {
- return `${diffHour} hours ago`
+ return t('n hours ago', { n: diffHour })
}
const diffMinute = now.diff(time, 'minute')
if (diffMinute >= 1) {
- return `${diffMinute} minutes ago`
+ return t('n minutes ago', { n: diffMinute })
}
- return 'just now'
+ return t('just now')
}
diff --git a/src/renderer/src/main.tsx b/src/renderer/src/main.tsx
index f4d40c7..e319268 100644
--- a/src/renderer/src/main.tsx
+++ b/src/renderer/src/main.tsx
@@ -1,4 +1,5 @@
import './assets/main.css'
+import './i18n'
import React from 'react'
import ReactDOM from 'react-dom/client'
diff --git a/src/renderer/src/pages/secondary/FollowingListPage/index.tsx b/src/renderer/src/pages/secondary/FollowingListPage/index.tsx
index 27f5896..08ab0c5 100644
--- a/src/renderer/src/pages/secondary/FollowingListPage/index.tsx
+++ b/src/renderer/src/pages/secondary/FollowingListPage/index.tsx
@@ -5,8 +5,10 @@ import Username from '@renderer/components/Username'
import { useFetchFollowings, useFetchProfile } from '@renderer/hooks'
import SecondaryPageLayout from '@renderer/layouts/SecondaryPageLayout'
import { useEffect, useRef, useState } from 'react'
+import { useTranslation } from 'react-i18next'
export default function FollowingListPage({ id }: { id?: string }) {
+ const { t } = useTranslation()
const { profile } = useFetchProfile(id)
const { followings } = useFetchFollowings(profile?.pubkey)
const [visibleFollowings, setVisibleFollowings] = useState
([])
@@ -46,7 +48,11 @@ export default function FollowingListPage({ id }: { id?: string }) {
return (
{visibleFollowings.map((pubkey, index) => (
diff --git a/src/renderer/src/pages/secondary/HomePage/index.tsx b/src/renderer/src/pages/secondary/HomePage/index.tsx
index 7c196f5..3fa28b8 100644
--- a/src/renderer/src/pages/secondary/HomePage/index.tsx
+++ b/src/renderer/src/pages/secondary/HomePage/index.tsx
@@ -1,10 +1,12 @@
import SecondaryPageLayout from '@renderer/layouts/SecondaryPageLayout'
+import { useTranslation } from 'react-i18next'
export default function HomePage() {
+ const { t } = useTranslation()
return (
- Welcome! 🥳
+ {t('Welcome! 🥳')}
)
diff --git a/src/renderer/src/pages/secondary/NotePage/index.tsx b/src/renderer/src/pages/secondary/NotePage/index.tsx
index 81c7d70..c985b5e 100644
--- a/src/renderer/src/pages/secondary/NotePage/index.tsx
+++ b/src/renderer/src/pages/secondary/NotePage/index.tsx
@@ -12,8 +12,10 @@ import { getParentEventId, getRootEventId } from '@renderer/lib/event'
import { toNote } from '@renderer/lib/link'
import { useMemo } from 'react'
import NotFoundPage from '../NotFoundPage'
+import { useTranslation } from 'react-i18next'
export default function NotePage({ id }: { id?: string }) {
+ const { t } = useTranslation()
const { event, isFetching } = useFetchEventById(id)
const parentEventId = useMemo(() => getParentEventId(event), [event])
const rootEventId = useMemo(() => getRootEventId(event), [event])
@@ -28,7 +30,7 @@ export default function NotePage({ id }: { id?: string }) {
if (!event) return
return (
-
+
diff --git a/src/renderer/src/pages/secondary/ProfilePage/index.tsx b/src/renderer/src/pages/secondary/ProfilePage/index.tsx
index bc22915..f3792c1 100644
--- a/src/renderer/src/pages/secondary/ProfilePage/index.tsx
+++ b/src/renderer/src/pages/secondary/ProfilePage/index.tsx
@@ -5,6 +5,7 @@ import ProfileAbout from '@renderer/components/ProfileAbout'
import ProfileBanner from '@renderer/components/ProfileBanner'
import { Avatar, AvatarFallback, AvatarImage } from '@renderer/components/ui/avatar'
import { Separator } from '@renderer/components/ui/separator'
+import { Skeleton } from '@renderer/components/ui/skeleton'
import { useFetchFollowings, useFetchProfile } from '@renderer/hooks'
import { useFetchRelayList } from '@renderer/hooks/useFetchRelayList'
import SecondaryPageLayout from '@renderer/layouts/SecondaryPageLayout'
@@ -14,13 +15,13 @@ import { SecondaryPageLink } from '@renderer/PageManager'
import { useFollowList } from '@renderer/providers/FollowListProvider'
import { useNostr } from '@renderer/providers/NostrProvider'
import { useMemo } from 'react'
+import NotFoundPage from '../NotFoundPage'
import PubkeyCopy from './PubkeyCopy'
import QrCodePopover from './QrCodePopover'
-import LoadingPage from '../LoadingPage'
-import NotFoundPage from '../NotFoundPage'
-import { Skeleton } from '@renderer/components/ui/skeleton'
+import { useTranslation } from 'react-i18next'
export default function ProfilePage({ id }: { id?: string }) {
+ const { t } = useTranslation()
const { profile, isFetching } = useFetchProfile(id)
const relayList = useFetchRelayList(profile?.pubkey)
const { pubkey: accountPubkey } = useNostr()
@@ -85,10 +86,10 @@ export default function ProfilePage({ id }: { id?: string }) {
{isSelf ? selfFollowings.length : followings.length}
- Following
+ {t('Following')}