Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

### Added

- Live Activity can show several labelled values from one poll, each with its

Check warning on line 12 in CHANGELOG.md

View workflow job for this annotation

GitHub Actions / test

entry is 220 characters; keep it to one line: "Live Activity can show several labelled values from one p..."
own name, colour, unit and threshold. The badge shows the first label that
reaches its threshold; the rest open on hover, tap or keyboard focus.

Expand All @@ -22,7 +22,7 @@
- Release images carry an SBOM attestation, verifiable with
`cosign verify-attestation`. See `docs/security.md`.

- Support for Windows High Contrast. The wallpaper and the blurred materials are

Check warning on line 25 in CHANGELOG.md

View workflow job for this annotation

GitHub Actions / test

entry is 179 characters; keep it to one line: "Support for Windows High Contrast. The wallpaper and the ..."
dropped, the current page is marked by shape as well as colour, and badges keep
their status colour.

Expand All @@ -36,7 +36,7 @@
- Badge text is white wherever white is readable, instead of whichever ink
scores higher.

- Badges show the number alone on every layout, so a unit cannot make one wider

Check warning on line 39 in CHANGELOG.md

View workflow job for this annotation

GitHub Actions / test

entry is 167 characters; keep it to one line: "Badges show the number alone on every layout, so a unit c..."
than the icon it marks. The unit stays in the popover and in the badge's
accessible name.

Expand All @@ -54,6 +54,10 @@
- Counted messages now use each language's own plural rules. French and Persian
put a count of zero in the plural form.

- Persian now uses the import, export, widget and file terms that shipped

Check warning on line 57 in CHANGELOG.md

View workflow job for this annotation

GitHub Actions / test

entry is 165 characters; keep it to one line: "Persian now uses the import, export, widget and file term..."
software uses, one word per concept, and no longer leaves "Import" in English
in one message.

- Opening and saving an app no longer rewrites a colour that was not changed.

- Admin form toggles now carry accessible names.
Expand All @@ -79,7 +83,7 @@
- The badge popover can be hovered without closing, and no longer truncates the
label names it exists to show.

- The page indicator now takes its tint from the wallpaper behind it, and marks

Check warning on line 86 in CHANGELOG.md

View workflow job for this annotation

GitHub Actions / test

entry is 180 characters; keep it to one line: "The page indicator now takes its tint from the wallpaper ..."
the current page by shape as well as colour. Over a light wallpaper the whole
indicator was invisible.

Expand All @@ -102,7 +106,7 @@
- In Settings, the logging note now sits with the row it describes, and the host
address note says what it does rather than naming the mechanism.

- Numbers on the dashboard now use the digits of the reader's locale. A Persian

Check warning on line 109 in CHANGELOG.md

View workflow job for this annotation

GitHub Actions / test

entry is 270 characters; keep it to one line: "Numbers on the dashboard now use the digits of the reader..."
reader whose device asks for Persian digits sees them in badges, the weather,
the system summary, disk health, Pi-hole and reading progress; one whose device
asks for Latin digits keeps those.
Expand All @@ -118,7 +122,7 @@
- Save is the same button on every Settings pane. It was green on two panes and
teal on a third, for one action.

- In right-to-left languages a badge now sits on the leading corner of its tile,

Check warning on line 125 in CHANGELOG.md

View workflow job for this annotation

GitHub Actions / test

entry is 211 characters; keep it to one line: "In right-to-left languages a badge now sits on the leadin..."
the badge popover follows it, a Settings row's title and subtitle share an
edge, and the Now Playing arrows point the way they read.

Expand Down
18 changes: 18 additions & 0 deletions docs/i18n.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,24 @@ Terms with a fixed meaning here:
| stale | Last reading is old; the service was not reached this time. |
| unavailable | The service answered, and it is not working. |

### Persian

One word per concept, taken from Persian that shipped software actually uses
rather than from the dictionary. Immich, Jellyfin and Uptime Kuma were read for
these; Immich is the closest to this project and ships
`وارد کردن پیکربندی` for "Import configuration" and `خروجی` for "Export".

| English | Use | Not |
| --- | --- | --- |
| Import | وارد کردن | درون‌ریزی |
| Export | خروجی | برون‌بری |
| Widget | ویجت | ابزارک |
| File | فایل | پرونده |

