feat: 国际化功能基础实现

This commit is contained in:
alger
2025-01-23 11:02:55 +08:00
parent 599b0251af
commit 174428b386
14 changed files with 189 additions and 17 deletions

View File

@@ -27,7 +27,8 @@
"electron-store": "^8.1.0",
"electron-updater": "^6.1.7",
"font-list": "^1.5.1",
"netease-cloud-music-api-alger": "^4.25.0"
"netease-cloud-music-api-alger": "^4.25.0",
"vue-i18n": "9"
},
"devDependencies": {
"@electron-toolkit/eslint-config": "^1.0.2",

29
src/i18n/lang/en-US.ts Normal file
View File

@@ -0,0 +1,29 @@
export default {
common: {
play: 'Play',
pause: 'Pause',
next: 'Next',
previous: 'Previous',
volume: 'Volume',
settings: 'Settings',
search: 'Search',
loading: 'Loading...'
},
settings: {
theme: 'Theme',
language: 'Language',
about: 'About',
logout: 'Logout'
},
player: {
nowPlaying: 'Now Playing',
playlist: 'Playlist',
lyrics: 'Lyrics'
},
artist: {
songs: 'Songs',
albums: 'Albums',
mv: 'MV',
description: 'Description'
}
};

35
src/i18n/lang/zh-CN.ts Normal file
View File

@@ -0,0 +1,35 @@
export default {
common: {
play: '播放',
next: '下一首',
previous: '上一首',
volume: '音量',
settings: '设置',
search: '搜索',
loading: '加载中...',
alipay: '支付宝',
wechat: '微信支付'
},
settings: {
theme: '主题',
language: '语言',
about: '关于',
logout: '退出登录'
},
player: {
nowPlaying: '正在播放',
playlist: '播放列表',
lyrics: '歌词'
},
artist: {
songs: '热门歌曲',
albums: '专辑',
mv: 'MV',
description: '简介'
},
donation: {
description: '您的捐赠将用于支持开发和维护工作,包括但不限于服务器维护、域名续费等。',
message: '留言时可留下您的邮箱或 github名称。',
refresh: '刷新列表'
}
};

28
src/i18n/main.ts Normal file
View File

@@ -0,0 +1,28 @@
import type { I18nOptions } from 'vue-i18n';
import { createI18n } from 'vue-i18n';
import { getStore } from '../main/modules/config';
import enUS from './lang/en-US';
import zhCN from './lang/zh-CN';
// 从配置中获取保存的语言设置
const store = getStore();
const savedLanguage = (store?.get('set.language') as string) || 'zh-CN';
const options = {
legacy: false,
locale: savedLanguage,
fallbackLocale: 'en-US',
messages: {
'zh-CN': zhCN,
'en-US': enUS
},
silentTranslationWarn: true,
silentFallbackWarn: true
} as I18nOptions;
const i18n = createI18n(options);
export const $t: typeof i18n.global.t = i18n.global.t;
export default i18n;

26
src/i18n/renderer.ts Normal file
View File

@@ -0,0 +1,26 @@
import type { I18nOptions } from 'vue-i18n';
import { createI18n } from 'vue-i18n';
import enUS from './lang/en-US';
import zhCN from './lang/zh-CN';
// 从配置中获取保存的语言设置
const savedLanguage =
window?.electron?.ipcRenderer?.sendSync('get-store-value', 'set.language') || 'zh-CN';
const options = {
legacy: false,
locale: savedLanguage,
fallbackLocale: 'en-US',
messages: {
'zh-CN': zhCN,
'en-US': enUS
},
globalInjection: true,
silentTranslationWarn: true,
silentFallbackWarn: true
} as I18nOptions;
const i18n = createI18n(options);
export default i18n;

View File

@@ -2,6 +2,7 @@ import { electronApp, optimizer } from '@electron-toolkit/utils';
import { app, ipcMain, nativeImage } from 'electron';
import { join } from 'path';
import i18n from '../i18n/main';
import { loadLyricWindow } from './lyric';
import { initializeConfig } from './modules/config';
import { initializeFileManager } from './modules/fileManager';
@@ -95,6 +96,13 @@ if (!isSingleInstance) {
registerShortcuts(mainWindow);
});
// 监听语言切换
ipcMain.on('change-language', (_, locale) => {
i18n.global.locale = locale;
// 通知所有窗口语言已更改
mainWindow?.webContents.send('language-changed', locale);
});
// 所有窗口关闭时的处理
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {

View File

@@ -23,6 +23,7 @@ type SetConfig = {
musicQuality: string;
fontFamily: string;
fontScope: 'global' | 'lyric';
language: string;
};
interface StoreType {
set: SetConfig;

View File

@@ -18,5 +18,6 @@
"fontFamily": "system-ui",
"fontScope": "global",
"autoPlay": false,
"downloadPath": ""
"downloadPath": "",
"language": "zh-CN"
}

View File

@@ -0,0 +1,36 @@
<script setup lang="ts">
import { onMounted } from 'vue';
import { useI18n } from 'vue-i18n';
const { locale } = useI18n();
const languages = [
{ label: '简体中文', value: 'zh-CN' },
{ label: 'English', value: 'en-US' }
];
// 从配置中读取语言设置
onMounted(() => {
const savedLanguage = window.electron.ipcRenderer.sendSync('get-store-value', 'set.language');
if (savedLanguage) {
locale.value = savedLanguage;
}
});
const handleLanguageChange = (value: string) => {
locale.value = value;
// 保存语言设置到配置中
window.electron.ipcRenderer.send('set-store-value', 'set.language', value);
// 通知主进程语言已更改
window.electron.ipcRenderer.send('change-language', value);
};
</script>
<template>
<n-select
v-model:value="locale"
:options="languages"
size="small"
@update:value="handleLanguageChange"
/>
</template>

View File

@@ -33,7 +33,7 @@
<!-- 标签页切换 -->
<n-tabs v-model:value="activeTab" class="flex-1" type="line" animated>
<n-tab-pane name="songs" tab="热门歌曲">
<n-tab-pane name="songs" :tab="$t('artist.songs')">
<div ref="songListRef" class="songs-list">
<n-scrollbar style="max-height: 61vh" :size="5" @scroll="handleSongScroll">
<div class="song-list-content">
@@ -44,14 +44,14 @@
:list="true"
@play="handlePlay"
/>
<div v-if="songLoading" class="loading-more">加载中...</div>
<div v-if="songLoading" class="loading-more">{{ $t('common.loading') }}</div>
</div>
<play-bottom />
</n-scrollbar>
</div>
</n-tab-pane>
<n-tab-pane name="albums" tab="专辑">
<n-tab-pane name="albums" :tab="$t('artist.albums')">
<div ref="albumListRef" class="albums-list">
<n-scrollbar style="max-height: 61vh" :size="5" @scroll="handleAlbumScroll">
<div class="albums-grid">
@@ -69,14 +69,14 @@
type: '专辑'
}"
/>
<div v-if="albumLoading" class="loading-more">加载中...</div>
<div v-if="albumLoading" class="loading-more">{{ $t('common.loading') }}</div>
</div>
<play-bottom />
</n-scrollbar>
</div>
</n-tab-pane>
<n-tab-pane name="about" tab="艺人介绍">
<n-tab-pane name="about" :tab="$t('artist.description')">
<div class="artist-description">
<n-scrollbar style="max-height: 60vh">
<div class="description-content" v-html="artistInfo?.briefDesc"></div>

