diff options
| author | Joseph Eiba <josepheiba@icloud.com> | 2025-09-30 15:26:40 +0100 |
|---|---|---|
| committer | mistivia <i@mistivia.com> | 2025-10-01 23:39:54 +0800 |
| commit | 2e7f198a8108c53c6162be0d156edfce85195aa3 (patch) | |
| tree | 2f9ace96817eb3c2c917f99f8e5d80f6f6be80dd | |
| parent | 78ccdfc3a8324b54f69806df1ac2da2289695002 (diff) | |
feat: Add initial language support for English and Japanese
- Add translation framework for UI elements
- Implement language switching functionality
- Add English and Japanese translations for UI components
- Card names and effects translation to be implemented in future commits
| -rw-r--r-- | src/components/left_panel.svelte | 5 | ||||
| -rw-r--r-- | src/components/loading.svelte | 3 | ||||
| -rw-r--r-- | src/components/main_panel.svelte | 41 | ||||
| -rw-r--r-- | src/components/right_panel.svelte | 7 | ||||
| -rw-r--r-- | src/language.js | 31 | ||||
| -rw-r--r-- | src/loading.js | 2 | ||||
| -rw-r--r-- | src/translations.js | 127 |
7 files changed, 193 insertions, 23 deletions
diff --git a/src/components/left_panel.svelte b/src/components/left_panel.svelte index a2c14c5..bcfdba7 100644 --- a/src/components/left_panel.svelte +++ b/src/components/left_panel.svelte @@ -9,6 +9,7 @@ } from '../left_panel'; import { cardImageUrl } from '../utils'; + import { currentTranslations } from '../language'; </script> @@ -24,8 +25,8 @@ <pre class="card-desc-text">{$leftPanelCardDesc}</pre> <div style="height:3em;line-height:1.0;overflow:hidden;"> <div style="break-inside:avoid;"><p style="text-align:center;"><small> - <a class="link" href="https://github.com/mistivia/ygo-deck-builder">源代码</a> - <br>关注 <a class="link" href="https://mistivia.com">Mistivia</a> 谢谢喵~ + <a class="link" href="https://github.com/mistivia/ygo-deck-builder">{$currentTranslations.sourceCode}</a> + <br>{$currentTranslations.followMistivia} <a class="link" href="https://mistivia.com">Mistivia</a> {$currentTranslations.thankYou} </small></p></div> </div> </div> diff --git a/src/components/loading.svelte b/src/components/loading.svelte index fb49495..42b3404 100644 --- a/src/components/loading.svelte +++ b/src/components/loading.svelte @@ -1,5 +1,6 @@ <script> import { isLoading } from '../loading'; + import { currentTranslations } from '../language'; </script> {#if $isLoading} @@ -7,7 +8,7 @@ <div class="loading-content"> <div class="loading-spinner"></div> <br> - <p>加载中...</p> + <p>{$currentTranslations.loading}</p> </div> </div> {/if} diff --git a/src/components/main_panel.svelte b/src/components/main_panel.svelte index 0d959c2..9029a16 100644 --- a/src/components/main_panel.svelte +++ b/src/components/main_panel.svelte @@ -8,6 +8,7 @@ genYdke, downloadStringAsFile, } from '../utils'; + import { language, setLanguage, currentTranslations } from '../language'; let fileInput; @@ -44,10 +45,10 @@ let deckString = genYdk($deck); navigator.clipboard.writeText(deckString) .then(() => { - alert('YDK卡组码已复制到剪贴板'); + alert($currentTranslations.ydkCopied); }) .catch(err => { - alert("失败!"); + alert($currentTranslations.failed); }); } @@ -57,10 +58,10 @@ url = url + '#' + genYdke($deck); navigator.clipboard.writeText(url) .then(() => { - alert('分享链接已复制到剪贴板'); + alert($currentTranslations.shareLinkCopied); }) .catch(err => { - alert("失败!"); + alert($currentTranslations.failed); }); } @@ -91,27 +92,32 @@ <div class="middle-panel"> <div class="control-bar"> - <button class="btn" onclick={openDeck}>打开</button> - <button class="btn" onclick={saveDeck}>保存</button> - <button class="btn" onclick={clearDeck}>清空</button> - <button class="btn" onclick={copyDeck}>复制到剪贴板</button> - <button class="btn" onclick={shareDeck}>分享</button> + <button class="btn" onclick={openDeck}>{$currentTranslations.open}</button> + <button class="btn" onclick={saveDeck}>{$currentTranslations.save}</button> + <button class="btn" onclick={clearDeck}>{$currentTranslations.clear}</button> + <button class="btn" onclick={copyDeck}>{$currentTranslations.copyToClipboard}</button> + <button class="btn" onclick={shareDeck}>{$currentTranslations.share}</button> + <select bind:value={$language} class="select-language" id="language" onchange={()=>setLanguage($language)}> + <option value="chinese">中文</option> + <option value="english">English</option> + <option value="japanese">日本語</option> + </select> <select bind:value={$format} class="select-format" id="format" onchange={()=>setFormat($format)}> - <option value="none">无禁限</option> + <option value="none">{$currentTranslations.noLimit}</option> <option value="ocg">OCG</option> <option value="tcg">TCG</option> - <option value="md">大师决斗</option> - <option value="cnocg">简中</option> + <option value="md">{$currentTranslations.masterDuel}</option> + <option value="cnocg">{$currentTranslations.cnSimplified}</option> <option value="genesys">Genesys</option> </select> {#if $format === 'genesys'} - <span>点数:{$deck.point} </span> + <span>{$currentTranslations.points}{$deck.point} </span> {/if} </div> <div class="deck-section"> <div class="deck-group"> - <h3>主卡组({$deck.main.length})</h3> + <h3>{$currentTranslations.mainDeck}({$deck.main.length})</h3> <div role="region" ondragover={(e)=>e.preventDefault()} ondrop={(e)=>onDrop("main", e, -1)} class="card-grid main-deck"> {#each $deck.main as card, i} <div class="card-grid-thumb" role="region" ondragover={(e)=>e.preventDefault()} ondrop={(e)=>onDrop("main", e, i)}> @@ -121,7 +127,7 @@ </div> </div> <div class="deck-group"> - <h3>额外卡组({$deck.extra.length})</h3> + <h3>{$currentTranslations.extraDeck}({$deck.extra.length})</h3> <div role="region" ondragover={(e)=>e.preventDefault()} ondrop={(e)=>onDrop("extra", e, -1)} class="card-grid extra-deck"> {#each $deck.extra as card, i} <div class="card-grid-thumb" role="region" ondragover={(e)=>e.preventDefault()} ondrop={(e)=>onDrop("extra", e, i)}> @@ -131,7 +137,7 @@ </div> </div> <div class="deck-group"> - <h3>副卡组({$deck.side.length})</h3> + <h3>{$currentTranslations.sideDeck}({$deck.side.length})</h3> <div role="region" ondragover={(e)=>e.preventDefault()} ondrop={(e)=>onDrop("side", e, -1)} class="card-grid side-deck"> {#each $deck.side as card, i} <div class="card-grid-thumb" role="region" ondragover={(e)=>e.preventDefault()} ondrop={(e)=>onDrop("side", e, i)}> @@ -172,7 +178,8 @@ } - .select-format { + .select-format, + .select-language { padding: 8px 8px; margin-right: 10px; cursor: pointer; diff --git a/src/components/right_panel.svelte b/src/components/right_panel.svelte index b0b8553..28f4307 100644 --- a/src/components/right_panel.svelte +++ b/src/components/right_panel.svelte @@ -3,6 +3,7 @@ import { changeInput, showingCards, onPrevPage, onNextPage } from '../search' import { deckOps, format } from '../deck'; import { cardLimit, cornerMark } from '../card_db'; + import { currentTranslations } from '../language'; function onChange(event) { changeInput(event.target.value); @@ -19,7 +20,7 @@ <div class="right-panel" role="region" ondragover={(e)=>e.preventDefault()} ondrop={onDrop}> <div class="search-bar"> - <input type="text" placeholder="搜索卡牌..." oninput={onChange}> + <input type="text" placeholder={$currentTranslations.searchPlaceholder} oninput={onChange}> </div> <div class="card-list"> {#each $showingCards as card} @@ -32,8 +33,8 @@ {/each} </div> <div class="pagination"> - <button class="page-btn" onclick={onPrevPage}>上一页</button> - <button class="page-btn" onclick={onNextPage}>下一页</button> + <button class="page-btn" onclick={onPrevPage}>{$currentTranslations.prevPage}</button> + <button class="page-btn" onclick={onNextPage}>{$currentTranslations.nextPage}</button> </div> </div> diff --git a/src/language.js b/src/language.js new file mode 100644 index 0000000..b4b9605 --- /dev/null +++ b/src/language.js @@ -0,0 +1,31 @@ +import { writable, derived } from "svelte/store"; +import translations from './translations.js'; + +let defaultLanguage = 'chinese'; +let language = writable(defaultLanguage); +let languageState = defaultLanguage; + +function setLanguage(newLanguage) { + localStorage.setItem('language', newLanguage); + languageState = newLanguage; + language.set(newLanguage); +} + +function initLanguage() { + let cachedLanguage = localStorage.getItem('language'); + if (cachedLanguage !== null) { + setLanguage(cachedLanguage); + } +} + +const currentTranslations = derived(language, ($language) => { + return translations[$language] || translations.chinese; +}); + +export { + language, + languageState, + setLanguage, + initLanguage, + currentTranslations, +};
\ No newline at end of file diff --git a/src/loading.js b/src/loading.js index 523f26c..94fb08c 100644 --- a/src/loading.js +++ b/src/loading.js @@ -1,6 +1,7 @@ import { writable } from 'svelte/store'; import { initSearch } from './search'; import { initDeck } from './deck'; +import { initLanguage } from './language'; import { setCardDb, setAltId } from './card_db'; import idChangelog from './id_changelog.json'; @@ -53,6 +54,7 @@ async function setidxdbitem(key, value) { } async function fetchCardDb() { + initLanguage(); // Load language before showing loading screen let localVer = localStorage.getItem('card_db_ver'); try { // load card db diff --git a/src/translations.js b/src/translations.js new file mode 100644 index 0000000..af430cb --- /dev/null +++ b/src/translations.js @@ -0,0 +1,127 @@ +const translations = { + chinese: { + // Buttons + open: "打开", + save: "保存", + clear: "清空", + copyToClipboard: "复制到剪贴板", + share: "分享", + + // Format options + noLimit: "无禁限", + masterDuel: "大师决斗", + cnSimplified: "简中", + + // Deck sections + mainDeck: "主卡组", + extraDeck: "额外卡组", + sideDeck: "副卡组", + + // Search + searchPlaceholder: "搜索卡牌...", + + // Pagination + prevPage: "上一页", + nextPage: "下一页", + + // Points display + points: "点数:", + + // Footer links + sourceCode: "源代码", + followMistivia: "关注", + thankYou: "谢谢喵~", + + // Alert messages + ydkCopied: "YDK卡组码已复制到剪贴板", + shareLinkCopied: "分享链接已复制到剪贴板", + failed: "失败!", + + // Loading + loading: "加载中..." + }, + english: { + // Buttons + open: "Open", + save: "Save", + clear: "Clear", + copyToClipboard: "Copy to Clipboard", + share: "Share", + + // Format options + noLimit: "No Limit", + masterDuel: "Master Duel", + cnSimplified: "Chinese OCG", + + // Deck sections + mainDeck: "Main Deck", + extraDeck: "Extra Deck", + sideDeck: "Side Deck", + + // Search + searchPlaceholder: "Search cards...", + + // Pagination + prevPage: "Previous", + nextPage: "Next", + + // Points display + points: "Points: ", + + // Footer links + sourceCode: "Source Code", + followMistivia: "Follow", + thankYou: "Thank you~", + + // Alert messages + ydkCopied: "YDK deck code copied to clipboard", + shareLinkCopied: "Share link copied to clipboard", + failed: "Failed!", + + // Loading + loading: "Loading..." + }, + japanese: { + // Buttons + open: "開く", + save: "保存", + clear: "クリア", + copyToClipboard: "クリップボードにコピー", + share: "シェア", + + // Format options + noLimit: "制限なし", + masterDuel: "マスターデュエル", + cnSimplified: "簡体字中国語", + + // Deck sections + mainDeck: "メインデッキ", + extraDeck: "エクストラデッキ", + sideDeck: "サイドデッキ", + + // Search + searchPlaceholder: "カードを検索...", + + // Pagination + prevPage: "前のページ", + nextPage: "次のページ", + + // Points display + points: "ポイント:", + + // Footer links + sourceCode: "ソースコード", + followMistivia: "フォロー", + thankYou: "ありがとうにゃ〜", + + // Alert messages + ydkCopied: "YDKデッキコードをクリップボードにコピーしました", + shareLinkCopied: "シェアリンクをクリップボードにコピーしました", + failed: "失敗!", + + // Loading + loading: "読み込み中..." + } +}; + +export default translations;
\ No newline at end of file |
