From 2a94e1124c950d4d44f57bd6dc190413e2df39cd Mon Sep 17 00:00:00 2001 From: Hare Date: Thu, 9 Apr 2026 02:28:44 +0900 Subject: [PATCH] =?UTF-8?q?EN/JP=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/content/ui/i18n.ts | 52 ++++++++++++++++++++++++++++++++ src/content/ui/table-renderer.ts | 31 ++++++++++--------- 2 files changed, 69 insertions(+), 14 deletions(-) create mode 100644 src/content/ui/i18n.ts diff --git a/src/content/ui/i18n.ts b/src/content/ui/i18n.ts new file mode 100644 index 0000000..1d22ac4 --- /dev/null +++ b/src/content/ui/i18n.ts @@ -0,0 +1,52 @@ +type MessageKey = + | "colIndex" + | "colTitle" + | "colChannel" + | "colDuration" + | "colAddedBy" + | "colVotes" + | "filterTitle" + | "filterChannel" + | "filterAddedBy" + | "badgeLive" + | "headerVideos"; + +const messages: Record> = { + ja: { + colIndex: "#", + colTitle: "タイトル", + colChannel: "チャンネル", + colDuration: "長さ", + colAddedBy: "追加者", + colVotes: "投票", + filterTitle: "タイトル検索...", + filterChannel: "チャンネル...", + filterAddedBy: "追加者...", + badgeLive: "ライブ", + headerVideos: "本の動画", + }, + en: { + colIndex: "#", + colTitle: "Title", + colChannel: "Channel", + colDuration: "Duration", + colAddedBy: "Added by", + colVotes: "Votes", + filterTitle: "Search title...", + filterChannel: "Channel...", + filterAddedBy: "Added by...", + badgeLive: "LIVE", + headerVideos: "videos", + }, +}; + +function detectLang(): string { + const htmlLang = document.documentElement.lang; + if (htmlLang?.startsWith("ja")) return "ja"; + return "en"; +} + +export function t(key: MessageKey): string { + const lang = detectLang(); + return (messages[lang] ?? messages.en)[key]; +} diff --git a/src/content/ui/table-renderer.ts b/src/content/ui/table-renderer.ts index 0170927..a0a484c 100644 --- a/src/content/ui/table-renderer.ts +++ b/src/content/ui/table-renderer.ts @@ -1,4 +1,5 @@ import type { PlaylistData, PlaylistVideo } from "../../types/playlist"; +import { t } from "./i18n"; type SortKey = "index" | "title" | "channel" | "duration" | "addedBy" | "votes"; type SortDir = "asc" | "desc"; @@ -10,14 +11,16 @@ interface Column { collab?: boolean; // only shown for collaborative playlists } -const allColumns: Column[] = [ - { label: "#", cls: "ytpf-col-index", key: "index" }, - { label: "Title", cls: "ytpf-col-title", key: "title" }, - { label: "Channel", cls: "ytpf-col-channel", key: "channel" }, - { label: "Duration", cls: "ytpf-col-duration", key: "duration" }, - { label: "Added by", cls: "ytpf-col-addedby", key: "addedBy", collab: true }, - { label: "Votes", cls: "ytpf-col-votes", key: "votes", collab: true }, -]; +function getAllColumns(): Column[] { + return [ + { label: t("colIndex"), cls: "ytpf-col-index", key: "index" }, + { label: t("colTitle"), cls: "ytpf-col-title", key: "title" }, + { label: t("colChannel"), cls: "ytpf-col-channel", key: "channel" }, + { label: t("colDuration"), cls: "ytpf-col-duration", key: "duration" }, + { label: t("colAddedBy"), cls: "ytpf-col-addedby", key: "addedBy", collab: true }, + { label: t("colVotes"), cls: "ytpf-col-votes", key: "votes", collab: true }, + ]; +} interface TagInput { container: HTMLElement; @@ -251,7 +254,7 @@ function buildRow( if (video.isLive) { const badge = document.createElement("span"); badge.className = "ytpf-live"; - badge.textContent = "LIVE"; + badge.textContent = t("badgeLive"); tdDuration.appendChild(badge); } else { tdDuration.textContent = video.durationText ?? "--"; @@ -279,7 +282,7 @@ export function renderPlaylistTable(data: PlaylistData): HTMLElement { const isCollab = data.videos.some( (v) => v.addedBy != null || v.voteCount != null, ); - const columns = allColumns.filter((c) => !c.collab || isCollab); + const columns = getAllColumns().filter((c) => !c.collab || isCollab); const wrapper = document.createElement("div"); wrapper.className = "ytpf-wrapper"; @@ -290,7 +293,7 @@ export function renderPlaylistTable(data: PlaylistData): HTMLElement { const titleSpan = document.createElement("span"); titleSpan.className = "ytpf-header-title"; - titleSpan.textContent = `${data.metadata.title} — ${data.extractedCount} videos`; + titleSpan.textContent = `${data.metadata.title} — ${data.extractedCount} ${t("headerVideos")}`; const metaSpan = document.createElement("span"); metaSpan.className = "ytpf-header-meta"; @@ -313,12 +316,12 @@ export function renderPlaylistTable(data: PlaylistData): HTMLElement { titleInput.className = "ytpf-filter-input"; titleInput.type = "text"; titleInput.name = "ytpf-title-filter"; - titleInput.placeholder = "タイトル検索..."; + titleInput.placeholder = t("filterTitle"); filters.appendChild(titleInput); // Channel tag input const channelNames = [...new Set(data.videos.map((v) => v.channel.name))].sort(); - const channelTagInput = createTagInput("チャンネル...", channelNames, () => applyFilters()); + const channelTagInput = createTagInput(t("filterChannel"), channelNames, () => applyFilters()); filters.appendChild(channelTagInput.container); // Added-by tag input (collab only) @@ -327,7 +330,7 @@ export function renderPlaylistTable(data: PlaylistData): HTMLElement { const addedByNames = [...new Set( data.videos.map((v) => v.addedBy).filter((n): n is string => n != null), )].sort(); - addedByTagInput = createTagInput("追加者...", addedByNames, () => applyFilters()); + addedByTagInput = createTagInput(t("filterAddedBy"), addedByNames, () => applyFilters()); filters.appendChild(addedByTagInput.container); }