`ui/test/i18n-coverage.test.mjs` fails on any term in the "Not" column, in the
dashboard catalogue and in every widget catalogue. Add a row there and here
together when a term is decided.

## Development locales

Two locales exist for testing and are never offered in the selector. Reach them
Expand Down
48 changes: 24 additions & 24 deletions ui/i18n/fa.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
},
"common": {
"save": "ذخیره",
"import": "درون‌ریزی",
"export": "برون‌بری",
"import": "وارد کردن",
"export": "خروجی",
"add": "+افزودن",
"back": "بازگشت",
"cancel": "لغو",
Expand Down Expand Up @@ -55,7 +55,7 @@
"backup": "پشتیبان‌گیری",
"importExport": "وارد کردن / خروجی گرفتن",
"signOut": "خروج از حساب",
"importForeign": "درون‌ریزی از داشبورد دیگر",
"importForeign": "وارد کردن از داشبورد دیگر",
"importForeignTip": "فایل‌های YAML مربوط به gethomepage و Dashy را می‌خواند. پیوندها و پوشه‌ها به داشبورد شما افزوده می‌شوند؛ ویجت‌های داشبورد دیگر به پیوند ساده تبدیل می‌شوند.",
"logDebug": "اشکال‌زدایی",
"logInfo": "اطلاعات",
Expand Down Expand Up @@ -108,13 +108,13 @@
"searchPh": "جستجو",
"filterAll": "همه",
"filterApps": "برنامه‌ها",
"filterWidgets": "ابزارک‌ها",
"filterWidgets": "ویجت‌ها",
"filterFolders": "پوشه‌ها"
},
"type": {
"addNew": "افزودن جدید",
"app": "برنامه",
"widget": "ابزارک",
"widget": "ویجت",
"folder": "پوشه"
},
"folder": {
Expand Down Expand Up @@ -223,12 +223,12 @@
"imported": "وارد شد",
"importFailed": "Import ناموفق بود: {err}",
"exportFailed": "Export ناموفق بود: {err}",
"importNoChange": "چیزی برای Import نیست — فایل با تنظیمات فعلی شما مطابقت دارد.",
"importNoChange": "چیزی برای وارد کردن نیست — فایل با تنظیمات فعلی شما مطابقت دارد.",
"pwWeak": "رمز عبور ضعیف است: {label}",
"sessionsRevoked": "همه دستگاه‌ها خارج شدند.",
"authNeedsPassword": "پیش از فعال‌سازی احراز هویت، یک گذرواژه تعیین کنید.",
"importForeignDone": "درون‌ریزی پایان یافت. برنامه‌ها: {apps}. پوشه‌ها: {folders}.",
"importForeignNothing": "در این فایل‌ها چیزی برای درون‌ریزی نیست.",
"importForeignDone": "وارد کردن پایان یافت. برنامه‌ها: {apps}. پوشه‌ها: {folders}.",
"importForeignNothing": "در این فایل‌ها چیزی برای وارد کردن نیست.",
"importYamlUnsupported": "{file}: {reason} (خط {line})",
"importUnknownFormat": "{file} پیکربندی gethomepage یا Dashy نیست.",
"secretsWithheld": "اعتبارنامه را دوباره وارد کنید برای: {items}. نگه داشته نشد، چون درخواستِ مربوط به آن تغییر کرده است.",
Expand All @@ -252,8 +252,8 @@
"noMatches": "نتیجه‌ای یافت نشد."
},
"import": {
"confirm": "{n} مورد درون‌ریزی شود؟ {added} افزوده، {updated} به‌روزرسانی، {deleted} حذف.",
"confirmTitle": "درون‌ریزی پیکربندی"
"confirm": "{n} مورد وارد شود؟ {added} افزوده، {updated} به‌روزرسانی، {deleted} حذف.",
"confirmTitle": "وارد کردن پیکربندی"
},
"status": {
"needsAttention": "وضعیت: نیاز به رسیدگی",
Expand Down Expand Up @@ -292,12 +292,12 @@
},
"widgetCfg": {
"unavailable": "تنظیمات در دسترس نیست",
"unavailableTip": "تعریف این ابزارک ({type}) بارگذاری نشد، بنابراین تنظیمات آن قابل نمایش یا ویرایش نیست. این تنظیمات روی سرور نگهداری می‌شوند و ذخیره‌کردن آن‌ها را تغییر نمی‌دهد.",
"refused_one": "‏{count} ابزارک بارگذاری نشد، بنابراین در فهرست زیر نیست.",
"refused_other": "‏{count} ابزارک بارگذاری نشدند، بنابراین در فهرست زیر نیستند.",
"unavailableTip": "تعریف این ویجت ({type}) بارگذاری نشد، بنابراین تنظیمات آن قابل نمایش یا ویرایش نیست. این تنظیمات روی سرور نگهداری می‌شوند و ذخیره‌کردن آن‌ها را تغییر نمی‌دهد.",
"refused_one": "‏{count} ویجت بارگذاری نشد، بنابراین در فهرست زیر نیست.",
"refused_other": "‏{count} ویجت بارگذاری نشدند، بنابراین در فهرست زیر نیستند.",
"name": "نام",
"type": "نوع ابزارک",
"namePh": "ابزارک من",
"type": "نوع ویجت",
"namePh": "ویجت من",
"editName": "ویرایش نام",
"size": "اندازه",
"iframeTip": "این نشانی به‌صورت آی‌فریم در داشبورد جاسازی می‌شود.",
Expand All @@ -314,12 +314,12 @@
"loaded_other": "{count} گزینه بارگیری شد"
},
"importForeign": {
"title": "درون‌ریزی از داشبورد دیگر",
"title": "وارد کردن از داشبورد دیگر",
"lead": "آنچه از پیش در داشبورد دارید تغییر نمی‌کند.",
"willAdd": "برنامه‌های افزودنی",
"willCreate": "پوشه‌های ساختنی",
"changed": "با تغییر درون‌ریزی شد",
"notImported": "درون‌ریزی نشد",
"willAdd": "برنامه‌هایی که افزوده می‌شوند",
"willCreate": "پوشه‌هایی که ساخته می‌شوند",
"changed": "با تغییر وارد شد",
"notImported": "وارد نشد",
"appCount": "({n})",
"skipNoLabel": "بدون نام",
"skipNoHref": "بدون پیوند",
Expand All @@ -330,15 +330,15 @@
"noteRemoteContainer": "کانتینر نادیده گرفته شد، روی {detail} اجرا می‌شود",
"noteFlattened": "گروه تودرتو به پوشه‌ای جداگانه تبدیل شد",
"noteSubItems": "زیرمورد به پوشه منتقل شد",
"noteWidgetLink": "ویجت به‌صورت پیوند درون‌ریزی شد",
"noteWidgetLink": "ویجت به‌صورت پیوند وارد شد",
"noteWidgetDropped": "{detail} ویجت بخش کنار گذاشته شد",
"noteFields": "فیلدهای کنارگذاشته: {detail}",
"notePages": "{detail} صفحه دیگر دنبال نشد",
"noteLocalUrl": "نشانی محلی دوم کنار گذاشته شد: {detail}",
"wholeFile": "همین پرونده",
"untitledFolder": "درون‌ریزی‌شده",
"wholeFile": "همین فایل",
"untitledFolder": "واردشده",
"skipRelativeHref": "پیوند نشانی ندارد، فقط یک مسیر",
"skipUnreadable": "این ورودی ساختاری ندارد که قالب پرونده بپذیرد",
"skipUnreadable": "این ورودی ساختاری ندارد که قالب فایل بپذیرد",
"skipUnparsable": "خط {detail} خوانده نشد",
"monitors": "· {url} را می‌پاید"
},
Expand Down
50 changes: 50 additions & 0 deletions ui/test/i18n-coverage.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,53 @@ for (const [file, allowed] of Object.entries(SAME_ON_PURPOSE)) {
assert.deepEqual(untranslated, [], `${file}: still English. Translate, or add to SAME_ON_PURPOSE with a reason.`);
});
}

