Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(stage-tamagotchi): electron i18n #25

Merged
merged 5 commits into from
Feb 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"@types/node": "^22.13.4",
"@unocss/eslint-config": "^65.5.0",
"@unocss/eslint-plugin": "^65.5.0",
"@vitest/coverage-v8": "3.0.5",
"bumpp": "^10.0.3",
"cross-env": "^7.0.3",
"eslint": "^9.20.1",
Expand Down
2 changes: 1 addition & 1 deletion packages/stage-tamagotchi/renderer.vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export default defineConfig({
runtimeOnly: true,
compositionOnly: true,
fullInstall: true,
include: [resolve(import.meta.dirname, 'locales/**')],
include: [resolve(import.meta.dirname, 'src', 'renderer', 'locales/**')],
}),
DownloadLive2DSDK(),
Download('https://dist.ayaka.moe/live2d-models/hiyori_free_zh.zip', 'hiyori_free_zh.zip', 'assets/live2d/models'),
Expand Down
40 changes: 27 additions & 13 deletions packages/stage-tamagotchi/src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import { app, BrowserWindow, dialog, ipcMain, Menu, screen, shell } from 'electr
import { animate } from 'popmotion'

import icon from '../../build/icon.png?asset'
import { createI18n } from './locales'

const { t, setLocale } = createI18n()