View File

@@ -5,7 +5,7 @@
<template #icon>
<i class="ri-refresh-line"></i>
</template>
刷新列表
{{ $t('donation.refresh') }}
</n-button>
</div>
<div class="donation-grid" :class="{ 'grid-expanded': isExpanded }">
@@ -78,27 +78,27 @@
<div class="p-6 rounded-lg shadow-lg bg-light dark:bg-gray-800">
<div class="description text-center text-sm text-gray-700 dark:text-gray-200">
<p>您的捐赠将用于支持开发和维护工作包括但不限于服务器维护域名续费等</p>
<p>留言时可留下您的邮箱或 github名称</p>
<p>{{ $t('donation.description') }}</p>
<p>{{ $t('donation.message') }}</p>
</div>
<div class="flex justify-between">
<div class="flex flex-col items-center gap-2">
<n-image
:src="alipay"
alt="支付宝收款码"
:alt="$t('common.alipay')"
class="w-60 h-60 rounded-lg cursor-none"
preview-disabled
/>
<span class="text-sm text-gray-700 dark:text-gray-200">支付宝</span>
<span class="text-sm text-gray-700 dark:text-gray-200">{{ $t('common.alipay') }}</span>
</div>
<div class="flex flex-col items-center gap-2">
<n-image
:src="wechat"
alt="微信收款码"
:alt="$t('common.wechat')"
class="w-60 h-60 rounded-lg cursor-none"
preview-disabled
/>
<span class="text-sm text-gray-700 dark:text-gray-200">微信支付</span>
<span class="text-sm text-gray-700 dark:text-gray-200">{{ $t('common.wechat') }}</span>
</div>
</div>
</div>

View File

@@ -7,6 +7,7 @@ import 'remixicon/fonts/remixicon.css';
import { createApp } from 'vue';
import i18n from '@/../i18n/renderer';
import router from '@/router';
import store from '@/store';
@@ -18,6 +19,8 @@ const app = createApp(App);
Object.keys(directives).forEach((key: string) => {
app.directive(key, directives[key as keyof typeof directives]);
});
app.use(router);
app.use(store);
app.use(i18n);
app.mount('#app');

View File

@@ -1,6 +1,6 @@
{
"extends": "@electron-toolkit/tsconfig/tsconfig.node.json",
"include": ["electron.vite.config.*", "src/main/**/*", "src/preload/**/*"],
"include": ["electron.vite.config.*", "src/main/**/*", "src/preload/**/*", "src/i18n/**/*"],
"compilerOptions": {
"composite": true,
"types": ["electron-vite/node"]

View File

@@ -3,7 +3,10 @@
"include": [
"src/preload/*.d.ts",
"src/renderer/**/*",
"src/renderer/**/*.vue"
"src/renderer/**/*.vue",
"src/i18n/**/*",
"src/main/modules/config.ts",
"src/main/modules/shortcuts.ts"
],
"compilerOptions": {
"composite": true,
@@ -24,7 +27,8 @@
],
"paths": {
"@/*": ["src/renderer/*"],
"@renderer/*": ["src/renderer/*"]
"@renderer/*": ["src/renderer/*"],
"@main/*": ["src/main/*"]
}
}
}