mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-25 03:10:08 +08:00
feat: integrate i18n support into migration process
- Added internationalization support to the MigrationApp component, enabling dynamic language changes. - Updated button labels and informational texts to use translation keys for better localization. - Introduced a language selector to allow users to switch between languages during the migration process. - Ensured that the migration process waits for i18n initialization before rendering the main application.
This commit is contained in:
parent
db10bdd539
commit
1685590a07
@ -1,9 +1,10 @@
|
||||
import { Button } from '@cherrystudio/ui'
|
||||
import { Button, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@cherrystudio/ui'
|
||||
import { AppLogo } from '@renderer/config/env'
|
||||
import { loggerService } from '@renderer/services/LoggerService'
|
||||
import { Progress, Space, Steps } from 'antd'
|
||||
import { AlertTriangle, CheckCircle, CheckCircle2, Database, Loader2, Rocket } from 'lucide-react'
|
||||
import React, { useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import { MigratorProgressList } from './components'
|
||||
@ -14,10 +15,15 @@ import { MigrationIpcChannels } from './types'
|
||||
const logger = loggerService.withContext('MigrationApp')
|
||||
|
||||
const MigrationApp: React.FC = () => {
|
||||
const { t, i18n } = useTranslation()
|
||||
const { progress, lastError, confirmComplete } = useMigrationProgress()
|
||||
const actions = useMigrationActions()
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
|
||||
const handleLanguageChange = (lang: string) => {
|
||||
i18n.changeLanguage(lang)
|
||||
}
|
||||
|
||||
const handleStartMigration = async () => {
|
||||
setIsLoading(true)
|
||||
try {
|
||||
@ -118,19 +124,19 @@ const MigrationApp: React.FC = () => {
|
||||
case 'introduction':
|
||||
return (
|
||||
<>
|
||||
<Button onClick={actions.cancel}>取消</Button>
|
||||
<Button onClick={actions.cancel}>{t('migration.buttons.cancel')}</Button>
|
||||
<Spacer />
|
||||
<Button onClick={actions.proceedToBackup}>下一步</Button>
|
||||
<Button onClick={actions.proceedToBackup}>{t('migration.buttons.next')}</Button>
|
||||
</>
|
||||
)
|
||||
case 'backup_required':
|
||||
return (
|
||||
<>
|
||||
<Button onClick={actions.cancel}>取消</Button>
|
||||
<Button onClick={actions.cancel}>{t('migration.buttons.cancel')}</Button>
|
||||
<Spacer />
|
||||
<Space>
|
||||
<Button onClick={actions.showBackupDialog}>创建备份</Button>
|
||||
<Button onClick={actions.confirmBackup}>我已备份,开始迁移</Button>
|
||||
<Button onClick={actions.showBackupDialog}>{t('migration.buttons.create_backup')}</Button>
|
||||
<Button onClick={actions.confirmBackup}>{t('migration.buttons.confirm_backup')}</Button>
|
||||
</Space>
|
||||
</>
|
||||
)
|
||||
@ -139,17 +145,17 @@ const MigrationApp: React.FC = () => {
|
||||
<ButtonRow>
|
||||
<div></div>
|
||||
<Button disabled loading>
|
||||
正在备份...
|
||||
{t('migration.buttons.backing_up')}
|
||||
</Button>
|
||||
</ButtonRow>
|
||||
)
|
||||
case 'backup_confirmed':
|
||||
return (
|
||||
<ButtonRow>
|
||||
<Button onClick={actions.cancel}>取消</Button>
|
||||
<Button onClick={actions.cancel}>{t('migration.buttons.cancel')}</Button>
|
||||
<Space>
|
||||
<Button onClick={handleStartMigration} loading={isLoading}>
|
||||
开始迁移
|
||||
{t('migration.buttons.start_migration')}
|
||||
</Button>
|
||||
</Space>
|
||||
</ButtonRow>
|
||||
@ -158,29 +164,29 @@ const MigrationApp: React.FC = () => {
|
||||
return (
|
||||
<ButtonRow>
|
||||
<div></div>
|
||||
<Button disabled>迁移进行中...</Button>
|
||||
<Button disabled>{t('migration.buttons.migrating')}</Button>
|
||||
</ButtonRow>
|
||||
)
|
||||
case 'migration_completed':
|
||||
return (
|
||||
<ButtonRow>
|
||||
<div></div>
|
||||
<Button onClick={confirmComplete}>确定</Button>
|
||||
<Button onClick={confirmComplete}>{t('migration.buttons.confirm')}</Button>
|
||||
</ButtonRow>
|
||||
)
|
||||
case 'completed':
|
||||
return (
|
||||
<ButtonRow>
|
||||
<div></div>
|
||||
<Button onClick={actions.restart}>重启应用</Button>
|
||||
<Button onClick={actions.restart}>{t('migration.buttons.restart')}</Button>
|
||||
</ButtonRow>
|
||||
)
|
||||
case 'error':
|
||||
return (
|
||||
<ButtonRow>
|
||||
<Button onClick={actions.cancel}>关闭应用</Button>
|
||||
<Button onClick={actions.cancel}>{t('migration.buttons.close')}</Button>
|
||||
<Space>
|
||||
<Button onClick={actions.retry}>重新尝试</Button>
|
||||
<Button onClick={actions.retry}>{t('migration.buttons.retry')}</Button>
|
||||
</Space>
|
||||
</ButtonRow>
|
||||
)
|
||||
@ -193,7 +199,7 @@ const MigrationApp: React.FC = () => {
|
||||
<Container>
|
||||
<Header>
|
||||
<HeaderLogo src={AppLogo} />
|
||||
<HeaderTitle>数据迁移向导</HeaderTitle>
|
||||
<HeaderTitle>{t('migration.title')}</HeaderTitle>
|
||||
</Header>
|
||||
|
||||
<MainContent>
|
||||
@ -204,9 +210,25 @@ const MigrationApp: React.FC = () => {
|
||||
current={currentStep}
|
||||
status={stepStatus}
|
||||
size="small"
|
||||
items={[{ title: '介绍' }, { title: '备份' }, { title: '迁移' }, { title: '完成' }]}
|
||||
items={[
|
||||
{ title: t('migration.stages.introduction') },
|
||||
{ title: t('migration.stages.backup') },
|
||||
{ title: t('migration.stages.migration') },
|
||||
{ title: t('migration.stages.completed') }
|
||||
]}
|
||||
/>
|
||||
</StepsContainer>
|
||||
<LanguageSelectorContainer>
|
||||
<Select value={i18n.language} onValueChange={handleLanguageChange}>
|
||||
<SelectTrigger size="sm">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="zh-CN">中文</SelectItem>
|
||||
<SelectItem value="en-US">English</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</LanguageSelectorContainer>
|
||||
</LeftSidebar>
|
||||
|
||||
<RightContent>
|
||||
@ -215,46 +237,44 @@ const MigrationApp: React.FC = () => {
|
||||
|
||||
{progress.stage === 'introduction' && (
|
||||
<InfoCard>
|
||||
<InfoTitle>将数据迁移到新的架构中</InfoTitle>
|
||||
<InfoTitle>{t('migration.introduction.title')}</InfoTitle>
|
||||
<InfoDescription>
|
||||
Cherry Studio对数据的存储和使用方式进行了重大重构,在新的架构下,效率和安全性将会得到极大提升。
|
||||
{t('migration.introduction.description_1')}
|
||||
<br />
|
||||
<br />
|
||||
数据必须进行迁移,才能在新版本中使用。
|
||||
{t('migration.introduction.description_2')}
|
||||
<br />
|
||||
<br />
|
||||
我们会指导你完成迁移,迁移过程不会损坏原来的数据,你随时可以取消迁移,并继续使用旧版本。
|
||||
{t('migration.introduction.description_3')}
|
||||
</InfoDescription>
|
||||
</InfoCard>
|
||||
)}
|
||||
|
||||
{progress.stage === 'backup_required' && (
|
||||
<InfoCard variant="warning">
|
||||
<InfoTitle>创建数据备份</InfoTitle>
|
||||
<InfoDescription>
|
||||
迁移前必须创建数据备份以确保数据安全。请选择备份位置或确认已有最新备份。
|
||||
</InfoDescription>
|
||||
<InfoTitle>{t('migration.backup_required.title')}</InfoTitle>
|
||||
<InfoDescription>{t('migration.backup_required.description')}</InfoDescription>
|
||||
</InfoCard>
|
||||
)}
|
||||
|
||||
{progress.stage === 'backup_progress' && (
|
||||
<InfoCard variant="warning">
|
||||
<InfoTitle>准备数据备份</InfoTitle>
|
||||
<InfoDescription>请选择备份位置,保存后等待备份完成。</InfoDescription>
|
||||
<InfoTitle>{t('migration.backup_progress.title')}</InfoTitle>
|
||||
<InfoDescription>{t('migration.backup_progress.description')}</InfoDescription>
|
||||
</InfoCard>
|
||||
)}
|
||||
|
||||
{progress.stage === 'backup_confirmed' && (
|
||||
<InfoCard variant="success">
|
||||
<InfoTitle>备份完成</InfoTitle>
|
||||
<InfoDescription>数据备份已完成,现在可以安全地开始迁移。</InfoDescription>
|
||||
<InfoTitle>{t('migration.backup_confirmed.title')}</InfoTitle>
|
||||
<InfoDescription>{t('migration.backup_confirmed.description')}</InfoDescription>
|
||||
</InfoCard>
|
||||
)}
|
||||
|
||||
{progress.stage === 'migration' && (
|
||||
<div style={{ width: '100%', maxWidth: '600px', margin: '0 auto' }}>
|
||||
<InfoCard>
|
||||
<InfoTitle>正在迁移数据...</InfoTitle>
|
||||
<InfoTitle>{t('migration.migration.title')}</InfoTitle>
|
||||
<InfoDescription>{progress.currentMessage}</InfoDescription>
|
||||
</InfoCard>
|
||||
<ProgressContainer>
|
||||
@ -273,8 +293,8 @@ const MigrationApp: React.FC = () => {
|
||||
{progress.stage === 'migration_completed' && (
|
||||
<div style={{ width: '100%', maxWidth: '600px', margin: '0 auto' }}>
|
||||
<InfoCard variant="success">
|
||||
<InfoTitle>数据迁移完成!</InfoTitle>
|
||||
<InfoDescription>所有数据已成功迁移到新架构,请点击确定继续。</InfoDescription>
|
||||
<InfoTitle>{t('migration.migration_completed.title')}</InfoTitle>
|
||||
<InfoDescription>{t('migration.migration_completed.description')}</InfoDescription>
|
||||
</InfoCard>
|
||||
<ProgressContainer>
|
||||
<Progress percent={100} strokeColor={getProgressColor()} trailColor="#f0f0f0" />
|
||||
@ -287,19 +307,20 @@ const MigrationApp: React.FC = () => {
|
||||
|
||||
{progress.stage === 'completed' && (
|
||||
<InfoCard variant="success">
|
||||
<InfoTitle>迁移完成</InfoTitle>
|
||||
<InfoDescription>数据已成功迁移,重启应用后即可正常使用。</InfoDescription>
|
||||
<InfoTitle>{t('migration.completed.title')}</InfoTitle>
|
||||
<InfoDescription>{t('migration.completed.description')}</InfoDescription>
|
||||
</InfoCard>
|
||||
)}
|
||||
|
||||
{progress.stage === 'error' && (
|
||||
<InfoCard variant="error">
|
||||
<InfoTitle>迁移失败</InfoTitle>
|
||||
<InfoTitle>{t('migration.error.title')}</InfoTitle>
|
||||
<InfoDescription>
|
||||
迁移过程遇到错误,您可以重新尝试或继续使用之前版本(原始数据完好保存)。
|
||||
{t('migration.error.description')}
|
||||
<br />
|
||||
<br />
|
||||
错误信息:{lastError || progress.error || '发生未知错误'}
|
||||
{t('migration.error.error_prefix')}
|
||||
{lastError || progress.error || 'Unknown error'}
|
||||
</InfoDescription>
|
||||
</InfoCard>
|
||||
)}
|
||||
@ -385,6 +406,11 @@ const StepsContainer = styled.div`
|
||||
}
|
||||
`
|
||||
|
||||
const LanguageSelectorContainer = styled.div`
|
||||
padding: 16px 24px 24px 24px;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
`
|
||||
|
||||
const RightContent = styled.div`
|
||||
flex: 1;
|
||||
display: flex;
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
|
||||
import { CheckCircle2, Circle, Loader2, XCircle } from 'lucide-react'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled, { keyframes } from 'styled-components'
|
||||
|
||||
import type { MigratorProgress as MigratorProgressType, MigratorStatus } from '../types'
|
||||
@ -41,14 +42,13 @@ const SpinningIcon = styled.div`
|
||||
animation: ${spin} 1s linear infinite;
|
||||
`
|
||||
|
||||
const statusTextMap: Record<MigratorStatus, string> = {
|
||||
pending: '等待中',
|
||||
running: '进行中',
|
||||
completed: '完成',
|
||||
failed: '失败'
|
||||
}
|
||||
|
||||
export const MigratorProgressList: React.FC<Props> = ({ migrators }) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const getStatusText = (status: MigratorStatus): string => {
|
||||
return t(`migration.status.${status}`)
|
||||
}
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<List>
|
||||
@ -58,7 +58,7 @@ export const MigratorProgressList: React.FC<Props> = ({ migrators }) => {
|
||||
<StatusIcon status={migrator.status} />
|
||||
<ItemName>{migrator.name}</ItemName>
|
||||
</ItemLeft>
|
||||
<ItemStatus status={migrator.status}>{migrator.error || statusTextMap[migrator.status]}</ItemStatus>
|
||||
<ItemStatus status={migrator.status}>{migrator.error || getStatusText(migrator.status)}</ItemStatus>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
|
||||
@ -9,6 +9,7 @@ import '@ant-design/v5-patch-for-react-19'
|
||||
import { loggerService } from '@logger'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
|
||||
import { initI18n } from './i18n'
|
||||
import MigrationApp from './MigrationApp'
|
||||
|
||||
// Initialize logger for this window
|
||||
@ -16,4 +17,7 @@ loggerService.initWindowSource('MigrationV2')
|
||||
|
||||
const root = createRoot(document.getElementById('root') as HTMLElement)
|
||||
|
||||
root.render(<MigrationApp />)
|
||||
// Wait for i18n to be fully initialized before rendering
|
||||
initI18n().then(() => {
|
||||
root.render(<MigrationApp />)
|
||||
})
|
||||
|
||||
43
src/renderer/src/windows/migrationV2/i18n/index.ts
Normal file
43
src/renderer/src/windows/migrationV2/i18n/index.ts
Normal file
@ -0,0 +1,43 @@
|
||||
/**
|
||||
* i18n initialization for migration window
|
||||
* Detects system language independently without relying on preferenceService
|
||||
*/
|
||||
|
||||
import i18n from 'i18next'
|
||||
import { initReactI18next } from 'react-i18next'
|
||||
|
||||
import { enUS, zhCN } from './locales'
|
||||
|
||||
/**
|
||||
* Detect system language independently
|
||||
* Rule: If system language contains 'zh', use Chinese, otherwise use English
|
||||
*/
|
||||
function detectLanguage(): 'zh-CN' | 'en-US' {
|
||||
const browserLang = navigator.language || navigator.languages?.[0] || 'en-US'
|
||||
|
||||
// If contains 'zh' (zh, zh-CN, zh-TW, zh-HK, etc.), use Chinese
|
||||
return browserLang.toLowerCase().includes('zh') ? 'zh-CN' : 'en-US'
|
||||
}
|
||||
|
||||
const language = detectLanguage()
|
||||
|
||||
/**
|
||||
* Initialize i18n asynchronously
|
||||
* Must be called and awaited before rendering components
|
||||
*/
|
||||
const initI18n = async () => {
|
||||
await i18n.use(initReactI18next).init({
|
||||
resources: {
|
||||
'zh-CN': { translation: zhCN },
|
||||
'en-US': { translation: enUS }
|
||||
},
|
||||
lng: language,
|
||||
fallbackLng: 'en-US',
|
||||
interpolation: {
|
||||
escapeValue: false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export default i18n
|
||||
export { initI18n }
|
||||
139
src/renderer/src/windows/migrationV2/i18n/locales.ts
Normal file
139
src/renderer/src/windows/migrationV2/i18n/locales.ts
Normal file
@ -0,0 +1,139 @@
|
||||
/**
|
||||
* Migration window translations
|
||||
* Supports Chinese (zh-CN) and English (en-US)
|
||||
*/
|
||||
|
||||
export const zhCN = {
|
||||
migration: {
|
||||
title: '数据迁移向导',
|
||||
stages: {
|
||||
introduction: '介绍',
|
||||
backup: '备份',
|
||||
migration: '迁移',
|
||||
completed: '完成'
|
||||
},
|
||||
buttons: {
|
||||
cancel: '取消',
|
||||
next: '下一步',
|
||||
create_backup: '创建备份',
|
||||
confirm_backup: '我已备份,开始迁移',
|
||||
start_migration: '开始迁移',
|
||||
confirm: '确定',
|
||||
restart: '重启应用',
|
||||
retry: '重新尝试',
|
||||
close: '关闭应用',
|
||||
backing_up: '正在备份...',
|
||||
migrating: '迁移进行中...'
|
||||
},
|
||||
status: {
|
||||
pending: '等待中',
|
||||
running: '进行中',
|
||||
completed: '完成',
|
||||
failed: '失败'
|
||||
},
|
||||
introduction: {
|
||||
title: '将数据迁移到新的架构中',
|
||||
description_1:
|
||||
'Cherry Studio对数据的存储和使用方式进行了重大重构,在新的架构下,效率和安全性将会得到极大提升。',
|
||||
description_2: '数据必须进行迁移,才能在新版本中使用。',
|
||||
description_3: '我们会指导你完成迁移,迁移过程不会损坏原来的数据,你随时可以取消迁移,并继续使用旧版本。'
|
||||
},
|
||||
backup_required: {
|
||||
title: '创建数据备份',
|
||||
description: '迁移前必须创建数据备份以确保数据安全。请选择备份位置或确认已有最新备份。'
|
||||
},
|
||||
backup_progress: {
|
||||
title: '准备数据备份',
|
||||
description: '请选择备份位置,保存后等待备份完成。'
|
||||
},
|
||||
backup_confirmed: {
|
||||
title: '备份完成',
|
||||
description: '数据备份已完成,现在可以安全地开始迁移。'
|
||||
},
|
||||
migration: {
|
||||
title: '正在迁移数据...'
|
||||
},
|
||||
migration_completed: {
|
||||
title: '数据迁移完成!',
|
||||
description: '所有数据已成功迁移到新架构,请点击确定继续。'
|
||||
},
|
||||
completed: {
|
||||
title: '迁移完成',
|
||||
description: '数据已成功迁移,重启应用后即可正常使用。'
|
||||
},
|
||||
error: {
|
||||
title: '迁移失败',
|
||||
description: '迁移过程遇到错误,您可以重新尝试或继续使用之前版本(原始数据完好保存)。',
|
||||
error_prefix: '错误信息:'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const enUS = {
|
||||
migration: {
|
||||
title: 'Data Migration Wizard',
|
||||
stages: {
|
||||
introduction: 'Introduction',
|
||||
backup: 'Backup',
|
||||
migration: 'Migration',
|
||||
completed: 'Completed'
|
||||
},
|
||||
buttons: {
|
||||
cancel: 'Cancel',
|
||||
next: 'Next',
|
||||
create_backup: 'Create Backup',
|
||||
confirm_backup: 'I Have Backup, Start Migration',
|
||||
start_migration: 'Start Migration',
|
||||
confirm: 'OK',
|
||||
restart: 'Restart App',
|
||||
retry: 'Retry',
|
||||
close: 'Close App',
|
||||
backing_up: 'Backing up...',
|
||||
migrating: 'Migrating...'
|
||||
},
|
||||
status: {
|
||||
pending: 'Pending',
|
||||
running: 'Running',
|
||||
completed: 'Completed',
|
||||
failed: 'Failed'
|
||||
},
|
||||
introduction: {
|
||||
title: 'Migrate Data to New Architecture',
|
||||
description_1:
|
||||
'Cherry Studio has undergone a major refactoring of data storage and usage. The new architecture will greatly improve efficiency and security.',
|
||||
description_2: 'Data migration is required to use the new version.',
|
||||
description_3:
|
||||
'We will guide you through the migration process. The migration will not damage your original data, and you can cancel at any time and continue using the old version.'
|
||||
},
|
||||
backup_required: {
|
||||
title: 'Create Data Backup',
|
||||
description:
|
||||
'A data backup must be created before migration to ensure data safety. Please select a backup location or confirm you have a recent backup.'
|
||||
},
|
||||
backup_progress: {
|
||||
title: 'Preparing Data Backup',
|
||||
description: 'Please select a backup location, save, and wait for the backup to complete.'
|
||||
},
|
||||
backup_confirmed: {
|
||||
title: 'Backup Completed',
|
||||
description: 'Data backup has been completed. You can now safely start the migration.'
|
||||
},
|
||||
migration: {
|
||||
title: 'Migrating Data...'
|
||||
},
|
||||
migration_completed: {
|
||||
title: 'Data Migration Completed!',
|
||||
description: 'All data has been successfully migrated to the new architecture. Please click OK to continue.'
|
||||
},
|
||||
completed: {
|
||||
title: 'Migration Completed',
|
||||
description: 'Data has been successfully migrated. The application will work normally after restart.'
|
||||
},
|
||||
error: {
|
||||
title: 'Migration Failed',
|
||||
description:
|
||||
'An error occurred during migration. You can retry or continue using the previous version (original data is intact).',
|
||||
error_prefix: 'Error: '
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user