feat(stage-tamagotchi): electron i18n (#25)

This commit is contained in:
LemonNeko
2025-02-19 22:39:53 +08:00
committed by GitHub
parent 58d14dacf9
commit 2854483b6a
12 changed files with 250 additions and 136 deletions
+1
View File
@@ -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",
@@ -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'),
+27 -13
View File
@@ -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
@@ -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
@@ -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()
@@ -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'],
},
}
@@ -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')
})
})
@@ -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,
}
}
@@ -0,0 +1,13 @@
export default {
menu: {
settings: '设置',
quit: '退出',
about: '关于',
toggleDevTools: '切换开发者工具',
},
quitDialog: {
title: '退出',
message: '确定要退出吗?',
buttons: ['退出', '取消'],
},
}
@@ -63,6 +63,8 @@ settings:
placeholder_mobile: OpenAI API BaseURL
title: Settings
voices: Voice
quit: Quit
viewer: Viewer
stage:
chat:
message:
@@ -50,6 +50,8 @@ settings:
placeholder_mobile: OpenAI BaseURL
title: 设置
voices: 声线
quit: 退出
viewer: 查看器
stage:
message: 消息
select-a-audio-input: 选择一个音频输入设备
@@ -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>
@@ -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
@@ -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">
@@ -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
@@ -202,7 +202,7 @@ function handleQuit() {
</div>
</div>
<h2 text="slate-800/80" font-bold>
Other
{{ t('settings.other') }}
</h2>
<div pb-2>
<div
@@ -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>
+102 -117
View File
@@ -53,6 +53,9 @@ importers:
'@unocss/eslint-plugin':
specifier: ^65.5.0
version: 65.5.0(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3)
'@vitest/coverage-v8':
specifier: 3.0.5
version: 3.0.5(vitest@3.0.5(@types/debug@4.1.12)(@types/node@22.13.4)(jiti@2.4.2)(jsdom@25.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(less@4.2.1)(terser@5.17.6)(tsx@4.19.2)(yaml@2.7.0))
bumpp:
specifier: ^10.0.3
version: 10.0.3(magicast@0.3.5)
@@ -335,9 +338,6 @@ importers:
'@huggingface/transformers':
specifier: ^3.3.3
version: 3.3.3
'@motionone/dom':
specifier: ^10.18.0
version: 10.18.0
'@pixi/app':
specifier: '6'
version: 6.5.10(@pixi/core@6.5.10(@pixi/constants@6.5.10)(@pixi/extensions@6.5.10)(@pixi/math@6.5.10)(@pixi/runner@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10))(@pixi/ticker@6.5.10(@pixi/extensions@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10)))(@pixi/utils@6.5.10(@pixi/constants@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10))))(@pixi/display@6.5.10(@pixi/constants@6.5.10)(@pixi/math@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10))(@pixi/utils@6.5.10(@pixi/constants@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10))))(@pixi/math@6.5.10)(@pixi/utils@6.5.10(@pixi/constants@6.5.10)(@pixi/settings@6.5.10(@pixi/constants@6.5.10)))
@@ -443,12 +443,6 @@ importers:
defu:
specifier: ^6.1.4
version: 6.1.4
jszip:
specifier: ^3.10.1
version: 3.10.1
motion:
specifier: ^12.4.4
version: 12.4.4(react@18.3.1)
nprogress:
specifier: ^0.2.0
version: 0.2.0
@@ -1806,6 +1800,10 @@ packages:
resolution: {integrity: sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg==}
engines: {node: '>=6.9.0'}
'@bcoe/v8-coverage@1.0.2':
resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==}
engines: {node: '>=18'}
'@clack/core@0.4.1':
resolution: {integrity: sha512-Pxhij4UXg8KSr7rPek6Zowm+5M22rbd2g1nfojHJkxp5YkFqiZ2+YLEM/XGVIzvGOcM0nqjIFxrpDwWRZYWYjA==}
@@ -3292,6 +3290,10 @@ packages:
resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==}
engines: {node: '>=18.0.0'}
'@istanbuljs/schema@0.1.3':
resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==}
engines: {node: '>=8'}
'@jridgewell/gen-mapping@0.3.5':
resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==}
engines: {node: '>=6.0.0'}
@@ -3336,24 +3338,6 @@ packages:
'@mdx-js/mdx@3.1.0':
resolution: {integrity: sha512-/QxEhPAvGwbQmy1Px8F899L5Uc2KZ6JtXwlCgJmjSTBedwOZkByYcBG4GceIGPXRDsmfxhHazuS+hlOShRLeDw==}
'@motionone/animation@10.18.0':
resolution: {integrity: sha512-9z2p5GFGCm0gBsZbi8rVMOAJCtw1WqBTIPw3ozk06gDvZInBPIsQcHgYogEJ4yuHJ+akuW8g1SEIOpTOvYs8hw==}
'@motionone/dom@10.18.0':
resolution: {integrity: sha512-bKLP7E0eyO4B2UaHBBN55tnppwRnaE3KFfh3Ps9HhnAkar3Cb69kUCJY9as8LrccVYKgHA+JY5dOQqJLOPhF5A==}
'@motionone/easing@10.18.0':
resolution: {integrity: sha512-VcjByo7XpdLS4o9T8t99JtgxkdMcNWD3yHU/n6CLEz3bkmKDRZyYQ/wmSf6daum8ZXqfUAgFeCZSpJZIMxaCzg==}
'@motionone/generators@10.18.0':
resolution: {integrity: sha512-+qfkC2DtkDj4tHPu+AFKVfR/C30O1vYdvsGYaR13W/1cczPrrcjdvYCj0VLFuRMN+lP1xvpNZHCRNM4fBzn1jg==}
'@motionone/types@10.17.1':
resolution: {integrity: sha512-KaC4kgiODDz8hswCrS0btrVrzyU2CSQKO7Ps90ibBVSQmjkrt2teqta6/sOG59v7+dPnKMAg13jyqtMKV2yJ7A==}
'@motionone/utils@10.18.0':
resolution: {integrity: sha512-3XVF7sgyTSI2KWvTf6uLlBJ5iAgRgmvp3bpuOiQJvInd4nZ19ET8lX5unn30SlmRH7hXbBbH+Gxd0m0klJ3Xtw==}
'@napi-rs/wasm-runtime@0.2.5':
resolution: {integrity: sha512-kwUxR7J9WLutBbulqg1dfOrMTwhMdXLdcGUhcbCcGwnPLt3gz19uHVdwH1syKVDbE022ZS2vZxOWflFLS0YTjw==}
@@ -4884,6 +4868,15 @@ packages:
vite: ^5.0.0 || ^6.0.0
vue: ^3.2.25
'@vitest/coverage-v8@3.0.5':
resolution: {integrity: sha512-zOOWIsj5fHh3jjGwQg+P+J1FW3s4jBu1Zqga0qW60yutsBtqEqNEJKWYh7cYn1yGD+1bdPsPdC/eL4eVK56xMg==}
peerDependencies:
'@vitest/browser': 3.0.5
vitest: 3.0.5
peerDependenciesMeta:
'@vitest/browser':
optional: true
'@vitest/eslint-plugin@1.1.31':
resolution: {integrity: sha512-xlsLr+e+AXZ/00eVZCtNmMeCJoJaRCoLDiAgLcxgQjSS1EertieB2MUHf8xIqPKs9lECc/UpL+y1xDcpvi02hw==}
peerDependencies:
@@ -7131,20 +7124,6 @@ packages:
fraction.js@4.3.7:
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
framer-motion@12.4.4:
resolution: {integrity: sha512-JWkVwbJBgVkeZHNcnk8ififgwTF+5de9wbJnTLI+g9YqaGo75Xd5uRVDm9FR8chqRDOKcXv/71f40CGescYVmg==}
peerDependencies:
'@emotion/is-prop-valid': '*'
react: ^18.0.0 || ^19.0.0
react-dom: ^18.0.0 || ^19.0.0
peerDependenciesMeta:
'@emotion/is-prop-valid':
optional: true
react:
optional: true
react-dom:
optional: true
framesync@6.1.2:
resolution: {integrity: sha512-jBTqhX6KaQVDyus8muwZbBeGGP0XgujBRbQ7gM7BRdS3CadCZIHiawyzYLnafYcvZIh5j8WE7cxZKFn7dXhu9g==}
@@ -7467,6 +7446,9 @@ packages:
resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==}
engines: {node: '>=18'}
html-escaper@2.0.2:
resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
html-escaper@3.0.3:
resolution: {integrity: sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==}
@@ -7834,6 +7816,22 @@ packages:
resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==}
engines: {node: '>=16'}
istanbul-lib-coverage@3.2.2:
resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==}
engines: {node: '>=8'}
istanbul-lib-report@3.0.1:
resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==}
engines: {node: '>=10'}
istanbul-lib-source-maps@5.0.6:
resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==}
engines: {node: '>=10'}
istanbul-reports@3.1.7:
resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==}
engines: {node: '>=8'}
jackspeak@3.4.3:
resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
@@ -8182,6 +8180,10 @@ packages:
resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==}
engines: {node: '>=8'}
make-dir@4.0.0:
resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==}
engines: {node: '>=10'}
make-synchronized@0.2.9:
resolution: {integrity: sha512-4wczOs8SLuEdpEvp3vGo83wh8rjJ78UsIk7DIX5fxdfmfMJGog4bQzxfvOwq7Q3yCHLC4jp1urPHIxRS/A93gA==}
@@ -8571,26 +8573,6 @@ packages:
moo@0.5.2:
resolution: {integrity: sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==}
motion-dom@12.4.4:
resolution: {integrity: sha512-D8Kjp8oqUNqxoAVmIlOH+YCMov/4koBAmG4OJs0VWfh18xkQEIsx9+S7yrXyx0XaMBEPtre6e9LiSW2Zs7vIhA==}
motion-utils@12.0.0:
resolution: {integrity: sha512-MNFiBKbbqnmvOjkPyOKgHUp3Q6oiokLkI1bEwm5QA28cxMZrv0CbbBGDNmhF6DIXsi1pCQBSs0dX8xjeER1tmA==}
motion@12.4.4:
resolution: {integrity: sha512-ItMaWczRFfHATEmWgFDBCS2ap3L9aBLp4wNimfXR/Ui04Z3Emj73fEfdoYWy/peRWbRWO4iYB/yX7Tw3Fs0UvQ==}
peerDependencies:
'@emotion/is-prop-valid': '*'
react: ^18.0.0 || ^19.0.0
react-dom: ^18.0.0 || ^19.0.0
peerDependenciesMeta:
'@emotion/is-prop-valid':
optional: true
react:
optional: true
react-dom:
optional: true
mri@1.2.0:
resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
engines: {node: '>=4'}
@@ -10352,6 +10334,10 @@ packages:
engines: {node: '>=10'}
hasBin: true
test-exclude@7.0.1:
resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==}
engines: {node: '>=18'}
three-custom-shader-material@5.4.0:
resolution: {integrity: sha512-Yn1lFlKOk3Vul3npEGAmbbFUZ5S2+yjPgM2XqJEZEYRSUUH2vk+WVYrtTB6Bcq15wa7hLUXAKoctAvbRmBmbYA==}
peerDependencies:
@@ -12396,6 +12382,8 @@ snapshots:
'@babel/helper-string-parser': 7.25.9
'@babel/helper-validator-identifier': 7.25.9
'@bcoe/v8-coverage@1.0.2': {}
'@clack/core@0.4.1':
dependencies:
picocolors: 1.1.1
@@ -13558,6 +13546,8 @@ snapshots:
dependencies:
minipass: 7.1.2
'@istanbuljs/schema@0.1.3': {}
'@jridgewell/gen-mapping@0.3.5':
dependencies:
'@jridgewell/set-array': 1.2.1
@@ -13639,41 +13629,6 @@ snapshots:
- acorn
- supports-color
'@motionone/animation@10.18.0':
dependencies:
'@motionone/easing': 10.18.0
'@motionone/types': 10.17.1
'@motionone/utils': 10.18.0
tslib: 2.8.1
'@motionone/dom@10.18.0':
dependencies:
'@motionone/animation': 10.18.0
'@motionone/generators': 10.18.0
'@motionone/types': 10.17.1
'@motionone/utils': 10.18.0
hey-listen: 1.0.8
tslib: 2.8.1
'@motionone/easing@10.18.0':
dependencies:
'@motionone/utils': 10.18.0
tslib: 2.8.1
'@motionone/generators@10.18.0':
dependencies:
'@motionone/types': 10.17.1
'@motionone/utils': 10.18.0
tslib: 2.8.1
'@motionone/types@10.17.1': {}
'@motionone/utils@10.18.0':
dependencies:
'@motionone/types': 10.17.1
hey-listen: 1.0.8
tslib: 2.8.1
'@napi-rs/wasm-runtime@0.2.5':
dependencies:
'@emnapi/core': 1.3.1
@@ -15277,6 +15232,24 @@ snapshots:
vite: 6.1.0(@types/node@22.13.4)(jiti@2.4.2)(less@4.2.1)(terser@5.17.6)(tsx@4.19.2)(yaml@2.7.0)
vue: 3.5.13(typescript@5.7.3)
'@vitest/coverage-v8@3.0.5(vitest@3.0.5(@types/debug@4.1.12)(@types/node@22.13.4)(jiti@2.4.2)(jsdom@25.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(less@4.2.1)(terser@5.17.6)(tsx@4.19.2)(yaml@2.7.0))':
dependencies:
'@ampproject/remapping': 2.3.0
'@bcoe/v8-coverage': 1.0.2
debug: 4.4.0
istanbul-lib-coverage: 3.2.2
istanbul-lib-report: 3.0.1
istanbul-lib-source-maps: 5.0.6
istanbul-reports: 3.1.7
magic-string: 0.30.17
magicast: 0.3.5
std-env: 3.8.0
test-exclude: 7.0.1
tinyrainbow: 2.0.0
vitest: 3.0.5(@types/debug@4.1.12)(@types/node@22.13.4)(jiti@2.4.2)(jsdom@25.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(less@4.2.1)(terser@5.17.6)(tsx@4.19.2)(yaml@2.7.0)
transitivePeerDependencies:
- supports-color
'@vitest/eslint-plugin@1.1.31(@typescript-eslint/utils@8.24.1(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3))(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3)(vitest@3.0.5(@types/debug@4.1.12)(@types/node@22.13.4)(jiti@2.4.2)(jsdom@25.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(less@4.2.1)(terser@5.17.6)(tsx@4.19.2)(yaml@2.7.0))':
dependencies:
'@typescript-eslint/utils': 8.24.1(eslint@9.20.1(jiti@2.4.2))(typescript@5.7.3)
@@ -18333,14 +18306,6 @@ snapshots:
fraction.js@4.3.7: {}
framer-motion@12.4.4(react@18.3.1):
dependencies:
motion-dom: 12.4.4
motion-utils: 12.0.0
tslib: 2.8.1
optionalDependencies:
react: 18.3.1
framesync@6.1.2:
dependencies:
tslib: 2.4.0
@@ -18844,6 +18809,8 @@ snapshots:
whatwg-encoding: 3.1.1
optional: true
html-escaper@2.0.2: {}
html-escaper@3.0.3: {}
html-tags@3.3.1: {}
@@ -19185,6 +19152,27 @@ snapshots:
isexe@3.1.1: {}
istanbul-lib-coverage@3.2.2: {}
istanbul-lib-report@3.0.1:
dependencies:
istanbul-lib-coverage: 3.2.2
make-dir: 4.0.0
supports-color: 7.2.0
istanbul-lib-source-maps@5.0.6:
dependencies:
'@jridgewell/trace-mapping': 0.3.25
debug: 4.4.0
istanbul-lib-coverage: 3.2.2
transitivePeerDependencies:
- supports-color
istanbul-reports@3.1.7:
dependencies:
html-escaper: 2.0.2
istanbul-lib-report: 3.0.1
jackspeak@3.4.3:
dependencies:
'@isaacs/cliui': 8.0.2
@@ -19571,6 +19559,10 @@ snapshots:
dependencies:
semver: 6.3.1
make-dir@4.0.0:
dependencies:
semver: 7.7.1
make-synchronized@0.2.9: {}
markdown-extensions@2.0.0: {}
@@ -20324,19 +20316,6 @@ snapshots:
moo@0.5.2: {}
motion-dom@12.4.4:
dependencies:
motion-utils: 12.0.0
motion-utils@12.0.0: {}
motion@12.4.4(react@18.3.1):
dependencies:
framer-motion: 12.4.4(react@18.3.1)
tslib: 2.8.1
optionalDependencies:
react: 18.3.1
mri@1.2.0: {}
mrmime@2.0.0: {}
@@ -22543,6 +22522,12 @@ snapshots:
commander: 2.20.3
source-map-support: 0.5.21
test-exclude@7.0.1:
dependencies:
'@istanbuljs/schema': 0.1.3
glob: 10.4.5
minimatch: 9.0.5
three-custom-shader-material@5.4.0(react@18.3.1)(three@0.173.0):
dependencies:
glsl-token-functions: 1.0.1