let globalMouseTracker: ReturnType<typeof setInterval> | null = null
let mainWindow: BrowserWindow
Expand Down Expand Up @@ -138,33 +141,39 @@ function createSettingsWindow(): void {
}
}

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(() => {
// Menu
const menu = Menu.buildFromTemplate([
function buildMenu(): Menu {
return Menu.buildFromTemplate([
{
label: 'airi',
role: 'appMenu',
submenu: [
{
role: 'about',
label: t('menu.about'),
},
{
role: 'toggleDevTools',
label: t('menu.toggleDevTools'),
},
{
label: 'Settings',
label: t('menu.settings'),
click: () => createSettingsWindow(),
},
{
label: 'Quit',
label: t('menu.quit'),
click: () => app.quit(),
},
],
},
])
}

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(() => {
// Menu
const menu = buildMenu()
Menu.setApplicationMenu(menu)

// Set app user model id for windows
Expand All @@ -177,14 +186,19 @@ app.whenReady().then(() => {
optimizer.watchWindowShortcuts(window)
})

// IPC test
// TODO: i18n
ipcMain.on('locale-changed', (_, locale: string) => {
setLocale(locale)

Menu.setApplicationMenu(buildMenu())
})

ipcMain.on('quit', () => {
dialog.showMessageBox({
type: 'info',
title: 'Quit',
message: 'Are you sure you want to quit?',
buttons: ['Quit', 'Cancel'],
title: t('quitDialog.title'),
message: t('quitDialog.message'),
buttons: [t('quitDialog.buttons.0'), t('quitDialog.buttons.1')],
defaultId: 0,
}).then((result) => {
if (result.response === 0) {
app.quit()
Expand Down
13 changes: 13 additions & 0 deletions packages/stage-tamagotchi/src/main/locales/en-US.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export default {
menu: {
settings: 'Settings',
quit: 'Quit',
about: 'About',
toggleDevTools: 'Toggle Developer Tools',
},
quitDialog: {
title: 'Quit',
message: 'Are you sure you want to quit?',
buttons: ['Quit', 'Cancel'],
},
}
27 changes: 27 additions & 0 deletions packages/stage-tamagotchi/src/main/locales/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { describe, expect, it } from 'vitest'

import { createI18n } from '.'

describe('createI18n', () => {
it('should return the correct locale', () => {
const { t } = createI18n()
expect(t('menu.settings')).toBe('Settings')
})

it('should return key if the key is not found', () => {
const { t } = createI18n()
expect(t('menu.not.found')).toBe('menu.not.found')
})

it('should set the correct locale', () => {
const { t, setLocale } = createI18n()
setLocale('zh-CN')
expect(t('menu.settings')).toBe('设置')
})

it('should return the correct locale in array', () => {
const { t } = createI18n()
expect(t('quitDialog.buttons.0')).toBe('Quit')
expect(t('quitDialog.buttons.1')).toBe('Cancel')
})
})
46 changes: 46 additions & 0 deletions packages/stage-tamagotchi/src/main/locales/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import enUS from './en-US'
import zhCN from './zh-CN'

// TODO: compact locales, such as 'en' can be 'en-US'
const locales = {
'en-US': enUS,
'zh-CN': zhCN,
}

export function createI18n() {
let locale = 'en-US'
let messages = locales['en-US']

function t(key: string) {
const path = key.split('.')
let current = messages
let result = ''

while (path.length > 0) {
const k = path.shift()
if (k && current && k in current) {
current = current[k]
}
else {
return key
}
}

if (typeof current === 'string') {
result = current
}

return result
}

function setLocale(l: string) {
locale = l
messages = locales[l]
}

return {
t,
setLocale,
locale,
}
}
13 changes: 13 additions & 0 deletions packages/stage-tamagotchi/src/main/locales/zh-CN.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export default {
menu: {
settings: '设置',
quit: '退出',
about: '关于',
toggleDevTools: '切换开发者工具',
},
quitDialog: {
title: '退出',
message: '确定要退出吗?',
buttons: ['退出', '取消'],
},
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ settings:
placeholder_mobile: OpenAI API BaseURL
title: Settings
voices: Voice
quit: Quit
viewer: Viewer
stage:
chat:
message:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ settings:
placeholder_mobile: OpenAI BaseURL
title: 设置
voices: 声线
quit: 退出
viewer: 查看器
stage:
message: 消息
select-a-audio-input: 选择一个音频输入设备
Expand Down
11 changes: 11 additions & 0 deletions packages/stage-tamagotchi/src/renderer/src/App.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
<script setup lang="ts">
import { useSettings } from '@proj-airi/stage-ui/stores'
import { watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { RouterView } from 'vue-router'

const settings = useSettings()
const i18n = useI18n()

watch(() => settings.language, (language) => {
i18n.locale.value = language
window.electron.ipcRenderer.send('locale-changed', language)
})
</script>

<template>
Expand Down
10 changes: 5 additions & 5 deletions packages/stage-tamagotchi/src/renderer/src/pages/settings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const { t, locale } = useI18n()
const settings = useSettings()
const supportedModels = ref<{ id: string, name?: string }[]>([])
const { models } = useLLM()
const { openAiModel, openAiApiBaseURL, openAiApiKey, elevenlabsVoiceEnglish, elevenlabsVoiceJapanese } = storeToRefs(settings)
const { openAiModel, openAiApiBaseURL, openAiApiKey, elevenlabsVoiceEnglish, elevenlabsVoiceJapanese, language } = storeToRefs(settings)

function handleModelChange(event: Event) {
const target = event.target as HTMLSelectElement
Expand Down Expand Up @@ -119,7 +119,7 @@ function handleQuit() {
</div>
<div flex="~ row" w-full text="xs">
<select
v-model="settings.language"
v-model="language"
h-6 w-full rounded-md bg-transparent px-2 py-1 text-right font-mono outline-none
>
<option value="en-US">
Expand Down Expand Up @@ -186,7 +186,7 @@ function handleQuit() {
bg="[#fff6fc]" px-2 py-1 text="pink-400"
>
<div text="xs pink-500">
<span>Viewer</span>
<span>{{ t('settings.viewer') }}</span>
</div>
<select
h-6 w-full rounded-md bg-transparent px-2 py-1 text-right text-xs font-mono outline-none
Expand All @@ -202,7 +202,7 @@ function handleQuit() {
</div>
</div>
<h2 text="slate-800/80" font-bold>
Other
{{ t('settings.other') }}
</h2>
<div pb-2>
<div
Expand All @@ -211,7 +211,7 @@ function handleQuit() {
>
<div text="xs pink-500">
<span>
Quit
{{ t('settings.quit') }}
</span>
</div>
<div text="sm pink-500" text-right>
Expand Down
Loading