diff --git a/site/src/i18n/en.json b/site/src/i18n/en.json new file mode 100644 index 0000000..45197fb --- /dev/null +++ b/site/src/i18n/en.json @@ -0,0 +1,34 @@ +{ + "nav.home": "Home", + "nav.install": "Install", + "nav.manual": "User manual", + "nav.github": "GitHub", + "hero.tabs": "termscp — {host} (sftp) connected", + "hero.local": "/home/veeso (local)", + "hero.remote": "/var/www (remote)", + "hero.status": "↹ switch · ↑↓ move · ↵ open · SPACE transfer", + "hero.title": "The dual-pane explorer for your terminal", + "hero.subtitle": "Local left, remote right. Transfer, sync, edit — never leave the keyboard.", + "hero.kicker": "// terminal file transfer", + "cta.install": "Install termscp →", + "cta.manual": "User manual", + "features.heading": "Why termscp", + "features.handy.title": "Handy UI", + "features.handy.body": "Explore and operate on the remote and local file system with a handy UI.", + "features.cross.title": "Cross platform", + "features.cross.body": "Runs on Windows, macOS, Linux and BSD.", + "features.custom.title": "Customizable", + "features.custom.body": "Customize the explorer, the text editor and default options.", + "features.bookmarks.title": "Bookmarks", + "features.bookmarks.body": "Connect to favourite hosts via bookmarks and recent connections.", + "features.security.title": "Security first", + "features.security.body": "Store passwords in your operating system key vault.", + "features.performance.title": "Eye on performance", + "features.performance.body": "Built with an eye on performance to keep CPU usage low.", + "protocols.heading": "Speaks every protocol", + "install.heading": "Install termscp", + "install.subtitle": "Pick your platform. One command and you're in.", + "footer.support": "Support me", + "footer.rights": "Released under the MIT license.", + "lang.label": "Language" +} diff --git a/site/src/i18n/es.json b/site/src/i18n/es.json new file mode 100644 index 0000000..4ecbd76 --- /dev/null +++ b/site/src/i18n/es.json @@ -0,0 +1,34 @@ +{ + "nav.home": "Inicio", + "nav.install": "Instalar", + "nav.manual": "Manual de usuario", + "nav.github": "GitHub", + "hero.tabs": "termscp — {host} (sftp) conectado", + "hero.local": "/home/veeso (local)", + "hero.remote": "/var/www (remoto)", + "hero.status": "↹ cambiar · ↑↓ mover · ↵ abrir · ESPACIO transferir", + "hero.title": "El explorador de doble panel para tu terminal", + "hero.subtitle": "Local a la izquierda, remoto a la derecha. Transfiere, sincroniza, edita — sin soltar el teclado.", + "hero.kicker": "// transferencia de archivos en terminal", + "cta.install": "Instalar termscp →", + "cta.manual": "Manual de usuario", + "features.heading": "Por qué termscp", + "features.handy.title": "Interfaz práctica", + "features.handy.body": "Explora y opera en el sistema de archivos remoto y local con una interfaz práctica.", + "features.cross.title": "Multiplataforma", + "features.cross.body": "Funciona en Windows, macOS, Linux y BSD.", + "features.custom.title": "Personalizable", + "features.custom.body": "Personaliza el explorador, el editor de texto y las opciones predeterminadas.", + "features.bookmarks.title": "Marcadores", + "features.bookmarks.body": "Conéctate a tus hosts favoritos mediante marcadores y conexiones recientes.", + "features.security.title": "Seguridad primero", + "features.security.body": "Guarda tus contraseñas en la bóveda de claves de tu sistema operativo.", + "features.performance.title": "Ojo en el rendimiento", + "features.performance.body": "Desarrollado teniendo en cuenta el rendimiento para mantener bajo el uso de CPU.", + "protocols.heading": "Habla todos los protocolos", + "install.heading": "Instalar termscp", + "install.subtitle": "Elige tu plataforma. Un comando y listo.", + "footer.support": "Apóyame", + "footer.rights": "Publicado bajo la licencia MIT.", + "lang.label": "Idioma" +} diff --git a/site/src/i18n/fr.json b/site/src/i18n/fr.json new file mode 100644 index 0000000..eb24478 --- /dev/null +++ b/site/src/i18n/fr.json @@ -0,0 +1,33 @@ +{ + "nav.home": "Accueil", + "nav.install": "Installer", + "nav.github": "GitHub", + "hero.tabs": "termscp — {host} (sftp) connecté", + "hero.local": "/home/veeso (local)", + "hero.remote": "/var/www (distant)", + "hero.status": "↹ basculer · ↑↓ déplacer · ↵ ouvrir · ESPACE transférer", + "hero.title": "L'explorateur à deux panneaux pour votre terminal", + "hero.subtitle": "Local à gauche, distant à droite. Transférez, synchronisez, éditez — sans quitter le clavier.", + "hero.kicker": "// transfert de fichiers en terminal", + "cta.install": "Installer termscp →", + "cta.manual": "Manuel d'utilisateur", + "features.heading": "Pourquoi termscp", + "features.handy.title": "Interface pratique", + "features.handy.body": "Explorez et utilisez le système de fichiers distant et local avec une interface pratique.", + "features.cross.title": "Multi-plateforme", + "features.cross.body": "Fonctionne sur Windows, macOS, Linux et BSD.", + "features.custom.title": "Personnalisable", + "features.custom.body": "Personnalisez l'explorateur, l'éditeur de texte et les options par défaut.", + "features.bookmarks.title": "Signets", + "features.bookmarks.body": "Connectez-vous à vos hôtes préférés via des signets et des connexions récentes.", + "features.security.title": "Sécurité avant tout", + "features.security.body": "Enregistrez vos mots de passe dans le coffre-fort de votre système.", + "features.performance.title": "Regard sur les performances", + "features.performance.body": "Développé en gardant un œil sur les performances pour éviter une utilisation élevée du processeur.", + "protocols.heading": "Parle tous les protocoles", + "install.heading": "Installer termscp", + "install.subtitle": "Choisissez votre plateforme. Une commande et c'est parti.", + "footer.support": "Me soutenir", + "footer.rights": "Publié sous licence MIT.", + "lang.label": "Langue" +} diff --git a/site/src/i18n/it.json b/site/src/i18n/it.json new file mode 100644 index 0000000..c4e1f0c --- /dev/null +++ b/site/src/i18n/it.json @@ -0,0 +1,34 @@ +{ + "nav.home": "Home", + "nav.install": "Installa", + "nav.manual": "Manuale utente", + "nav.github": "GitHub", + "hero.tabs": "termscp — {host} (sftp) connesso", + "hero.local": "/home/veeso (locale)", + "hero.remote": "/var/www (remoto)", + "hero.status": "↹ cambia · ↑↓ muovi · ↵ apri · SPAZIO trasferisci", + "hero.title": "L'explorer a doppio pannello per il tuo terminale", + "hero.subtitle": "Locale a sinistra, remoto a destra. Trasferisci, sincronizza, modifica — senza mai lasciare la tastiera.", + "hero.kicker": "// file transfer da terminale", + "cta.install": "Installa termscp →", + "cta.manual": "Manuale utente", + "features.heading": "Perché termscp", + "features.handy.title": "UI ergonomica", + "features.handy.body": "Naviga e lavora sul file system remoto e locale attraverso una UI di facile utilizzo.", + "features.cross.title": "Multi piattaforma", + "features.cross.body": "Gira su Windows, macOS, Linux e BSD.", + "features.custom.title": "Personalizzabile", + "features.custom.body": "Personalizza l'explorer, l'editor di testo e le opzioni predefinite.", + "features.bookmarks.title": "Preferiti", + "features.bookmarks.body": "Connettiti agli host preferiti tramite preferiti e connessioni recenti.", + "features.security.title": "Sicurezza in prima classe", + "features.security.body": "Salva le password nel vault del tuo sistema operativo.", + "features.performance.title": "Focus sulle performance", + "features.performance.body": "Sviluppato con un occhio alle performance per mantenere basso l'uso della CPU.", + "protocols.heading": "Parla ogni protocollo", + "install.heading": "Installa termscp", + "install.subtitle": "Scegli la tua piattaforma. Un comando e sei dentro.", + "footer.support": "Supportami", + "footer.rights": "Rilasciato sotto licenza MIT.", + "lang.label": "Lingua" +} diff --git a/site/src/i18n/ui.test.ts b/site/src/i18n/ui.test.ts new file mode 100644 index 0000000..1836602 --- /dev/null +++ b/site/src/i18n/ui.test.ts @@ -0,0 +1,29 @@ +import { describe, expect, it } from "vitest"; +import { useTranslations, locales, defaultLocale } from "./ui"; + +describe("i18n", () => { + it("exposes the five supported locales with en default", () => { + expect(locales).toEqual(["en", "zh-CN", "it", "fr", "es"]); + expect(defaultLocale).toBe("en"); + }); + + it("resolves a key in the requested locale", () => { + const t = useTranslations("it"); + expect(t("nav.install")).toBe("Installa"); + }); + + it("falls back to en when a key is missing in the locale", () => { + const t = useTranslations("fr"); + expect(t("nav.manual")).toBe("User manual"); + }); + + it("returns the key itself when missing everywhere", () => { + const t = useTranslations("en"); + expect(t("does.not.exist")).toBe("does.not.exist"); + }); + + it("interpolates {vars}", () => { + const t = useTranslations("en"); + expect(t("hero.tabs", { host: "1.2.3.4" })).toContain("1.2.3.4"); + }); +}); diff --git a/site/src/i18n/ui.ts b/site/src/i18n/ui.ts new file mode 100644 index 0000000..9c3219b --- /dev/null +++ b/site/src/i18n/ui.ts @@ -0,0 +1,35 @@ +import en from "./en.json"; +import zhCN from "./zh-CN.json"; +import it from "./it.json"; +import fr from "./fr.json"; +import es from "./es.json"; + +export const locales = ["en", "zh-CN", "it", "fr", "es"] as const; +export type Locale = (typeof locales)[number]; +export const defaultLocale: Locale = "en"; + +const dictionaries: Record> = { + en: en as Record, + "zh-CN": zhCN as Record, + it: it as Record, + fr: fr as Record, + es: es as Record, +}; + +export function isLocale(value: string): value is Locale { + return (locales as readonly string[]).includes(value); +} + +/** Resolve a translator for `locale`, falling back to en, then to the key. */ +export function useTranslations(locale: Locale) { + const dict = dictionaries[locale] ?? dictionaries[defaultLocale]; + return (key: string, vars?: Record): string => { + let value = dict[key] ?? dictionaries[defaultLocale][key] ?? key; + if (vars) { + for (const [k, v] of Object.entries(vars)) { + value = value.replaceAll(`{${k}}`, v); + } + } + return value; + }; +} diff --git a/site/src/i18n/zh-CN.json b/site/src/i18n/zh-CN.json new file mode 100644 index 0000000..9145362 --- /dev/null +++ b/site/src/i18n/zh-CN.json @@ -0,0 +1,34 @@ +{ + "nav.home": "主页", + "nav.install": "安装", + "nav.manual": "用户手册", + "nav.github": "GitHub", + "hero.tabs": "termscp — {host} (sftp) 已连接", + "hero.local": "/home/veeso (本地)", + "hero.remote": "/var/www (远程)", + "hero.status": "↹ 切换 · ↑↓ 移动 · ↵ 打开 · 空格 传输", + "hero.title": "为你的终端打造的双栏文件浏览器", + "hero.subtitle": "左边本地,右边远程。传输、同步、编辑 — 始终不离键盘。", + "hero.kicker": "// 终端文件传输", + "cta.install": "安装 termscp →", + "cta.manual": "用户手册", + "features.heading": "为什么选择 termscp", + "features.handy.title": "方便的用户界面", + "features.handy.body": "使用方便的 UI 在远程和本地文件系统上探索和操作。", + "features.cross.title": "跨平台", + "features.cross.body": "在 Windows、macOS、Linux 和 BSD 上运行。", + "features.custom.title": "可定制", + "features.custom.body": "自定义文件浏览器、文本编辑器和默认选项。", + "features.bookmarks.title": "书签", + "features.bookmarks.body": "通过书签和最近的连接连接到你最喜欢的主机。", + "features.security.title": "安全第一", + "features.security.body": "将你的密码保存到操作系统的密钥保管库中。", + "features.performance.title": "关注性能", + "features.performance.body": "在开发时关注性能,以保持较低的 CPU 使用率。", + "protocols.heading": "支持各种协议", + "install.heading": "安装 termscp", + "install.subtitle": "选择你的平台。一条命令即可上手。", + "footer.support": "支持我", + "footer.rights": "基于 MIT 许可证发布。", + "lang.label": "语言" +} diff --git a/site/tsconfig.json b/site/tsconfig.json index 8bf91d3..0645975 100644 --- a/site/tsconfig.json +++ b/site/tsconfig.json @@ -1,5 +1,8 @@ { "extends": "astro/tsconfigs/strict", + "compilerOptions": { + "resolveJsonModule": true + }, "include": [".astro/types.d.ts", "**/*"], "exclude": ["dist"] } diff --git a/site/vitest.config.ts b/site/vitest.config.ts new file mode 100644 index 0000000..77318ed --- /dev/null +++ b/site/vitest.config.ts @@ -0,0 +1,5 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { include: ["src/**/*.test.ts", "scripts/**/*.test.ts"] }, +});