/* ── one word per concept ─────────────────────────────────────────────────── */

/* A machine translates each string on its own, so the same concept comes back
under a different word in each. The Persian catalogue named a widget both
ابزارک and ویجت, and a file both فایل and پرونده, in one interface.

It also reached for فرهنگستان coinages that correct Persian dictionaries
carry but shipped software does not use. درون‌ریزی and برون‌بری for import and
export appear in none of Immich, Jellyfin or Uptime Kuma, all of which build
import on وارد کردن; Immich, the closest of the three to this project, ships
وارد کردن پیکربندی for "Import configuration" and خروجی for "Export".

Each entry is the rejected term and what to write instead. Adding one is how
a terminology decision is recorded, so it survives the next bulk retranslation.
The glossary in docs/i18n.md carries the same list in prose. */
const REJECTED = {
'fa.json': [
['درون‌ریزی', 'وارد کردن'],
['برون‌بری', 'خروجی'],
['ابزارک', 'ویجت'],
['پرونده', 'فایل'],
],
};

const widgetCatalogues = locale => {
const dir = path.join(DIR, '..', 'widgets');
return fs
.readdirSync(dir, { withFileTypes: true })
.filter(e => e.isDirectory() && fs.existsSync(path.join(dir, e.name, 'i18n', locale)))
.map(e => [
`widgets/${e.name}/i18n/${locale}`,
JSON.parse(fs.readFileSync(path.join(dir, e.name, 'i18n', locale), 'utf8')),
]);
};

