blog locale

This commit is contained in:
rustdesk
2026-07-04 10:41:20 +08:00
parent 1ab9d9afbb
commit 82afa32934
62 changed files with 1683 additions and 141 deletions
+1
View File
@@ -13,6 +13,7 @@
"build": "astro build",
"preview": "astro preview",
"astro": "astro",
"test": "node --test tests/*.test.mjs",
"check": "npm run check:astro && npm run check:eslint && npm run check:prettier",
"check:astro": "astro check",
"check:eslint": "eslint .",
+5 -3
View File
@@ -65,7 +65,9 @@ const link = APP_BLOG?.post?.isEnabled ? getPermalink(post.permalink, 'post') :
<div class="mb-1">
<span class="text-sm">
<Icon name="tabler:clock" class="w-3.5 h-3.5 inline-block -mt-0.5 dark:text-gray-400" />
<time datetime={String(post.publishDate)} class="inline-block">{getFormattedDate(post.publishDate)}</time>
<time datetime={String(post.publishDate)} class="inline-block"
>{getFormattedDate(post.publishDate, post.lang)}</time
>
{
post.author && (
<>
@@ -80,7 +82,7 @@ const link = APP_BLOG?.post?.isEnabled ? getPermalink(post.permalink, 'post') :
<>
{' '}
·{' '}
<a class="hover:underline" href={getPermalink(post.category.slug, 'category')}>
<a class="hover:underline" href={getPermalink(post.category.slug, 'category', post.lang)}>
{post.category.title}
</a>
</>
@@ -108,7 +110,7 @@ const link = APP_BLOG?.post?.isEnabled ? getPermalink(post.permalink, 'post') :
{
post.tags && Array.isArray(post.tags) ? (
<footer class="mt-5">
<PostTags tags={post.tags} />
<PostTags tags={post.tags} lang={post.lang} />
</footer>
) : (
<Fragment />
+6 -1
View File
@@ -2,15 +2,20 @@
import { Icon } from 'astro-icon/components';
import { getPermalink } from '~/utils/permalinks';
import Button from '~/components/ui/Button.astro';
import { getBlogCopy } from '@/i18n/blog';
import type { Lang } from '@/i18n';
export interface Props {
prevUrl?: string;
nextUrl?: string;
prevText?: string;
nextText?: string;
lang?: Lang;
}
const { prevUrl, nextUrl, prevText = 'Newer posts', nextText = 'Older posts' } = Astro.props;
const { lang = Astro.currentLocale as Lang } = Astro.props;
const copy = getBlogCopy(lang);
const { prevUrl, nextUrl, prevText = copy.newerPosts, nextText = copy.olderPosts } = Astro.props;
---
{
+5 -3
View File
@@ -5,6 +5,7 @@ import { getRelatedPosts } from '~/utils/blog';
import BlogHighlightedPosts from '../widgets/BlogHighlightedPosts.astro';
import type { Post } from '~/types';
import { getBlogPermalink } from '~/utils/permalinks';
import { getBlogCopy } from '@/i18n/blog';
export interface Props {
post: Post;
@@ -13,15 +14,16 @@ export interface Props {
const { post } = Astro.props;
const relatedPosts = post.tags ? await getRelatedPosts(post, 4) : [];
const copy = getBlogCopy(post.lang);
---
{
APP_BLOG.isRelatedPostsEnabled ? (
<BlogHighlightedPosts
classes={{ container: 'pt-0 lg:pt-0 md:pt-0' }}
title="Related Posts"
linkText="View All Posts"
linkUrl={getBlogPermalink()}
title={copy.relatedPosts}
linkText={copy.viewAllPosts}
linkUrl={getBlogPermalink(post.lang)}
postIds={relatedPosts.map((post) => post.id)}
/>
) : null
+18 -6
View File
@@ -9,6 +9,7 @@ import { getPermalink } from '~/utils/permalinks';
import { getFormattedDate } from '~/utils/utils';
import type { Post } from '~/types';
import { getBlogCopy } from '@/i18n/blog';
export interface Props {
post: Post;
@@ -17,6 +18,7 @@ export interface Props {
const { post, url } = Astro.props;
const authorName = post.author || 'RustDesk Team';
const copy = getBlogCopy(post.lang);
---
<section class="py-8 sm:py-16 lg:py-20 mx-auto">
@@ -25,13 +27,18 @@ const authorName = post.author || 'RustDesk Team';
<div class="flex justify-between flex-col sm:flex-row max-w-4xl mx-auto mt-0 mb-2 px-4 sm:px-6 sm:items-center">
<p>
<Icon name="tabler:clock" class="w-4 h-4 inline-block -mt-0.5 dark:text-gray-400" />
<time datetime={String(post.publishDate)} class="inline-block">{getFormattedDate(post.publishDate)}</time>
<time datetime={String(post.publishDate)} class="inline-block"
>{getFormattedDate(post.publishDate, post.lang)}</time
>
{
post.updateDate && (
<>
{' '}
· <Icon name="tabler:refresh" class="w-4 h-4 inline-block -mt-0.5 dark:text-gray-400" />
<span class="inline-block text-sm text-gray-500">Updated: <time datetime={String(post.updateDate)}>{getFormattedDate(post.updateDate)}</time></span>
<span class="inline-block text-sm text-gray-500">
{copy.updated}:{' '}
<time datetime={String(post.updateDate)}>{getFormattedDate(post.updateDate, post.lang)}</time>
</span>
</>
)
}
@@ -43,7 +50,7 @@ const authorName = post.author || 'RustDesk Team';
<>
{' '}
·{' '}
<a class="hover:underline inline-block" href={getPermalink(post.category.slug, 'category')}>
<a class="hover:underline inline-block" href={getPermalink(post.category.slug, 'category', post.lang)}>
{post.category.title}
</a>
</>
@@ -52,7 +59,7 @@ const authorName = post.author || 'RustDesk Team';
{
post.readingTime && (
<>
&nbsp;· <span>{post.readingTime}</span> min read
&nbsp;· <span>{post.readingTime}</span> {copy.minRead}
</>
)
}
@@ -96,8 +103,13 @@ const authorName = post.author || 'RustDesk Team';
<slot />
</div>
<div class="mx-auto px-6 sm:px-6 max-w-4xl mt-8 flex justify-between flex-col sm:flex-row">
<PostTags tags={post.tags} class="mr-5 rtl:mr-0 rtl:ml-5" />
<SocialShare url={url} text={post.title} class="mt-5 sm:mt-1 align-middle text-gray-500 dark:text-slate-600" />
<PostTags tags={post.tags} lang={post.lang} class="mr-5 rtl:mr-0 rtl:ml-5" />
<SocialShare
url={url}
text={post.title}
label={copy.share}
class="mt-5 sm:mt-1 align-middle text-gray-500 dark:text-slate-600"
/>
</div>
</article>
</section>
+10 -2
View File
@@ -3,15 +3,23 @@ import { getPermalink } from '~/utils/permalinks';
import { APP_BLOG } from 'astrowind:config';
import type { Post } from '~/types';
import type { Lang } from '@/i18n';
export interface Props {
tags: Post['tags'];
class?: string;
title?: string | undefined;
isCategory?: boolean;
lang?: Lang;
}
const { tags, class: className = 'text-sm', title = undefined, isCategory = false } = Astro.props;
const {
tags,
class: className = 'text-sm',
title = undefined,
isCategory = false,
lang = Astro.currentLocale as Lang,
} = Astro.props;
---
{
@@ -31,7 +39,7 @@ const { tags, class: className = 'text-sm', title = undefined, isCategory = fals
tag.title
) : (
<a
href={getPermalink(tag.slug, isCategory ? 'category' : 'tag')}
href={getPermalink(tag.slug, isCategory ? 'category' : 'tag', lang)}
class="text-muted dark:text-slate-300 hover:text-primary dark:hover:text-gray-200"
>
{tag.title}
+12 -4
View File
@@ -1,20 +1,28 @@
---
import { Icon } from 'astro-icon/components';
import { getBlogPermalink } from '~/utils/permalinks';
import { I18N } from 'astrowind:config';
import Button from '~/components/ui/Button.astro';
import { getBlogCopy } from '@/i18n/blog';
import { LOCALES, type Lang } from '@/i18n';
const { textDirection } = I18N;
export interface Props {
lang?: Lang;
}
const { lang = Astro.currentLocale as Lang } = Astro.props;
const copy = getBlogCopy(lang);
const textDirection = LOCALES[lang]?.dir || 'ltr';
---
<div class="mx-auto px-6 sm:px-6 max-w-4xl pt-8 md:pt-4 pb-12 md:pb-20">
<Button variant="tertiary" class="px-3 md:px-3" href={getBlogPermalink()}>
<Button variant="tertiary" class="px-3 md:px-3" href={getBlogPermalink(lang)}>
{
textDirection === 'rtl' ? (
<Icon name="tabler:chevron-right" class="w-5 h-5 mr-1 -ml-1.5 rtl:-mr-1.5 rtl:ml-1" />
) : (
<Icon name="tabler:chevron-left" class="w-5 h-5 mr-1 -ml-1.5 rtl:-mr-1.5 rtl:ml-1" />
)
} Back to Blog
}
{copy.backToBlog}
</Button>
</div>
+3 -2
View File
@@ -4,14 +4,15 @@ import { Icon } from 'astro-icon/components';
export interface Props {
text: string;
url: string | URL;
label?: string;
class?: string;
}
const { text, url, class: className = 'inline-block' } = Astro.props;
const { text, url, label = 'Share', class: className = 'inline-block' } = Astro.props;
---
<div class={className}>
<span class="align-super font-bold text-slate-500 dark:text-slate-400">Share:</span>
<span class="align-super font-bold text-slate-500 dark:text-slate-400">{label}:</span>
<button
class="ml-2 rtl:ml-0 rtl:mr-2"
title="Twitter Share"
+26 -12
View File
@@ -1,14 +1,28 @@
---
import type { Lang } from '@/i18n';
import { getBlogCopy } from '@/i18n/blog';
import { findLatestPosts } from '~/utils/blog';
import { getPermalink } from '~/utils/permalinks';
export interface Props {
lang?: Lang;
}
const { lang = Astro.currentLocale as Lang } = Astro.props;
const [post] = await findLatestPosts({ count: 1, lang });
const copy = getBlogCopy(lang);
const href = post ? getPermalink(post.permalink, 'post') : '';
---
---
<div
class="dark text-muted text-sm bg-black dark:bg-transparent dark:border-b dark:border-slate-800 dark:text-slate-400 hidden md:flex gap-1 overflow-hidden px-3 py-2 relative text-ellipsis whitespace-nowrap justify-center items-center"
>
<span class="bg-secondary text-slate-300 font-semibold px-1 py-0.5 text-xs mr-0.5 rtl:mr-0 rtl:ml-0.5 inline-block"
>NEW</span
>
<a href="/blog/2025/02/enhanced-acl-in-rustdesk-server-pro-1-5-0" class="text-muted hover:underline dark:text-slate-400 font-medium"
>Enhanced ACL in RustDesk Server Pro 1.5.0! »</a
>
</div>
{
post && (
<div class="dark text-muted text-sm bg-black dark:bg-transparent dark:border-b dark:border-slate-800 dark:text-slate-400 hidden md:flex gap-1 overflow-hidden px-3 py-2 relative text-ellipsis whitespace-nowrap justify-center items-center">
<span class="bg-secondary text-slate-300 font-semibold px-1 py-0.5 text-xs mr-0.5 rtl:mr-0 rtl:ml-0.5 inline-block">
{copy.newPost}
</span>
<a href={href} class="text-muted hover:underline dark:text-slate-400 font-medium">
{post.title} »
</a>
</div>
)
}
@@ -8,6 +8,8 @@ import { findLatestPosts } from '~/utils/blog';
import WidgetWrapper from '~/components/ui/WidgetWrapper.astro';
import type { Widget } from '~/types';
import Button from '../ui/Button.astro';
import { getBlogCopy } from '@/i18n/blog';
import type { Lang } from '@/i18n';
export interface Props extends Widget {
title?: string;
@@ -17,10 +19,12 @@ export interface Props extends Widget {
count?: number;
}
const lang = Astro.currentLocale as Lang;
const copy = getBlogCopy(lang);
const {
title = await Astro.slots.render('title'),
linkText = 'View all posts',
linkUrl = getBlogPermalink(),
linkText = copy.viewAllPosts,
linkUrl = getBlogPermalink(lang),
information = await Astro.slots.render('information'),
count = 4,
@@ -30,7 +34,7 @@ const {
bg = await Astro.slots.render('bg'),
} = Astro.props;
const posts = APP_BLOG.isEnabled ? await findLatestPosts({ count }) : [];
const posts = APP_BLOG.isEnabled ? await findLatestPosts({ count, lang }) : [];
---
{
+15 -7
View File
@@ -4,12 +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 { getLocalPath, type Lang } 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';
import { getLocalePaths, LOCALES, DEFAULT_LOCALE, type LocalePath } from '@/i18n';
interface Link {
text?: string;
@@ -34,6 +34,8 @@ export interface Props {
position?: string;
showGithubStar?: boolean;
i18n?: boolean;
lang?: Lang;
localePaths?: LocalePath[];
}
const {
@@ -48,9 +50,13 @@ const {
showGithubStar = false,
position = 'center',
i18n = false,
lang: pageLocale,
localePaths,
} = Astro.props;
const currentPath = `/${trimSlash(new URL(Astro.url).pathname)}`;
const languagePaths = localePaths ?? getLocalePaths(Astro.url);
const locale = pageLocale || (Astro.currentLocale as Lang);
---
<header
@@ -77,7 +83,7 @@ const currentPath = `/${trimSlash(new URL(Astro.url).pathname)}`;
]}
>
<div class:list={[{ 'mr-auto rtl:mr-0 rtl:ml-auto': position === 'right' }, 'flex justify-between']}>
<a class="flex items-center" href={getHomePermalink(getLocalPath(Astro.currentLocale || '', '/'))}>
<a class="flex items-center" href={getHomePermalink(getLocalPath(locale || '', '/'))}>
<Logo />
</a>
<div class="flex items-center md:hidden">
@@ -156,23 +162,25 @@ const currentPath = `/${trimSlash(new URL(Astro.url).pathname)}`;
id="languages"
class="md:mt-12 hidden bg-white dark:bg-dark dropdown-menu md:backdrop-blur-md dark:md:bg-dark rounded absolute pl-4 md:pl-0 font-medium md:bg-white/90 md:min-w-[200px] drop-shadow-xl"
>
{getLocalePaths(Astro.url).map(({ path, lang }) => {
{languagePaths.map(({ path, lang }) => {
let path2 = path.replace(/\/$/, '');
if (path2.indexOf('/' + DEFAULT_LOCALE) === 0) {
path2 = path2 + '?lang=en';
path2 = path2.replace('/' + DEFAULT_LOCALE, '');
}
path2 = path2.replace('/' + DEFAULT_LOCALE, '');
if (!path2) {
path2 = '/';
} else if (path2[0] != '/') {
path2 = '/' + path2;
}
if (lang === DEFAULT_LOCALE) {
path2 += `${path2.includes('?') ? '&' : '?'}lang=en`;
}
return (
<li>
<a
class:list={[
'first:rounded-t last:rounded-b md:hover:bg-gray-100 hover:text-link dark:hover:text-white dark:hover:bg-gray-700 py-2 px-5 block whitespace-no-wrap',
{ 'text-primary': lang === Astro.currentLocale },
{ 'text-primary': lang === locale },
]}
href={path2}
>
+2
View File
@@ -49,6 +49,8 @@ const metadataDefinition = () =>
const postCollection = defineCollection({
schema: z.object({
lang: z.enum(['en', 'de', 'es', 'fr', 'it', 'ja', 'ko', 'pt', 'zh-cn', 'zh-tw', 'ar']),
translationKey: z.string(),
publishDate: z.date().optional(),
updateDate: z.date().optional(),
draft: z.boolean().optional(),
@@ -0,0 +1,27 @@
---
publishDate: 2025-02-19T00:00:00Z
lang: ar
translationKey: enhanced-acl-in-rustdesk-server-pro-1-5-0
title: تحسين ACL في RustDesk Server Pro 1.5.0
excerpt: يدعم RustDesk Server Pro الآن قوائم ACL لكل مستخدم ومجموعات الأجهزة إلى جانب مجموعات المستخدمين.
image: ~/assets/images/blog/device-group.png
category: إصدار
slug: tahsin-acl-fi-rustdesk-server-pro-1-5-0
tags: [RustDesk Server Pro, إصدار]
---
أصبح RustDesk Server Pro 1.5.0 متاحًا الآن.
## سجل التغييرات
* إضافة مجموعات الأجهزة وقوائم ACL الخاصة بها
* إضافة ACL دقيقة على مستوى المستخدم
* إضافة سياسات لمجموعات الأجهزة
* إضافة جداول قابلة للفرز
* ربط OIDC بالحساب القديم عند تعارض البريد الإلكتروني
* السماح بالشرطة `-` في المعرّف
* إصلاح الفشل عندما يكون اسم OIDC فارغًا
تُعد مجموعات الأجهزة ميزة جديدة في RustDesk Server Pro 1.5.0. اعتمد RustDesk سابقًا على مجموعات المستخدمين وحدها لإدارة قوائم التحكم في الوصول حفاظًا على البساطة، لكن ضبط الصلاحيات أصبح مرهقًا في المؤسسات الكبيرة. لذلك أضفنا مجموعات الأجهزة وACL على مستوى المستخدم. يمكن الآن تجميع الأجهزة وتطبيق السياسات عليها، مما يحسن الإدارة في البيئات واسعة النطاق.
يمكن تعيين كل جهاز إلى مجموعة أجهزة واحدة إلى جانب ارتباطه بمستخدم. يتيح ذلك تطبيق سياسات إضافية مثل تقييد الوصول إلى أجهزة بعينها أو منحه. كما تسمح ACL على مستوى المستخدم بتحديد صلاحيات فردية، مما يوفر مرونة وتحكمًا أكبر.
@@ -0,0 +1,43 @@
---
publishDate: 2024-12-02T00:00:00Z
lang: ar
translationKey: how-to-make-flutter-3-24-run-on-windows-7
title: كيف تشغّل Flutter 3.24 على Windows 7؟
excerpt: أوقف Flutter دعم Windows 7 / 8 منذ الإصدار 3.22، لذا نحتاج إلى تعديل Flutter Engine.
image: ~/assets/images/blog/flutter-win7.png
category: إصدار
slug: tashghil-flutter-3-24-ala-windows-7
tags: [تطوير]
---
أدخلنا مؤخرًا Flutter 3.24.5 إلى [RustDesk](https://github.com/rustdesk/rustdesk)، فتوقف عن العمل على Windows 7. لحل المشكلة عدّلنا Flutter Engine.
## حل تشغيل Flutter 3.24.5 على Windows 7
أعلنت Flutter وDart أن Dart 3.3 وFlutter 3.19 هما آخر إصدارين يدعمان Windows 7 و8. لاستخدام Flutter 3.24.5 يجب التراجع عن بعض تغييرات Dart.
الرابط: [الإعلان الرسمي](https://groups.google.com/g/flutter-announce/c/s0tM5gxAgs4/m/ryxV2vZYAQAJ?pli=1)
## 1. إصلاح `Platform.localHostname` في البيئات الصينية
استخدم Dart واجهة `gethostname` التي سببت فشل `Platform.localHostname` في البيئات الصينية، ثم انتقل إلى `GetHostNameW` المتاحة بدءًا من Windows 8. لدعم Windows 7 يجب التراجع عن هذا التغيير، مع تأثير جانبي على `Platform.localHostname` في تلك البيئات.
Issue: [Platform.localHostname fails on Chinese Windows](https://github.com/dart-lang/sdk/issues/52701)
## 2. استعادة معالجة unwinding records في Windows 7
كان Dart يعالج غياب `RtlAddGrowableFunctionTable` في Windows 7، ثم أزيل هذا التوافق. يجب التراجع عن الالتزام الذي أزاله.
Commit: [Remove Windows7 handling of unwinding records API](https://github.com/dart-lang/sdk/commit/34213ba60578e46fc2455c5a56b09d9efabc532b)
## 3. إصلاح الروابط الرمزية النسبية للمجلدات
عدّل إصلاح Dart الدالة `File::GetType` مستخدمًا `PathCchCombineEx` المتاحة بدءًا من Windows 8 فقط. يجب التراجع عن هذا التغيير أيضًا.
Commit: [Fix a bug where relative symlinks to directories did not work on Windows](https://github.com/dart-lang/sdk/commit/b4574904f9cd0d6f7147950c02ee2447569d0be9)
## الخلاصة
يتطلب تشغيل Flutter 3.24.5 على Windows 7 التراجع عن تغييرات أساسية في Dart. يؤثر ذلك في `Platform.localHostname` بالبيئات الصينية والروابط الرمزية النسبية، لكنه ضروري لمستخدمي Windows 7.
راجع [نظام CI](https://github.com/rustdesk/engine/blob/main/.github/workflows/flutter-engine-windows-x64-release-build.yml) للتنفيذ الكامل، و[هذا الرابط](https://github.com/rustdesk/rustdesk/blob/dea99ffb3ab34fee562090d214af419b557debdf/.github/workflows/flutter-build.yml#L109) لمعرفة طريقة تطبيق المحرك المعدّل.
@@ -0,0 +1,42 @@
---
publishDate: 2024-10-12T00:00:00Z
lang: ar
translationKey: rustdesk-web-client-v2-preview
title: معاينة عميل RustDesk Web V2
excerpt: يقدم V2 ترميزات أفضل ودعم لوحة المفاتيح الدولية والحافظة ونقل الملفات.
image: ~/assets/images/blog/web-client.jpg
category: إصدار
slug: muayinat-rustdesk-web-client-v2
tags: [عميل الويب, إصدار]
---
لم يتلق عميل RustDesk Web V1 تحديثًا منذ أكثر من عامين وكان يفتقد ميزات عديدة. أمضى فريقنا ثلاثة أشهر في تطوير V2. تحسّن المعاينة فك الترميز ودعم لوحات المفاتيح الدولية، وتدعم حافظة النصوص والصور ونقل الملفات. ورغم قيود المتصفح، نسعى إلى تجربة متسقة مع إصدار سطح المكتب.
جرّبه على https://rustdesk.com/web. للوصول إلى خادمك، اضبط WebSocket Secure (WSS) على المنفذين 21118/21119 وفق [الوثائق](https://rustdesk.com/docs/en/self-host/rustdesk-server-pro/faq/#8-add-websocket-secure-wss-support-for-the-id-server-and-relay-server-to-enable-secure-communication-for-the-web-client).
```nginx
location /ws/id {
proxy_pass http://localhost:21118;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /ws/relay {
proxy_pass http://localhost:21119;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
```
بعد استقرار V2 سندمج عميل الويب في إصدار Pro أيضًا، وسيكون متاحًا عبر `https://rustdesk.yourcompany.com/web`.
يسعدنا تقديم هذه التحسينات، ونرحب بملاحظاتكم بينما نواصل تطوير RustDesk Web Client V2.
@@ -0,0 +1,27 @@
---
publishDate: 2025-02-19T00:00:00Z
lang: de
translationKey: enhanced-acl-in-rustdesk-server-pro-1-5-0
title: Erweiterte ACL in RustDesk Server Pro 1.5.0
excerpt: RustDesk Server Pro unterstützt jetzt zusätzlich zu Benutzergruppen benutzerspezifische ACLs und Gerätegruppen.
image: ~/assets/images/blog/device-group.png
category: Veröffentlichung
slug: erweiterte-acl-in-rustdesk-server-pro-1-5-0
tags: [RustDesk Server Pro, Veröffentlichung]
---
RustDesk Server Pro 1.5.0 ist jetzt verfügbar.
## Änderungsprotokoll
* Gerätegruppen und deren ACL hinzugefügt
* Feingranulare ACLs auf Benutzerebene hinzugefügt
* Strategien für Gerätegruppen hinzugefügt
* Sortierbare Tabellen hinzugefügt
* OIDC verknüpft bei einem E-Mail-Konflikt das alte Konto
* `-` (Bindestrich) in IDs erlaubt
* Fehler bei einem leeren OIDC-Namen behoben
Gerätegruppen sind eine neue Funktion in RustDesk Server Pro 1.5.0. Bisher setzte RustDesk zugunsten der Einfachheit ausschließlich auf Benutzergruppen zur Verwaltung von ACLs (Access Control Lists). In großen Organisationen wurde die Konfiguration von Zugriffsrechten dadurch jedoch umständlich. Deshalb führen wir in Version 1.5.0 Gerätegruppen und ACLs auf Benutzerebene ein. Geräte lassen sich nun gruppieren und Richtlinien auf diese Gruppen anwenden, was die Verwaltung großer Umgebungen deutlich verbessert.
Zuvor unterstützte RustDesk Server Pro nur Benutzergruppen; nun werden auch Gerätegruppen unterstützt. Jedes Gerät kann zusätzlich zu seinem Benutzer genau einer Gerätegruppe zugeordnet werden. Damit lassen sich weitere Richtlinien umsetzen, etwa der Zugriff auf bestimmte Geräte einschränken oder erlauben. ACLs auf Benutzerebene ermöglichen außerdem individuelle Berechtigungen zusätzlich zu Benutzergruppen und bieten so mehr Flexibilität und Kontrolle.
@@ -0,0 +1,43 @@
---
publishDate: 2024-12-02T00:00:00Z
lang: de
translationKey: how-to-make-flutter-3-24-run-on-windows-7
title: Wie lässt sich Flutter 3.24 unter Windows 7 ausführen?
excerpt: Seit Flutter 3.22 Windows 7 / 8 nicht mehr unterstützt, muss die Flutter Engine angepasst werden.
image: ~/assets/images/blog/flutter-win7.png
category: Veröffentlichung
slug: flutter-3-24-unter-windows-7-ausfuehren
tags: [Entwicklung]
---
Wir haben kürzlich Flutter 3.24.5 in [RustDesk](https://github.com/rustdesk/rustdesk) eingeführt. Dadurch lief RustDesk nicht mehr unter Windows 7. Um das Problem zu lösen, haben wir die Flutter Engine angepasst.
## Lösung für Flutter 3.24.5 unter Windows 7
Flutter und Dart haben angekündigt, dass Dart 3.3 und Flutter 3.19 die letzten Versionen mit Unterstützung für Windows 7 und 8 sind. Damit Flutter 3.24.5 dennoch funktioniert, müssen einige Dart-Änderungen zurückgenommen werden.
Weiterführender Link: [Offizielle Ankündigung](https://groups.google.com/g/flutter-announce/c/s0tM5gxAgs4/m/ryxV2vZYAQAJ?pli=1)
## 1. `Platform.localHostname` in chinesischen Umgebungen
Dart verwendete ursprünglich die API `gethostname`, wodurch `Platform.localHostname` in chinesischen Umgebungen fehlschlug. Als Lösung wechselte Dart zur erst ab Windows 8 verfügbaren API `GetHostNameW`. Für Windows 7 muss diese Änderung zurückgenommen werden. Dadurch wird allerdings `Platform.localHostname` in chinesischen Umgebungen beeinträchtigt.
Issue: [Platform.localHostname fails on Chinese Windows](https://github.com/dart-lang/sdk/issues/52701)
## 2. Behandlung der Unwinding-Records-API für Windows 7 wiederherstellen
Dart enthielt eine Kompatibilitätsbehandlung für die unter Windows 7 fehlende API `RtlAddGrowableFunctionTable`, die später entfernt wurde. Für die Windows-7-Kompatibilität muss der entsprechende Commit zurückgenommen werden.
Commit: [Remove Windows7 handling of unwinding records API](https://github.com/dart-lang/sdk/commit/34213ba60578e46fc2455c5a56b09d9efabc532b)
## 3. Relative symbolische Verzeichnislinks unter Windows
Eine Dart-Korrektur für relative symbolische Verzeichnislinks änderte `File::GetType` und verwendete dabei die erst ab Windows 8 verfügbare API `PathCchCombineEx`. Auch diese Änderung muss für Windows 7 zurückgenommen werden.
Commit: [Fix a bug where relative symlinks to directories did not work on Windows](https://github.com/dart-lang/sdk/commit/b4574904f9cd0d6f7147950c02ee2447569d0be9)
## Fazit
Für Flutter 3.24.5 unter Windows 7 sind mehrere Rücknahmen in Dart nötig. Sie haben Nebenwirkungen bei `Platform.localHostname` in chinesischen Umgebungen und bei relativen symbolischen Links, sind für Nutzer von Windows 7 aber erforderlich.
Die vollständige Umsetzung finden Sie in [unserer CI](https://github.com/rustdesk/engine/blob/main/.github/workflows/flutter-engine-windows-x64-release-build.yml). [Hier](https://github.com/rustdesk/rustdesk/blob/dea99ffb3ab34fee562090d214af419b557debdf/.github/workflows/flutter-build.yml#L109) wird die angepasste Engine in RustDesk eingebunden.
@@ -0,0 +1,42 @@
---
publishDate: 2024-10-12T00:00:00Z
lang: de
translationKey: rustdesk-web-client-v2-preview
title: Vorschau auf RustDesk Web Client V2
excerpt: V2 bietet bessere Codecs, internationale Tastaturen, Zwischenablage und Dateiübertragung.
image: ~/assets/images/blog/web-client.jpg
category: Veröffentlichung
slug: vorschau-rustdesk-web-client-v2
tags: [Web-Client, Veröffentlichung]
---
RustDesk Web Client V1 wurde seit über zwei Jahren nicht aktualisiert und bietet nicht alle benötigten Funktionen. Unser Team hat drei Monate lang V2 entwickelt. Die Vorschau verbessert die Dekodierung und die Unterstützung internationaler Tastaturen, unterstützt Text- und Bild-Zwischenablagen sowie Dateiübertragungen. Trotz der Grenzen eines Browsers möchten wir Funktionsumfang und Bedienung mit der Desktop-Version in Einklang bringen.
Die Vorschau ist unter https://rustdesk.com/web verfügbar. Für den Zugriff auf einen eigenen Server muss WebSocket Secure (WSS) gemäß der [Dokumentation](https://rustdesk.com/docs/en/self-host/rustdesk-server-pro/faq/#8-add-websocket-secure-wss-support-for-the-id-server-and-relay-server-to-enable-secure-communication-for-the-web-client) auf den Ports 21118/21119 eingerichtet werden.
```nginx
location /ws/id {
proxy_pass http://localhost:21118;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /ws/relay {
proxy_pass http://localhost:21119;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
```
Sobald V2 stabil ist, integrieren wir den Web-Client auch in die Pro-Version. Er ist dann unter `https://rustdesk.yourcompany.com/web` erreichbar.
Wir freuen uns, diese Verbesserungen bereitzustellen, und begrüßen Ihr Feedback zur weiteren Entwicklung von RustDesk Web Client V2.
@@ -1,5 +1,7 @@
---
publishDate: 2025-02-19T00:00:00Z
lang: en
translationKey: enhanced-acl-in-rustdesk-server-pro-1-5-0
title: Enhanced ACL in RustDesk Server Pro 1.5.0
excerpt: RustDesk Server Pro now supports user-level ACL and device groups in addition of user groups.
image: ~/assets/images/blog/device-group.png
@@ -1,5 +1,7 @@
---
publishDate: 2024-12-02T00:00:00Z
lang: en
translationKey: how-to-make-flutter-3-24-run-on-windows-7
title: How to make Flutter 3.24 run on Windows 7?
excerpt: Since Flutter 3.22 starts to drop support for Windows 7 / 8, we need to modify Flutter engine to restore support for Windows 7.
image: ~/assets/images/blog/flutter-win7.png
@@ -40,4 +42,3 @@ To make Flutter 3.24.5 run on Windows 7, we need to revert some key modification
For full implementation, please check [our CI](https://github.com/rustdesk/engine/blob/main/.github/workflows/flutter-engine-windows-x64-release-build.yml).
[Here](https://github.com/rustdesk/rustdesk/blob/dea99ffb3ab34fee562090d214af419b557debdf/.github/workflows/flutter-build.yml#L109) is how we apply the modified engine to our project.
@@ -1,5 +1,7 @@
---
publishDate: 2024-10-12T00:00:00Z
lang: en
translationKey: rustdesk-web-client-v2-preview
title: RustDesk web client V2 Preview
excerpt: V2 offers better codecs, international keyboard support, clipboard support, file transfer etc.
image: ~/assets/images/blog/web-client.jpg
@@ -39,4 +41,3 @@ You can access the RustDesk web client V2 Preview by visiting https://rustdesk.c
Once the V2 version is stable, we will also integrate the web client into the Pro version, allowing you to access it via `https://rustdesk.yourcompany.com/web`.
We are excited to bring these enhancements to our users and look forward to your feedback as we continue to refine and improve the RustDesk Web Client V2.
@@ -0,0 +1,27 @@
---
publishDate: 2025-02-19T00:00:00Z
lang: es
translationKey: enhanced-acl-in-rustdesk-server-pro-1-5-0
title: ACL mejoradas en RustDesk Server Pro 1.5.0
excerpt: RustDesk Server Pro ahora admite ACL por usuario y grupos de dispositivos, además de grupos de usuarios.
image: ~/assets/images/blog/device-group.png
category: Lanzamiento
slug: acl-mejoradas-rustdesk-server-pro-1-5-0
tags: [RustDesk Server Pro, lanzamiento]
---
RustDesk Server Pro 1.5.0 ya está disponible.
## Registro de cambios
* Se añaden grupos de dispositivos y sus ACL
* Se añaden ACL detalladas por usuario
* Se añaden estrategias para grupos de dispositivos
* Se añaden tablas ordenables
* OIDC vincula la cuenta antigua si el correo entra en conflicto
* Se permite `-` (guion) en el ID
* Se corrige el fallo cuando OIDC tiene un nombre nulo
Los grupos de dispositivos son una novedad de RustDesk Server Pro 1.5.0. Antes, RustDesk priorizaba la sencillez y administraba las ACL (listas de control de acceso) solo mediante grupos de usuarios. En organizaciones grandes esto complicaba los permisos. Por ello, la versión 1.5.0 incorpora grupos de dispositivos y ACL por usuario. Ahora se pueden agrupar dispositivos y aplicar políticas al grupo, lo que mejora notablemente la administración a gran escala.
Cada dispositivo puede asignarse a un único grupo de dispositivos además de estar asociado a un usuario. Esto permite aplicar políticas adicionales, como restringir o conceder acceso a dispositivos concretos. Las ACL por usuario también permiten definir permisos individuales además de los grupos, con mayor flexibilidad y control.
@@ -0,0 +1,43 @@
---
publishDate: 2024-12-02T00:00:00Z
lang: es
translationKey: how-to-make-flutter-3-24-run-on-windows-7
title: ¿Cómo ejecutar Flutter 3.24 en Windows 7?
excerpt: Flutter dejó de admitir Windows 7 / 8 desde la versión 3.22, por lo que debemos modificar Flutter Engine.
image: ~/assets/images/blog/flutter-win7.png
category: Lanzamiento
slug: ejecutar-flutter-3-24-en-windows-7
tags: [desarrollo]
---
Hace poco incorporamos Flutter 3.24.5 a [RustDesk](https://github.com/rustdesk/rustdesk), lo que impidió ejecutarlo en Windows 7. Para resolverlo, modificamos Flutter Engine.
## Solución para ejecutar Flutter 3.24.5 en Windows 7
Flutter y Dart anunciaron que Dart 3.3 y Flutter 3.19 serían las últimas versiones compatibles con Windows 7 y 8. Para usar Flutter 3.24.5 debemos revertir varios cambios de Dart.
Enlace: [Anuncio oficial](https://groups.google.com/g/flutter-announce/c/s0tM5gxAgs4/m/ryxV2vZYAQAJ?pli=1)
## 1. Corregir `Platform.localHostname` en entornos chinos
Dart usaba la API `gethostname`, que hacía fallar `Platform.localHostname` en entornos chinos. Cambió entonces a `GetHostNameW`, disponible solo desde Windows 8. Para Windows 7 hay que revertir ese cambio, aunque afectará a `Platform.localHostname` en dichos entornos.
Issue: [Platform.localHostname fails on Chinese Windows](https://github.com/dart-lang/sdk/issues/52701)
## 2. Recuperar el manejo de registros de desenrollado de Windows 7
Dart tenía compatibilidad para la ausencia de `RtlAddGrowableFunctionTable` en Windows 7, pero se eliminó. Debemos revertir el commit que retiró ese manejo.
Commit: [Remove Windows7 handling of unwinding records API](https://github.com/dart-lang/sdk/commit/34213ba60578e46fc2455c5a56b09d9efabc532b)
## 3. Corregir enlaces simbólicos relativos a directorios
La corrección de Dart para enlaces simbólicos relativos modificó `File::GetType` y usó `PathCchCombineEx`, disponible solo desde Windows 8. También debemos revertirla.
Commit: [Fix a bug where relative symlinks to directories did not work on Windows](https://github.com/dart-lang/sdk/commit/b4574904f9cd0d6f7147950c02ee2447569d0be9)
## Conclusión
Ejecutar Flutter 3.24.5 en Windows 7 exige revertir cambios clave de Dart. Esto afecta a `Platform.localHostname` en entornos chinos y a enlaces simbólicos relativos, pero es necesario para quienes siguen usando Windows 7.
Consulte la implementación completa en [nuestro CI](https://github.com/rustdesk/engine/blob/main/.github/workflows/flutter-engine-windows-x64-release-build.yml) y [cómo aplicamos el motor modificado](https://github.com/rustdesk/rustdesk/blob/dea99ffb3ab34fee562090d214af419b557debdf/.github/workflows/flutter-build.yml#L109).
@@ -0,0 +1,42 @@
---
publishDate: 2024-10-12T00:00:00Z
lang: es
translationKey: rustdesk-web-client-v2-preview
title: Vista previa del cliente web RustDesk V2
excerpt: V2 ofrece mejores códecs, teclado internacional, portapapeles y transferencia de archivos.
image: ~/assets/images/blog/web-client.jpg
category: Lanzamiento
slug: vista-previa-cliente-web-rustdesk-v2
tags: [cliente web, lanzamiento]
---
El cliente web RustDesk V1 llevaba más de dos años sin actualizarse y carecía de muchas funciones. Nuestro equipo dedicó tres meses a V2. Esta vista previa mejora la decodificación y los teclados internacionales, admite portapapeles de texto e imágenes y permite transferir archivos. Pese a los límites del navegador, buscamos una experiencia coherente con la versión de escritorio.
Puede probarla en https://rustdesk.com/web. Para acceder a su propio servidor, configure WebSocket Secure (WSS) en los puertos 21118/21119 según la [documentación](https://rustdesk.com/docs/en/self-host/rustdesk-server-pro/faq/#8-add-websocket-secure-wss-support-for-the-id-server-and-relay-server-to-enable-secure-communication-for-the-web-client).
```nginx
location /ws/id {
proxy_pass http://localhost:21118;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /ws/relay {
proxy_pass http://localhost:21119;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
```
Cuando V2 sea estable, integraremos el cliente web en la versión Pro, accesible desde `https://rustdesk.yourcompany.com/web`.
Esperamos que estas mejoras resulten útiles y agradecemos sus comentarios mientras seguimos perfeccionando RustDesk Web Client V2.
@@ -0,0 +1,27 @@
---
publishDate: 2025-02-19T00:00:00Z
lang: fr
translationKey: enhanced-acl-in-rustdesk-server-pro-1-5-0
title: ACL améliorées dans RustDesk Server Pro 1.5.0
excerpt: RustDesk Server Pro prend désormais en charge les ACL par utilisateur et les groupes dappareils.
image: ~/assets/images/blog/device-group.png
category: Version
slug: acl-ameliorees-rustdesk-server-pro-1-5-0
tags: [RustDesk Server Pro, version]
---
RustDesk Server Pro 1.5.0 est maintenant disponible.
## Journal des modifications
* Ajout des groupes dappareils et de leurs ACL
* Ajout dACL fines par utilisateur
* Ajout de stratégies pour les groupes dappareils
* Ajout de tableaux triables
* OIDC relie lancien compte en cas de conflit dadresse e-mail
* Le caractère `-` est autorisé dans lidentifiant
* Correction de l’échec lorsque le nom OIDC est nul
Les groupes dappareils sont une nouveauté de RustDesk Server Pro 1.5.0. RustDesk privilégiait auparavant la simplicité et gérait les ACL uniquement avec des groupes dutilisateurs. Cette approche compliquait les autorisations dans les grandes organisations. La version 1.5.0 introduit donc les groupes dappareils et les ACL par utilisateur. Il devient possible de regrouper les appareils et de leur appliquer des politiques, ce qui facilite nettement la gestion à grande échelle.
Chaque appareil peut appartenir à un groupe dappareils en plus d’être associé à un utilisateur. Des politiques supplémentaires peuvent ainsi limiter ou autoriser laccès à des appareils précis. Les ACL par utilisateur permettent aussi de définir des autorisations individuelles en complément des groupes, avec davantage de souplesse et de contrôle.
@@ -0,0 +1,43 @@
---
publishDate: 2024-12-02T00:00:00Z
lang: fr
translationKey: how-to-make-flutter-3-24-run-on-windows-7
title: Comment exécuter Flutter 3.24 sous Windows 7 ?
excerpt: Flutter ne prenant plus en charge Windows 7 / 8 depuis la version 3.22, il faut modifier Flutter Engine.
image: ~/assets/images/blog/flutter-win7.png
category: Version
slug: executer-flutter-3-24-sous-windows-7
tags: [développement]
---
Nous avons récemment intégré Flutter 3.24.5 à [RustDesk](https://github.com/rustdesk/rustdesk), qui ne fonctionnait alors plus sous Windows 7. Nous avons donc modifié Flutter Engine.
## Solution pour Flutter 3.24.5 sous Windows 7
Flutter et Dart ont annoncé que Dart 3.3 et Flutter 3.19 seraient les dernières versions compatibles avec Windows 7 et 8. Pour utiliser Flutter 3.24.5, plusieurs changements de Dart doivent être annulés.
Lien : [Annonce officielle](https://groups.google.com/g/flutter-announce/c/s0tM5gxAgs4/m/ryxV2vZYAQAJ?pli=1)
## 1. Corriger `Platform.localHostname` dans les environnements chinois
Dart utilisait `gethostname`, ce qui faisait échouer `Platform.localHostname` dans ces environnements. Dart est passé à `GetHostNameW`, disponible seulement à partir de Windows 8. Il faut annuler ce changement pour Windows 7, au prix dun impact sur `Platform.localHostname` en environnement chinois.
Issue : [Platform.localHostname fails on Chinese Windows](https://github.com/dart-lang/sdk/issues/52701)
## 2. Rétablir la gestion de lAPI des enregistrements de déroulement
Dart gérait labsence de `RtlAddGrowableFunctionTable` sous Windows 7, puis cette compatibilité a été supprimée. Il faut annuler le commit correspondant.
Commit : [Remove Windows7 handling of unwinding records API](https://github.com/dart-lang/sdk/commit/34213ba60578e46fc2455c5a56b09d9efabc532b)
## 3. Corriger les liens symboliques relatifs vers des répertoires
La correction de Dart a modifié `File::GetType` en utilisant `PathCchCombineEx`, disponible uniquement depuis Windows 8. Ce changement doit également être annulé.
Commit : [Fix a bug where relative symlinks to directories did not work on Windows](https://github.com/dart-lang/sdk/commit/b4574904f9cd0d6f7147950c02ee2447569d0be9)
## Conclusion
Flutter 3.24.5 sous Windows 7 nécessite lannulation de plusieurs changements importants de Dart. Cela affecte `Platform.localHostname` dans les environnements chinois et les liens symboliques relatifs, mais reste nécessaire pour les utilisateurs de Windows 7.
Consultez [notre CI](https://github.com/rustdesk/engine/blob/main/.github/workflows/flutter-engine-windows-x64-release-build.yml) pour limplémentation complète et [cet exemple](https://github.com/rustdesk/rustdesk/blob/dea99ffb3ab34fee562090d214af419b557debdf/.github/workflows/flutter-build.yml#L109) pour son intégration.
@@ -0,0 +1,42 @@
---
publishDate: 2024-10-12T00:00:00Z
lang: fr
translationKey: rustdesk-web-client-v2-preview
title: Aperçu du client Web RustDesk V2
excerpt: V2 apporte de meilleurs codecs, le clavier international, le presse-papiers et le transfert de fichiers.
image: ~/assets/images/blog/web-client.jpg
category: Version
slug: apercu-client-web-rustdesk-v2
tags: [client Web, version]
---
Le client Web RustDesk V1 navait pas été mis à jour depuis plus de deux ans et manquait de fonctions. Notre équipe a consacré trois mois à V2. Cet aperçu améliore le décodage et les claviers internationaux, prend en charge les presse-papiers texte et image ainsi que le transfert de fichiers. Malgré les limites du navigateur, nous visons une expérience cohérente avec la version de bureau.
Essayez-le sur https://rustdesk.com/web. Pour accéder à votre serveur, configurez WebSocket Secure (WSS) sur les ports 21118/21119 selon la [documentation](https://rustdesk.com/docs/en/self-host/rustdesk-server-pro/faq/#8-add-websocket-secure-wss-support-for-the-id-server-and-relay-server-to-enable-secure-communication-for-the-web-client).
```nginx
location /ws/id {
proxy_pass http://localhost:21118;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /ws/relay {
proxy_pass http://localhost:21119;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
```
Quand V2 sera stable, le client Web sera intégré à la version Pro et accessible via `https://rustdesk.yourcompany.com/web`.
Nous sommes heureux de proposer ces améliorations et attendons vos retours pour continuer à perfectionner RustDesk Web Client V2.
@@ -0,0 +1,27 @@
---
publishDate: 2025-02-19T00:00:00Z
lang: it
translationKey: enhanced-acl-in-rustdesk-server-pro-1-5-0
title: ACL migliorate in RustDesk Server Pro 1.5.0
excerpt: RustDesk Server Pro ora supporta ACL per utente e gruppi di dispositivi oltre ai gruppi di utenti.
image: ~/assets/images/blog/device-group.png
category: Rilascio
slug: acl-migliorate-rustdesk-server-pro-1-5-0
tags: [RustDesk Server Pro, rilascio]
---
RustDesk Server Pro 1.5.0 è ora disponibile.
## Registro delle modifiche
* Aggiunti gruppi di dispositivi e relative ACL
* Aggiunte ACL granulari per utente
* Aggiunte strategie per gruppi di dispositivi
* Aggiunte tabelle ordinabili
* OIDC collega il vecchio account in caso di conflitto e-mail
* Consentito `-` nellID
* Corretto lerrore con nome OIDC nullo
I gruppi di dispositivi sono una novità di RustDesk Server Pro 1.5.0. In precedenza RustDesk privilegiava la semplicità e gestiva le ACL solo tramite gruppi di utenti. Nelle grandi organizzazioni ciò rendeva complessa la configurazione dei permessi. La versione 1.5.0 introduce quindi gruppi di dispositivi e ACL per utente. È possibile raggruppare i dispositivi e applicare criteri al gruppo, migliorando notevolmente la gestione su larga scala.
Ogni dispositivo può essere assegnato a un gruppo oltre a essere associato a un utente. Si possono così applicare criteri aggiuntivi, ad esempio limitare o consentire laccesso a dispositivi specifici. Le ACL per utente permettono inoltre permessi individuali, offrendo maggiore flessibilità e controllo.
@@ -0,0 +1,43 @@
---
publishDate: 2024-12-02T00:00:00Z
lang: it
translationKey: how-to-make-flutter-3-24-run-on-windows-7
title: Come eseguire Flutter 3.24 su Windows 7?
excerpt: Da Flutter 3.22 il supporto a Windows 7 / 8 è terminato; occorre modificare Flutter Engine.
image: ~/assets/images/blog/flutter-win7.png
category: Rilascio
slug: eseguire-flutter-3-24-su-windows-7
tags: [sviluppo]
---
Abbiamo recentemente introdotto Flutter 3.24.5 in [RustDesk](https://github.com/rustdesk/rustdesk), che ha smesso di funzionare su Windows 7. Per risolvere il problema abbiamo modificato Flutter Engine.
## Soluzione per Flutter 3.24.5 su Windows 7
Flutter e Dart hanno annunciato che Dart 3.3 e Flutter 3.19 sarebbero state le ultime versioni per Windows 7 e 8. Per usare Flutter 3.24.5 dobbiamo annullare alcune modifiche di Dart.
Link: [Annuncio ufficiale](https://groups.google.com/g/flutter-announce/c/s0tM5gxAgs4/m/ryxV2vZYAQAJ?pli=1)
## 1. Correggere `Platform.localHostname` negli ambienti cinesi
Dart usava `gethostname`, che causava errori di `Platform.localHostname` negli ambienti cinesi. È quindi passato a `GetHostNameW`, disponibile solo da Windows 8. Per Windows 7 occorre annullare la modifica, con conseguenze su `Platform.localHostname` in tali ambienti.
Issue: [Platform.localHostname fails on Chinese Windows](https://github.com/dart-lang/sdk/issues/52701)
## 2. Ripristinare la gestione dei record di unwinding
Dart gestiva lassenza di `RtlAddGrowableFunctionTable` in Windows 7, ma tale compatibilità è stata rimossa. Va annullato il commit corrispondente.
Commit: [Remove Windows7 handling of unwinding records API](https://github.com/dart-lang/sdk/commit/34213ba60578e46fc2455c5a56b09d9efabc532b)
## 3. Correggere i link simbolici relativi alle directory
La correzione di Dart ha modificato `File::GetType` usando `PathCchCombineEx`, disponibile solo da Windows 8. Anche questa modifica va annullata.
Commit: [Fix a bug where relative symlinks to directories did not work on Windows](https://github.com/dart-lang/sdk/commit/b4574904f9cd0d6f7147950c02ee2447569d0be9)
## Conclusione
Flutter 3.24.5 su Windows 7 richiede di annullare modifiche chiave di Dart. Ciò influisce su `Platform.localHostname` in ambienti cinesi e sui link simbolici relativi, ma è necessario per chi dipende da Windows 7.
Vedi [la nostra CI](https://github.com/rustdesk/engine/blob/main/.github/workflows/flutter-engine-windows-x64-release-build.yml) per limplementazione completa e [qui](https://github.com/rustdesk/rustdesk/blob/dea99ffb3ab34fee562090d214af419b557debdf/.github/workflows/flutter-build.yml#L109) per lintegrazione.
@@ -0,0 +1,42 @@
---
publishDate: 2024-10-12T00:00:00Z
lang: it
translationKey: rustdesk-web-client-v2-preview
title: Anteprima del client Web RustDesk V2
excerpt: V2 offre codec migliori, tastiere internazionali, appunti e trasferimento di file.
image: ~/assets/images/blog/web-client.jpg
category: Rilascio
slug: anteprima-client-web-rustdesk-v2
tags: [client Web, rilascio]
---
RustDesk Web Client V1 non veniva aggiornato da oltre due anni e mancava di molte funzioni. Il team ha lavorato tre mesi su V2. Lanteprima migliora la decodifica e le tastiere internazionali, supporta appunti di testo e immagini e il trasferimento di file. Nonostante i limiti del browser, puntiamo a unesperienza coerente con la versione desktop.
Provalo su https://rustdesk.com/web. Per accedere al tuo server, configura WebSocket Secure (WSS) sulle porte 21118/21119 seguendo la [documentazione](https://rustdesk.com/docs/en/self-host/rustdesk-server-pro/faq/#8-add-websocket-secure-wss-support-for-the-id-server-and-relay-server-to-enable-secure-communication-for-the-web-client).
```nginx
location /ws/id {
proxy_pass http://localhost:21118;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /ws/relay {
proxy_pass http://localhost:21119;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
```
Quando V2 sarà stabile, integreremo il client Web nella versione Pro, accessibile da `https://rustdesk.yourcompany.com/web`.
Siamo lieti di offrire questi miglioramenti e attendiamo il tuo feedback mentre continuiamo a perfezionare RustDesk Web Client V2.
@@ -0,0 +1,27 @@
---
publishDate: 2025-02-19T00:00:00Z
lang: ja
translationKey: enhanced-acl-in-rustdesk-server-pro-1-5-0
title: RustDesk Server Pro 1.5.0 の ACL 強化
excerpt: ユーザーグループに加え、ユーザー単位の ACL とデバイスグループに対応しました。
image: ~/assets/images/blog/device-group.png
category: リリース
slug: rustdesk-server-pro-1-5-0-acl-kyouka
tags: [RustDesk Server Pro, リリース]
---
RustDesk Server Pro 1.5.0 をリリースしました。
## 変更履歴
* デバイスグループとその ACL を追加
* ユーザー単位のきめ細かな ACL を追加
* デバイスグループのポリシーを追加
* 並べ替え可能なテーブルを追加
* メールアドレスが競合した場合に OIDC を既存アカウントへリンク
* ID で `-` を許可
* OIDC の名前が null の場合の失敗を修正
デバイスグループは RustDesk Server Pro 1.5.0 の新機能です。従来はシンプルさを重視し、ACL(アクセス制御リスト)の管理にユーザーグループだけを使用していました。しかし、大規模組織では権限設定が煩雑になります。そこで 1.5.0 ではデバイスグループとユーザー単位の ACL を導入しました。デバイスをグループ化してポリシーを適用でき、大規模環境の管理が大きく向上します。
各デバイスはユーザーとの関連付けに加え、1 つのデバイスグループへ割り当てられます。特定デバイスへのアクセスを制限または許可するなど、追加ポリシーを設定できます。ユーザー単位の ACL により個別の権限も設定でき、柔軟性と制御性が高まりました。
@@ -0,0 +1,43 @@
---
publishDate: 2024-12-02T00:00:00Z
lang: ja
translationKey: how-to-make-flutter-3-24-run-on-windows-7
title: Flutter 3.24 を Windows 7 で動かす方法
excerpt: Flutter 3.22 から Windows 7 / 8 のサポートが終了したため、Flutter Engine を修正します。
image: ~/assets/images/blog/flutter-win7.png
category: リリース
slug: flutter-3-24-windows-7-de-ugokasu-houhou
tags: [開発]
---
最近 [RustDesk](https://github.com/rustdesk/rustdesk) に Flutter 3.24.5 を導入したところ、Windows 7 で動作しなくなりました。この問題を解決するため Flutter Engine を修正しました。
## Windows 7 で Flutter 3.24.5 を実行する方法
Dart 3.3 と Flutter 3.19 が Windows 7 / 8 をサポートする最後のバージョンであると公式発表されています。Flutter 3.24.5 を動かすには、Dart の変更をいくつか戻す必要があります。
関連リンク:[公式発表](https://groups.google.com/g/flutter-announce/c/s0tM5gxAgs4/m/ryxV2vZYAQAJ?pli=1)
## 1. 中国語環境の `Platform.localHostname` を修正
Dart は以前 `gethostname` API を使用しており、中国語環境で `Platform.localHostname` が失敗していました。その後 Windows 8 以降のみの `GetHostNameW` に変更されました。Windows 7 のためにはこの変更を戻しますが、中国語環境の `Platform.localHostname` に影響します。
Issue[Platform.localHostname fails on Chinese Windows](https://github.com/dart-lang/sdk/issues/52701)
## 2. Windows 7 の unwind records API 処理を復元
Dart には Windows 7 に `RtlAddGrowableFunctionTable` がない場合の互換処理がありましたが、削除されました。そのコミットを戻します。
Commit[Remove Windows7 handling of unwinding records API](https://github.com/dart-lang/sdk/commit/34213ba60578e46fc2455c5a56b09d9efabc532b)
## 3. ディレクトリへの相対シンボリックリンクを修正
Dart の修正は `File::GetType` で Windows 8 以降のみの `PathCchCombineEx` を使用します。Windows 7 のため、この変更も戻します。
Commit[Fix a bug where relative symlinks to directories did not work on Windows](https://github.com/dart-lang/sdk/commit/b4574904f9cd0d6f7147950c02ee2447569d0be9)
## まとめ
Flutter 3.24.5 を Windows 7 で実行するには Dart の主要な変更を戻す必要があります。中国語環境の `Platform.localHostname` や相対シンボリックリンクに副作用がありますが、Windows 7 ユーザーには必要です。
完全な実装は[当社の CI](https://github.com/rustdesk/engine/blob/main/.github/workflows/flutter-engine-windows-x64-release-build.yml)、適用方法は[こちら](https://github.com/rustdesk/rustdesk/blob/dea99ffb3ab34fee562090d214af419b557debdf/.github/workflows/flutter-build.yml#L109)をご覧ください。
@@ -0,0 +1,42 @@
---
publishDate: 2024-10-12T00:00:00Z
lang: ja
translationKey: rustdesk-web-client-v2-preview
title: RustDesk Web クライアント V2 プレビュー
excerpt: V2 はコーデック、国際キーボード、クリップボード、ファイル転送を強化します。
image: ~/assets/images/blog/web-client.jpg
category: リリース
slug: rustdesk-web-client-v2-preview-ja
tags: [Web クライアント, リリース]
---
RustDesk Web クライアント V1 は 2 年以上更新されず、多くの機能が不足していました。チームは 3 か月かけて V2 を開発しました。このプレビューではデコード性能と国際キーボード対応を改善し、テキストだけでなく画像のクリップボードとファイル転送にも対応します。ブラウザーの制約がある中でも、デスクトップ版と一貫した機能と操作性を目指しています。
https://rustdesk.com/web からお試しください。独自サーバーへ接続する場合は、[ドキュメント](https://rustdesk.com/docs/en/self-host/rustdesk-server-pro/faq/#8-add-websocket-secure-wss-support-for-the-id-server-and-relay-server-to-enable-secure-communication-for-the-web-client)に従い、ポート 21118/21119 で WebSocket SecureWSS)を設定してください。
```nginx
location /ws/id {
proxy_pass http://localhost:21118;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /ws/relay {
proxy_pass http://localhost:21119;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
```
V2 の安定後は Web クライアントを Pro 版にも統合し、`https://rustdesk.yourcompany.com/web` から利用できるようにします。
これらの改善を皆様にお届けできることを嬉しく思います。RustDesk Web Client V2 の改良に向けたフィードバックをお待ちしています。
@@ -0,0 +1,27 @@
---
publishDate: 2025-02-19T00:00:00Z
lang: ko
translationKey: enhanced-acl-in-rustdesk-server-pro-1-5-0
title: RustDesk Server Pro 1.5.0의 향상된 ACL
excerpt: 사용자 그룹뿐 아니라 사용자별 ACL과 장치 그룹도 지원합니다.
image: ~/assets/images/blog/device-group.png
category: 릴리스
slug: rustdesk-server-pro-1-5-0-hyangsangdoen-acl
tags: [RustDesk Server Pro, 릴리스]
---
RustDesk Server Pro 1.5.0이 출시되었습니다.
## 변경 사항
* 장치 그룹 및 ACL 추가
* 사용자별 세분화 ACL 추가
* 장치 그룹 전략 추가
* 정렬 가능한 표 추가
* 이메일 충돌 시 OIDC가 기존 계정에 연결
* ID에 `-` 허용
* OIDC 이름이 null일 때 발생하던 오류 수정
장치 그룹은 RustDesk Server Pro 1.5.0의 새로운 기능입니다. 이전에는 단순성을 위해 ACL(액세스 제어 목록)을 사용자 그룹만으로 관리했습니다. 하지만 대규모 조직에서는 권한 설정이 번거로웠습니다. 이에 1.5.0부터 장치 그룹과 사용자별 ACL을 도입했습니다. 장치를 그룹화하고 정책을 적용할 수 있어 대규모 환경의 장치 관리가 크게 개선됩니다.
각 장치는 사용자와 연결되는 동시에 하나의 장치 그룹에 할당될 수 있습니다. 특정 장치에 대한 접근을 제한하거나 허용하는 추가 정책을 적용할 수 있습니다. 사용자별 ACL로 개별 권한도 설정할 수 있어 더 유연하고 정밀한 접근 관리가 가능합니다.
@@ -0,0 +1,43 @@
---
publishDate: 2024-12-02T00:00:00Z
lang: ko
translationKey: how-to-make-flutter-3-24-run-on-windows-7
title: Windows 7에서 Flutter 3.24를 실행하는 방법
excerpt: Flutter 3.22부터 Windows 7 / 8 지원이 중단되어 Flutter Engine을 수정해야 합니다.
image: ~/assets/images/blog/flutter-win7.png
category: 릴리스
slug: windows-7-flutter-3-24-silhaeng-haneun-bangbeop
tags: [개발]
---
최근 [RustDesk](https://github.com/rustdesk/rustdesk)에 Flutter 3.24.5를 도입한 뒤 Windows 7에서 실행되지 않았습니다. 이를 해결하기 위해 Flutter Engine을 수정했습니다.
## Windows 7에서 Flutter 3.24.5 실행하기
Dart 3.3과 Flutter 3.19가 Windows 7 및 8을 지원하는 마지막 버전이라고 공식 발표되었습니다. Flutter 3.24.5를 사용하려면 Dart의 일부 변경 사항을 되돌려야 합니다.
관련 링크: [공식 발표](https://groups.google.com/g/flutter-announce/c/s0tM5gxAgs4/m/ryxV2vZYAQAJ?pli=1)
## 1. 중국어 환경의 `Platform.localHostname` 수정
Dart는 원래 `gethostname` API를 사용해 중국어 환경에서 `Platform.localHostname`이 실패했습니다. 이후 Windows 8 이상에서만 제공되는 `GetHostNameW`로 변경했습니다. Windows 7을 위해 이 변경을 되돌려야 하며, 중국어 환경의 `Platform.localHostname`에는 영향이 생깁니다.
Issue: [Platform.localHostname fails on Chinese Windows](https://github.com/dart-lang/sdk/issues/52701)
## 2. Windows 7 unwinding records API 처리 복원
Dart에는 Windows 7에 `RtlAddGrowableFunctionTable`이 없을 때의 호환 처리가 있었지만 삭제되었습니다. 해당 커밋을 되돌려야 합니다.
Commit: [Remove Windows7 handling of unwinding records API](https://github.com/dart-lang/sdk/commit/34213ba60578e46fc2455c5a56b09d9efabc532b)
## 3. 디렉터리 상대 심볼릭 링크 수정
Dart의 수정은 `File::GetType`에서 Windows 8 이상 전용 `PathCchCombineEx`를 사용합니다. Windows 7 지원을 위해 이 변경도 되돌립니다.
Commit: [Fix a bug where relative symlinks to directories did not work on Windows](https://github.com/dart-lang/sdk/commit/b4574904f9cd0d6f7147950c02ee2447569d0be9)
## 결론
Windows 7에서 Flutter 3.24.5를 실행하려면 Dart의 주요 변경을 되돌려야 합니다. 중국어 환경의 `Platform.localHostname`과 상대 심볼릭 링크에 부작용이 있지만 Windows 7 사용자에게는 필요한 단계입니다.
전체 구현은 [CI](https://github.com/rustdesk/engine/blob/main/.github/workflows/flutter-engine-windows-x64-release-build.yml), 수정 엔진 적용 방법은 [여기](https://github.com/rustdesk/rustdesk/blob/dea99ffb3ab34fee562090d214af419b557debdf/.github/workflows/flutter-build.yml#L109)에서 확인하세요.
@@ -0,0 +1,42 @@
---
publishDate: 2024-10-12T00:00:00Z
lang: ko
translationKey: rustdesk-web-client-v2-preview
title: RustDesk 웹 클라이언트 V2 미리보기
excerpt: V2는 코덱, 국제 키보드, 클립보드 및 파일 전송 기능을 개선합니다.
image: ~/assets/images/blog/web-client.jpg
category: 릴리스
slug: rustdesk-web-client-v2-miribogi
tags: [웹 클라이언트, 릴리스]
---
RustDesk 웹 클라이언트 V1은 2년 넘게 업데이트되지 않았고 많은 기능이 부족했습니다. 팀은 3개월 동안 V2를 개발했습니다. 이번 미리보기는 디코딩과 국제 키보드 지원을 개선하고 텍스트와 이미지 클립보드, 파일 전송을 지원합니다. 브라우저의 제약 속에서도 데스크톱 버전과 일관된 기능과 사용 경험을 제공하는 것이 목표입니다.
https://rustdesk.com/web 에서 이용할 수 있습니다. 자체 서버에 접속하려면 [문서](https://rustdesk.com/docs/en/self-host/rustdesk-server-pro/faq/#8-add-websocket-secure-wss-support-for-the-id-server-and-relay-server-to-enable-secure-communication-for-the-web-client)에 따라 포트 21118/21119에서 WebSocket Secure(WSS)를 설정하세요.
```nginx
location /ws/id {
proxy_pass http://localhost:21118;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /ws/relay {
proxy_pass http://localhost:21119;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
```
V2가 안정화되면 웹 클라이언트를 Pro 버전에도 통합하여 `https://rustdesk.yourcompany.com/web`에서 이용할 수 있게 할 예정입니다.
이러한 개선을 제공하게 되어 기쁘며 RustDesk Web Client V2를 계속 다듬기 위한 의견을 기다립니다.
@@ -0,0 +1,27 @@
---
publishDate: 2025-02-19T00:00:00Z
lang: pt
translationKey: enhanced-acl-in-rustdesk-server-pro-1-5-0
title: ACL aprimorada no RustDesk Server Pro 1.5.0
excerpt: O RustDesk Server Pro agora oferece ACL por usuário e grupos de dispositivos, além de grupos de usuários.
image: ~/assets/images/blog/device-group.png
category: Lançamento
slug: acl-aprimorada-rustdesk-server-pro-1-5-0
tags: [RustDesk Server Pro, lançamento]
---
O RustDesk Server Pro 1.5.0 já está disponível.
## Registro de alterações
* Adição de grupos de dispositivos e suas ACLs
* Adição de ACL granular por usuário
* Adição de estratégias para grupos de dispositivos
* Adição de tabelas ordenáveis
* OIDC vincula a conta antiga em caso de conflito de e-mail
* `-` permitido no ID
* Correção da falha quando o nome do OIDC é nulo
Os grupos de dispositivos são uma novidade do RustDesk Server Pro 1.5.0. Antes, o RustDesk priorizava a simplicidade e gerenciava ACLs apenas com grupos de usuários. Em organizações grandes, isso tornava as permissões trabalhosas. Por isso, a versão 1.5.0 inclui grupos de dispositivos e ACL por usuário. Agora é possível agrupar dispositivos e aplicar políticas ao grupo, melhorando bastante a administração em grande escala.
Cada dispositivo pode pertencer a um grupo além de estar associado a um usuário. Assim, políticas adicionais podem restringir ou permitir o acesso a dispositivos específicos. A ACL por usuário também permite permissões individuais, oferecendo maior flexibilidade e controle.
@@ -0,0 +1,43 @@
---
publishDate: 2024-12-02T00:00:00Z
lang: pt
translationKey: how-to-make-flutter-3-24-run-on-windows-7
title: Como executar o Flutter 3.24 no Windows 7?
excerpt: O Flutter deixou de oferecer suporte ao Windows 7 / 8 na versão 3.22; precisamos modificar o Flutter Engine.
image: ~/assets/images/blog/flutter-win7.png
category: Lançamento
slug: executar-flutter-3-24-no-windows-7
tags: [desenvolvimento]
---
Recentemente adotamos o Flutter 3.24.5 no [RustDesk](https://github.com/rustdesk/rustdesk), que deixou de funcionar no Windows 7. Para resolver o problema, modificamos o Flutter Engine.
## Solução para o Flutter 3.24.5 no Windows 7
Flutter e Dart anunciaram que Dart 3.3 e Flutter 3.19 seriam as últimas versões compatíveis com Windows 7 e 8. Para usar o Flutter 3.24.5, precisamos reverter algumas alterações do Dart.
Link: [Anúncio oficial](https://groups.google.com/g/flutter-announce/c/s0tM5gxAgs4/m/ryxV2vZYAQAJ?pli=1)
## 1. Corrigir `Platform.localHostname` em ambientes chineses
O Dart usava `gethostname`, fazendo `Platform.localHostname` falhar nesses ambientes. Depois adotou `GetHostNameW`, disponível apenas a partir do Windows 8. Para o Windows 7, essa mudança deve ser revertida, embora isso afete `Platform.localHostname` em ambientes chineses.
Issue: [Platform.localHostname fails on Chinese Windows](https://github.com/dart-lang/sdk/issues/52701)
## 2. Restaurar o tratamento de registros de unwinding
O Dart tratava a ausência de `RtlAddGrowableFunctionTable` no Windows 7, mas essa compatibilidade foi removida. É necessário reverter o commit correspondente.
Commit: [Remove Windows7 handling of unwinding records API](https://github.com/dart-lang/sdk/commit/34213ba60578e46fc2455c5a56b09d9efabc532b)
## 3. Corrigir links simbólicos relativos para diretórios
A correção do Dart alterou `File::GetType` usando `PathCchCombineEx`, disponível apenas a partir do Windows 8. Essa alteração também deve ser revertida.
Commit: [Fix a bug where relative symlinks to directories did not work on Windows](https://github.com/dart-lang/sdk/commit/b4574904f9cd0d6f7147950c02ee2447569d0be9)
## Conclusão
Executar o Flutter 3.24.5 no Windows 7 exige reverter mudanças importantes do Dart. Isso afeta `Platform.localHostname` em ambientes chineses e links simbólicos relativos, mas é necessário para quem depende do Windows 7.
Veja a implementação em [nosso CI](https://github.com/rustdesk/engine/blob/main/.github/workflows/flutter-engine-windows-x64-release-build.yml) e [como aplicamos o motor modificado](https://github.com/rustdesk/rustdesk/blob/dea99ffb3ab34fee562090d214af419b557debdf/.github/workflows/flutter-build.yml#L109).
@@ -0,0 +1,42 @@
---
publishDate: 2024-10-12T00:00:00Z
lang: pt
translationKey: rustdesk-web-client-v2-preview
title: Prévia do cliente Web RustDesk V2
excerpt: A V2 oferece codecs melhores, teclado internacional, área de transferência e transferência de arquivos.
image: ~/assets/images/blog/web-client.jpg
category: Lançamento
slug: previa-cliente-web-rustdesk-v2
tags: [cliente Web, lançamento]
---
O RustDesk Web Client V1 não recebia atualizações havia mais de dois anos e não tinha vários recursos. Nossa equipe trabalhou três meses na V2. A prévia melhora a decodificação e os teclados internacionais, aceita áreas de transferência de texto e imagem e transfere arquivos. Apesar dos limites do navegador, buscamos uma experiência consistente com a versão desktop.
Acesse https://rustdesk.com/web. Para usar seu próprio servidor, configure WebSocket Secure (WSS) nas portas 21118/21119 conforme a [documentação](https://rustdesk.com/docs/en/self-host/rustdesk-server-pro/faq/#8-add-websocket-secure-wss-support-for-the-id-server-and-relay-server-to-enable-secure-communication-for-the-web-client).
```nginx
location /ws/id {
proxy_pass http://localhost:21118;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /ws/relay {
proxy_pass http://localhost:21119;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
```
Quando a V2 estiver estável, integraremos o cliente Web à versão Pro, acessível em `https://rustdesk.yourcompany.com/web`.
Esperamos que essas melhorias sejam úteis e contamos com seu feedback enquanto aperfeiçoamos o RustDesk Web Client V2.
@@ -0,0 +1,29 @@
---
publishDate: 2025-02-19T00:00:00Z
lang: zh-cn
translationKey: enhanced-acl-in-rustdesk-server-pro-1-5-0
title: RustDesk Server Pro 1.5.0 增强 ACL
excerpt: RustDesk Server Pro 现已在用户组之外支持用户级 ACL 和设备组。
image: ~/assets/images/blog/device-group.png
category: 发布
slug: rustdesk-server-pro-1-5-0-zeng-qiang-acl
tags:
- RustDesk Server Pro
- 发布
---
RustDesk Server Pro 1.5.0 现已发布。
## 更新日志
* 新增设备组及其 ACL
* 新增用户级细粒度 ACL
* 新增设备组策略
* 新增可排序表格
* 当电子邮件冲突时,OIDC 会关联旧账户
* ID 中允许使用 `-`(连字符)
* 修复 OIDC 名称为空时的失败问题
设备组是 RustDesk Server Pro 1.5.0 的一项新功能。此前,RustDesk 注重简洁,没有引入设备组,而是仅依靠用户组管理 ACL(访问控制列表)。但在大型组织中,这种方式会让访问权限配置变得繁琐。因此,我们在 1.5.0 中加入了设备组和用户级 ACL。现在可以对设备进行分组并向组应用策略,从而显著改善大规模环境中的设备管理。
RustDesk Server Pro 原先只支持用户组,本次更新后也支持设备组。每台设备除了关联一个用户外,还可以分配到一个设备组。这样便可根据设备组实施额外策略,例如限制或允许访问特定设备。此外,本版本加入了用户级 ACL,可在用户组之外为单个用户设置权限,让访问管理更加灵活、可控。
@@ -0,0 +1,46 @@
---
publishDate: 2024-12-02T00:00:00Z
lang: zh-cn
translationKey: how-to-make-flutter-3-24-run-on-windows-7
title: 如何让 Flutter 3.24 在 Windows 7 上运行?
excerpt: Flutter 从 3.22 开始停止支持 Windows 7 / 8,因此需要修改 Flutter 引擎以恢复 Windows 7 支持。
image: ~/assets/images/blog/flutter-win7.png
category: 发布
slug: ru-he-rang-flutter-3-24-yun-xing-yu-windows-7
tags:
- 开发
---
我们最近在 [RustDesk](https://github.com/rustdesk/rustdesk) 中引入了 Flutter 3.24.5,结果程序无法在 Windows 7 上运行。为解决这个问题,我们尝试修改 Flutter Engine。
## 在 Windows 7 上运行 Flutter 3.24.5 的解决方案
Flutter 和 Dart 官方已经宣布,Dart 3.3 与 Flutter 3.19 是最后支持 Windows 7 和 8 的版本。因此,Flutter 3.24.5 无法直接在 Windows 7 上运行。要解决这个问题,需要撤销 Dart 中的部分更改以恢复 Windows 7 支持,具体如下。
相关链接:[官方公告](https://groups.google.com/g/flutter-announce/c/s0tM5gxAgs4/m/ryxV2vZYAQAJ?pli=1)
## 1. 修复中文环境中的 `Platform.localHostname` 问题
Dart 原先使用 `gethostname` API,这会导致 `Platform.localHostname` 在中文环境中失败。为解决该问题,Dart 改用了仅在 Windows 8 及更高版本中提供的 `GetHostNameW` API。为了让 Flutter 在 Windows 7 上运行,需要撤销这项更改。请注意,撤销后会影响中文环境中的 `Platform.localHostname` 功能。
相关 Issue[Platform.localHostname fails on Chinese Windows](https://github.com/dart-lang/sdk/issues/52701)
## 2. 恢复 Windows 7 的展开记录 API 兼容处理
Dart 此前针对 Windows 7 缺少 `RtlAddGrowableFunctionTable` API 做过兼容处理,但后来删除了这项修改。为了保持 Windows 7 兼容性,需要撤销删除该处理的提交。
相关 Commit[Remove Windows7 handling of unwinding records API](https://github.com/dart-lang/sdk/commit/34213ba60578e46fc2455c5a56b09d9efabc532b)
## 3. 修复 Windows 上目录相对符号链接失效的问题
Dart 曾修复 Windows 上目录相对符号链接无法工作的问题,并修改了 `File::GetType` 方法。但该修改使用了仅在 Windows 8 及更高版本中提供的 `PathCchCombineEx` API。为恢复 Windows 7 支持,也需要撤销这项修改。
相关 Commit[Fix a bug where relative symlinks to directories did not work on Windows](https://github.com/dart-lang/sdk/commit/b4574904f9cd0d6f7147950c02ee2447569d0be9)
## 总结
要让 Flutter 3.24.5 在 Windows 7 上运行,需要撤销 Dart 中的几项关键修改。这些撤销操作会带来副作用,例如影响中文环境中的 `Platform.localHostname`,以及目录相对符号链接功能。但对于仍依赖 Windows 7 的用户,这是必要的步骤。
完整实现请参阅[我们的 CI](https://github.com/rustdesk/engine/blob/main/.github/workflows/flutter-engine-windows-x64-release-build.yml)。
[这里](https://github.com/rustdesk/rustdesk/blob/dea99ffb3ab34fee562090d214af419b557debdf/.github/workflows/flutter-build.yml#L109)展示了如何在项目中应用修改后的引擎。
@@ -0,0 +1,44 @@
---
publishDate: 2024-10-12T00:00:00Z
lang: zh-cn
translationKey: rustdesk-web-client-v2-preview
title: RustDesk Web 客户端 V2 预览版
excerpt: V2 带来更好的编解码器、国际键盘支持、剪贴板支持和文件传输等功能。
image: ~/assets/images/blog/web-client.jpg
category: 发布
slug: rustdesk-web-ke-hu-duan-v2-yu-lan
tags:
- Web 客户端
- 发布
---
RustDesk Web 客户端 V1 已有两年多没有更新,并且缺少许多功能。我们的团队用三个月时间开发了 V2。今天,我们很高兴推出 V2 预览版,它带来了多项改进,包括更强的解码能力、更好的国际键盘支持、剪贴板支持(不仅支持文本,还支持图像剪贴板)以及文件传输等。尽管浏览器能力有所限制,我们仍努力让 Web 版与桌面版保持一致的功能和用户体验。
访问 https://rustdesk.com/web 即可体验 RustDesk Web 客户端 V2 预览版。如果希望通过 V2 访问自己的服务器,请按照[文档](https://rustdesk.com/docs/en/self-host/rustdesk-server-pro/faq/#8-add-websocket-secure-wss-support-for-the-id-server-and-relay-server-to-enable-secure-communication-for-the-web-client)配置 WebSocket SecureWSS)支持,并使用端口 21118/21119。
```nginx
location /ws/id {
proxy_pass http://localhost:21118;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /ws/relay {
proxy_pass http://localhost:21119;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
```
V2 稳定后,我们还会把 Web 客户端集成到 Pro 版本中,届时可通过 `https://rustdesk.yourcompany.com/web` 访问。
我们期待这些改进为用户带来更好的体验,也欢迎大家在我们持续完善 RustDesk Web Client V2 的过程中提供反馈。
@@ -0,0 +1,27 @@
---
publishDate: 2025-02-19T00:00:00Z
lang: zh-tw
translationKey: enhanced-acl-in-rustdesk-server-pro-1-5-0
title: RustDesk Server Pro 1.5.0 強化 ACL
excerpt: RustDesk Server Pro 現已在使用者群組之外支援使用者層級 ACL 與裝置群組。
image: ~/assets/images/blog/device-group.png
category: 發布
slug: rustdesk-server-pro-1-5-0-qiang-hua-acl
tags: [RustDesk Server Pro, 發布]
---
RustDesk Server Pro 1.5.0 現已推出。
## 更新日誌
* 新增裝置群組及其 ACL
* 新增使用者層級細緻 ACL
* 新增裝置群組策略
* 新增可排序表格
* 電子郵件衝突時,OIDC 會連結舊帳戶
* ID 中允許使用 `-`(連字號)
* 修正 OIDC 名稱為空時的失敗問題
裝置群組是 RustDesk Server Pro 1.5.0 的新功能。過去 RustDesk 重視簡潔,未引入裝置群組,而只依賴使用者群組管理 ACL(存取控制清單)。但在大型組織中,這會使權限設定變得繁瑣。因此,我們在 1.5.0 加入裝置群組與使用者層級 ACL。現在可將裝置分組並套用群組策略,大幅改善大規模環境的裝置管理。
RustDesk Server Pro 原本僅支援使用者群組,這次更新後也支援裝置群組。每台裝置除了關聯一位使用者,也能指派至一個裝置群組。如此便可依裝置群組實施額外策略,例如限制或允許存取特定裝置。本版本也加入使用者層級 ACL,可在使用者群組之外為個別使用者設定權限,使存取管理更靈活、更可控。
@@ -0,0 +1,45 @@
---
publishDate: 2024-12-02T00:00:00Z
lang: zh-tw
translationKey: how-to-make-flutter-3-24-run-on-windows-7
title: 如何讓 Flutter 3.24 在 Windows 7 上執行?
excerpt: Flutter 自 3.22 起停止支援 Windows 7 / 8,因此需要修改 Flutter 引擎以恢復 Windows 7 支援。
image: ~/assets/images/blog/flutter-win7.png
category: 發布
slug: ru-he-rang-flutter-3-24-zai-windows-7-shang-zhi-xing
tags: [開發]
---
我們最近在 [RustDesk](https://github.com/rustdesk/rustdesk) 中導入 Flutter 3.24.5,結果程式無法在 Windows 7 上執行。為解決此問題,我們嘗試修改 Flutter Engine。
## 在 Windows 7 上執行 Flutter 3.24.5 的方案
Flutter 與 Dart 官方已宣布,Dart 3.3 和 Flutter 3.19 是最後支援 Windows 7、8 的版本。因此 Flutter 3.24.5 無法直接在 Windows 7 執行。要恢復支援,必須撤銷 Dart 中的部分變更。
相關連結:[官方公告](https://groups.google.com/g/flutter-announce/c/s0tM5gxAgs4/m/ryxV2vZYAQAJ?pli=1)
## 1. 修正中文環境中的 `Platform.localHostname`
Dart 原先使用 `gethostname` API,這會使 `Platform.localHostname` 在中文環境中失敗。Dart 因而改用僅支援 Windows 8 以上版本的 `GetHostNameW` API。為讓 Flutter 在 Windows 7 執行,需要撤銷此變更;但這會影響中文環境中的 `Platform.localHostname`
相關 Issue[Platform.localHostname fails on Chinese Windows](https://github.com/dart-lang/sdk/issues/52701)
## 2. 恢復 Windows 7 的展開記錄 API 處理
Dart 曾針對 Windows 7 缺少 `RtlAddGrowableFunctionTable` API 做相容處理,但之後移除了這項修改。為保持相容,需要撤銷移除該處理的提交。
相關 Commit[Remove Windows7 handling of unwinding records API](https://github.com/dart-lang/sdk/commit/34213ba60578e46fc2455c5a56b09d9efabc532b)
## 3. 修正 Windows 目錄相對符號連結問題
Dart 修正目錄相對符號連結時修改了 `File::GetType`,卻使用僅支援 Windows 8 以上版本的 `PathCchCombineEx` API。為恢復 Windows 7 支援,也需要撤銷此修改。
相關 Commit[Fix a bug where relative symlinks to directories did not work on Windows](https://github.com/dart-lang/sdk/commit/b4574904f9cd0d6f7147950c02ee2447569d0be9)
## 結論
要讓 Flutter 3.24.5 在 Windows 7 執行,必須撤銷 Dart 的幾項關鍵修改。這會帶來副作用,包括影響中文環境的 `Platform.localHostname` 與目錄相對符號連結;但對仍依賴 Windows 7 的使用者而言,這些步驟不可或缺。
完整實作請參閱[我們的 CI](https://github.com/rustdesk/engine/blob/main/.github/workflows/flutter-engine-windows-x64-release-build.yml)。
[這裡](https://github.com/rustdesk/rustdesk/blob/dea99ffb3ab34fee562090d214af419b557debdf/.github/workflows/flutter-build.yml#L109)展示如何在專案中套用修改後的引擎。
@@ -0,0 +1,42 @@
---
publishDate: 2024-10-12T00:00:00Z
lang: zh-tw
translationKey: rustdesk-web-client-v2-preview
title: RustDesk Web 用戶端 V2 預覽版
excerpt: V2 帶來更好的編解碼器、國際鍵盤支援、剪貼簿與檔案傳輸等功能。
image: ~/assets/images/blog/web-client.jpg
category: 發布
slug: rustdesk-web-yong-hu-duan-v2-yu-lan
tags: [Web 用戶端, 發布]
---
RustDesk Web 用戶端 V1 已有兩年多未更新,且缺少許多功能。我們的團隊投入三個月開發 V2。今天推出的 V2 預覽版提供更強的解碼能力、更好的國際鍵盤支援、剪貼簿支援(不只文字,也支援圖片)與檔案傳輸等功能。儘管瀏覽器能力有限,我們仍努力讓 Web 版與桌面版具備一致的功能和使用體驗。
前往 https://rustdesk.com/web 即可體驗。若要透過 V2 存取自己的伺服器,請依照[文件](https://rustdesk.com/docs/en/self-host/rustdesk-server-pro/faq/#8-add-websocket-secure-wss-support-for-the-id-server-and-relay-server-to-enable-secure-communication-for-the-web-client)設定 WebSocket SecureWSS),使用連接埠 21118/21119。
```nginx
location /ws/id {
proxy_pass http://localhost:21118;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /ws/relay {
proxy_pass http://localhost:21119;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
```
V2 穩定後,我們也會將 Web 用戶端整合到 Pro 版本,屆時可透過 `https://rustdesk.yourcompany.com/web` 存取。
我們期待這些改進為使用者帶來更好的體驗,也歡迎大家在我們持續完善 RustDesk Web Client V2 時提供意見。
+1 -1
View File
@@ -69,7 +69,7 @@ export function getLocalePaths(url: URL): LocalePath[] {
};
});
}
type LocalePath = {
export type LocalePath = {
lang: Lang;
path: string;
};
+90
View File
@@ -0,0 +1,90 @@
import { DEFAULT_LOCALE, type Lang } from '@/i18n';
export interface BlogCopy {
title: string;
subtitle: string;
page: string;
newerPosts: string;
olderPosts: string;
updated: string;
minRead: string;
backToBlog: string;
relatedPosts: string;
viewAllPosts: string;
category: string;
tag: string;
share: string;
newPost: string;
}
const copy: Record<Lang, BlogCopy> = {
en: {
title: 'The Blog',
subtitle: 'News, tutorials, resources, and product updates from RustDesk.',
page: 'Page', newerPosts: 'Newer posts', olderPosts: 'Older posts', updated: 'Updated', minRead: 'min read',
backToBlog: 'Back to Blog', relatedPosts: 'Related Posts', viewAllPosts: 'View All Posts', category: 'Category',
tag: 'Tag', share: 'Share', newPost: 'New',
},
de: {
title: 'Der Blog', subtitle: 'Neuigkeiten, Anleitungen, Ressourcen und Produktupdates von RustDesk.', page: 'Seite',
newerPosts: 'Neuere Beiträge', olderPosts: 'Ältere Beiträge', updated: 'Aktualisiert', minRead: 'Min. Lesezeit',
backToBlog: 'Zurück zum Blog', relatedPosts: 'Ähnliche Beiträge', viewAllPosts: 'Alle Beiträge anzeigen',
category: 'Kategorie', tag: 'Tag', share: 'Teilen', newPost: 'Neu',
},
es: {
title: 'El blog', subtitle: 'Noticias, tutoriales, recursos y actualizaciones de RustDesk.', page: 'Página',
newerPosts: 'Publicaciones más recientes', olderPosts: 'Publicaciones anteriores', updated: 'Actualizado',
minRead: 'min de lectura', backToBlog: 'Volver al blog', relatedPosts: 'Publicaciones relacionadas',
viewAllPosts: 'Ver todas las publicaciones', category: 'Categoría', tag: 'Etiqueta', share: 'Compartir',
newPost: 'Nuevo',
},
fr: {
title: 'Le blog', subtitle: 'Actualités, tutoriels, ressources et mises à jour de RustDesk.', page: 'Page',
newerPosts: 'Articles plus récents', olderPosts: 'Articles plus anciens', updated: 'Mis à jour', minRead: 'min de lecture',
backToBlog: 'Retour au blog', relatedPosts: 'Articles associés', viewAllPosts: 'Voir tous les articles',
category: 'Catégorie', tag: 'Étiquette', share: 'Partager', newPost: 'Nouveau',
},
it: {
title: 'Il blog', subtitle: 'Notizie, tutorial, risorse e aggiornamenti di RustDesk.', page: 'Pagina',
newerPosts: 'Articoli più recenti', olderPosts: 'Articoli meno recenti', updated: 'Aggiornato', minRead: 'min di lettura',
backToBlog: 'Torna al blog', relatedPosts: 'Articoli correlati', viewAllPosts: 'Vedi tutti gli articoli',
category: 'Categoria', tag: 'Etichetta', share: 'Condividi', newPost: 'Novità',
},
ja: {
title: 'ブログ', subtitle: 'RustDesk のニュース、チュートリアル、資料、製品アップデート。', page: 'ページ',
newerPosts: '新しい記事', olderPosts: '過去の記事', updated: '更新', minRead: '分で読めます', backToBlog: 'ブログに戻る',
relatedPosts: '関連記事', viewAllPosts: 'すべての記事を見る', category: 'カテゴリー', tag: 'タグ', share: '共有',
newPost: '新着',
},
ko: {
title: '블로그', subtitle: 'RustDesk의 뉴스, 튜토리얼, 자료 및 제품 업데이트입니다.', page: '페이지',
newerPosts: '최신 글', olderPosts: '이전 글', updated: '업데이트', minRead: '분 소요', backToBlog: '블로그로 돌아가기',
relatedPosts: '관련 글', viewAllPosts: '모든 글 보기', category: '카테고리', tag: '태그', share: '공유',
newPost: '새 글',
},
pt: {
title: 'O blog', subtitle: 'Notícias, tutoriais, recursos e atualizações do RustDesk.', page: 'Página',
newerPosts: 'Publicações mais recentes', olderPosts: 'Publicações anteriores', updated: 'Atualizado',
minRead: 'min de leitura', backToBlog: 'Voltar ao blog', relatedPosts: 'Publicações relacionadas',
viewAllPosts: 'Ver todas as publicações', category: 'Categoria', tag: 'Etiqueta', share: 'Compartilhar',
newPost: 'Novo',
},
'zh-cn': {
title: '博客', subtitle: 'RustDesk 新闻、教程、资源和产品更新。', page: '第', newerPosts: '较新文章', olderPosts: '较早文章',
updated: '更新于', minRead: '分钟阅读', backToBlog: '返回博客', relatedPosts: '相关文章', viewAllPosts: '查看所有文章',
category: '分类', tag: '标签', share: '分享', newPost: '最新',
},
'zh-tw': {
title: '部落格', subtitle: 'RustDesk 新聞、教學、資源與產品更新。', page: '第', newerPosts: '較新文章', olderPosts: '較早文章',
updated: '更新於', minRead: '分鐘閱讀', backToBlog: '返回部落格', relatedPosts: '相關文章', viewAllPosts: '查看所有文章',
category: '分類', tag: '標籤', share: '分享', newPost: '最新',
},
ar: {
title: 'المدونة', subtitle: 'أخبار RustDesk وشروحاتها ومواردها وتحديثات منتجاتها.', page: 'الصفحة',
newerPosts: 'منشورات أحدث', olderPosts: 'منشورات أقدم', updated: 'حُدّث في', minRead: 'دقائق للقراءة',
backToBlog: 'العودة إلى المدونة', relatedPosts: 'منشورات ذات صلة', viewAllPosts: 'عرض جميع المنشورات',
category: 'الفئة', tag: 'الوسم', share: 'مشاركة', newPost: 'جديد',
},
};
export const getBlogCopy = (lang?: Lang): BlogCopy => copy[lang || DEFAULT_LOCALE] || copy[DEFAULT_LOCALE];
+25 -10
View File
@@ -19,13 +19,16 @@ import { DEFAULT_SITE_DESCRIPTION } from '~/utils/seo';
// Comment the line below to disable View Transitions
import { ViewTransitions } from 'astro:transitions';
import { useTranslations, getLocalePaths, LOCALES, DEFAULT_LOCALE, type Lang } from '@/i18n';
import { useTranslations, getLocalePaths, LOCALES, DEFAULT_LOCALE, type Lang, type LocalePath } from '@/i18n';
import type { MetaData as MetaDataType } from '~/types';
export interface Props {
metadata?: MetaDataType;
i18n?: boolean;
lang?: Lang;
localePaths?: LocalePath[];
alternateLinks?: LocalePath[];
structuredDataType?: 'website' | 'article' | 'software' | 'faq';
articleData?: {
title: string;
@@ -39,13 +42,25 @@ export interface Props {
faqItems?: Array<{ question: string; answer: string }>;
}
const { metadata = {}, i18n, structuredDataType = 'website', articleData, faqItems } = Astro.props;
const {
metadata = {},
i18n,
lang: pageLocale,
localePaths,
alternateLinks,
structuredDataType = 'website',
articleData,
faqItems,
} = 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;
const locale = pageLocale || (Astro.currentLocale as Lang) || defaultLocale;
const t = useTranslations(locale);
const resolvedAlternateLinks = alternateLinks ?? localePaths ?? getLocalePaths(Astro.url);
const defaultAlternate = resolvedAlternateLinks.find(({ lang }) => lang === defaultLocale);
const stripDefaultLocalePrefix = (path: string) => path.replace(new RegExp(`^/${defaultLocale}(?=/|$)`), '');
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.",
@@ -77,24 +92,24 @@ metadata.description ||= (t({
<ViewTransitions fallback="swap" />
{
i18n &&
getLocalePaths(Astro.url).map(({ path, lang }) => (
resolvedAlternateLinks.map(({ path, lang }) => (
<link
rel="alternate"
hreflang={LOCALES[lang].lang || lang}
href={Astro.site?.origin + path.replace('/' + defaultLocale, '').replace(/\/$/, '')}
href={Astro.site?.origin + stripDefaultLocalePrefix(path).replace(/\/$/, '')}
/>
))
}
{
i18n && (
i18n && defaultAlternate && (
<link
rel="alternate"
hreflang="x-default"
href={Astro.site?.origin + Astro.url.pathname.replace('/' + defaultLocale, '').replace(/\/$/, '')}
href={Astro.site?.origin + stripDefaultLocalePrefix(defaultAlternate.path).replace(/\/$/, '')}
/>
)
}
{ i18n && Astro.currentLocale == defaultLocale && <script is:inline define:vars={{ langs, baseUrl, defaultLocale }}>
{ i18n && locale == defaultLocale && <script is:inline define:vars={{ langs, baseUrl, defaultLocale }}>
let lang;
try {
const urlParams = new URLSearchParams(window.location.search);
@@ -122,7 +137,7 @@ metadata.description ||= (t({
</head>
<body class="antialiased text-default bg-page tracking-tight">
<Scamming lang={Astro.currentLocale as Lang} client:load />
<Scamming lang={locale} client:load />
<slot />
<BasicScripts />
+27 -5
View File
@@ -7,10 +7,14 @@ import Announcement from '~/components/widgets/Announcement.astro';
import { headerData, footerData } from '~/navigation';
import type { MetaData } from '~/types';
import type { Lang, LocalePath } from '@/i18n';
export interface Props {
metadata?: MetaData;
i18n?: boolean;
lang?: Lang;
localePaths?: LocalePath[];
alternateLinks?: LocalePath[];
structuredDataType?: 'website' | 'article' | 'software' | 'faq';
articleData?: {
title: string;
@@ -24,20 +28,38 @@ export interface Props {
faqItems?: Array<{ question: string; answer: string }>;
}
const { metadata, i18n, structuredDataType, articleData, faqItems } = Astro.props;
const { metadata, i18n, lang, localePaths, alternateLinks, structuredDataType, articleData, faqItems } = Astro.props;
const locale = lang || (Astro.currentLocale as Lang);
---
<Layout metadata={metadata} i18n={i18n} structuredDataType={structuredDataType} articleData={articleData} faqItems={faqItems}>
<Layout
metadata={metadata}
i18n={i18n}
lang={locale}
localePaths={localePaths}
alternateLinks={alternateLinks}
structuredDataType={structuredDataType}
articleData={articleData}
faqItems={faqItems}
>
<slot name="announcement">
<Announcement />
<Announcement lang={locale} />
</slot>
<slot name="header">
<Header {...headerData(Astro.currentLocale)} isSticky showGithubStar showToggleTheme i18n={i18n} />
<Header
{...headerData(locale)}
isSticky
showGithubStar
showToggleTheme
i18n={i18n}
lang={locale}
localePaths={localePaths}
/>
</slot>
<main>
<slot />
</main>
<slot name="footer">
<Footer {...footerData(Astro.currentLocale) as FD} />
<Footer {...footerData(locale) as FD} />
</slot>
</Layout>
+7 -2
View File
@@ -125,13 +125,18 @@ export const headerData = (locale?: Lang) => {
{
text: t({
en: 'Blog',
de: 'Blog',
es: 'Blog',
fr: 'Blog',
it: 'Blog',
ja: 'ブログ',
pt: 'Blog',
'zh-cn': '博客',
'zh-tw': '博客',
'zh-tw': '部落格',
ar: 'مدونة',
ko: '블로그',
}),
href: getPermalink('/blog'),
href: getPermalink(getLocalPath(locale || '', '/blog')),
},
],
actions: [{
+9 -13
View File
@@ -8,6 +8,8 @@ import Pagination from '~/components/blog/Pagination.astro';
// import PostTags from "~/components/blog/Tags.astro";
import { blogListRobots, getStaticPathsBlogList } from '~/utils/blog';
import { getBlogCopy } from '@/i18n/blog';
import type { Lang } from '@/i18n';
export const prerender = true;
@@ -17,18 +19,16 @@ export const getStaticPaths = (async ({ paginate }) => {
type Props = InferGetStaticPropsType<typeof getStaticPaths>;
const { page } = Astro.props as Props;
const { page, lang } = Astro.props as Props & { lang: Lang };
const currentPage = page.currentPage ?? 1;
const copy = getBlogCopy(lang);
// const allCategories = await findCategories();
// const allTags = await findTags();
const metadata = {
title: `Blog${currentPage > 1 ? ` — Page ${currentPage}` : ''}`,
description:
currentPage > 1
? `Browse page ${currentPage} of the RustDesk blog for release notes, product updates, self-hosting guides, and remote access best practices.`
: 'Read the RustDesk blog for release notes, self-hosting tutorials, remote support guidance, and product updates.',
title: `${copy.title}${currentPage > 1 ? ` — ${copy.page} ${currentPage}` : ''}`,
description: copy.subtitle,
robots: {
index: blogListRobots?.index && currentPage === 1,
follow: blogListRobots?.follow,
@@ -39,15 +39,11 @@ const metadata = {
};
---
<Layout metadata={metadata}>
<Layout metadata={metadata} i18n lang={lang}>
<section class="px-6 sm:px-6 py-12 sm:py-16 lg:py-20 mx-auto max-w-4xl">
<Headline
subtitle="A statically generated blog example with news, tutorials, resources and other interesting content related to RustDesk"
>
The Blog
</Headline>
<Headline subtitle={copy.subtitle}>{copy.title}</Headline>
<BlogList posts={page.data} />
<Pagination prevUrl={page.url.prev} nextUrl={page.url.next} />
<Pagination prevUrl={page.url.prev} nextUrl={page.url.next} lang={lang} />
<!--
<PostTags tags={allCategories} class="mb-2" title="Search by Categories:" isCategory />
<PostTags tags={allTags} title="Search by Tags:" />
@@ -6,6 +6,9 @@ import Layout from '~/layouts/PageLayout.astro';
import BlogList from '~/components/blog/List.astro';
import Headline from '~/components/blog/Headline.astro';
import Pagination from '~/components/blog/Pagination.astro';
import { getBlogCopy } from '@/i18n/blog';
import { LOCALES, type Lang } from '@/i18n';
import { getBlogPermalink } from '~/utils/permalinks';
export const prerender = true;
@@ -15,16 +18,18 @@ export const getStaticPaths = (async ({ paginate }) => {
type Props = InferGetStaticPropsType<typeof getStaticPaths> & { category: Record<string, string> };
const { page, category } = Astro.props as Props;
const { page, category, lang } = Astro.props as Props & { lang: Lang };
const currentPage = page.currentPage ?? 1;
const copy = getBlogCopy(lang);
const localePaths = (Object.keys(LOCALES) as Lang[]).map((targetLang) => ({
lang: targetLang,
path: getBlogPermalink(targetLang),
}));
const metadata = {
title: `Category '${category.title}' ${currentPage > 1 ? ` — Page ${currentPage}` : ''}`,
description:
currentPage > 1
? `Browse page ${currentPage} of RustDesk blog posts filed under ${category.title}.`
: `Browse RustDesk blog posts and release notes filed under the ${category.title} category.`,
title: `${copy.category}: ${category.title}${currentPage > 1 ? ` — ${copy.page} ${currentPage}` : ''}`,
description: `${copy.subtitle} ${copy.category}: ${category.title}.`,
robots: {
index: blogCategoryRobots?.index,
follow: blogCategoryRobots?.follow,
@@ -32,10 +37,10 @@ const metadata = {
};
---
<Layout metadata={metadata}>
<Layout metadata={metadata} i18n lang={lang} localePaths={localePaths} alternateLinks={[]}>
<section class="px-4 md:px-6 py-12 sm:py-16 lg:py-20 mx-auto max-w-4xl">
<Headline>{category.title}</Headline>
<BlogList posts={page.data} />
<Pagination prevUrl={page.url.prev} nextUrl={page.url.next} />
<Pagination prevUrl={page.url.prev} nextUrl={page.url.next} lang={lang} />
</section>
</Layout>
+14 -9
View File
@@ -6,6 +6,9 @@ import Layout from '~/layouts/PageLayout.astro';
import BlogList from '~/components/blog/List.astro';
import Headline from '~/components/blog/Headline.astro';
import Pagination from '~/components/blog/Pagination.astro';
import { getBlogCopy } from '@/i18n/blog';
import { LOCALES, type Lang } from '@/i18n';
import { getBlogPermalink } from '~/utils/permalinks';
export const prerender = true;
@@ -15,16 +18,18 @@ export const getStaticPaths = (async ({ paginate }) => {
type Props = InferGetStaticPropsType<typeof getStaticPaths>;
const { page, tag } = Astro.props as Props;
const { page, tag, lang } = Astro.props as Props & { lang: Lang };
const currentPage = page.currentPage ?? 1;
const copy = getBlogCopy(lang);
const localePaths = (Object.keys(LOCALES) as Lang[]).map((targetLang) => ({
lang: targetLang,
path: getBlogPermalink(targetLang),
}));
const metadata = {
title: `Posts by tag '${tag.title}'${currentPage > 1 ? ` — Page ${currentPage} ` : ''}`,
description:
currentPage > 1
? `Browse page ${currentPage} of RustDesk blog posts tagged ${tag.title}.`
: `Browse RustDesk blog posts, tutorials, and release notes tagged ${tag.title}.`,
title: `${copy.tag}: ${tag.title}${currentPage > 1 ? ` — ${copy.page} ${currentPage}` : ''}`,
description: `${copy.subtitle} ${copy.tag}: ${tag.title}.`,
robots: {
index: blogTagRobots?.index,
follow: blogTagRobots?.follow,
@@ -32,10 +37,10 @@ const metadata = {
};
---
<Layout metadata={metadata}>
<Layout metadata={metadata} i18n lang={lang} localePaths={localePaths} alternateLinks={[]}>
<section class="px-4 md:px-6 py-12 sm:py-16 lg:py-20 mx-auto max-w-4xl">
<Headline>Tag: {tag.title}</Headline>
<Headline>{copy.tag}: {tag.title}</Headline>
<BlogList posts={page.data} />
<Pagination prevUrl={page.url.prev} nextUrl={page.url.next} />
<Pagination prevUrl={page.url.prev} nextUrl={page.url.next} lang={lang} />
</section>
</Layout>
+12 -3
View File
@@ -8,7 +8,7 @@ import SinglePost from '~/components/blog/SinglePost.astro';
import ToBlogLink from '~/components/blog/ToBlogLink.astro';
import { getCanonical, getPermalink } from '~/utils/permalinks';
import { getStaticPathsBlogPost, blogPostRobots } from '~/utils/blog';
import { getPostAlternates, getStaticPathsBlogPost, blogPostRobots } from '~/utils/blog';
import { findImage } from '~/utils/images';
import type { MetaData } from '~/types';
import RelatedPosts from '~/components/blog/RelatedPosts.astro';
@@ -22,6 +22,7 @@ export const getStaticPaths = (async () => {
type Props = InferGetStaticPropsType<typeof getStaticPaths>;
const { post } = Astro.props as Props;
const localePaths = await getPostAlternates(post);
const url = getCanonical(getPermalink(post.permalink, 'post'));
const image = (await findImage(post.image)) as ImageMetadata | string | undefined;
@@ -59,10 +60,18 @@ const articleData = {
};
---
<Layout metadata={metadata} structuredDataType="article" articleData={articleData}>
<Layout
metadata={metadata}
i18n
lang={post.lang}
localePaths={localePaths}
alternateLinks={localePaths}
structuredDataType="article"
articleData={articleData}
>
<SinglePost post={{ ...post, image: image }} url={url}>
{post.Content ? <post.Content /> : <Fragment set:html={post.content || ''} />}
</SinglePost>
<ToBlogLink />
<ToBlogLink lang={post.lang} />
<RelatedPosts post={post} />
</Layout>
+1 -1
View File
@@ -12,7 +12,7 @@ export const GET = async () => {
});
}
const posts = await fetchPosts();
const posts = await fetchPosts('en');
const rss = await getRssString({
title: `${SITE.name}s Blog`,
+7
View File
@@ -1,7 +1,14 @@
import type { AstroComponentFactory } from 'astro/runtime/server/index.js';
import type { HTMLAttributes, ImageMetadata } from 'astro/types';
import type { Lang } from './i18n';
export interface Post {
/** Locale used by this translated post. */
lang: Lang;
/** Stable identifier shared by every translation of the same article. */
translationKey: string;
/** A unique ID number that identifies a post. */
id: string;
+64
View File
@@ -0,0 +1,64 @@
const DEFAULT_LOCALE = 'en';
const trimSlashes = (value = '') => String(value).replace(/^\/+|\/+$/g, '');
export const localizedPath = (lang, path = '') => {
const segments = [lang === DEFAULT_LOCALE ? '' : lang, trimSlashes(path)].filter(Boolean);
return segments.length ? `/${segments.join('/')}` : '/';
};
export function groupPostsByTranslationKey(posts) {
const groups = new Map();
for (const post of posts) {
const group = groups.get(post.translationKey) || [];
group.push(post);
groups.set(post.translationKey, group);
}
return groups;
}
export const findTranslatedPost = (post, posts, lang) =>
posts.find((candidate) => candidate.translationKey === post.translationKey && candidate.lang === lang);
export function validatePostTranslations(posts, locales) {
const supportedLocales = new Set(locales);
const slugs = new Set();
for (const post of posts) {
if (!supportedLocales.has(post.lang)) {
throw new Error(`Post ${post.translationKey} uses unsupported locale ${post.lang}`);
}
const directory = post.id?.split('/')[0];
if (directory && supportedLocales.has(directory) && directory !== post.lang) {
throw new Error(`Post directory ${directory} does not match locale ${post.lang}`);
}
const slugKey = `${post.lang}:${post.slug}`;
if (slugs.has(slugKey)) {
throw new Error(`Post locale ${post.lang} has duplicate slug ${post.slug}`);
}
slugs.add(slugKey);
}
for (const [translationKey, group] of groupPostsByTranslationKey(posts)) {
for (const locale of locales) {
const count = group.filter((post) => post.lang === locale).length;
if (count === 0) {
throw new Error(`Translation group ${translationKey} is missing locale ${locale}`);
}
if (count > 1) {
throw new Error(`Translation group ${translationKey} has duplicate locale ${locale}`);
}
}
}
}
export const buildPostAlternates = (post, posts, locales) =>
locales.map((lang) => {
const translated = findTranslatedPost(post, posts, lang);
return {
lang,
path: translated ? `/${trimSlashes(translated.permalink)}` : localizedPath(lang, 'blog'),
};
});
+61 -29
View File
@@ -4,17 +4,21 @@ import type { CollectionEntry } from 'astro:content';
import type { Post } from '~/types';
import { APP_BLOG } from 'astrowind:config';
import { cleanSlug, trimSlash, BLOG_BASE, POST_PERMALINK_PATTERN, CATEGORY_BASE, TAG_BASE } from './permalinks';
import { LOCALES, type Lang } from '@/i18n';
import { buildPostAlternates, localizedPath, validatePostTranslations } from './blog-i18n.mjs';
const generatePermalink = async ({
id,
slug,
publishDate,
category,
lang,
}: {
id: string;
slug: string;
publishDate: Date;
category: string | undefined;
lang: Lang;
}) => {
const year = String(publishDate.getFullYear()).padStart(4, '0');
const month = String(publishDate.getMonth() + 1).padStart(2, '0');
@@ -33,11 +37,13 @@ const generatePermalink = async ({
.replace('%minute%', minute)
.replace('%second%', second);
return permalink
const path = permalink
.split('/')
.map((el) => trimSlash(el))
.filter((el) => !!el)
.join('/');
return trimSlash(localizedPath(lang, path));
};
const getNormalizedPost = async (post: CollectionEntry<'post'>): Promise<Post> => {
@@ -55,9 +61,11 @@ const getNormalizedPost = async (post: CollectionEntry<'post'>): Promise<Post> =
author,
draft = false,
metadata = {},
lang,
translationKey,
} = data;
const slug = cleanSlug(rawSlug); // cleanSlug(rawSlug.split('/').pop());
const slug = cleanSlug(rawSlug.split('/').pop());
const publishDate = new Date(rawPublishDate);
const updateDate = rawUpdateDate ? new Date(rawUpdateDate) : undefined;
@@ -75,8 +83,10 @@ const getNormalizedPost = async (post: CollectionEntry<'post'>): Promise<Post> =
return {
id: id,
lang,
translationKey,
slug: slug,
permalink: await generatePermalink({ id, slug, publishDate, category: category?.slug }),
permalink: await generatePermalink({ id, slug, publishDate, category: category?.slug, lang }),
publishDate: publishDate,
updateDate: updateDate,
@@ -108,6 +118,8 @@ const load = async function (): Promise<Array<Post>> {
.sort((a, b) => b.publishDate.valueOf() - a.publishDate.valueOf())
.filter((post) => !post.draft);
validatePostTranslations(results, Object.keys(LOCALES));
return results;
};
@@ -129,12 +141,12 @@ export const blogTagRobots = APP_BLOG.tag.robots;
export const blogPostsPerPage = APP_BLOG?.postsPerPage;
/** */
export const fetchPosts = async (): Promise<Array<Post>> => {
export const fetchPosts = async (lang?: Lang): Promise<Array<Post>> => {
if (!_posts) {
_posts = await load();
}
return _posts;
return lang ? _posts.filter((post) => post.lang === lang) : _posts;
};
/** */
@@ -166,9 +178,9 @@ export const findPostsByIds = async (ids: Array<string>): Promise<Array<Post>> =
};
/** */
export const findLatestPosts = async ({ count }: { count?: number }): Promise<Array<Post>> => {
export const findLatestPosts = async ({ count, lang }: { count?: number; lang?: Lang }): Promise<Array<Post>> => {
const _count = count || 4;
const posts = await fetchPosts();
const posts = await fetchPosts(lang);
return posts ? posts.slice(0, _count) : [];
};
@@ -176,10 +188,17 @@ export const findLatestPosts = async ({ count }: { count?: number }): Promise<Ar
/** */
export const getStaticPathsBlogList = async ({ paginate }: { paginate: PaginateFunction }) => {
if (!isBlogEnabled || !isBlogListRouteEnabled) return [];
return paginate(await fetchPosts(), {
params: { blog: BLOG_BASE || undefined },
pageSize: blogPostsPerPage,
});
const posts = await fetchPosts();
return (Object.keys(LOCALES) as Lang[]).flatMap((lang) =>
paginate(
posts.filter((post) => post.lang === lang),
{
params: { blog: trimSlash(localizedPath(lang, BLOG_BASE)) || undefined },
pageSize: blogPostsPerPage,
props: { lang },
}
)
);
};
/** */
@@ -201,20 +220,25 @@ export const getStaticPathsBlogCategory = async ({ paginate }: { paginate: Pagin
const categories = {};
posts.map((post) => {
if (post.category?.slug) {
categories[post.category?.slug] = post.category;
categories[`${post.lang}:${post.category.slug}`] = post.category;
}
});
return Array.from(Object.keys(categories)).flatMap((categorySlug) =>
paginate(
posts.filter((post) => post.category?.slug && categorySlug === post.category?.slug),
return Array.from(Object.keys(categories)).flatMap((categoryKey) => {
const separator = categoryKey.indexOf(':');
const lang = categoryKey.slice(0, separator) as Lang;
const categorySlug = categoryKey.slice(separator + 1);
return paginate(
posts.filter(
(post) => post.lang === lang && post.category?.slug && categorySlug === post.category.slug
),
{
params: { category: categorySlug, blog: CATEGORY_BASE || undefined },
params: { category: categorySlug, blog: trimSlash(localizedPath(lang, CATEGORY_BASE)) || undefined },
pageSize: blogPostsPerPage,
props: { category: categories[categorySlug] },
props: { category: categories[categoryKey], lang },
}
)
);
);
});
};
/** */
@@ -226,30 +250,35 @@ export const getStaticPathsBlogTag = async ({ paginate }: { paginate: PaginateFu
posts.map((post) => {
if (Array.isArray(post.tags)) {
post.tags.map((tag) => {
tags[tag?.slug] = tag;
tags[`${post.lang}:${tag?.slug}`] = tag;
});
}
});
return Array.from(Object.keys(tags)).flatMap((tagSlug) =>
paginate(
posts.filter((post) => Array.isArray(post.tags) && post.tags.find((elem) => elem.slug === tagSlug)),
return Array.from(Object.keys(tags)).flatMap((tagKey) => {
const separator = tagKey.indexOf(':');
const lang = tagKey.slice(0, separator) as Lang;
const tagSlug = tagKey.slice(separator + 1);
return paginate(
posts.filter(
(post) => post.lang === lang && Array.isArray(post.tags) && post.tags.find((tag) => tag.slug === tagSlug)
),
{
params: { tag: tagSlug, blog: TAG_BASE || undefined },
params: { tag: tagSlug, blog: trimSlash(localizedPath(lang, TAG_BASE)) || undefined },
pageSize: blogPostsPerPage,
props: { tag: tags[tagSlug] },
props: { tag: tags[tagKey], lang },
}
)
);
);
});
};
/** */
export async function getRelatedPosts(originalPost: Post, maxResults: number = 4): Promise<Post[]> {
const allPosts = await fetchPosts();
const allPosts = (await fetchPosts()).filter((post) => post.lang === originalPost.lang);
const originalTagsSet = new Set(originalPost.tags ? originalPost.tags.map((tag) => tag.slug) : []);
const postsWithScores = allPosts.reduce((acc: { post: Post; score: number }[], iteratedPost: Post) => {
if (iteratedPost.slug === originalPost.slug) return acc;
if (iteratedPost.translationKey === originalPost.translationKey) return acc;
let score = 0;
if (iteratedPost.category && originalPost.category && iteratedPost.category.slug === originalPost.category.slug) {
@@ -279,3 +308,6 @@ export async function getRelatedPosts(originalPost: Post, maxResults: number = 4
return selectedPosts;
}
export const getPostAlternates = async (post: Post) =>
buildPostAlternates(post, await fetchPosts(), Object.keys(LOCALES) as Lang[]);
+10 -6
View File
@@ -3,6 +3,7 @@ import slugify from 'limax';
import { SITE, APP_BLOG } from 'astrowind:config';
import { trim } from '~/utils/utils';
import type { Lang } from '@/i18n';
export const trimSlash = (s: string) => trim(trim(s, '/'));
const createPath = (...params: string[]) => {
@@ -18,7 +19,7 @@ const BASE_PATHNAME = SITE.base || '/';
export const cleanSlug = (text = '') =>
trimSlash(text)
.split('/')
.map((slug) => slugify(slug))
.map((slug) => slugify(slug) || slug.trim().toLowerCase().replace(/\s+/g, '-'))
.join('/');
export const BLOG_BASE = cleanSlug(APP_BLOG?.list?.pathname);
@@ -39,7 +40,9 @@ export const getCanonical = (path = ''): string | URL => {
};
/** */
export const getPermalink = (slug = '', type = 'page'): string => {
const getLocalePrefix = (locale?: Lang): string => (locale && locale !== 'en' ? locale : '');
export const getPermalink = (slug = '', type = 'page', locale?: Lang): string => {
let permalink: string;
if (
@@ -58,7 +61,7 @@ export const getPermalink = (slug = '', type = 'page'): string => {
break;
case 'blog':
permalink = getBlogPermalink();
permalink = createPath(getLocalePrefix(locale), BLOG_BASE);
break;
case 'asset':
@@ -66,11 +69,11 @@ export const getPermalink = (slug = '', type = 'page'): string => {
break;
case 'category':
permalink = createPath(CATEGORY_BASE, trimSlash(slug));
permalink = createPath(getLocalePrefix(locale), CATEGORY_BASE, trimSlash(slug));
break;
case 'tag':
permalink = createPath(TAG_BASE, trimSlash(slug));
permalink = createPath(getLocalePrefix(locale), TAG_BASE, trimSlash(slug));
break;
case 'post':
@@ -90,7 +93,8 @@ export const getPermalink = (slug = '', type = 'page'): string => {
export const getHomePermalink = (path?: string): string => getPermalink(path || '/');
/** */
export const getBlogPermalink = (): string => getPermalink(BLOG_BASE);
export const getBlogPermalink = (locale?: Lang): string =>
definitivePermalink(createPath(getLocalePrefix(locale), BLOG_BASE));
/** */
export const getAsset = (path: string): string =>
+10 -9
View File
@@ -1,13 +1,14 @@
import { I18N } from 'astrowind:config';
import type { Lang } from '@/i18n';
export const formatter: Intl.DateTimeFormat = new Intl.DateTimeFormat(I18N?.language, {
year: 'numeric',
month: 'short',
day: 'numeric',
timeZone: 'UTC',
});
export const getFormattedDate = (date: Date): string => (date ? formatter.format(date) : '');
export const getFormattedDate = (date: Date, lang: Lang = 'en'): string =>
date
? new Intl.DateTimeFormat(lang, {
year: 'numeric',
month: 'short',
day: 'numeric',
timeZone: 'UTC',
}).format(date)
: '';
export const trim = (str = '', ch?: string) => {
let start = 0,
+14
View File
@@ -0,0 +1,14 @@
import assert from 'node:assert/strict';
import { readFile } from 'node:fs/promises';
import test from 'node:test';
test('announcement resolves the latest post for the active locale', async () => {
const source = await readFile(
new URL('../src/components/widgets/Announcement.astro', import.meta.url),
'utf8'
);
assert.match(source, /findLatestPosts\(\{ count: 1, lang \}\)/);
assert.doesNotMatch(source, /Enhanced ACL in RustDesk Server Pro 1\.5\.0/);
assert.doesNotMatch(source, />NEW</);
});
+73
View File
@@ -0,0 +1,73 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import {
buildPostAlternates,
findTranslatedPost,
groupPostsByTranslationKey,
localizedPath,
validatePostTranslations,
} from '../src/utils/blog-i18n.mjs';
const posts = [
{
lang: 'en',
translationKey: 'acl-1-5',
slug: 'acl',
permalink: 'blog/2025/02/acl',
},
{
lang: 'zh-cn',
translationKey: 'acl-1-5',
slug: '增强-acl',
permalink: 'zh-cn/blog/2025/02/增强-acl',
},
];
test('groups and resolves translations by stable key', () => {
assert.equal(groupPostsByTranslationKey(posts).get('acl-1-5').length, 2);
assert.equal(findTranslatedPost(posts[0], posts, 'zh-cn'), posts[1]);
});
test('builds localized and default-locale paths', () => {
assert.equal(localizedPath('en', 'blog'), '/blog');
assert.equal(localizedPath('ja', 'blog'), '/ja/blog');
assert.equal(localizedPath('zh-cn', 'blog/2025/02/增强-acl'), '/zh-cn/blog/2025/02/增强-acl');
});
test('reports missing locales and duplicate locale entries', () => {
assert.throws(() => validatePostTranslations(posts, ['en', 'zh-cn', 'de']), /missing locale de/i);
assert.throws(
() => validatePostTranslations([...posts, { ...posts[0], slug: 'acl-copy' }], ['en', 'zh-cn']),
/duplicate locale en/i
);
});
test('rejects unsupported locales and duplicate slugs in one locale', () => {
assert.throws(() => validatePostTranslations([{ ...posts[0], lang: 'xx' }], ['en']), /unsupported locale xx/i);
assert.throws(
() =>
validatePostTranslations(
[
{ ...posts[0], translationKey: 'a', slug: 'same' },
{ ...posts[0], translationKey: 'b', slug: 'same' },
],
['en']
),
/duplicate slug same/i
);
});
test('rejects a locale that does not match the content directory', () => {
assert.throws(
() => validatePostTranslations([{ ...posts[0], id: 'zh-cn/acl.md' }], ['en', 'zh-cn']),
/directory zh-cn does not match locale en/i
);
});
test('builds alternate links from actual translated permalinks', () => {
assert.deepEqual(buildPostAlternates(posts[0], posts, ['en', 'zh-cn']), [
{ lang: 'en', path: '/blog/2025/02/acl' },
{ lang: 'zh-cn', path: '/zh-cn/blog/2025/02/增强-acl' },
]);
});