add more lang

This commit is contained in:
Julian Freeman
2026-02-22 17:08:33 -04:00
parent c6c1627686
commit fa278a6a8b
2 changed files with 145 additions and 34 deletions

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import { ref, computed } from 'vue';
import { ref, computed, onMounted, onUnmounted } from 'vue';
import {
Settings,
Languages,
@@ -9,7 +9,8 @@ import {
ArrowRightLeft,
Loader2,
Check,
FileText
FileText,
ChevronDown
} from 'lucide-vue-next';
import { fetch } from '@tauri-apps/plugin-http';
import { useSettingsStore, LANGUAGES, DEFAULT_TEMPLATE } from './stores/settings';
@@ -23,6 +24,40 @@ function cn(...inputs: ClassValue[]) {
const settings = useSettingsStore();
const view = ref<'translate' | 'settings' | 'logs'>('translate');
// Dropdown State
const sourceDropdownOpen = ref(false);
const targetDropdownOpen = ref(false);
const closeAllDropdowns = () => {
sourceDropdownOpen.value = false;
targetDropdownOpen.value = false;
};
const toggleDropdown = (type: 'source' | 'target') => {
if (type === 'source') {
sourceDropdownOpen.value = !sourceDropdownOpen.value;
targetDropdownOpen.value = false;
} else {
targetDropdownOpen.value = !targetDropdownOpen.value;
sourceDropdownOpen.value = false;
}
};
const handleGlobalClick = (e: MouseEvent) => {
const target = e.target as HTMLElement;
if (!target.closest('.lang-dropdown')) {
closeAllDropdowns();
}
};
onMounted(() => {
window.addEventListener('click', handleGlobalClick);
});
onUnmounted(() => {
window.removeEventListener('click', handleGlobalClick);
});
// Translation State
const sourceText = ref('');
const targetText = ref('');
@@ -50,9 +85,9 @@ const sourceLang = computed(() => settings.sourceLang);
const targetLang = computed(() => settings.targetLang);
const swapLanguages = () => {
const temp = sourceLangCode.value;
sourceLangCode.value = targetLangCode.value;
targetLangCode.value = temp;
const temp = { ...settings.sourceLang };
settings.sourceLang = { ...settings.targetLang };
settings.targetLang = temp;
};
const clearSource = () => {
@@ -79,9 +114,9 @@ const translate = async () => {
targetText.value = '';
const prompt = settings.systemPromptTemplate
.replace(/{SOURCE_LANG}/g, sourceLang.value.name)
.replace(/{SOURCE_LANG}/g, sourceLang.value.englishName)
.replace(/{SOURCE_CODE}/g, sourceLang.value.code)
.replace(/{TARGET_LANG}/g, targetLang.value.name)
.replace(/{TARGET_LANG}/g, targetLang.value.englishName)
.replace(/{TARGET_CODE}/g, targetLang.value.code)
.replace(/{TEXT}/g, sourceText.value);
@@ -176,14 +211,47 @@ const translate = async () => {
<!-- Translation View -->
<div v-if="view === 'translate'" class="flex-1 flex flex-col md:flex-row divide-y md:divide-y-0 md:divide-x bg-white overflow-hidden">
<!-- Source Pane -->
<div class="flex-1 flex flex-col min-h-0">
<div class="flex items-center gap-4 px-6 py-3 border-b bg-slate-50/50">
<select
v-model="sourceLangCode"
class="bg-transparent border-none focus:ring-0 font-medium text-slate-700 cursor-pointer text-sm outline-none appearance-none"
>
<option v-for="lang in LANGUAGES" :key="lang.code" :value="lang.code">{{ lang.name }}</option>
</select>
<div class="flex-1 flex flex-col min-h-0 relative">
<div class="flex items-center gap-3 px-6 py-3 border-b bg-slate-50/50 relative z-40">
<!-- Custom Source Dropdown -->
<div class="relative lang-dropdown min-w-[120px]">
<button
@click.stop="toggleDropdown('source')"
class="flex items-center gap-2 px-3 py-1.5 rounded-lg hover:bg-slate-200/60 transition-colors text-sm font-semibold text-slate-700 w-full justify-between group"
>
<span class="truncate">{{ sourceLang.displayName }}</span>
<ChevronDown :class="cn('w-4 h-4 text-slate-400 transition-transform duration-200', sourceDropdownOpen && 'rotate-180')" />
</button>
<!-- Dropdown Menu -->
<transition
enter-active-class="transition duration-100 ease-out"
enter-from-class="transform scale-95 opacity-0"
enter-to-class="transform scale-100 opacity-100"
leave-active-class="transition duration-75 ease-in"
leave-from-class="transform scale-100 opacity-100"
leave-to-class="transform scale-95 opacity-0"
>
<div
v-if="sourceDropdownOpen"
class="absolute left-0 mt-2 w-56 max-h-80 overflow-y-auto bg-white rounded-xl shadow-xl border border-slate-200 z-50 py-2 py-1 flex flex-col custom-scrollbar"
>
<button
v-for="lang in LANGUAGES"
:key="lang.code"
@click="sourceLangCode = lang.code; sourceDropdownOpen = false"
:class="cn(
'px-4 py-2.5 text-sm text-left transition-colors flex items-center justify-between',
sourceLangCode === lang.code ? 'bg-blue-50 text-blue-600 font-bold' : 'text-slate-600 hover:bg-slate-50'
)"
>
{{ lang.displayName }}
<Check v-if="sourceLangCode === lang.code" class="w-3.5 h-3.5" />
</button>
</div>
</transition>
</div>
<button @click="swapLanguages" class="p-1.5 hover:bg-slate-200 rounded-md transition-colors" title="交换语言">
<ArrowRightLeft class="w-4 h-4 text-slate-500" />
</button>
@@ -212,14 +280,46 @@ const translate = async () => {
</div>
<!-- Target Pane -->
<div class="flex-1 flex flex-col min-h-0 bg-slate-50/30">
<div class="flex items-center gap-4 px-6 py-3 border-b bg-slate-50/50">
<select
v-model="targetLangCode"
class="bg-transparent border-none focus:ring-0 font-medium text-slate-700 cursor-pointer text-sm outline-none appearance-none"
>
<option v-for="lang in LANGUAGES" :key="lang.code" :value="lang.code">{{ lang.name }}</option>
</select>
<div class="flex-1 flex flex-col min-h-0 bg-slate-50/30 relative">
<div class="flex items-center gap-3 px-6 py-3 border-b bg-slate-50/50 relative z-40">
<!-- Custom Target Dropdown -->
<div class="relative lang-dropdown min-w-[120px]">
<button
@click.stop="toggleDropdown('target')"
class="flex items-center gap-2 px-3 py-1.5 rounded-lg hover:bg-slate-200/60 transition-colors text-sm font-semibold text-slate-700 w-full justify-between"
>
<span class="truncate">{{ targetLang.displayName }}</span>
<ChevronDown :class="cn('w-4 h-4 text-slate-400 transition-transform duration-200', targetDropdownOpen && 'rotate-180')" />
</button>
<!-- Dropdown Menu -->
<transition
enter-active-class="transition duration-100 ease-out"
enter-from-class="transform scale-95 opacity-0"
enter-to-class="transform scale-100 opacity-100"
leave-active-class="transition duration-75 ease-in"
leave-from-class="transform scale-100 opacity-100"
leave-to-class="transform scale-95 opacity-0"
>
<div
v-if="targetDropdownOpen"
class="absolute left-0 mt-2 w-56 max-h-80 overflow-y-auto bg-white rounded-xl shadow-xl border border-slate-200 z-50 py-2 flex flex-col custom-scrollbar"
>
<button
v-for="lang in LANGUAGES"
:key="lang.code"
@click="targetLangCode = lang.code; targetDropdownOpen = false"
:class="cn(
'px-4 py-2.5 text-sm text-left transition-colors flex items-center justify-between',
targetLangCode === lang.code ? 'bg-blue-50 text-blue-600 font-bold' : 'text-slate-600 hover:bg-slate-50'
)"
>
{{ lang.displayName }}
<Check v-if="targetLangCode === lang.code" class="w-3.5 h-3.5" />
</button>
</div>
</transition>
</div>
<div class="ml-auto flex items-center gap-2">
<button @click="copyTarget" class="p-1.5 hover:bg-slate-200 rounded-md transition-colors relative" title="复制结果">
<Check v-if="showCopyFeedback" class="w-4 h-4 text-green-600" />