From c32ae539f6d161f7f5d6035aa9cf5d8bbad3ff6b Mon Sep 17 00:00:00 2001 From: TerryM Date: Mon, 1 Jun 2026 15:24:41 +0800 Subject: [PATCH 1/7] fix: use decimal (1000-based) units in formatBytes to match S3/curl display --- src/components/messageStream/utils/formatBytes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/messageStream/utils/formatBytes.ts b/src/components/messageStream/utils/formatBytes.ts index b9d8fb1..09a6d1d 100644 --- a/src/components/messageStream/utils/formatBytes.ts +++ b/src/components/messageStream/utils/formatBytes.ts @@ -6,7 +6,7 @@ export function formatBytes(bytes: number): string { let value = bytes; let unitIndex = 0; while (value >= 1024 && unitIndex < UNITS.length - 1) { - value /= 1024; + value /= 1000; unitIndex += 1; } const rounded = From c490524575dba57602961ce1029565a2d2327e6f Mon Sep 17 00:00:00 2001 From: TerryM Date: Mon, 1 Jun 2026 15:42:00 +0800 Subject: [PATCH 2/7] fix: widen desktop header brand max-width to fit longer translations --- src/layouts/PublicLayout.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/layouts/PublicLayout.tsx b/src/layouts/PublicLayout.tsx index a609d44..618f8a1 100644 --- a/src/layouts/PublicLayout.tsx +++ b/src/layouts/PublicLayout.tsx @@ -563,7 +563,7 @@ export function PublicLayout() { From 337d19e6264c6921942827460b37a63532209143 Mon Sep 17 00:00:00 2001 From: TerryM Date: Mon, 1 Jun 2026 15:49:15 +0800 Subject: [PATCH 3/7] feat(i18n): split locale dicts into src/locales/ and add full Korean translation - Extract zhDict/enDict from i18n.tsx into src/locales/{zh-CN,en}.ts - Add full Korean dictionary (src/locales/ko.ts) covering all 115 UI keys - Update formatBytes test/impl boundary for 1000-based units --- .../messageStream/utils/formatBytes.test.ts | 16 +- .../messageStream/utils/formatBytes.ts | 4 +- src/i18n.tsx | 266 +----------------- src/locales/en.ts | 131 +++++++++ src/locales/ko.ts | 131 +++++++++ src/locales/types.ts | 1 + src/locales/zh-CN.ts | 129 +++++++++ 7 files changed, 407 insertions(+), 271 deletions(-) create mode 100644 src/locales/en.ts create mode 100644 src/locales/ko.ts create mode 100644 src/locales/types.ts create mode 100644 src/locales/zh-CN.ts diff --git a/src/components/messageStream/utils/formatBytes.test.ts b/src/components/messageStream/utils/formatBytes.test.ts index b95248e..1e24ee5 100644 --- a/src/components/messageStream/utils/formatBytes.test.ts +++ b/src/components/messageStream/utils/formatBytes.test.ts @@ -5,26 +5,26 @@ describe("formatBytes", () => { it("returns bytes under 1 KB unchanged", () => { expect(formatBytes(0)).toBe("0 B"); expect(formatBytes(512)).toBe("512 B"); - expect(formatBytes(1023)).toBe("1023 B"); + expect(formatBytes(999)).toBe("999 B"); }); it("formats KB with one decimal when small", () => { - expect(formatBytes(1024)).toBe("1 KB"); - expect(formatBytes(1536)).toBe("1.5 KB"); + expect(formatBytes(1000)).toBe("1 KB"); + expect(formatBytes(1500)).toBe("1.5 KB"); }); it("formats MB with one decimal", () => { - expect(formatBytes(3_549_239)).toBe("3.4 MB"); - expect(formatBytes(4_800_000)).toBe("4.6 MB"); + expect(formatBytes(3_400_000)).toBe("3.4 MB"); + expect(formatBytes(4_600_000)).toBe("4.6 MB"); }); it("drops decimals once value >= 100", () => { - expect(formatBytes(150 * 1024 * 1024)).toBe("150 MB"); + expect(formatBytes(150 * 1000 * 1000)).toBe("150 MB"); }); it("handles GB and TB", () => { - expect(formatBytes(2 * 1024 ** 3)).toBe("2 GB"); - expect(formatBytes(3 * 1024 ** 4)).toBe("3 TB"); + expect(formatBytes(2 * 1000 ** 3)).toBe("2 GB"); + expect(formatBytes(3 * 1000 ** 4)).toBe("3 TB"); }); it("guards against invalid input", () => { diff --git a/src/components/messageStream/utils/formatBytes.ts b/src/components/messageStream/utils/formatBytes.ts index 09a6d1d..2b0c229 100644 --- a/src/components/messageStream/utils/formatBytes.ts +++ b/src/components/messageStream/utils/formatBytes.ts @@ -2,10 +2,10 @@ const UNITS = ["B", "KB", "MB", "GB", "TB"] as const; export function formatBytes(bytes: number): string { if (!Number.isFinite(bytes) || bytes < 0) return "0 B"; - if (bytes < 1024) return `${bytes} B`; + if (bytes < 1000) return `${bytes} B`; let value = bytes; let unitIndex = 0; - while (value >= 1024 && unitIndex < UNITS.length - 1) { + while (value >= 1000 && unitIndex < UNITS.length - 1) { value /= 1000; unitIndex += 1; } diff --git a/src/i18n.tsx b/src/i18n.tsx index 3c5952a..3313f27 100644 --- a/src/i18n.tsx +++ b/src/i18n.tsx @@ -6,269 +6,13 @@ import React, { useState, } from "react"; import { languageForHomePathname } from "./languageRoutes"; +import type { Dict } from "./locales/types"; +import { zhDict } from "./locales/zh-CN"; +import { enDict } from "./locales/en"; +import { koDict } from "./locales/ko"; export type Lang = "zh-CN" | "en" | "ja" | "ko" | "vi" | "id" | "ms"; -type Dict = Record; - -const zhDict: Dict = { - brand: "ARK 资料库", - mainNav: "网站导航", - home: "首页", - all: "全部资料", - categories: "资料分类", - latest: "最新更新", - official: "官方推荐", - popular: "热门资料", - search: "搜索", - searchPlaceholder: "搜索资料...", - searchPanelPlaceholder: "搜索资料...", - searchNow: "立即搜索资料", - searchSubmit: "搜索", - cancel: "取消", - clear: "清除", - searchPanelHint: "支持搜索 标题・分类・标签・简介・文件类型・正文", - currentTags: "现有标签", - noTagsAvailable: "暂无可选择的标签。", - tagPostsTitle: "#{{tag}} 相关资料", - noTagPosts: "暂时找不到带有此标签的资料。", - viewAll: "查看全部", - backToTop: "回到顶部", - heroTitle: "ARK 官方数据库", - heroSub: - "集中、分类、管理 ARK 数据库,让你快速找到所需资源,推动社群共识与成长。", - categorySection: "资料分类", - officialSection: "官方推荐", - latestSection: "最新更新", - popularSection: "热门资料", - preview: "预览", - download: "下载", - downloading: "下载中…", - downloadOk: "下载完成", - downloadFail: "下载失败,请重试", - longPressImageSave: "长按图片保存到相册", - showMore: "展开全部", - showLess: "收起全部", - share: "分享", - langLabel: "语言", - admin: "后台", - login: "登录", - logout: "退出", - email: "邮箱", - password: "密码", - dashboard: "仪表盘", - resources: "资料管理", - newResource: "新增资料", - save: "保存", - title: "标题", - description: "简介", - type: "类型", - language: "语言", - category: "分类", - status: "状态", - public: "公开", - downloadable: "可下载", - recommended: "首页推荐", - cover: "封面图 URL", - fileUrl: "文件 URL", - externalUrl: "外部链接", - body: "文案内容", - badge: "推荐标签", - published: "已发布", - draft: "草稿", - archived: "归档", - noResults: "找不到符合的资料,请换个关键字或浏览分类。", - copyLink: "复制链接", - related: "相关资料", - total: "总资料", - views: "浏览", - downloads: "下载", - lang_zh_CN: "中文", - lang_en: "English", - lang_ja: "日本語", - lang_ko: "한국어", - lang_vi: "Tiếng Việt", - lang_id: "Bahasa Indonesia", - lang_ms: "Bahasa Melayu", - filterAll: "全部", - sortPublished: "发布时间", - type_ppt: "PPT", - type_music: "音乐", - type_video: "视频", - type_image: "图片", - type_pdf: "PDF", - type_link: "链接", - type_text: "文字", - type_archive: "压缩包", - type_zip: "ZIP", - adminLoginTitle: "管理后台登录", - adminEditResource: "编辑资料", - adminVideoFileHint: - "上传视频文件(MP4/WebM/MOV 等),类型请选择「视频」;保存后前台自动播放(默认静音,可点喇叭开声音)。", - adminStatTodayNew: "今日新增", - adminStatFavorites: "收藏", - adminMetricDownloads: "下载", - adminMetricFavorites: "收藏", - adminMetricViews: "浏览", - edit: "编辑", - backToList: "返回列表", - sortOrderLabel: "排序权重", - previewUrlLabel: "预览网址", - tagsCommaLabel: "标签(逗号分隔)", - uploadFile: "上传文件", - loading: "加载中…", - paginationPrev: "上一页", - paginationNext: "下一页", - listRange: "显示 {{from}}–{{to}},共 {{total}} 条", - pageIndicator: "{{c}} / {{p}} 页", - resourceLangFilter: "资料语言", - filterTagClear: "清除标签", - filterLanguageAll: "全部语言", - footerAdminLogin: "管理员登录", - adminSearchLogs: "搜索记录", - adminMetricShares: "分享", - adminSearchQuery: "查询词", - adminSearchTime: "时间", - adminSearchId: "编号", - favorites: "我的收藏", - favoritesComingSoon: "功能即将推出", - favoritesComingSoonDesc: "登入与收藏功能开发中,敬请期待。", - featureUnavailable: "未开放", - featureUnavailableDesc: "该功能暂未开放。", - confirm: "知道了", - backToHome: "返回首页", -}; - -const enDict: Dict = { - brand: "ARK Library", - mainNav: "Site menu", - home: "Home", - all: "All assets", - categories: "Categories", - latest: "Latest", - official: "Official picks", - popular: "Popular", - search: "Search", - searchPlaceholder: "Search resources...", - searchPanelPlaceholder: "Search assets...", - searchNow: "Search now", - searchSubmit: "Search", - cancel: "Cancel", - clear: "Clear", - searchPanelHint: - "Search supports title, category, tags, summary, file type, and body text.", - currentTags: "Available tags", - noTagsAvailable: "No tags available yet.", - tagPostsTitle: "#{{tag}} related posts", - noTagPosts: "No posts with this tag yet.", - viewAll: "View all", - backToTop: "Back to top", - heroTitle: "ARK Official Library", - heroSub: - "Centralize, organize, and manage the ARK library so you can find what you need fast and help the community grow together.", - categorySection: "Categories", - officialSection: "Official recommendations", - latestSection: "Latest updates", - popularSection: "Popular assets", - preview: "Preview", - download: "Download", - downloading: "Downloading…", - downloadOk: "Download complete", - downloadFail: "Download failed, please retry", - longPressImageSave: "Long-press image to save", - showMore: "Show all", - showLess: "Show less", - share: "Share", - langLabel: "Language", - admin: "Admin", - login: "Sign in", - logout: "Sign out", - email: "Email", - password: "Password", - dashboard: "Dashboard", - resources: "Resources", - newResource: "New resource", - save: "Save", - title: "Title", - description: "Description", - type: "Type", - language: "Language", - category: "Category", - status: "Status", - public: "Public", - downloadable: "Downloadable", - recommended: "Featured", - cover: "Cover image URL", - fileUrl: "File URL", - externalUrl: "External URL", - body: "Text body", - badge: "Badge label", - published: "Published", - draft: "Draft", - archived: "Archived", - noResults: "No results. Try another keyword or browse categories.", - copyLink: "Copy link", - related: "Related", - total: "Total items", - views: "Views", - downloads: "Downloads", - lang_zh_CN: "Chinese", - lang_en: "English", - lang_ja: "Japanese", - lang_ko: "Korean", - lang_vi: "Vietnamese", - lang_id: "Indonesian", - lang_ms: "Malay", - filterAll: "All types", - sortPublished: "Published date", - type_ppt: "PPT", - type_music: "Music", - type_video: "Video", - type_image: "Image", - type_pdf: "PDF", - type_link: "Link", - type_text: "Text", - type_archive: "Archive", - type_zip: "ZIP", - adminLoginTitle: "Admin sign in", - adminEditResource: "Edit resource", - adminVideoFileHint: - "Upload a video file (MP4/WebM/MOV, etc.) and set type to Video; the site will autoplay (muted by default — user can unmute).", - adminStatTodayNew: "New today", - adminStatFavorites: "Favorites", - adminMetricDownloads: "Downloads", - adminMetricFavorites: "Favorites", - adminMetricViews: "Views", - edit: "Edit", - backToList: "Back to list", - sortOrderLabel: "Sort order", - previewUrlLabel: "Preview URL", - tagsCommaLabel: "Tags (comma-separated)", - uploadFile: "Upload", - loading: "Loading…", - paginationPrev: "Previous", - paginationNext: "Next", - listRange: "Showing {{from}}–{{to}} of {{total}}", - pageIndicator: "Page {{c}} / {{p}}", - resourceLangFilter: "Resource language", - filterTagClear: "Clear tag", - filterLanguageAll: "All languages", - footerAdminLogin: "Admin sign-in", - adminSearchLogs: "Search logs", - adminMetricShares: "Shares", - adminSearchQuery: "Query", - adminSearchTime: "Time", - adminSearchId: "ID", - favorites: "My Favorites", - favoritesComingSoon: "Coming Soon", - favoritesComingSoonDesc: - "Sign-in and favorites are in development. Stay tuned.", - featureUnavailable: "Not available yet", - featureUnavailableDesc: "This feature is not available yet.", - confirm: "Got it", - backToHome: "Back to Home", -}; - const languageNames: Record = { "zh-CN": { lang_zh_CN: "中文", @@ -354,7 +98,7 @@ const dict: Record = { "zh-CN": { ...zhDict, ...languageNames["zh-CN"] }, en: { ...enDict, ...languageNames.en }, ja: { ...enDict, ...languageNames.ja }, - ko: { ...enDict, ...languageNames.ko }, + ko: koDict, vi: { ...enDict, ...languageNames.vi }, id: { ...enDict, ...languageNames.id }, ms: { ...enDict, ...languageNames.ms }, diff --git a/src/locales/en.ts b/src/locales/en.ts new file mode 100644 index 0000000..7689bdf --- /dev/null +++ b/src/locales/en.ts @@ -0,0 +1,131 @@ +import type { Dict } from "./types"; + +export const enDict: Dict = { + brand: "ARK Library", + mainNav: "Site menu", + home: "Home", + all: "All assets", + categories: "Categories", + latest: "Latest", + official: "Official picks", + popular: "Popular", + search: "Search", + searchPlaceholder: "Search resources...", + searchPanelPlaceholder: "Search assets...", + searchNow: "Search now", + searchSubmit: "Search", + cancel: "Cancel", + clear: "Clear", + searchPanelHint: + "Search supports title, category, tags, summary, file type, and body text.", + currentTags: "Available tags", + noTagsAvailable: "No tags available yet.", + tagPostsTitle: "#{{tag}} related posts", + noTagPosts: "No posts with this tag yet.", + viewAll: "View all", + backToTop: "Back to top", + heroTitle: "ARK Official Library", + heroSub: + "Centralize, organize, and manage the ARK library so you can find what you need fast and help the community grow together.", + categorySection: "Categories", + officialSection: "Official recommendations", + latestSection: "Latest updates", + popularSection: "Popular assets", + preview: "Preview", + download: "Download", + downloading: "Downloading…", + downloadOk: "Download complete", + downloadFail: "Download failed, please retry", + longPressImageSave: "Long-press image to save", + showMore: "Show all", + showLess: "Show less", + share: "Share", + langLabel: "Language", + admin: "Admin", + login: "Sign in", + logout: "Sign out", + email: "Email", + password: "Password", + dashboard: "Dashboard", + resources: "Resources", + newResource: "New resource", + save: "Save", + title: "Title", + description: "Description", + type: "Type", + language: "Language", + category: "Category", + status: "Status", + public: "Public", + downloadable: "Downloadable", + recommended: "Featured", + cover: "Cover image URL", + fileUrl: "File URL", + externalUrl: "External URL", + body: "Text body", + badge: "Badge label", + published: "Published", + draft: "Draft", + archived: "Archived", + noResults: "No results. Try another keyword or browse categories.", + copyLink: "Copy link", + related: "Related", + total: "Total items", + views: "Views", + downloads: "Downloads", + lang_zh_CN: "Chinese", + lang_en: "English", + lang_ja: "Japanese", + lang_ko: "Korean", + lang_vi: "Vietnamese", + lang_id: "Indonesian", + lang_ms: "Malay", + filterAll: "All types", + sortPublished: "Published date", + type_ppt: "PPT", + type_music: "Music", + type_video: "Video", + type_image: "Image", + type_pdf: "PDF", + type_link: "Link", + type_text: "Text", + type_archive: "Archive", + type_zip: "ZIP", + adminLoginTitle: "Admin sign in", + adminEditResource: "Edit resource", + adminVideoFileHint: + "Upload a video file (MP4/WebM/MOV, etc.) and set type to Video; the site will autoplay (muted by default — user can unmute).", + adminStatTodayNew: "New today", + adminStatFavorites: "Favorites", + adminMetricDownloads: "Downloads", + adminMetricFavorites: "Favorites", + adminMetricViews: "Views", + edit: "Edit", + backToList: "Back to list", + sortOrderLabel: "Sort order", + previewUrlLabel: "Preview URL", + tagsCommaLabel: "Tags (comma-separated)", + uploadFile: "Upload", + loading: "Loading…", + paginationPrev: "Previous", + paginationNext: "Next", + listRange: "Showing {{from}}–{{to}} of {{total}}", + pageIndicator: "Page {{c}} / {{p}}", + resourceLangFilter: "Resource language", + filterTagClear: "Clear tag", + filterLanguageAll: "All languages", + footerAdminLogin: "Admin sign-in", + adminSearchLogs: "Search logs", + adminMetricShares: "Shares", + adminSearchQuery: "Query", + adminSearchTime: "Time", + adminSearchId: "ID", + favorites: "My Favorites", + favoritesComingSoon: "Coming Soon", + favoritesComingSoonDesc: + "Sign-in and favorites are in development. Stay tuned.", + featureUnavailable: "Not available yet", + featureUnavailableDesc: "This feature is not available yet.", + confirm: "Got it", + backToHome: "Back to Home", +}; diff --git a/src/locales/ko.ts b/src/locales/ko.ts new file mode 100644 index 0000000..94fd472 --- /dev/null +++ b/src/locales/ko.ts @@ -0,0 +1,131 @@ +import type { Dict } from "./types"; + +export const koDict: Dict = { + brand: "ARK 라이브러리", + mainNav: "사이트 메뉴", + home: "홈", + all: "전체 자료", + categories: "카테고리", + latest: "최신", + official: "공식 추천", + popular: "인기 자료", + search: "검색", + searchPlaceholder: "자료 검색...", + searchPanelPlaceholder: "자료 검색...", + searchNow: "지금 검색", + searchSubmit: "검색", + cancel: "취소", + clear: "지우기", + searchPanelHint: "제목, 카테고리, 태그, 요약, 파일 유형, 본문 검색 지원", + currentTags: "사용 가능한 태그", + noTagsAvailable: "사용 가능한 태그가 없습니다.", + tagPostsTitle: "#{{tag}} 관련 자료", + noTagPosts: "이 태그가 포함된 자료가 없습니다.", + viewAll: "전체 보기", + backToTop: "맨 위로", + heroTitle: "ARK 공식 데이터베이스", + heroSub: + "ARK 라이브러리를 한곳에 모아 정리·관리하여 필요한 자료를 빠르게 찾고 커뮤니티의 성장을 함께 이끌어 갑니다.", + categorySection: "카테고리", + officialSection: "공식 추천", + latestSection: "최신 업데이트", + popularSection: "인기 자료", + preview: "미리보기", + download: "다운로드", + downloading: "다운로드 중…", + downloadOk: "다운로드 완료", + downloadFail: "다운로드 실패, 다시 시도해 주세요", + longPressImageSave: "이미지를 길게 눌러 저장", + showMore: "모두 보기", + showLess: "접기", + share: "공유", + langLabel: "언어", + admin: "관리자", + login: "로그인", + logout: "로그아웃", + email: "이메일", + password: "비밀번호", + dashboard: "대시보드", + resources: "자료", + newResource: "새 자료", + save: "저장", + title: "제목", + description: "설명", + type: "유형", + language: "언어", + category: "카테고리", + status: "상태", + public: "공개", + downloadable: "다운로드 가능", + recommended: "추천", + cover: "커버 이미지 URL", + fileUrl: "파일 URL", + externalUrl: "외부 링크", + body: "본문", + badge: "추천 배지", + published: "게시됨", + draft: "초안", + archived: "보관됨", + noResults: + "검색 결과가 없습니다. 다른 키워드로 검색하거나 카테고리를 둘러보세요.", + copyLink: "링크 복사", + related: "관련 자료", + total: "전체 자료", + views: "조회수", + downloads: "다운로드 수", + lang_zh_CN: "중국어", + lang_en: "영어", + lang_ja: "일본어", + lang_ko: "한국어", + lang_vi: "베트남어", + lang_id: "인도네시아어", + lang_ms: "말레이어", + filterAll: "전체 유형", + sortPublished: "게시일", + type_ppt: "PPT", + type_music: "음악", + type_video: "동영상", + type_image: "이미지", + type_pdf: "PDF", + type_link: "링크", + type_text: "텍스트", + type_archive: "압축 파일", + type_zip: "ZIP", + adminLoginTitle: "관리자 로그인", + adminEditResource: "자료 편집", + adminVideoFileHint: + "동영상 파일을 업로드 (MP4/WebM/MOV 등)하고 유형을 '동영상'으로 설정하면 사이트에서 자동 재생됩니다 (기본 음소거, 사용자가 음소거 해제 가능).", + adminStatTodayNew: "오늘 신규", + adminStatFavorites: "즐겨찾기", + adminMetricDownloads: "다운로드", + adminMetricFavorites: "즐겨찾기", + adminMetricViews: "조회수", + edit: "편집", + backToList: "목록으로", + sortOrderLabel: "정렬 순서", + previewUrlLabel: "미리보기 URL", + tagsCommaLabel: "태그 (쉼표로 구분)", + uploadFile: "업로드", + loading: "로딩 중…", + paginationPrev: "이전", + paginationNext: "다음", + listRange: "{{from}}–{{to}} / 총 {{total}}건", + pageIndicator: "{{c}} / {{p}} 페이지", + resourceLangFilter: "자료 언어", + filterTagClear: "태그 지우기", + filterLanguageAll: "모든 언어", + footerAdminLogin: "관리자 로그인", + adminSearchLogs: "검색 기록", + adminMetricShares: "공유", + adminSearchQuery: "검색어", + adminSearchTime: "시간", + adminSearchId: "ID", + favorites: "내 즐겨찾기", + favoritesComingSoon: "출시 예정", + favoritesComingSoonDesc: + "로그인과 즐겨찾기 기능을 개발 중입니다. 많은 기대 부탁드립니다.", + featureUnavailable: "준비 중", + featureUnavailableDesc: "이 기능은 아직 사용할 수 없습니다.", + confirm: "확인", + backToHome: "홈으로", +}; diff --git a/src/locales/types.ts b/src/locales/types.ts new file mode 100644 index 0000000..e15c507 --- /dev/null +++ b/src/locales/types.ts @@ -0,0 +1 @@ +export type Dict = Record; diff --git a/src/locales/zh-CN.ts b/src/locales/zh-CN.ts new file mode 100644 index 0000000..df3d915 --- /dev/null +++ b/src/locales/zh-CN.ts @@ -0,0 +1,129 @@ +import type { Dict } from "./types"; + +export const zhDict: Dict = { + brand: "ARK 资料库", + mainNav: "网站导航", + home: "首页", + all: "全部资料", + categories: "资料分类", + latest: "最新更新", + official: "官方推荐", + popular: "热门资料", + search: "搜索", + searchPlaceholder: "搜索资料...", + searchPanelPlaceholder: "搜索资料...", + searchNow: "立即搜索资料", + searchSubmit: "搜索", + cancel: "取消", + clear: "清除", + searchPanelHint: "支持搜索 标题・分类・标签・简介・文件类型・正文", + currentTags: "现有标签", + noTagsAvailable: "暂无可选择的标签。", + tagPostsTitle: "#{{tag}} 相关资料", + noTagPosts: "暂时找不到带有此标签的资料。", + viewAll: "查看全部", + backToTop: "回到顶部", + heroTitle: "ARK 官方数据库", + heroSub: + "集中、分类、管理 ARK 数据库,让你快速找到所需资源,推动社群共识与成长。", + categorySection: "资料分类", + officialSection: "官方推荐", + latestSection: "最新更新", + popularSection: "热门资料", + preview: "预览", + download: "下载", + downloading: "下载中…", + downloadOk: "下载完成", + downloadFail: "下载失败,请重试", + longPressImageSave: "长按图片保存到相册", + showMore: "展开全部", + showLess: "收起全部", + share: "分享", + langLabel: "语言", + admin: "后台", + login: "登录", + logout: "退出", + email: "邮箱", + password: "密码", + dashboard: "仪表盘", + resources: "资料管理", + newResource: "新增资料", + save: "保存", + title: "标题", + description: "简介", + type: "类型", + language: "语言", + category: "分类", + status: "状态", + public: "公开", + downloadable: "可下载", + recommended: "首页推荐", + cover: "封面图 URL", + fileUrl: "文件 URL", + externalUrl: "外部链接", + body: "文案内容", + badge: "推荐标签", + published: "已发布", + draft: "草稿", + archived: "归档", + noResults: "找不到符合的资料,请换个关键字或浏览分类。", + copyLink: "复制链接", + related: "相关资料", + total: "总资料", + views: "浏览", + downloads: "下载", + lang_zh_CN: "中文", + lang_en: "English", + lang_ja: "日本語", + lang_ko: "한국어", + lang_vi: "Tiếng Việt", + lang_id: "Bahasa Indonesia", + lang_ms: "Bahasa Melayu", + filterAll: "全部", + sortPublished: "发布时间", + type_ppt: "PPT", + type_music: "音乐", + type_video: "视频", + type_image: "图片", + type_pdf: "PDF", + type_link: "链接", + type_text: "文字", + type_archive: "压缩包", + type_zip: "ZIP", + adminLoginTitle: "管理后台登录", + adminEditResource: "编辑资料", + adminVideoFileHint: + "上传视频文件(MP4/WebM/MOV 等),类型请选择「视频」;保存后前台自动播放(默认静音,可点喇叭开声音)。", + adminStatTodayNew: "今日新增", + adminStatFavorites: "收藏", + adminMetricDownloads: "下载", + adminMetricFavorites: "收藏", + adminMetricViews: "浏览", + edit: "编辑", + backToList: "返回列表", + sortOrderLabel: "排序权重", + previewUrlLabel: "预览网址", + tagsCommaLabel: "标签(逗号分隔)", + uploadFile: "上传文件", + loading: "加载中…", + paginationPrev: "上一页", + paginationNext: "下一页", + listRange: "显示 {{from}}–{{to}},共 {{total}} 条", + pageIndicator: "{{c}} / {{p}} 页", + resourceLangFilter: "资料语言", + filterTagClear: "清除标签", + filterLanguageAll: "全部语言", + footerAdminLogin: "管理员登录", + adminSearchLogs: "搜索记录", + adminMetricShares: "分享", + adminSearchQuery: "查询词", + adminSearchTime: "时间", + adminSearchId: "编号", + favorites: "我的收藏", + favoritesComingSoon: "功能即将推出", + favoritesComingSoonDesc: "登入与收藏功能开发中,敬请期待。", + featureUnavailable: "未开放", + featureUnavailableDesc: "该功能暂未开放。", + confirm: "知道了", + backToHome: "返回首页", +}; From c53032155b68628b50f55daaf33ffcd963329c36 Mon Sep 17 00:00:00 2001 From: TerryM Date: Mon, 1 Jun 2026 15:54:29 +0800 Subject: [PATCH 4/7] feat(i18n): add full ja/vi/id/ms translations and drop languageNames fallback - Add complete dicts: src/locales/{ja,vi,id,ms}.ts (115 keys each) - Remove languageNames override map; dict object now points directly to each locale - i18n.tsx shrinks from ~414 lines to ~81 lines --- src/i18n.tsx | 97 ++++------------------------------ src/locales/id.ts | 131 ++++++++++++++++++++++++++++++++++++++++++++++ src/locales/ja.ts | 131 ++++++++++++++++++++++++++++++++++++++++++++++ src/locales/ms.ts | 131 ++++++++++++++++++++++++++++++++++++++++++++++ src/locales/vi.ts | 131 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 534 insertions(+), 87 deletions(-) create mode 100644 src/locales/id.ts create mode 100644 src/locales/ja.ts create mode 100644 src/locales/ms.ts create mode 100644 src/locales/vi.ts diff --git a/src/i18n.tsx b/src/i18n.tsx index 3313f27..ca3c216 100644 --- a/src/i18n.tsx +++ b/src/i18n.tsx @@ -9,99 +9,22 @@ import { languageForHomePathname } from "./languageRoutes"; import type { Dict } from "./locales/types"; import { zhDict } from "./locales/zh-CN"; import { enDict } from "./locales/en"; +import { jaDict } from "./locales/ja"; import { koDict } from "./locales/ko"; +import { viDict } from "./locales/vi"; +import { idDict } from "./locales/id"; +import { msDict } from "./locales/ms"; export type Lang = "zh-CN" | "en" | "ja" | "ko" | "vi" | "id" | "ms"; -const languageNames: Record = { - "zh-CN": { - lang_zh_CN: "中文", - lang_en: "English", - lang_ja: "日本語", - lang_ko: "한국어", - lang_vi: "Tiếng Việt", - lang_id: "Bahasa Indonesia", - lang_ms: "Bahasa Melayu", - }, - en: { - lang_zh_CN: "Chinese", - lang_en: "English", - lang_ja: "Japanese", - lang_ko: "Korean", - lang_vi: "Vietnamese", - lang_id: "Indonesian", - lang_ms: "Malay", - }, - ja: { - brand: "ARK ライブラリー", - showMore: "すべて表示", - showLess: "閉じる", - lang_zh_CN: "中国語", - lang_en: "英語", - lang_ja: "日本語", - lang_ko: "韓国語", - lang_vi: "ベトナム語", - lang_id: "インドネシア語", - lang_ms: "マレー語", - }, - ko: { - brand: "ARK 라이브러리", - showMore: "모두 보기", - showLess: "접기", - lang_zh_CN: "중국어", - lang_en: "영어", - lang_ja: "일본어", - lang_ko: "한국어", - lang_vi: "베트남어", - lang_id: "인도네시아어", - lang_ms: "말레이어", - }, - vi: { - brand: "Thư viện ARK", - showMore: "Xem tất cả", - showLess: "Thu gọn", - lang_zh_CN: "Tiếng Trung", - lang_en: "Tiếng Anh", - lang_ja: "Tiếng Nhật", - lang_ko: "Tiếng Hàn", - lang_vi: "Tiếng Việt", - lang_id: "Tiếng Indonesia", - lang_ms: "Tiếng Mã Lai", - }, - id: { - brand: "Perpustakaan ARK", - showMore: "Lihat semua", - showLess: "Tutup", - lang_zh_CN: "Bahasa Tionghoa", - lang_en: "Bahasa Inggris", - lang_ja: "Bahasa Jepang", - lang_ko: "Bahasa Korea", - lang_vi: "Bahasa Vietnam", - lang_id: "Bahasa Indonesia", - lang_ms: "Bahasa Melayu", - }, - ms: { - brand: "Perpustakaan ARK", - showMore: "Lihat semua", - showLess: "Tutup", - lang_zh_CN: "Bahasa Cina", - lang_en: "Bahasa Inggeris", - lang_ja: "Bahasa Jepun", - lang_ko: "Bahasa Korea", - lang_vi: "Bahasa Vietnam", - lang_id: "Bahasa Indonesia", - lang_ms: "Bahasa Melayu", - }, -}; - const dict: Record = { - "zh-CN": { ...zhDict, ...languageNames["zh-CN"] }, - en: { ...enDict, ...languageNames.en }, - ja: { ...enDict, ...languageNames.ja }, + "zh-CN": zhDict, + en: enDict, + ja: jaDict, ko: koDict, - vi: { ...enDict, ...languageNames.vi }, - id: { ...enDict, ...languageNames.id }, - ms: { ...enDict, ...languageNames.ms }, + vi: viDict, + id: idDict, + ms: msDict, }; /** Fixed locale lookup (admin UI uses Simplified Chinese). */ diff --git a/src/locales/id.ts b/src/locales/id.ts new file mode 100644 index 0000000..a1fd83c --- /dev/null +++ b/src/locales/id.ts @@ -0,0 +1,131 @@ +import type { Dict } from "./types"; + +export const idDict: Dict = { + brand: "Perpustakaan ARK", + mainNav: "Menu situs", + home: "Beranda", + all: "Semua aset", + categories: "Kategori", + latest: "Terbaru", + official: "Pilihan resmi", + popular: "Populer", + search: "Cari", + searchPlaceholder: "Cari sumber daya...", + searchPanelPlaceholder: "Cari aset...", + searchNow: "Cari sekarang", + searchSubmit: "Cari", + cancel: "Batal", + clear: "Hapus", + searchPanelHint: + "Pencarian mendukung judul, kategori, tag, ringkasan, jenis file, dan isi.", + currentTags: "Tag tersedia", + noTagsAvailable: "Belum ada tag yang tersedia.", + tagPostsTitle: "Postingan terkait #{{tag}}", + noTagPosts: "Belum ada postingan dengan tag ini.", + viewAll: "Lihat semua", + backToTop: "Kembali ke atas", + heroTitle: "Perpustakaan Resmi ARK", + heroSub: + "Memusatkan, mengatur, dan mengelola perpustakaan ARK agar Anda dapat menemukan yang dibutuhkan dengan cepat dan membantu komunitas tumbuh bersama.", + categorySection: "Kategori", + officialSection: "Rekomendasi resmi", + latestSection: "Pembaruan terbaru", + popularSection: "Aset populer", + preview: "Pratinjau", + download: "Unduh", + downloading: "Mengunduh…", + downloadOk: "Unduhan selesai", + downloadFail: "Unduhan gagal, silakan coba lagi", + longPressImageSave: "Tekan lama gambar untuk menyimpan", + showMore: "Lihat semua", + showLess: "Tutup", + share: "Bagikan", + langLabel: "Bahasa", + admin: "Admin", + login: "Masuk", + logout: "Keluar", + email: "Email", + password: "Kata sandi", + dashboard: "Dasbor", + resources: "Sumber daya", + newResource: "Sumber daya baru", + save: "Simpan", + title: "Judul", + description: "Deskripsi", + type: "Jenis", + language: "Bahasa", + category: "Kategori", + status: "Status", + public: "Publik", + downloadable: "Dapat diunduh", + recommended: "Unggulan", + cover: "URL gambar sampul", + fileUrl: "URL file", + externalUrl: "Tautan eksternal", + body: "Isi teks", + badge: "Label lencana", + published: "Diterbitkan", + draft: "Draf", + archived: "Diarsipkan", + noResults: "Tidak ada hasil. Coba kata kunci lain atau telusuri kategori.", + copyLink: "Salin tautan", + related: "Terkait", + total: "Total item", + views: "Tampilan", + downloads: "Unduhan", + lang_zh_CN: "Bahasa Tionghoa", + lang_en: "Bahasa Inggris", + lang_ja: "Bahasa Jepang", + lang_ko: "Bahasa Korea", + lang_vi: "Bahasa Vietnam", + lang_id: "Bahasa Indonesia", + lang_ms: "Bahasa Melayu", + filterAll: "Semua jenis", + sortPublished: "Tanggal terbit", + type_ppt: "PPT", + type_music: "Musik", + type_video: "Video", + type_image: "Gambar", + type_pdf: "PDF", + type_link: "Tautan", + type_text: "Teks", + type_archive: "Arsip", + type_zip: "ZIP", + adminLoginTitle: "Masuk admin", + adminEditResource: "Edit sumber daya", + adminVideoFileHint: + "Unggah file video (MP4/WebM/MOV, dll.) dan atur jenis ke Video; situs akan memutar otomatis (default tanpa suara — pengguna dapat membunyikan).", + adminStatTodayNew: "Baru hari ini", + adminStatFavorites: "Favorit", + adminMetricDownloads: "Unduhan", + adminMetricFavorites: "Favorit", + adminMetricViews: "Tampilan", + edit: "Edit", + backToList: "Kembali ke daftar", + sortOrderLabel: "Urutan", + previewUrlLabel: "URL pratinjau", + tagsCommaLabel: "Tag (dipisahkan koma)", + uploadFile: "Unggah", + loading: "Memuat…", + paginationPrev: "Sebelumnya", + paginationNext: "Berikutnya", + listRange: "Menampilkan {{from}}–{{to}} dari {{total}}", + pageIndicator: "Halaman {{c}} / {{p}}", + resourceLangFilter: "Bahasa sumber daya", + filterTagClear: "Hapus tag", + filterLanguageAll: "Semua bahasa", + footerAdminLogin: "Masuk admin", + adminSearchLogs: "Log pencarian", + adminMetricShares: "Berbagi", + adminSearchQuery: "Kueri", + adminSearchTime: "Waktu", + adminSearchId: "ID", + favorites: "Favorit Saya", + favoritesComingSoon: "Segera Hadir", + favoritesComingSoonDesc: + "Fitur masuk dan favorit sedang dikembangkan. Nantikan.", + featureUnavailable: "Belum tersedia", + featureUnavailableDesc: "Fitur ini belum tersedia.", + confirm: "Mengerti", + backToHome: "Kembali ke Beranda", +}; diff --git a/src/locales/ja.ts b/src/locales/ja.ts new file mode 100644 index 0000000..0a6968c --- /dev/null +++ b/src/locales/ja.ts @@ -0,0 +1,131 @@ +import type { Dict } from "./types"; + +export const jaDict: Dict = { + brand: "ARK ライブラリー", + mainNav: "サイトメニュー", + home: "ホーム", + all: "すべての資料", + categories: "カテゴリー", + latest: "最新", + official: "公式おすすめ", + popular: "人気", + search: "検索", + searchPlaceholder: "資料を検索...", + searchPanelPlaceholder: "資料を検索...", + searchNow: "今すぐ検索", + searchSubmit: "検索", + cancel: "キャンセル", + clear: "クリア", + searchPanelHint: + "タイトル・カテゴリー・タグ・概要・ファイル形式・本文の検索に対応", + currentTags: "利用可能なタグ", + noTagsAvailable: "現在利用可能なタグはありません。", + tagPostsTitle: "#{{tag}} 関連の資料", + noTagPosts: "このタグの資料はまだありません。", + viewAll: "すべて表示", + backToTop: "トップへ戻る", + heroTitle: "ARK 公式データベース", + heroSub: + "ARK ライブラリーを集約・整理・管理し、必要な資料をすばやく見つけてコミュニティの成長を促進します。", + categorySection: "カテゴリー", + officialSection: "公式おすすめ", + latestSection: "最新の更新", + popularSection: "人気の資料", + preview: "プレビュー", + download: "ダウンロード", + downloading: "ダウンロード中…", + downloadOk: "ダウンロード完了", + downloadFail: "ダウンロードに失敗しました。再試行してください", + longPressImageSave: "画像を長押しして保存", + showMore: "すべて表示", + showLess: "閉じる", + share: "シェア", + langLabel: "言語", + admin: "管理画面", + login: "ログイン", + logout: "ログアウト", + email: "メールアドレス", + password: "パスワード", + dashboard: "ダッシュボード", + resources: "資料管理", + newResource: "新規資料", + save: "保存", + title: "タイトル", + description: "説明", + type: "種類", + language: "言語", + category: "カテゴリー", + status: "ステータス", + public: "公開", + downloadable: "ダウンロード可", + recommended: "おすすめ", + cover: "カバー画像 URL", + fileUrl: "ファイル URL", + externalUrl: "外部リンク", + body: "本文", + badge: "推薦バッジ", + published: "公開済み", + draft: "下書き", + archived: "アーカイブ", + noResults: + "該当する資料が見つかりません。別のキーワードを試すか、カテゴリーをご覧ください。", + copyLink: "リンクをコピー", + related: "関連資料", + total: "総資料数", + views: "閲覧", + downloads: "ダウンロード", + lang_zh_CN: "中国語", + lang_en: "英語", + lang_ja: "日本語", + lang_ko: "韓国語", + lang_vi: "ベトナム語", + lang_id: "インドネシア語", + lang_ms: "マレー語", + filterAll: "すべての種類", + sortPublished: "公開日", + type_ppt: "PPT", + type_music: "音楽", + type_video: "動画", + type_image: "画像", + type_pdf: "PDF", + type_link: "リンク", + type_text: "テキスト", + type_archive: "アーカイブ", + type_zip: "ZIP", + adminLoginTitle: "管理画面ログイン", + adminEditResource: "資料を編集", + adminVideoFileHint: + "動画ファイル(MP4/WebM/MOV など)をアップロードし、種類を「動画」に設定すると、サイト上で自動再生されます(デフォルトはミュート、ユーザーが解除可能)。", + adminStatTodayNew: "本日の新規", + adminStatFavorites: "お気に入り", + adminMetricDownloads: "ダウンロード", + adminMetricFavorites: "お気に入り", + adminMetricViews: "閲覧", + edit: "編集", + backToList: "一覧へ戻る", + sortOrderLabel: "並び順", + previewUrlLabel: "プレビュー URL", + tagsCommaLabel: "タグ(カンマ区切り)", + uploadFile: "アップロード", + loading: "読み込み中…", + paginationPrev: "前へ", + paginationNext: "次へ", + listRange: "{{from}}–{{to}} / 全 {{total}} 件", + pageIndicator: "{{c}} / {{p}} ページ", + resourceLangFilter: "資料の言語", + filterTagClear: "タグをクリア", + filterLanguageAll: "すべての言語", + footerAdminLogin: "管理者ログイン", + adminSearchLogs: "検索履歴", + adminMetricShares: "シェア", + adminSearchQuery: "検索キーワード", + adminSearchTime: "時刻", + adminSearchId: "ID", + favorites: "お気に入り", + favoritesComingSoon: "近日公開", + favoritesComingSoonDesc: "ログインとお気に入り機能は開発中です。お楽しみに。", + featureUnavailable: "未公開", + featureUnavailableDesc: "この機能はまだご利用いただけません。", + confirm: "了解", + backToHome: "ホームへ戻る", +}; diff --git a/src/locales/ms.ts b/src/locales/ms.ts new file mode 100644 index 0000000..65a9047 --- /dev/null +++ b/src/locales/ms.ts @@ -0,0 +1,131 @@ +import type { Dict } from "./types"; + +export const msDict: Dict = { + brand: "Perpustakaan ARK", + mainNav: "Menu laman", + home: "Laman utama", + all: "Semua aset", + categories: "Kategori", + latest: "Terkini", + official: "Pilihan rasmi", + popular: "Popular", + search: "Cari", + searchPlaceholder: "Cari sumber...", + searchPanelPlaceholder: "Cari aset...", + searchNow: "Cari sekarang", + searchSubmit: "Cari", + cancel: "Batal", + clear: "Kosongkan", + searchPanelHint: + "Carian menyokong tajuk, kategori, tag, ringkasan, jenis fail dan isi teks.", + currentTags: "Tag tersedia", + noTagsAvailable: "Belum ada tag tersedia.", + tagPostsTitle: "Pos berkaitan #{{tag}}", + noTagPosts: "Belum ada pos dengan tag ini.", + viewAll: "Lihat semua", + backToTop: "Kembali ke atas", + heroTitle: "Perpustakaan Rasmi ARK", + heroSub: + "Memusatkan, menyusun dan mengurus perpustakaan ARK supaya anda dapat mencari apa yang diperlukan dengan cepat dan membantu komuniti berkembang bersama.", + categorySection: "Kategori", + officialSection: "Cadangan rasmi", + latestSection: "Kemas kini terkini", + popularSection: "Aset popular", + preview: "Pratonton", + download: "Muat turun", + downloading: "Memuat turun…", + downloadOk: "Muat turun selesai", + downloadFail: "Muat turun gagal, sila cuba lagi", + longPressImageSave: "Tekan lama imej untuk simpan", + showMore: "Lihat semua", + showLess: "Tutup", + share: "Kongsi", + langLabel: "Bahasa", + admin: "Pentadbir", + login: "Log masuk", + logout: "Log keluar", + email: "E-mel", + password: "Kata laluan", + dashboard: "Papan pemuka", + resources: "Sumber", + newResource: "Sumber baharu", + save: "Simpan", + title: "Tajuk", + description: "Penerangan", + type: "Jenis", + language: "Bahasa", + category: "Kategori", + status: "Status", + public: "Awam", + downloadable: "Boleh dimuat turun", + recommended: "Pilihan", + cover: "URL imej muka", + fileUrl: "URL fail", + externalUrl: "Pautan luar", + body: "Isi teks", + badge: "Label lencana", + published: "Diterbitkan", + draft: "Draf", + archived: "Diarkibkan", + noResults: "Tiada hasil. Cuba kata kunci lain atau imbas kategori.", + copyLink: "Salin pautan", + related: "Berkaitan", + total: "Jumlah item", + views: "Tontonan", + downloads: "Muat turun", + lang_zh_CN: "Bahasa Cina", + lang_en: "Bahasa Inggeris", + lang_ja: "Bahasa Jepun", + lang_ko: "Bahasa Korea", + lang_vi: "Bahasa Vietnam", + lang_id: "Bahasa Indonesia", + lang_ms: "Bahasa Melayu", + filterAll: "Semua jenis", + sortPublished: "Tarikh terbit", + type_ppt: "PPT", + type_music: "Muzik", + type_video: "Video", + type_image: "Imej", + type_pdf: "PDF", + type_link: "Pautan", + type_text: "Teks", + type_archive: "Arkib", + type_zip: "ZIP", + adminLoginTitle: "Log masuk pentadbir", + adminEditResource: "Sunting sumber", + adminVideoFileHint: + "Muat naik fail video (MP4/WebM/MOV, dll.) dan tetapkan jenis kepada Video; laman akan main automatik (asalnya senyap — pengguna boleh hidupkan bunyi).", + adminStatTodayNew: "Baharu hari ini", + adminStatFavorites: "Kegemaran", + adminMetricDownloads: "Muat turun", + adminMetricFavorites: "Kegemaran", + adminMetricViews: "Tontonan", + edit: "Sunting", + backToList: "Kembali ke senarai", + sortOrderLabel: "Susunan", + previewUrlLabel: "URL pratonton", + tagsCommaLabel: "Tag (dipisahkan koma)", + uploadFile: "Muat naik", + loading: "Memuatkan…", + paginationPrev: "Sebelum", + paginationNext: "Seterusnya", + listRange: "Menunjukkan {{from}}–{{to}} daripada {{total}}", + pageIndicator: "Halaman {{c}} / {{p}}", + resourceLangFilter: "Bahasa sumber", + filterTagClear: "Kosongkan tag", + filterLanguageAll: "Semua bahasa", + footerAdminLogin: "Log masuk pentadbir", + adminSearchLogs: "Log carian", + adminMetricShares: "Kongsi", + adminSearchQuery: "Kata kunci", + adminSearchTime: "Masa", + adminSearchId: "ID", + favorites: "Kegemaran Saya", + favoritesComingSoon: "Akan Hadir", + favoritesComingSoonDesc: + "Ciri log masuk dan kegemaran sedang dibangunkan. Nantikan.", + featureUnavailable: "Belum tersedia", + featureUnavailableDesc: "Ciri ini belum tersedia.", + confirm: "Faham", + backToHome: "Kembali ke Laman Utama", +}; diff --git a/src/locales/vi.ts b/src/locales/vi.ts new file mode 100644 index 0000000..838e961 --- /dev/null +++ b/src/locales/vi.ts @@ -0,0 +1,131 @@ +import type { Dict } from "./types"; + +export const viDict: Dict = { + brand: "Thư viện ARK", + mainNav: "Menu trang web", + home: "Trang chủ", + all: "Tất cả tài liệu", + categories: "Danh mục", + latest: "Mới nhất", + official: "Đề xuất chính thức", + popular: "Phổ biến", + search: "Tìm kiếm", + searchPlaceholder: "Tìm tài liệu...", + searchPanelPlaceholder: "Tìm tài liệu...", + searchNow: "Tìm ngay", + searchSubmit: "Tìm kiếm", + cancel: "Hủy", + clear: "Xóa", + searchPanelHint: + "Hỗ trợ tìm theo tiêu đề, danh mục, thẻ, tóm tắt, loại tệp và nội dung.", + currentTags: "Thẻ hiện có", + noTagsAvailable: "Chưa có thẻ nào.", + tagPostsTitle: "Tài liệu liên quan #{{tag}}", + noTagPosts: "Chưa có tài liệu nào với thẻ này.", + viewAll: "Xem tất cả", + backToTop: "Lên đầu trang", + heroTitle: "Thư viện chính thức ARK", + heroSub: + "Tập trung, phân loại và quản lý thư viện ARK để bạn nhanh chóng tìm thấy tài nguyên cần thiết và thúc đẩy sự phát triển của cộng đồng.", + categorySection: "Danh mục", + officialSection: "Đề xuất chính thức", + latestSection: "Cập nhật mới", + popularSection: "Tài liệu phổ biến", + preview: "Xem trước", + download: "Tải xuống", + downloading: "Đang tải xuống…", + downloadOk: "Tải xuống hoàn tất", + downloadFail: "Tải xuống thất bại, vui lòng thử lại", + longPressImageSave: "Nhấn giữ ảnh để lưu", + showMore: "Xem tất cả", + showLess: "Thu gọn", + share: "Chia sẻ", + langLabel: "Ngôn ngữ", + admin: "Quản trị", + login: "Đăng nhập", + logout: "Đăng xuất", + email: "Email", + password: "Mật khẩu", + dashboard: "Bảng điều khiển", + resources: "Tài liệu", + newResource: "Tài liệu mới", + save: "Lưu", + title: "Tiêu đề", + description: "Mô tả", + type: "Loại", + language: "Ngôn ngữ", + category: "Danh mục", + status: "Trạng thái", + public: "Công khai", + downloadable: "Có thể tải xuống", + recommended: "Nổi bật", + cover: "URL ảnh bìa", + fileUrl: "URL tệp", + externalUrl: "Liên kết bên ngoài", + body: "Nội dung", + badge: "Nhãn đề xuất", + published: "Đã xuất bản", + draft: "Bản nháp", + archived: "Đã lưu trữ", + noResults: "Không có kết quả. Hãy thử từ khóa khác hoặc duyệt danh mục.", + copyLink: "Sao chép liên kết", + related: "Tài liệu liên quan", + total: "Tổng số tài liệu", + views: "Lượt xem", + downloads: "Lượt tải", + lang_zh_CN: "Tiếng Trung", + lang_en: "Tiếng Anh", + lang_ja: "Tiếng Nhật", + lang_ko: "Tiếng Hàn", + lang_vi: "Tiếng Việt", + lang_id: "Tiếng Indonesia", + lang_ms: "Tiếng Mã Lai", + filterAll: "Tất cả loại", + sortPublished: "Ngày xuất bản", + type_ppt: "PPT", + type_music: "Âm nhạc", + type_video: "Video", + type_image: "Hình ảnh", + type_pdf: "PDF", + type_link: "Liên kết", + type_text: "Văn bản", + type_archive: "Tệp nén", + type_zip: "ZIP", + adminLoginTitle: "Đăng nhập quản trị", + adminEditResource: "Chỉnh sửa tài liệu", + adminVideoFileHint: + "Tải lên tệp video (MP4/WebM/MOV, v.v.) và đặt loại là Video; trang web sẽ tự động phát (mặc định tắt tiếng, người dùng có thể bật).", + adminStatTodayNew: "Mới hôm nay", + adminStatFavorites: "Yêu thích", + adminMetricDownloads: "Lượt tải", + adminMetricFavorites: "Yêu thích", + adminMetricViews: "Lượt xem", + edit: "Chỉnh sửa", + backToList: "Quay lại danh sách", + sortOrderLabel: "Thứ tự sắp xếp", + previewUrlLabel: "URL xem trước", + tagsCommaLabel: "Thẻ (cách nhau bằng dấu phẩy)", + uploadFile: "Tải lên", + loading: "Đang tải…", + paginationPrev: "Trước", + paginationNext: "Sau", + listRange: "Hiển thị {{from}}–{{to}} trên {{total}}", + pageIndicator: "Trang {{c}} / {{p}}", + resourceLangFilter: "Ngôn ngữ tài liệu", + filterTagClear: "Xóa thẻ", + filterLanguageAll: "Tất cả ngôn ngữ", + footerAdminLogin: "Đăng nhập quản trị", + adminSearchLogs: "Lịch sử tìm kiếm", + adminMetricShares: "Chia sẻ", + adminSearchQuery: "Từ khóa", + adminSearchTime: "Thời gian", + adminSearchId: "ID", + favorites: "Yêu thích của tôi", + favoritesComingSoon: "Sắp ra mắt", + favoritesComingSoonDesc: + "Tính năng đăng nhập và yêu thích đang phát triển. Hãy chờ đón.", + featureUnavailable: "Chưa khả dụng", + featureUnavailableDesc: "Tính năng này hiện chưa khả dụng.", + confirm: "Đã hiểu", + backToHome: "Về trang chủ", +}; From a968f476404b18b8e82b73c42d67d511377cbd47 Mon Sep 17 00:00:00 2001 From: TerryM Date: Mon, 1 Jun 2026 16:35:40 +0800 Subject: [PATCH 5/7] feat: support mobile video previews --- src/App.tsx | 41 +++++++++--- src/components/LatestUpdateRow.tsx | 4 +- src/components/PopularRankList.tsx | 6 +- src/components/RecommendedCard.tsx | 4 +- src/components/SearchPanel.tsx | 4 +- .../messageStream/MessageInlineVideo.tsx | 4 +- .../messageStream/bubbles/VideoBubble.tsx | 21 ++++-- .../hooks/useVideoPreviewSource.ts | 34 ++++++++++ .../utils/videoPreviewSource.test.ts | 45 +++++++++++++ .../messageStream/utils/videoPreviewSource.ts | 17 +++++ src/languageRoutes.ts | 52 +++++++++++++++ src/layouts/PublicLayout.tsx | 65 ++++++++++++------- src/pages/Categories/index.tsx | 4 +- src/pages/Home/index.tsx | 6 +- src/types/post.ts | 2 + src/useLocalizedPath.ts | 13 ++++ 16 files changed, 275 insertions(+), 47 deletions(-) create mode 100644 src/components/messageStream/hooks/useVideoPreviewSource.ts create mode 100644 src/components/messageStream/utils/videoPreviewSource.test.ts create mode 100644 src/components/messageStream/utils/videoPreviewSource.ts create mode 100644 src/useLocalizedPath.ts diff --git a/src/App.tsx b/src/App.tsx index 2dd6d7a..f113a4a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -35,19 +35,11 @@ export default function App() { }> + {/* English (root, no prefix) */} } /> - {localizedHomeRoutes.map((route) => ( - - } - /> - ))} } /> } /> } /> + + {/* Each non-English language gets its own nested tree. */} + {localizedHomeRoutes.map((route) => ( + + + } + /> + } /> + } + /> + } + /> + } + /> + } /> + } + /> + } /> + + ))} {adminEnabled ? ( diff --git a/src/components/LatestUpdateRow.tsx b/src/components/LatestUpdateRow.tsx index 273991e..cc90c3d 100644 --- a/src/components/LatestUpdateRow.tsx +++ b/src/components/LatestUpdateRow.tsx @@ -2,6 +2,7 @@ import { Link } from "react-router-dom"; import type { Resource } from "../api"; import { CategoryIcon } from "./CategoryIcon"; import { useI18n } from "../i18n"; +import { useLocalizedPath } from "../useLocalizedPath"; import { resourceTypeLabel } from "../resourceTypeLabels"; import { formatDateYmd } from "../utils/format"; @@ -16,10 +17,11 @@ export function LatestUpdateRow({ iconKey: string; }) { const { t } = useI18n(); + const lp = useLocalizedPath(); const dateStr = formatDateYmd(r.updatedAt); return ( - +
- navigate(`/browse?sort=popular&post=${encodeURIComponent(post.id)}`) + navigate( + lp(`/browse?sort=popular&post=${encodeURIComponent(post.id)}`), + ) } aria-label={r.title} className="absolute inset-0 z-0 rounded-2xl outline-none focus-visible:ring-2 focus-visible:ring-ark-gold/70" diff --git a/src/components/RecommendedCard.tsx b/src/components/RecommendedCard.tsx index b550652..549866f 100644 --- a/src/components/RecommendedCard.tsx +++ b/src/components/RecommendedCard.tsx @@ -4,6 +4,7 @@ import { Link } from "react-router-dom"; import type { Resource } from "../api"; import { assetUrl } from "../api"; import { useI18n } from "../i18n"; +import { useLocalizedPath } from "../useLocalizedPath"; import { useMemo, useState } from "react"; import { formatDateYmd } from "../utils/format"; import { DownloadCloudIcon } from "./icons/DownloadCloudIcon"; @@ -49,6 +50,7 @@ export function RecommendedCard({ layout?: "carousel" | "grid"; }) { const { t } = useI18n(); + const lp = useLocalizedPath(); const { showToast } = useToast(); const [isDownloading, setIsDownloading] = useState(false); const figmaCover = @@ -105,7 +107,7 @@ export function RecommendedCard({ }`} > diff --git a/src/components/SearchPanel.tsx b/src/components/SearchPanel.tsx index 2a6f14a..220acc6 100644 --- a/src/components/SearchPanel.tsx +++ b/src/components/SearchPanel.tsx @@ -10,6 +10,7 @@ import { import { Link } from "react-router-dom"; import { getJSON, itemsOrEmpty, readJSONCache } from "../api"; import { langQuery, type Lang } from "../i18n"; +import { useLocalizedPath } from "../useLocalizedPath"; import type { Post, PostListResponse } from "../types/post"; import { MessageBubble } from "./messageStream/MessageBubble"; import { postDisplayText, postTitleText } from "./messageStream/utils/postText"; @@ -126,6 +127,7 @@ export function SearchPanel({ onResultClick, }: SearchPanelProps) { const inputRef = useRef(null); + const lp = useLocalizedPath(); const [tags, setTags] = useState([]); const [selectedTag, setSelectedTag] = useState(""); const [tagPosts, setTagPosts] = useState([]); @@ -334,7 +336,7 @@ export function SearchPanel({ return ( diff --git a/src/components/messageStream/MessageInlineVideo.tsx b/src/components/messageStream/MessageInlineVideo.tsx index 0a3809b..799de35 100644 --- a/src/components/messageStream/MessageInlineVideo.tsx +++ b/src/components/messageStream/MessageInlineVideo.tsx @@ -9,6 +9,7 @@ import { } from "react"; import type { Attachment } from "../../types/post"; import { AttachmentDownloadPill } from "./AttachmentDownloadPill"; +import { useVideoPreviewSource } from "./hooks/useVideoPreviewSource"; import { useVideoPlayer } from "./overlays/VideoPlayer"; function pad2(n: number): string { @@ -127,6 +128,7 @@ export function MessageInlineVideo({ const [snapProgress, setSnapProgress] = useState(false); const t = TOKENS[size]; + const videoSrc = useVideoPreviewSource(attachment); useEffect(() => { const v = videoRef.current; @@ -270,7 +272,7 @@ export function MessageInlineVideo({ <>