for (const [file, pairs] of Object.entries(REJECTED)) {
test(`${file} uses one agreed word per concept`, () => {
const catalogues = [[file, read(file)], ...widgetCatalogues(file)];
const found = [];
for (const [name, catalogue] of catalogues) {
for (const [key, value] of flat(catalogue)) {
for (const [rejected, instead] of pairs) {
if (String(value).includes(rejected)) found.push(`${name} ${key}: ${rejected} -> ${instead}`);
}
}
}
assert.deepEqual(found, [], `A term the glossary rules out. See docs/i18n.md:\n ${found.join('\n ')}`);
});
}
2 changes: 1 addition & 1 deletion ui/widgets/dns/i18n/fa.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"dnsUrl.placeholder": "dns-host:80",
"dnsUrl.hint": "نام کانتینر و پورت، یا یک نشانی کامل.",
"dnsHref.label": "نشانی کلیک",
"dnsHref.hint": "جایی که با کلیک روی ابزارک باز می‌شود. برای غیرفعال کردن خالی بگذارید.",
"dnsHref.hint": "جایی که با کلیک روی ویجت باز می‌شود. برای غیرفعال کردن خالی بگذارید.",
"dnsUser.label": "نام کاربری",
"dnsPass.label": "گذرواژه",
"dnsPiholePassword.label": "گذرواژه Pi-hole",
Expand Down
4 changes: 2 additions & 2 deletions ui/widgets/github/i18n/fa.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
"githubUser.label": "نام کاربری GitHub",
"githubUser.placeholder": "octocat",
"githubToken.label": "توکن دسترسی شخصی",
"githubToken.hint": "توکن کلاسیک با دسترسی repo و read:user (یا توکن دقیق با همان سطح خواندن). فقط همین ابزارک از آن استفاده می‌کند.",
"githubToken.hint": "توکن کلاسیک با دسترسی repo و read:user (یا توکن دقیق با همان سطح خواندن). فقط همین ویجت از آن استفاده می‌کند.",
"githubHref.label": "نشانی کلیک",
"githubHref.hint": "جایی که با کلیک روی ابزارک باز می‌شود. برای غیرفعال کردن خالی بگذارید.",
"githubHref.hint": "جایی که با کلیک روی ویجت باز می‌شود. برای غیرفعال کردن خالی بگذارید.",
"githubPrFilters.label": "پالایه‌های درخواست ادغام",
"githubPrFilters.hint": "کدام درخواست‌های باز نمایش داده شوند. چند پالایه با هم ترکیب می‌شوند.",
"githubPrFilters.opt.created": "ساخته‌شده",
Expand Down
4 changes: 2 additions & 2 deletions ui/widgets/system-summary/i18n/fa.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@
"slots.primary.placeholder": "/",
"slots.secondary.label": "دومین مسیر اتصال",
"slots.secondary.placeholder": "/mnt/data",
"slots.fsPrimary.label": "نخستین سامانهٔ پرونده",
"slots.fsPrimary.label": "نخستین سیستم فایل",
"slots.fsPrimary.placeholder": "root",
"slots.fsSecondary.label": "دومین سامانهٔ پرونده",
"slots.fsSecondary.label": "دومین سیستم فایل",
"slots.fsSecondary.placeholder": "media",
"slots.sharePrimary.label": "نخستین اشتراک",
"slots.sharePrimary.placeholder": "media",
Expand Down