aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJoseph Eiba <josepheiba@icloud.com>2025-09-30 15:26:40 +0100
committermistivia <i@mistivia.com>2025-10-01 23:39:54 +0800
commit2e7f198a8108c53c6162be0d156edfce85195aa3 (patch)
tree2f9ace96817eb3c2c917f99f8e5d80f6f6be80dd /src
parent78ccdfc3a8324b54f69806df1ac2da2289695002 (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
Diffstat (limited to 'src')
-rw-r--r--src/components/left_panel.svelte5
-rw-r--r--src/components/loading.svelte3
-rw-r--r--src/components/main_panel.svelte41
-rw-r--r--src/components/right_panel.svelte7
-rw-r--r--src/language.js31
-rw-r--r--src/loading.js2
-rw-r--r--src/translations.js127
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