From 20951eb04fe54e4da1c73adf39b2899c6f2c70ef Mon Sep 17 00:00:00 2001 From: rustdesk Date: Tue, 22 Oct 2024 14:53:53 +0800 Subject: [PATCH] i18n --- v3/astro.config.ts | 9 +- v3/deploy.sh | 1 - v3/src/assets/styles/tailwind.css | 2 +- v3/src/components/common/BasicScripts.astro | 19 + v3/src/components/widgets/Footer.astro | 3 +- v3/src/components/widgets/Header.astro | 49 ++- v3/src/config.yaml | 1 - v3/src/i18n.ts | 92 ++++ v3/src/layouts/Layout.astro | 59 ++- v3/src/layouts/PageLayout.astro | 11 +- v3/src/locales.ts | 51 +++ v3/src/navigation.ts | 309 +++++++++---- v3/src/pages/de/index.astro | 255 +++++++++++ v3/src/pages/de/pricing.astro | 449 +++++++++++++++++++ v3/src/pages/de/support.astro | 43 ++ v3/src/pages/de/team.astro | 64 +++ v3/src/pages/es/index.astro | 240 +++++++++++ v3/src/pages/es/pricing.astro | 449 +++++++++++++++++++ v3/src/pages/es/support.astro | 43 ++ v3/src/pages/es/team.astro | 69 +++ v3/src/pages/fr/index.astro | 240 +++++++++++ v3/src/pages/fr/pricing.astro | 449 +++++++++++++++++++ v3/src/pages/fr/support.astro | 43 ++ v3/src/pages/fr/team.astro | 70 +++ v3/src/pages/index.astro | 24 +- v3/src/pages/it/index.astro | 240 +++++++++++ v3/src/pages/it/pricing.astro | 449 +++++++++++++++++++ v3/src/pages/it/support.astro | 43 ++ v3/src/pages/it/team.astro | 64 +++ v3/src/pages/ja/index.astro | 239 +++++++++++ v3/src/pages/ja/pricing.astro | 449 +++++++++++++++++++ v3/src/pages/ja/support.astro | 43 ++ v3/src/pages/ja/team.astro | 64 +++ v3/src/pages/pricing.astro | 23 +- v3/src/pages/pt/index.astro | 240 +++++++++++ v3/src/pages/pt/pricing.astro | 449 +++++++++++++++++++ v3/src/pages/pt/support.astro | 43 ++ v3/src/pages/pt/team.astro | 70 +++ v3/src/pages/support.astro | 8 +- v3/src/pages/team.astro | 2 +- v3/src/pages/zh-cn/index.astro | 239 +++++++++++ v3/src/pages/zh-cn/pricing.astro | 453 ++++++++++++++++++++ v3/src/pages/zh-cn/support.astro | 43 ++ v3/src/pages/zh-cn/team.astro | 64 +++ v3/src/pages/zh-tw/index.astro | 239 +++++++++++ v3/src/pages/zh-tw/pricing.astro | 449 +++++++++++++++++++ v3/src/pages/zh-tw/support.astro | 43 ++ v3/src/pages/zh-tw/team.astro | 64 +++ v3/src/utils/permalinks.ts | 2 +- 49 files changed, 6935 insertions(+), 131 deletions(-) create mode 100644 v3/src/i18n.ts create mode 100644 v3/src/locales.ts create mode 100644 v3/src/pages/de/index.astro create mode 100644 v3/src/pages/de/pricing.astro create mode 100644 v3/src/pages/de/support.astro create mode 100644 v3/src/pages/de/team.astro create mode 100644 v3/src/pages/es/index.astro create mode 100644 v3/src/pages/es/pricing.astro create mode 100644 v3/src/pages/es/support.astro create mode 100644 v3/src/pages/es/team.astro create mode 100644 v3/src/pages/fr/index.astro create mode 100644 v3/src/pages/fr/pricing.astro create mode 100644 v3/src/pages/fr/support.astro create mode 100644 v3/src/pages/fr/team.astro create mode 100644 v3/src/pages/it/index.astro create mode 100644 v3/src/pages/it/pricing.astro create mode 100644 v3/src/pages/it/support.astro create mode 100644 v3/src/pages/it/team.astro create mode 100644 v3/src/pages/ja/index.astro create mode 100644 v3/src/pages/ja/pricing.astro create mode 100644 v3/src/pages/ja/support.astro create mode 100644 v3/src/pages/ja/team.astro create mode 100644 v3/src/pages/pt/index.astro create mode 100644 v3/src/pages/pt/pricing.astro create mode 100644 v3/src/pages/pt/support.astro create mode 100644 v3/src/pages/pt/team.astro create mode 100644 v3/src/pages/zh-cn/index.astro create mode 100644 v3/src/pages/zh-cn/pricing.astro create mode 100644 v3/src/pages/zh-cn/support.astro create mode 100644 v3/src/pages/zh-cn/team.astro create mode 100644 v3/src/pages/zh-tw/index.astro create mode 100644 v3/src/pages/zh-tw/pricing.astro create mode 100644 v3/src/pages/zh-tw/support.astro create mode 100644 v3/src/pages/zh-tw/team.astro diff --git a/v3/astro.config.ts b/v3/astro.config.ts index 2f5778f..c393151 100644 --- a/v3/astro.config.ts +++ b/v3/astro.config.ts @@ -25,7 +25,14 @@ const whenExternalScripts = (items: (() => AstroIntegration) | (() => AstroInteg export default defineConfig({ output: 'static', - + i18n: { + defaultLocale: 'en', + locales: ['en', 'de', 'es', 'fr', 'it', 'ja', 'pt', 'zh-cn', 'zh-tw'], + routing: { + prefixDefaultLocale: true, + redirectToDefaultLocale: false, + }, + }, integrations: [ react(), tailwind({ diff --git a/v3/deploy.sh b/v3/deploy.sh index fcb7385..27b6fc8 100755 --- a/v3/deploy.sh +++ b/v3/deploy.sh @@ -11,7 +11,6 @@ ln -s open-source/index.html open-source.html ln -s ../v2/self-host ln -s ../v2/web - cp -rf ../zh ./ tar czf x * scp x ot:/tmp/ ssh ot "sudo tar xzf /tmp/x -C /var/www/html/v3 && /bin/rm /tmp/x && sudo chown www-data:www-data /var/www/html/v3 -R" diff --git a/v3/src/assets/styles/tailwind.css b/v3/src/assets/styles/tailwind.css index d36f8fa..d209acc 100644 --- a/v3/src/assets/styles/tailwind.css +++ b/v3/src/assets/styles/tailwind.css @@ -51,7 +51,7 @@ @apply py-3; } */ -#header.expanded nav { +#header.expanded nav, #languages.expanded { position: fixed; top: 70px; left: 0; diff --git a/v3/src/components/common/BasicScripts.astro b/v3/src/components/common/BasicScripts.astro index 4ee6a64..fa3c9a9 100644 --- a/v3/src/components/common/BasicScripts.astro +++ b/v3/src/components/common/BasicScripts.astro @@ -44,6 +44,24 @@ import { UI } from 'astrowind:config'; let lastKnownScrollPosition = window.scrollY; let ticking = true; + attachEvent('#languageSelect', 'click', function () { + document.getElementById('languages')?.classList.remove('hidden'); + }); + + /* + attachEvent('#languages', 'mouseleave', function () { + document.getElementById('languages')?.classList.add('hidden'); + }); + */ + + document.addEventListener('click', function (event) { + const languagesDropdown = document.getElementById('languages'); + const languageSelect = document.getElementById('languageSelect'); + if (languagesDropdown && !languagesDropdown.contains(event.target) && !languageSelect.contains(event.target)) { + languagesDropdown.classList.add('hidden'); + } + }); + attachEvent('#header nav', 'click', function () { document.querySelector('[data-aw-toggle-menu]')?.classList.remove('expanded'); document.body.classList.remove('overflow-hidden'); @@ -56,6 +74,7 @@ import { UI } from 'astrowind:config'; attachEvent('[data-aw-toggle-menu]', 'click', function (_, elem) { elem.classList.toggle('expanded'); + document.getElementById('languages')?.classList.toggle('expanded'); document.body.classList.toggle('overflow-hidden'); document.getElementById('header')?.classList.toggle('h-screen'); document.getElementById('header')?.classList.toggle('expanded'); diff --git a/v3/src/components/widgets/Footer.astro b/v3/src/components/widgets/Footer.astro index 2b7fb6e..9b28180 100644 --- a/v3/src/components/widgets/Footer.astro +++ b/v3/src/components/widgets/Footer.astro @@ -2,6 +2,7 @@ import { Icon } from 'astro-icon/components'; import { SITE } from 'astrowind:config'; import { getHomePermalink } from '~/utils/permalinks'; +import { getLocalPath } from '@/i18n'; interface Link { text?: string; @@ -32,7 +33,7 @@ const { socialLinks = [], secondaryLinks = [], links = [], footNote = '', theme
{ diff --git a/v3/src/components/widgets/Header.astro b/v3/src/components/widgets/Header.astro index f0b7bcf..cff3844 100644 --- a/v3/src/components/widgets/Header.astro +++ b/v3/src/components/widgets/Header.astro @@ -4,10 +4,12 @@ import Logo from '~/components/Logo.astro'; import ToggleTheme from '~/components/common/ToggleTheme.astro'; import ToggleMenu from '~/components/common/ToggleMenu.astro'; import Button from '~/components/ui/Button.astro'; +import { getLocalPath } from '@/i18n'; import { getHomePermalink } from '~/utils/permalinks'; import { trimSlash, getAsset } from '~/utils/permalinks'; import type { CallToAction } from '~/types'; +import { getLocalePaths, LOCALES, DEFAULT_LOCALE } from '@/i18n'; interface Link { text?: string; @@ -31,6 +33,7 @@ export interface Props { showRssFeed?: boolean; position?: string; showGithubStar?: boolean; + i18n?: boolean; } const { @@ -44,6 +47,7 @@ const { showRssFeed = false, showGithubStar = false, position = 'center', + i18n = false, } = Astro.props; const currentPath = `/${trimSlash(new URL(Astro.url).pathname)}`; @@ -73,7 +77,7 @@ const currentPath = `/${trimSlash(new URL(Astro.url).pathname)}`; ]} >
- +
@@ -137,6 +141,49 @@ const currentPath = `/${trimSlash(new URL(Astro.url).pathname)}`;
{showToggleTheme && } + { + i18n && ( + <> + + + + ) + } { showRssFeed && ( ; +type LocaleConfig = { + readonly label: string; + readonly lang?: string; + readonly dir?: "ltr" | "rtl"; +}; + + +/** + * Type for the language code + * @example + * "en" | "ja" | ... + */ +export type Lang = keyof typeof LOCALES; + + +/** + * Default locale code + * @constant @readonly +*/ +export const DEFAULT_LOCALE = DEFAULT_LOCALE_SETTING as Lang; + + +/** + * Type for the multilingual object + * @example + * { en: "Hello", ja: "こんにちは", ... } + */ +export type Multilingual = { [key in Lang]?: string }; + + +/** + * Helper to get the translation function + * @param - The current language + * @returns - The translation function + */ +export function useTranslations(lang: Lang) { + return function t(multilingual: Multilingual | string): string { + if (typeof multilingual === "string") { + return multilingual; + } else { + return multilingual[lang] || multilingual[DEFAULT_LOCALE] || ""; + } + }; +} + + +/** + * Helper to get corresponding path list for all locales + * @param url - The current URL object + * @returns - The list of locale paths + */ +export function getLocalePaths(url: URL): LocalePath[] { + const path = url.pathname.replace(/^\/([a-zA-Z-]+)(?=\/|$)/, (match, p1) => { + return LOCALES[p1] ? '' : match; + }); + return Object.keys(LOCALES).map((lang) => { + return { + lang: lang as Lang, + path: getRelativeLocaleUrl(lang, path) + }; + }); +} +type LocalePath = { + lang: Lang; + path: string; +}; + + +/** + * Helper to get locale parms for Astro's `getStaticPaths` function + * @returns - The list of locale params + * @see https://docs.astro.build/en/guides/routing/#dynamic-routes + */ +export const localeParams = Object.keys(LOCALES).map((lang) => ({ + params: { lang }, +})); + +export const getLocalPath = (lang: Lang, path: string) => { + if (!lang || lang === DEFAULT_LOCALE) { + return path + '?lang=en'; + } + return lang + path; +} diff --git a/v3/src/layouts/Layout.astro b/v3/src/layouts/Layout.astro index 0399592..21ee643 100644 --- a/v3/src/layouts/Layout.astro +++ b/v3/src/layouts/Layout.astro @@ -17,18 +17,38 @@ import Scamming from '../components/common/Scamming.jsx'; // Comment the line below to disable View Transitions import { ViewTransitions } from 'astro:transitions'; +import { useTranslations, getLocalePaths, LOCALES, DEFAULT_LOCALE, type Lang } from '@/i18n'; + import type { MetaData as MetaDataType } from '~/types'; export interface Props { metadata?: MetaDataType; + i18n?: boolean; } -const { metadata = {} } = Astro.props; +const { metadata = {}, i18n } = Astro.props; const { language, textDirection } = I18N; +const t = useTranslations(Astro.currentLocale as Lang); +const langs = Object.keys(LOCALES); +const baseUrl = import.meta.env.PROD ? Astro.site : '/'; +const defaultLocale = DEFAULT_LOCALE; +const locale = Astro.currentLocale as Lang; + +metadata.description = t({ + en: "RustDesk offers an open-source remote desktop solution with self-hosted server options. Perfect TeamViewer alternative for secure, private, and customizable remote access. Explore our professional on-premise licenses.", + es: "RustDesk ofrece una solución de escritorio remoto de código abierto con opciones de servidor autohospedado. La alternativa perfecta a TeamViewer para un acceso remoto seguro, privado y personalizable. Explore nuestras licencias profesionales locales.", + pt: "RustDesk oferece uma solução de desktop remoto de código aberto com opções de servidor auto-hospedado. Alternativa perfeita ao TeamViewer para acesso remoto seguro, privado e personalizável. Explore nossas licenças profissionais locais.", + fr: "RustDesk propose une solution de bureau à distance open source avec des options de serveur auto-hébergé. Alternative parfaite à TeamViewer pour un accès à distance sécurisé, privé et personnalisable. Explorez nos licences professionnel.", + de: "RustDesk bietet eine Open-Source-Lösung für Remote-Desktops mit selbstgehosteten Serveroptionen. Perfekte TeamViewer-Alternative für sicheren, privaten", + it: "RustDesk offre una soluzione open source per desktop remoto con opzioni di server self-hosted. Alternativa perfetta a TeamViewer per un accesso remoto sicuro, privato e personalizzabile. Esplora le nostre licenze professionali on-premise.", + ja: "RustDeskは、セルフホスト型サーバーオプションを備えたオープンソースのリモートデスクトップソリューションを提供しています。安全でプライベート、カスタマイズ可能なリモートアクセスのための完璧なTeamViewerの代替です。プロフェッショナルなオンプレミスライセンスをご覧ください。", + "zh-cn": "RustDesk提供了一个开源的远程桌面解决方案,具有自托管服务器选项。是安全、私密和可定制的远程访问的完美TeamViewer替代品。探索我们的专业本地许可证。", + "zh-tw": "RustDesk 提供了一個開源的遠程桌面解決方案,具有自托管伺服器選項。是安全、私密和可定制的遠程訪問的完美 TeamViewer 替代品。探索我們的專業本地許可證。", + }); --- - + @@ -40,6 +60,41 @@ const { language, textDirection } = I18N; + { + i18n && + getLocalePaths(Astro.url).map(({ path, lang }) => ( + + )) + } + { i18n && Astro.currentLocale == defaultLocale && } diff --git a/v3/src/layouts/PageLayout.astro b/v3/src/layouts/PageLayout.astro index 1c5ef02..616c401 100644 --- a/v3/src/layouts/PageLayout.astro +++ b/v3/src/layouts/PageLayout.astro @@ -1,7 +1,7 @@ --- import Layout from '~/layouts/Layout.astro'; import Header from '~/components/widgets/Header.astro'; -import Footer, {type Props as FD} from '~/components/widgets/Footer.astro'; +import Footer, { type Props as FD } from '~/components/widgets/Footer.astro'; import Announcement from '~/components/widgets/Announcement.astro'; import { headerData, footerData } from '~/navigation'; @@ -10,22 +10,23 @@ import type { MetaData } from '~/types'; export interface Props { metadata?: MetaData; + i18n?: boolean; } -const { metadata } = Astro.props; +const { metadata, i18n } = Astro.props; --- - + -
+
-