fix(i18n): persist CMS translations and validate message catalogs
CI / check (push) Successful in 1m14s
CI / deploy (push) Successful in 1m27s

This commit is contained in:
Simo committed 2026-09-06 17:55:11 +02:00
1 parent 96234075f8
commit ef94646d60
37 files changed
+1594 -436

No files matched your search

+3
View File
@@ -37,6 +37,9 @@ jobs:
- name: Lint
run: pnpm biome:lint
- name: CMS translation contracts
run: pnpm i18n:check
- name: Typecheck
run: pnpm typecheck
+30
View File
@@ -0,0 +1,30 @@
# CMS translation audit and editor
The CMS editor at `/admin/translations/cms` now uses the same bundled catalogs as the request-time translator. Runtime changes are stored separately in `storage/cms-translations/<locale>.json`, inside the existing persistent `/app/storage` mount. No source-file write, environment-variable change or rebuild is required to apply an edit.
Only overrides are saved. Unchanged messages continue to receive updates from Git. Saves use a file lock, an atomic replacement and a revision check; a stale editor cannot overwrite another operator's changes. ICU syntax, argument names, rich-text tags and allowed keys are checked server-side. Invalid or obsolete overrides are excluded when reading a new release. Storage read errors are reported to the CMS error monitor and public pages fall back to bundled text.
The page starts in the operator's language. It shows all English reference keys, including missing translations, and supports search by key, translated text or English source. Filters separate missing, identical and modified text. Drafts survive language switches and failed saves. Users without `SETTINGS_EDIT` can review and export but cannot save.
## Audit outcome, 6 September 2026
- Scanned 25 JSON catalogs; 22 languages are selectable. The small Arabic, Finnish and Japanese catalogs are legacy files and remain outside the supported locale list.
- Repaired 66 malformed ICU messages across the 22 active catalogs, including HTML fragments and unescaped JSON examples.
- Added 199 missing English reference keys used by page components, with Italian translations, and fixed the incorrect navigation namespace in the admin error page.
- Completed the 31 previously missing Italian reference keys.
- Repaired missing `count` and `preset` variables in other locales.
- Final checks: zero malformed messages, zero argument/tag mismatches and zero missing references among the statically resolved translation calls.
- English contains 3,423 reference keys. Italian covers all of them; 629 values match English. Dutch is missing 524 reference keys and has 618 identical values. Matching English can be intentional for names and technical labels; this is not proof of translation quality.
- Found 1,577 literal JSX text candidates outside translation calls. These include labels, technical strings and names; they are an editorial inventory, not 1,577 confirmed bugs. The largest concentrations are the catalog item table (118), Studio main component (79), import audit (53), sound management (50) and permission editor (42).
## Repeatable checks
- `pnpm i18n:check`: fails on malformed messages, incompatible variables/tags, empty messages, source parsing errors or missing statically referenced keys. Runs in Gitea CI.
- `pnpm i18n:audit`: prints coverage and findings.
- `node scripts/audit-cms-translations.mjs --json`: full machine-readable inventory, including file and line references for literal JSX candidates.
Static analysis resolves literal translator namespaces and literal message keys. Dynamic key construction, prose embedded in arbitrary JavaScript strings, and the linguistic accuracy of all 22 translations still require targeted review. English fallback remains explicit; copying English into other catalogs would hide untranslated entries and is intentionally avoided.
## Validation
Automated tests exercise message syntax, actual translator output, persistent overrides, invalid-message rejection, revision conflicts and reset behavior. A browser fixture mounts the real editor and verifies missing-key editing, validation, failed-save preservation, language switching, successful saves, read-only access and mobile layout. Server actions are simulated in that fixture; authenticated production editing requires a staff session.
+7 -3
View File
@@ -30,7 +30,9 @@
"prepare": "node scripts/prepare-hooks.mjs",
"assets:editor": "node scripts/copy-editor-assets.mjs",
"deps:audit": "pnpm audit --audit-level=high",
"analyze": "next experimental-analyze"
"analyze": "next experimental-analyze",
"i18n:check": "node scripts/audit-cms-translations.mjs --check",
"i18n:audit": "node scripts/audit-cms-translations.mjs"
},
"lint-staged": {
"*.{js,ts,jsx,tsx,json}": "biome check --write --no-errors-on-unmatched",
@@ -41,6 +43,7 @@
"@dnd-kit/core": "6.3.1",
"@dnd-kit/sortable": "10.0.0",
"@dnd-kit/utilities": "3.2.2",
"@formatjs/icu-messageformat-parser": "3.5.17",
"@hookform/resolvers": "5.9.1",
"@tanstack/react-virtual": "3.14.10",
"class-variance-authority": "0.7.1",
@@ -72,10 +75,11 @@
"sharp": "^0.35.4",
"sonner": "2.0.8",
"tailwind-merge": "3.6.0",
"zod": "4.5.4",
"tinymce": "8.9.0"
"tinymce": "8.9.0",
"zod": "4.5.4"
},
"devDependencies": {
"@babel/parser": "7.29.8",
"@biomejs/biome": "2.5.12",
"@playwright/test": "^1.62.1",
"@tailwindcss/forms": "0.5.11",
+6
View File
@@ -25,6 +25,9 @@ importers:
'@dnd-kit/utilities':
specifier: 3.2.2
version: 3.2.2([email protected])
'@formatjs/icu-messageformat-parser':
specifier: 3.5.17
version: 3.5.17
'@hookform/resolvers':
specifier: 5.9.1
version: 5.9.1(@standard-schema/[email protected])([email protected]([email protected]))([email protected])
@@ -125,6 +128,9 @@ importers:
specifier: 4.5.4
version: 4.5.4
devDependencies:
'@babel/parser':
specifier: 7.29.8
version: 7.29.8
'@biomejs/biome':
specifier: 2.5.12
version: 2.5.12
File diff suppressed because it is too large. Load diff
+24 -37
View File
@@ -3,60 +3,47 @@
import fs from "node:fs/promises";
import path from "node:path";
import * as JSONC from "jsonc-parser";
import { revalidatePath } from "next/cache";
import { getTranslations } from "next-intl/server";
import { z } from "zod";
import { SUPPORTED_LOCALES } from "@/i18n/locales";
import {
CLIENT_TRANSLATION_FILES,
getClientTranslationFile,
} from "@/lib/client-translation-files";
import {
CmsTranslationError,
saveCmsTranslation,
} from "@/lib/cms-translations";
import { patchJson5 } from "@/lib/json5-patch";
import { PERMS } from "@/lib/permissions";
import { adminAction } from "@/lib/safe-action";
import { ActionError, actionOk } from "@/lib/safe-action-shared";
const saveTranslationsSchema = z.object({
locale: z.enum([
"en",
"it",
"nl",
"de",
"fr",
"es",
"pt",
"pl",
"sv",
"tr",
"ro",
"hu",
"cs",
"sk",
"da",
"no",
"el",
"bg",
"hr",
"sr",
"uk",
"ru",
]),
data: z.record(z.string(), z.unknown()),
locale: z.enum(SUPPORTED_LOCALES),
revision: z.string().regex(/^[a-f0-9]{64}$/),
changes: z
.record(z.string().max(300), z.string().max(20000).nullable())
.refine((value) => Object.keys(value).length <= 5000),
});
export const saveTranslations = adminAction(
{ permission: PERMS.SETTINGS_EDIT, schema: saveTranslationsSchema },
async (ctx) => {
const filePath = path.join(
process.cwd(),
"src",
"messages",
`${ctx.data.locale}.json`,
try {
const snapshot = await saveCmsTranslation(
ctx.data.locale,
ctx.data.revision,
ctx.data.changes,
);
await fs.writeFile(
filePath,
JSON.stringify(ctx.data.data, null, 2),
"utf-8",
);
return actionOk();
revalidatePath("/", "layout");
return actionOk({ snapshot });
} catch (error) {
if (!(error instanceof CmsTranslationError)) throw error;
const t = await getTranslations("pages.admin.translations.cms");
return { ok: false, error: t(error.code, { key: error.key }) };
}
},
);
+1 -1
View File
@@ -18,7 +18,7 @@ export default function AdminErrorPage({
reset: () => void;
}) {
const t = useTranslations("pages.error");
const tNav = useTranslations("admin.nav");
const tNav = useTranslations("pages.admin.nav");
const [reference, setReference] = useState(error.digest);
useEffect(() => {
+19 -43
View File
@@ -1,48 +1,24 @@
import fs from "node:fs/promises";
import path from "node:path";
import { getLocale } from "next-intl/server";
import { SUPPORTED_LOCALES } from "@/i18n/locales";
import { readCmsTranslation } from "@/lib/cms-translations";
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
import { TranslationsClient } from "../translations-client";
const LOCALES = [
"en",
"it",
"nl",
"de",
"fr",
"es",
"pt",
"pl",
"sv",
"tr",
"ro",
"hu",
"cs",
"sk",
"da",
"no",
"el",
"bg",
"hr",
"sr",
"uk",
"ru",
] as const;
export default async function CmsTranslationsPage() {
const messagesDir = path.join(process.cwd(), "src", "messages");
const contents: Record<string, string> = {};
await Promise.all(
LOCALES.map(async (locale) => {
try {
contents[locale] = await fs.readFile(
path.join(messagesDir, `${locale}.json`),
"utf-8",
const [{ session, permissions }, locale, entries] = await Promise.all([
getAdminContext(),
getLocale(),
Promise.all(
SUPPORTED_LOCALES.map(
async (locale) => [locale, await readCmsTranslation(locale)] as const,
),
),
]);
return (
<TranslationsClient
snapshots={Object.fromEntries(entries)}
initialLocale={locale}
canEdit={canAccess(permissions, PERMS.SETTINGS_EDIT, session.user.rank)}
/>
);
} catch {
// Skip missing locale files instead of failing the whole page.
}
}),
);
return <TranslationsClient localeContents={contents} />;
}
File diff suppressed because it is too large. Load diff
+39
View File
@@ -0,0 +1,39 @@
import { readdir, readFile } from "node:fs/promises";
import { parse } from "@formatjs/icu-messageformat-parser";
import { describe, expect, it } from "vitest";
import { flattenObject } from "@/lib/translations-utils";
describe("CMS message catalogs", () => {
it("contains valid ICU messages in every shipped locale", async () => {
const errors: string[] = [];
for (const file of await readdir("src/messages")) {
if (!file.endsWith(".json")) continue;
const messages = flattenObject(
JSON.parse(await readFile(`src/messages/${file}`, "utf8")),
);
for (const [key, value] of Object.entries(messages)) {
try {
parse(value);
} catch {
errors.push(`${file}: ${key}`);
}
}
}
expect(errors).toEqual([]);
});
it("provides actual text for the search result headings and dashboard counts", async () => {
const { createTranslator } = await import("next-intl");
const messages = JSON.parse(await readFile("src/messages/en.json", "utf8"));
const errors: unknown[] = [];
const t = createTranslator({
locale: "en",
messages,
onError: (error) => errors.push(error),
});
expect(t("pages.admin.dashboard.totalAccounts", { count: 12 })).toContain(
"12",
);
expect(t("pages.admin.search.noResults")).not.toContain("pages.");
expect(errors).toEqual([]);
});
});
+32
View File
@@ -0,0 +1,32 @@
export const SUPPORTED_LOCALES = [
"en",
"it",
"nl",
"de",
"fr",
"es",
"pt",
"pl",
"sv",
"tr",
"ro",
"hu",
"cs",
"sk",
"da",
"no",
"el",
"bg",
"hr",
"sr",
"uk",
"ru",
] as const;
export type AppLocale = (typeof SUPPORTED_LOCALES)[number];
export const DEFAULT_LOCALE: AppLocale = "en";
export function isSupportedLocale(
value: string | undefined,
): value is AppLocale {
return !!value && (SUPPORTED_LOCALES as readonly string[]).includes(value);
}
+12 -39
View File
@@ -1,41 +1,17 @@
import { cookies, headers } from "next/headers";
import { getRequestConfig } from "next-intl/server";
import { loadCmsMessages } from "@/lib/cms-translations";
import { mergeMessages } from "./merge-messages";
// i18n WITHOUT routing: the locale is chosen by a `NEXT_LOCALE` cookie, then
// the Accept-Language header, and finally English as fallback.
export const SUPPORTED_LOCALES = [
"en",
"it",
"nl",
"de",
"fr",
"es",
"pt",
"pl",
"sv",
"tr",
"ro",
"hu",
"cs",
"sk",
"da",
"no",
"el",
"bg",
"hr",
"sr",
"uk",
"ru",
] as const;
export type AppLocale = (typeof SUPPORTED_LOCALES)[number];
export const DEFAULT_LOCALE: AppLocale = "en";
export {
DEFAULT_LOCALE,
isSupportedLocale,
SUPPORTED_LOCALES,
} from "./locales";
export function isSupportedLocale(
value: string | undefined,
): value is AppLocale {
return !!value && (SUPPORTED_LOCALES as readonly string[]).includes(value);
}
import { type AppLocale, DEFAULT_LOCALE, isSupportedLocale } from "./locales";
function detectLocaleFromAcceptLanguage(
acceptLanguage: string | null,
@@ -73,14 +49,10 @@ export default getRequestConfig(async () => {
locale = detected ?? DEFAULT_LOCALE;
}
// Safe: `locale` is validated against SUPPORTED_LOCALES above (only en/it/nl/de/fr/es).
const messages = (await import(`../messages/${locale}.json`)).default;
// English is the source of truth; fall back to it for any key missing from a
// translation so the UI never shows a raw key path.
const fallback =
locale === DEFAULT_LOCALE
? messages
: (await import(`../messages/${DEFAULT_LOCALE}.json`)).default;
const [messages, fallback] = await Promise.all([
loadCmsMessages(locale),
loadCmsMessages(DEFAULT_LOCALE),
]);
return {
locale,
@@ -108,6 +80,7 @@ export default getRequestConfig(async () => {
if (typeof fromEnglish === "string") return fromEnglish;
const seg = key.split(".").pop() ?? key;
return seg
.replace(/([a-z])([A-Z])/g, "$1 $2")
.replace(/[._-]/g, " ")
.replace(/\b\w/g, (c) => c.toUpperCase());
},
+36
View File
@@ -0,0 +1,36 @@
import {
type MessageFormatElement,
parse,
TYPE,
} from "@formatjs/icu-messageformat-parser";
/** Argument names and rich-text tags are a contract with the calling component. */
export function messageVariables(value: string): string[] {
const names = new Set<string>();
function visit(nodes: MessageFormatElement[]) {
for (const node of nodes) {
if (node.type !== TYPE.literal && node.type !== TYPE.pound)
names.add(node.value);
if (node.type === TYPE.select || node.type === TYPE.plural) {
for (const option of Object.values(node.options)) visit(option.value);
}
if (node.type === TYPE.tag) visit(node.children);
}
}
visit(parse(value));
return [...names].sort();
}
export function validateCmsMessage(
value: string,
source: string,
): "empty" | "invalid" | "variables" | null {
if (!value.trim()) return "empty";
try {
return messageVariables(value).join("\0") ===
messageVariables(source).join("\0")
? null
: "variables";
} catch {
return "invalid";
}
}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+5 -5
View File
@@ -1708,7 +1708,7 @@
"noNowPlayingHint": "няма зададен API за игра в момента",
"noListenersHint": "няма зададен API за слушатели",
"endpointDiagnostics": "Диагностика на крайна точка",
"diagnosticsLegend": "<span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-green-600/20 text-[var(--color-text)]\">ОК</span> достъпно · <span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-[var(--color-primary)]/18 text-[var(--color-text)]\">WARN</span> не е конфигуриран · <span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-red-500/22 text-[var(--color-text)]\">ГРЕШКА</span> извличането е неуспешно / изтече времето за изчакване.",
"diagnosticsLegend": "OK: достъпно · WARN: не е конфигурирано · ERROR: неуспешна заявка или изтекло време.",
"streamUrl": "URL адрес на потока",
"unreachable": "недостижим",
"refresh": "Опресняване"
@@ -1767,7 +1767,7 @@
"noStreamUrlText": "Няма зададен radio_stream_url, така че все още няма нищо за вграждане.",
"goToSettings": "Отидете на Радио настройки",
"livePreview": "Преглед на живо",
"livePreviewText": "Това е голият <audio> плейър, както ще го видят посетителите. Натиснете възпроизвеждане, за да потвърдите, че потокът е достъпен.",
"livePreviewText": "Това е голият audio плейър, както ще го видят посетителите. Натиснете възпроизвеждане, за да потвърдите, че потокът е достъпен.",
"source": "Източник",
"audioSnippet": "Фрагмент от аудио плейър",
"audioSnippetDesc": "Вмъкващ се HTML5 аудио плейър, насочен право към потока. Работи навсякъде, където е разрешен обикновен HTML.",
@@ -1998,7 +1998,7 @@
"title": "Сигнали",
"subtitle": "Изпращайте сигнали за целия хотел и вижте последните сигнали",
"sendAlert": "Изпратете сигнал за хотел",
"recentAlerts": "Последни сигнали",
"recentAlerts": "Последни сигнали ({count})",
"noRecentAlerts": "Няма скорошни сигнали",
"form": {
"title": "Заглавие на предупреждение",
@@ -2209,7 +2209,7 @@
"tabTest": "Test",
"tabAudit": "Audit log",
"importPermissions": "Импортиране Разрешения",
"importHint": "Paste a JSON array of разрешение objects. Format: [{ \"permission\": \"...\", \"minRank\": 7, \"description\": \"...\", \"groupName\": \"...\", \"dependsOn\": \"...\" }]",
"importHint": "Paste a JSON array of разрешение objects. Format: ['{' \"permission\": \"...\", \"minRank\": 7, \"description\": \"...\", \"groupName\": \"...\", \"dependsOn\": \"...\" '}']",
"importPlaceholder": "Paste JSON here…",
"import": "Импортиране",
"exportTitle": "Export Разрешения",
@@ -2346,7 +2346,7 @@
"presetsTitle": "Предварително зададени теми",
"presetsSubtitle": "Изберете предварително изградена цветова схема",
"applyPreset": "Кандидатствайте",
"presetApplied": "Предварителната настройка е приложена",
"presetApplied": "Предварителната настройка е приложена: {preset}",
"customiseTitle": "Персонализирани цветове",
"primary": "Първичен",
"secondary": "Вторичен",
+5 -5
View File
@@ -1708,7 +1708,7 @@
"noNowPlayingHint": "žádná nyní přehrávající sada API",
"noListenersHint": "není nastaveno žádné rozhraní API pro posluchače",
"endpointDiagnostics": "Diagnostika koncových bodů",
"diagnosticsLegend": "<span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-green-600/20 text-[var(--color-text)]\">OK</span> dosažitelné · <span class=\"inline-block text-[0.72rem] font-bold px-2 py-primaryl]-1 bg-8 rounded text-[var(--color-text)]\">WARN</span> není nakonfigurováno · <span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-red-500/22 text-[var(--color-text)]\">CHYBA</span> se nezdařilo / vypršel časový limit.",
"diagnosticsLegend": "OK: dostupné · WARN: nenastaveno · ERROR: požadavek selhal nebo vypršel časový limit.",
"streamUrl": "Adresa URL streamu",
"unreachable": "nedosažitelný",
"refresh": "Obnovit"
@@ -1767,7 +1767,7 @@
"noStreamUrlText": "Neexistuje žádná sada radio_stream_url, takže zatím není co vkládat.",
"goToSettings": "Přejděte do Nastavení rádia",
"livePreview": "Živý náhled",
"livePreviewText": "Toto je holý <audio> přehrávač, jak jej uvidí návštěvníci. Stisknutím tlačítka Přehrát potvrďte, že je stream dosažitelný.",
"livePreviewText": "Toto je holý audio přehrávač, jak jej uvidí návštěvníci. Stisknutím tlačítka Přehrát potvrďte, že je stream dosažitelný.",
"source": "Zdroj",
"audioSnippet": "Úryvek audio přehrávače",
"audioSnippetDesc": "Zvukový přehrávač HTML5 namířil přímo na stream. Funguje všude, kde je povoleno prosté HTML.",
@@ -1998,7 +1998,7 @@
"title": "Upozornění",
"subtitle": "Odesílejte upozornění pro celý hotel a prohlížejte si nedávná upozornění",
"sendAlert": "Odeslat upozornění na hotel",
"recentAlerts": "Nedávná upozornění",
"recentAlerts": "Nedávná upozornění ({count})",
"noRecentAlerts": "Žádná nedávná upozornění",
"form": {
"title": "Název upozornění",
@@ -2209,7 +2209,7 @@
"tabTest": "Test",
"tabAudit": "Audit log",
"importPermissions": "Importovat Oprávnění",
"importHint": "Paste a JSON array of Povolení objects. Format: [{ \"permission\": \"...\", \"minRank\": 7, \"description\": \"...\", \"groupName\": \"...\", \"dependsOn\": \"...\" }]",
"importHint": "Paste a JSON array of Povolení objects. Format: ['{' \"permission\": \"...\", \"minRank\": 7, \"description\": \"...\", \"groupName\": \"...\", \"dependsOn\": \"...\" '}']",
"importPlaceholder": "Paste JSON here…",
"import": "Importovat",
"exportTitle": "Export Oprávnění",
@@ -2346,7 +2346,7 @@
"presetsTitle": "Předvolby motivu",
"presetsSubtitle": "Vyberte předem připravené barevné schéma",
"applyPreset": "Použít",
"presetApplied": "Předvolba použita",
"presetApplied": "Předvolba použita: {preset}",
"customiseTitle": "Vlastní barvy",
"primary": "Primární",
"secondary": "Sekundární",
+5 -5
View File
@@ -1708,7 +1708,7 @@
"noNowPlayingHint": "intet API-sæt, der nu spiller",
"noListenersHint": "no listeners API set",
"endpointDiagnostics": "Slutpunktsdiagnostik",
"diagnosticsLegend": "<span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-green-600/20 text-[var(--color-text)]\">OK</span> tilgængelig · <span class=\"inline-block text-[0.72rem] font-bold px-2 rounded-full. bg-[var(--color-primary)]/18 text-[var(--color-text)]\">ADVARSEL</span> ikke konfigureret · <span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-red-500/22 text-[var(--color-text) / 22 text-[var(--farve mislykket] OR.",
"diagnosticsLegend": "OK: tilgængelig · WARN: ikke konfigureret · ERROR: anmodningen mislykkedes eller fik timeout.",
"streamUrl": "Stream URL",
"unreachable": "unreachable",
"refresh": "Opdater"
@@ -1767,7 +1767,7 @@
"noStreamUrlText": "Der er ingen radio_stream_url sat, så der er intet at indlejre endnu.",
"goToSettings": "Gå til Radioindstillinger",
"livePreview": "Live preview",
"livePreviewText": "Dette er den blottede <audio>-afspiller, som besøgende vil se den. Tryk på afspil for at bekræfte, at streamen er tilgængelig.",
"livePreviewText": "Dette er den blottede audio-afspiller, som besøgende vil se den. Tryk på afspil for at bekræfte, at streamen er tilgængelig.",
"source": "Kilde",
"audioSnippet": "Uddrag af lydafspiller",
"audioSnippetDesc": "Drop-in HTML5-lydafspiller pegede direkte mod strømmen. Virker overalt, hvor almindelig HTML er tilladt.",
@@ -1998,7 +1998,7 @@
"title": "Advarsler",
"subtitle": "Send underretninger for hele hotellet, og se de seneste underretninger",
"sendAlert": "Send hotelalarm",
"recentAlerts": "Seneste underretninger",
"recentAlerts": "Seneste underretninger ({count})",
"noRecentAlerts": "No recent alerts",
"form": {
"title": "Alert title",
@@ -2209,7 +2209,7 @@
"tabTest": "Test",
"tabAudit": "Audit log",
"importPermissions": "Importer Tilladelser",
"importHint": "Paste a JSON array of Tilladelse objects. Format: [{ \"permission\": \"...\", \"minRank\": 7, \"description\": \"...\", \"groupName\": \"...\", \"dependsOn\": \"...\" }]",
"importHint": "Paste a JSON array of Tilladelse objects. Format: ['{' \"permission\": \"...\", \"minRank\": 7, \"description\": \"...\", \"groupName\": \"...\", \"dependsOn\": \"...\" '}']",
"importPlaceholder": "Paste JSON here…",
"import": "Importer",
"exportTitle": "Export Tilladelser",
@@ -2346,7 +2346,7 @@
"presetsTitle": "Forudindstillinger af tema",
"presetsSubtitle": "Vælg et forudbygget farveskema",
"applyPreset": "Ansøg",
"presetApplied": "Forudindstilling anvendt",
"presetApplied": "Forudindstilling anvendt: {preset}",
"customiseTitle": "Brugerdefinerede farver",
"primary": "Primær",
"secondary": "Sekundær",
+5 -5
View File
@@ -1679,7 +1679,7 @@
"noNowPlayingHint": "no now-playing API set",
"noListenersHint": "no listeners API set",
"endpointDiagnostics": "Endpoint diagnostics",
"diagnosticsLegend": "<span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-green-600/20 text-[var(--color-text)]\">OK</span> reachable · <span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-[var(--color-primary)]/18 text-[var(--color-text)]\">WARN</span> not configured · <span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-red-500/22 text-[var(--color-text)]\">ERROR</span> fetch failed / timed out.",
"diagnosticsLegend": "OK: erreichbar · WARN: nicht konfiguriert · ERROR: Anfrage fehlgeschlagen oder Zeitüberschreitung.",
"streamUrl": "Stream URL",
"unreachable": "unreachable",
"refresh": "Aktualisieren"
@@ -1738,7 +1738,7 @@
"noStreamUrlText": "There is no radio_stream_url set, so there is nothing to embed yet.",
"goToSettings": "Go to Radio Settings",
"livePreview": "Live preview",
"livePreviewText": "This is the bare <audio> player as visitors will see it. Press play to confirm the stream is reachable.",
"livePreviewText": "This is the bare audio player as visitors will see it. Press play to confirm the stream is reachable.",
"source": "Source",
"audioSnippet": "Audio player snippet",
"audioSnippetDesc": "Drop-in HTML5 audio player pointed straight at the stream. Works anywhere plain HTML is allowed.",
@@ -1969,7 +1969,7 @@
"title": "Alarme",
"subtitle": "Hotelweite Alarme senden und aktuelle anzeigen",
"sendAlert": "Hotel-Alarm senden",
"recentAlerts": "Letzte Alarme",
"recentAlerts": "Letzte Alarme ({count})",
"noRecentAlerts": "Keine aktuellen Alarme",
"form": {
"title": "Alarm-Titel",
@@ -2180,7 +2180,7 @@
"tabTest": "Test",
"tabAudit": "Audit log",
"importPermissions": "Importieren Berechtigungen",
"importHint": "Paste a JSON array of Berechtigung objects. Format: [{ \"permission\": \"...\", \"minRank\": 7, \"description\": \"...\", \"groupName\": \"...\", \"dependsOn\": \"...\" }]",
"importHint": "Paste a JSON array of Berechtigung objects. Format: ['{' \"permission\": \"...\", \"minRank\": 7, \"description\": \"...\", \"groupName\": \"...\", \"dependsOn\": \"...\" '}']",
"importPlaceholder": "Paste JSON here…",
"import": "Importieren",
"exportTitle": "Export Berechtigungen",
@@ -2317,7 +2317,7 @@
"presetsTitle": "Design-Voreinstellungen",
"presetsSubtitle": "Wähle ein vorgefertigtes Farbschema",
"applyPreset": "Anwenden",
"presetApplied": "Voreinstellung angewendet",
"presetApplied": "Voreinstellung angewendet: {preset}",
"customiseTitle": "Benutzerdefinierte Farben",
"primary": "Primär",
"secondary": "Sekundär",
+5 -5
View File
@@ -1708,7 +1708,7 @@
"noNowPlayingHint": "δεν υπάρχει σύνολο API που παίζει τώρα",
"noListenersHint": "δεν έχει οριστεί API ακροατών",
"endpointDiagnostics": "Διαγνωστικά τελικού σημείου",
"diagnosticsLegend": "<span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-green-600/20 text-[var(--color-text)]\">ΟΚ</span> προσβάσιμο · <span class=\"inline-block text-[0.72-rounded-5-0.72remful. bg-[var(--color-primary)]/18 text-[var(--color-text)]\">ΠΡΟΕΙΔΟΠΟΙΗΣΗ</span> δεν έχει διαμορφωθεί · <span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-red-500/2 text-[var(--color-text)]\">ΣΦΑΛΜΑ</span> η ανάκτηση απέτυχε / έληξε.",
"diagnosticsLegend": "OK: διαθέσιμο · WARN: δεν έχει ρυθμιστεί · ERROR: αποτυχία αιτήματος ή λήξη χρονικού ορίου.",
"streamUrl": "URL ροής",
"unreachable": "απρόσιτος",
"refresh": "Ανανέωση"
@@ -1767,7 +1767,7 @@
"noStreamUrlText": "Δεν υπάρχει set radio_stream_url, επομένως δεν υπάρχει τίποτα για ενσωμάτωση ακόμα.",
"goToSettings": "Μεταβείτε στις Ρυθμίσεις ραδιοφώνου",
"livePreview": "Ζωντανή προεπισκόπηση",
"livePreviewText": "Αυτό είναι το γυμνό πρόγραμμα αναπαραγωγής <audio> όπως θα το δουν οι επισκέπτες. Πατήστε το play για να επιβεβαιώσετε ότι η ροή είναι προσβάσιμη.",
"livePreviewText": "Αυτό είναι το γυμνό πρόγραμμα αναπαραγωγής audio όπως θα το δουν οι επισκέπτες. Πατήστε το play για να επιβεβαιώσετε ότι η ροή είναι προσβάσιμη.",
"source": "Πηγή",
"audioSnippet": "Απόσπασμα αναπαραγωγής ήχου",
"audioSnippetDesc": "Το αναπτυσσόμενο πρόγραμμα αναπαραγωγής ήχου HTML5 κατευθείαν στη ροή. Λειτουργεί οπουδήποτε επιτρέπεται απλό HTML.",
@@ -1998,7 +1998,7 @@
"title": "Ειδοποιήσεις",
"subtitle": "Στείλτε ειδοποιήσεις σε όλο το ξενοδοχείο και δείτε πρόσφατες ειδοποιήσεις",
"sendAlert": "Αποστολή ειδοποίησης ξενοδοχείου",
"recentAlerts": "Πρόσφατες ειδοποιήσεις",
"recentAlerts": "Πρόσφατες ειδοποιήσεις ({count})",
"noRecentAlerts": "Δεν υπάρχουν πρόσφατες ειδοποιήσεις",
"form": {
"title": "Τίτλος ειδοποίησης",
@@ -2209,7 +2209,7 @@
"tabTest": "Test",
"tabAudit": "Audit log",
"importPermissions": "Εισαγωγή Άδειες",
"importHint": "Paste a JSON array of Άδεια objects. Format: [{ \"permission\": \"...\", \"minRank\": 7, \"description\": \"...\", \"groupName\": \"...\", \"dependsOn\": \"...\" }]",
"importHint": "Paste a JSON array of Άδεια objects. Format: ['{' \"permission\": \"...\", \"minRank\": 7, \"description\": \"...\", \"groupName\": \"...\", \"dependsOn\": \"...\" '}']",
"importPlaceholder": "Paste JSON here…",
"import": "Εισαγωγή",
"exportTitle": "Export Άδειες",
@@ -2346,7 +2346,7 @@
"presetsTitle": "Προεπιλογές θεμάτων",
"presetsSubtitle": "Επιλέξτε έναν προκατασκευασμένο συνδυασμό χρωμάτων",
"applyPreset": "Εφαρμόστε",
"presetApplied": "Εφαρμόστηκε η προεπιλογή",
"presetApplied": "Εφαρμόστηκε η προεπιλογή: {preset}",
"customiseTitle": "Προσαρμοσμένα χρώματα",
"primary": "Πρωτοβάθμια",
"secondary": "Δευτερεύουσα",
+268 -32
View File
File diff suppressed because it is too large. Load diff
+5 -5
View File
@@ -1679,7 +1679,7 @@
"noNowPlayingHint": "no now-playing API set",
"noListenersHint": "no listeners API set",
"endpointDiagnostics": "Endpoint diagnostics",
"diagnosticsLegend": "<span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-green-600/20 text-[var(--color-text)]\">OK</span> reachable · <span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-[var(--color-primary)]/18 text-[var(--color-text)]\">WARN</span> not configured · <span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-red-500/22 text-[var(--color-text)]\">ERROR</span> fetch failed / timed out.",
"diagnosticsLegend": "OK: accesible · WARN: sin configurar · ERROR: solicitud fallida o tiempo agotado.",
"streamUrl": "Stream URL",
"unreachable": "unreachable",
"refresh": "Actualizar"
@@ -1738,7 +1738,7 @@
"noStreamUrlText": "There is no radio_stream_url set, so there is nothing to embed yet.",
"goToSettings": "Go to Radio Settings",
"livePreview": "Live preview",
"livePreviewText": "This is the bare <audio> player as visitors will see it. Press play to confirm the stream is reachable.",
"livePreviewText": "This is the bare audio player as visitors will see it. Press play to confirm the stream is reachable.",
"source": "Source",
"audioSnippet": "Audio player snippet",
"audioSnippetDesc": "Drop-in HTML5 audio player pointed straight at the stream. Works anywhere plain HTML is allowed.",
@@ -1969,7 +1969,7 @@
"title": "Alertas",
"subtitle": "Enviar alertas a todo el hotel y ver alertas recientes",
"sendAlert": "Enviar alerta al hotel",
"recentAlerts": "Alertas recientes",
"recentAlerts": "Alertas recientes ({count})",
"noRecentAlerts": "No hay alertas recientes",
"form": {
"title": "Título de la alerta",
@@ -2180,7 +2180,7 @@
"tabTest": "Test",
"tabAudit": "Audit log",
"importPermissions": "Importar Permisos",
"importHint": "Paste a JSON array of Permiso objects. Format: [{ \"permission\": \"...\", \"minRank\": 7, \"description\": \"...\", \"groupName\": \"...\", \"dependsOn\": \"...\" }]",
"importHint": "Paste a JSON array of Permiso objects. Format: ['{' \"permission\": \"...\", \"minRank\": 7, \"description\": \"...\", \"groupName\": \"...\", \"dependsOn\": \"...\" '}']",
"importPlaceholder": "Paste JSON here…",
"import": "Importar",
"exportTitle": "Export Permisos",
@@ -2317,7 +2317,7 @@
"presetsTitle": "Preajustes de tema",
"presetsSubtitle": "Elige un esquema de color predefinido",
"applyPreset": "Aplicar",
"presetApplied": "Preajuste aplicado",
"presetApplied": "Preajuste aplicado: {preset}",
"customiseTitle": "Colores personalizados",
"primary": "Primario",
"secondary": "Secundario",
+5 -5
View File
@@ -1679,7 +1679,7 @@
"noNowPlayingHint": "no now-playing API set",
"noListenersHint": "no listeners API set",
"endpointDiagnostics": "Endpoint diagnostics",
"diagnosticsLegend": "<span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-green-600/20 text-[var(--color-text)]\">OK</span> reachable · <span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-[var(--color-primary)]/18 text-[var(--color-text)]\">WARN</span> not configured · <span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-red-500/22 text-[var(--color-text)]\">ERROR</span> fetch failed / timed out.",
"diagnosticsLegend": "OK : accessible · WARN : non configuré · ERROR : échec de la requête ou délai dépassé.",
"streamUrl": "Stream URL",
"unreachable": "unreachable",
"refresh": "Actualiser"
@@ -1738,7 +1738,7 @@
"noStreamUrlText": "There is no radio_stream_url set, so there is nothing to embed yet.",
"goToSettings": "Go to Radio Settings",
"livePreview": "Live preview",
"livePreviewText": "This is the bare <audio> player as visitors will see it. Press play to confirm the stream is reachable.",
"livePreviewText": "This is the bare audio player as visitors will see it. Press play to confirm the stream is reachable.",
"source": "Source",
"audioSnippet": "Audio player snippet",
"audioSnippetDesc": "Drop-in HTML5 audio player pointed straight at the stream. Works anywhere plain HTML is allowed.",
@@ -1969,7 +1969,7 @@
"title": "Alertes",
"subtitle": "Envoyer des alertes à tout l'hôtel et voir les alertes récentes",
"sendAlert": "Envoyer une alerte à l'hôtel",
"recentAlerts": "Alertes récentes",
"recentAlerts": "Alertes récentes ({count})",
"noRecentAlerts": "Aucune alerte récente",
"form": {
"title": "Titre de l'alerte",
@@ -2180,7 +2180,7 @@
"tabTest": "Test",
"tabAudit": "Audit log",
"importPermissions": "Importer permissions",
"importHint": "Paste a JSON array of permission objects. Format: [{ \"permission\": \"...\", \"minRank\": 7, \"description\": \"...\", \"groupName\": \"...\", \"dependsOn\": \"...\" }]",
"importHint": "Paste a JSON array of permission objects. Format: ['{' \"permission\": \"...\", \"minRank\": 7, \"description\": \"...\", \"groupName\": \"...\", \"dependsOn\": \"...\" '}']",
"importPlaceholder": "Paste JSON here…",
"import": "Importer",
"exportTitle": "Export permissions",
@@ -2317,7 +2317,7 @@
"presetsTitle": "Préréglages de thème",
"presetsSubtitle": "Choisir un jeu de couleurs prédéfini",
"applyPreset": "Appliquer",
"presetApplied": "Préréglage appliqué",
"presetApplied": "Préréglage appliqué: {preset}",
"customiseTitle": "Couleurs personnalisées",
"primary": "Primaire",
"secondary": "Secondaire",
+5 -5
View File
@@ -1708,7 +1708,7 @@
"noNowPlayingHint": "nema postavljenog API-ja koji se sada igra",
"noListenersHint": "nije postavljen API slušatelja",
"endpointDiagnostics": "Dijagnostika krajnje točke",
"diagnosticsLegend": "<span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-green-600/20 text-[var(--color-text)]\">OK</span> dostupno · <span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-[var(--color-primary)]/18 text-[var(--color-text)]\">WARN</span> nije konfigurirano · <span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-red-500/22 text-[var(--color-text)]\">GREŠKA</span> dohvaćanje nije uspjelo / isteklo je vrijeme.",
"diagnosticsLegend": "OK: dostupno · WARN: nije konfigurirano · ERROR: zahtjev nije uspio ili je istekao.",
"streamUrl": "URL streama",
"unreachable": "nedostižan",
"refresh": "Osvježi"
@@ -1767,7 +1767,7 @@
"noStreamUrlText": "Nije postavljen radio_stream_url, tako da još nema ništa za ugraditi.",
"goToSettings": "Idite na Radio Settings",
"livePreview": "Pregled uživo",
"livePreviewText": "Ovo je goli <audio> player kako će ga posjetitelji vidjeti. Pritisnite play kako biste potvrdili da je stream dostupan.",
"livePreviewText": "Ovo je goli audio player kako će ga posjetitelji vidjeti. Pritisnite play kako biste potvrdili da je stream dostupan.",
"source": "Izvor",
"audioSnippet": "Isječak audio playera",
"audioSnippetDesc": "Umetnuti HTML5 audio player usmjeren ravno prema streamu. Radi svugdje gdje je dopušten obični HTML.",
@@ -1998,7 +1998,7 @@
"title": "upozorenja",
"subtitle": "Šaljite upozorenja cijelom hotelu i pogledajte nedavna upozorenja",
"sendAlert": "Pošalji hotelsko upozorenje",
"recentAlerts": "Nedavna upozorenja",
"recentAlerts": "Nedavna upozorenja ({count})",
"noRecentAlerts": "Nema nedavnih upozorenja",
"form": {
"title": "Naslov upozorenja",
@@ -2209,7 +2209,7 @@
"tabTest": "Test",
"tabAudit": "Audit log",
"importPermissions": "Uvoz Dozvole",
"importHint": "Paste a JSON array of Dozvola objects. Format: [{ \"permission\": \"...\", \"minRank\": 7, \"description\": \"...\", \"groupName\": \"...\", \"dependsOn\": \"...\" }]",
"importHint": "Paste a JSON array of Dozvola objects. Format: ['{' \"permission\": \"...\", \"minRank\": 7, \"description\": \"...\", \"groupName\": \"...\", \"dependsOn\": \"...\" '}']",
"importPlaceholder": "Paste JSON here…",
"import": "uvoz",
"exportTitle": "Export Dozvole",
@@ -2346,7 +2346,7 @@
"presetsTitle": "Unaprijed postavljene teme",
"presetsSubtitle": "Odaberite unaprijed pripremljenu shemu boja",
"applyPreset": "primijeniti",
"presetApplied": "Unaprijed postavljeno",
"presetApplied": "Unaprijed postavljeno: {preset}",
"customiseTitle": "Prilagođene boje",
"primary": "Primarni",
"secondary": "Sekundarna",
+5 -5
View File
@@ -1708,7 +1708,7 @@
"noNowPlayingHint": "nincs jelenleg lejátszott API-készlet",
"noListenersHint": "nincs hallgató API beállítva",
"endpointDiagnostics": "Végpont diagnosztika",
"diagnosticsLegend": "<span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-green-600/20 text-[var(--color-text)]\">OK</span> elérhető · <span class=\"inline-block text-[0.72rem] font-bold.5 round-0 px-2 edpy-0 px-2 ed bg-[var(--color-primary)]/18 text-[var(--color-text)]\">FIGYELMEZTETÉS</span> nincs konfigurálva · <span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-red-500/22 text-[var(]--ERtch failed-R)\">fe.",
"diagnosticsLegend": "OK: elérhető · WARN: nincs beállítva · ERROR: sikertelen kérés vagy időtúllépés.",
"streamUrl": "Stream URL",
"unreachable": "elérhetetlen",
"refresh": "Frissítés"
@@ -1767,7 +1767,7 @@
"noStreamUrlText": "Nincs beállítva radio_stream_url, így még nincs mit beágyazni.",
"goToSettings": "Menjen a Rádióbeállításokhoz",
"livePreview": "Élő előnézet",
"livePreviewText": "Ez a csupasz <audio> lejátszó, ahogy a látogatók látni fogják. Nyomja meg a lejátszás gombot, hogy megerősítse, hogy az adatfolyam elérhető.",
"livePreviewText": "Ez a csupasz audio lejátszó, ahogy a látogatók látni fogják. Nyomja meg a lejátszás gombot, hogy megerősítse, hogy az adatfolyam elérhető.",
"source": "Forrás",
"audioSnippet": "Hanglejátszó részlet",
"audioSnippetDesc": "A beugró HTML5 audiolejátszó egyenesen a streamre mutatott. Mindenhol működik, ahol a sima HTML megengedett.",
@@ -1998,7 +1998,7 @@
"title": "Figyelmeztetések",
"subtitle": "A szálloda egészére kiterjedő figyelmeztetések küldése és a legutóbbi figyelmeztetések megtekintése",
"sendAlert": "Szállodai figyelmeztetés küldése",
"recentAlerts": "Legutóbbi figyelmeztetések",
"recentAlerts": "Legutóbbi figyelmeztetések ({count})",
"noRecentAlerts": "Nincsenek friss figyelmeztetések",
"form": {
"title": "Figyelmeztetés címe",
@@ -2209,7 +2209,7 @@
"tabTest": "Test",
"tabAudit": "Audit log",
"importPermissions": "Importálás Engedélyek",
"importHint": "Paste a JSON array of Engedély objects. Format: [{ \"permission\": \"...\", \"minRank\": 7, \"description\": \"...\", \"groupName\": \"...\", \"dependsOn\": \"...\" }]",
"importHint": "Paste a JSON array of Engedély objects. Format: ['{' \"permission\": \"...\", \"minRank\": 7, \"description\": \"...\", \"groupName\": \"...\", \"dependsOn\": \"...\" '}']",
"importPlaceholder": "Paste JSON here…",
"import": "Importálás",
"exportTitle": "Export Engedélyek",
@@ -2346,7 +2346,7 @@
"presetsTitle": "Előre beállított téma",
"presetsSubtitle": "Válasszon egy előre elkészített színsémát",
"applyPreset": "Alkalmazni",
"presetApplied": "Előbeállítás alkalmazva",
"presetApplied": "Előbeállítás alkalmazva: {preset}",
"customiseTitle": "Egyedi színek",
"primary": "Elsődleges",
"secondary": "Másodlagos",
+306 -39
View File
File diff suppressed because it is too large. Load diff
+41 -4
View File
@@ -2045,7 +2045,7 @@
"noStreamUrlText": "There is no radio_stream_url set, so there is nothing to embed yet.",
"goToSettings": "Go to Radio Settings",
"livePreview": "Live preview",
"livePreviewText": "This is the bare <audio> player as visitors will see it. Press play to confirm the stream is reachable.",
"livePreviewText": "This is the bare audio player as visitors will see it. Press play to confirm the stream is reachable.",
"source": "Source",
"audioSnippet": "Audio player snippet",
"audioSnippetDesc": "Drop-in HTML5 audio player pointed straight at the stream. Works anywhere plain HTML is allowed.",
@@ -2222,7 +2222,7 @@
"noNowPlayingHint": "no now-playing API set",
"noListenersHint": "no listeners API set",
"endpointDiagnostics": "Endpoint diagnostics",
"diagnosticsLegend": "<span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-green-600/20 text-[var(--color-text)]\">OK</span> reachable · <span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-[var(--color-primary)]/18 text-[var(--color-text)]\">WARN</span> not configured · <span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-red-500/22 text-[var(--color-text)]\">ERROR</span> fetch failed / timed out.",
"diagnosticsLegend": "OK: bereikbaar · WARN: niet ingesteld · ERROR: aanvraag mislukt of time-out.",
"streamUrl": "Stream URL",
"unreachable": "unreachable",
"refresh": "Vernieuwen"
@@ -2297,7 +2297,7 @@
"title": "Meldingen",
"subtitle": "Hotelbrede meldingen verzenden en recente meldingen bekijken",
"sendAlert": "Hotelmelding verzenden",
"recentAlerts": "Recente meldingen",
"recentAlerts": "Recente meldingen ({count})",
"noRecentAlerts": "Geen recente meldingen",
"form": {
"title": "Melding titel",
@@ -2516,7 +2516,7 @@
"tabTest": "Test",
"tabAudit": "Auditlog",
"importPermissions": "Permissies importeren",
"importHint": "Plak een JSON-array met permissie-objecten. Formaat: [{ \"permission\": \"...\", \"minRank\": 7, \"description\": \"...\", \"groupName\": \"...\", \"dependsOn\": \"...\" }]",
"importHint": "Plak een JSON-array met permissie-objecten. Formaat: ['{' \"permission\": \"...\", \"minRank\": 7, \"description\": \"...\", \"groupName\": \"...\", \"dependsOn\": \"...\" '}']",
"importPlaceholder": "Plak JSON hier…",
"import": "Importeren",
"exportTitle": "Permissies exporteren",
@@ -2774,6 +2774,43 @@
"subtitle": "User-facing strings from emulator_settings (commands, wordfilter, pet/matchmaking messages).",
"infoBox": "Configuration settings (ports, timeouts, IDs) do not appear here — use the full settings link. Saved changes are synced to the emulator via RCON.",
"fullSettings": "Full settings"
},
"cms": {
"title": "CMS-vertalingen",
"description": "Bewerk de teksten op de website en in housekeeping. Opgeslagen wijzigingen worden bij de volgende paginalading toegepast en blijven behouden na deployments.",
"export": "JSON exporteren",
"saving": "Opslaan…",
"saveLocale": "{locale} opslaan",
"saved": "Vertalingen opgeslagen en toegepast.",
"readOnly": "Je kunt vertalingen bekijken en exporteren. Bewerken vereist toestemming om instellingen te wijzigen.",
"language": "Taal",
"coverage": "{total} sleutels · {missing} ontbreken · {identical} gelijk aan Engels",
"search": "Zoeken",
"searchPlaceholder": "Sleutel, vertaling of Engelse brontekst…",
"group": "Groep",
"allGroups": "Alle groepen",
"status": "Tonen",
"all": "Alle teksten",
"missing": "Ontbrekende vertalingen",
"identical": "Gelijk aan Engels",
"modified": "Niet-opgeslagen wijzigingen",
"fallbackHint": "Ontbrekende vertalingen gebruiken Engels. Identieke tekst kan bewust zijn gekozen. Laat de namen van variabelen tussen accolades ongewijzigd.",
"unsaved": "{count} niet-opgeslagen wijzigingen in deze taal",
"discard": "Wijzigingen verwerpen",
"discardConfirm": "Niet-opgeslagen wijzigingen in deze taal verwerpen?",
"fixErrors": "Corrigeer {count} ongeldige teksten voordat je opslaat.",
"noResults": "Geen teksten gevonden met deze filters.",
"source": "Engelse brontekst",
"missingPlaceholder": "Vertaling ontbreekt; Engels wordt gebruikt.",
"page": "Pagina {page} van {total} · {count} sleutels",
"previous": "Vorige",
"next": "Volgende",
"leaveWarning": "Je hebt niet-opgeslagen vertalingen. Deze pagina verlaten?",
"conflict": "Deze vertalingen zijn gewijzigd sinds je de editor opende. Exporteer je wijzigingen en herlaad de pagina voordat je opslaat.",
"unknownKey": "Onbekende vertaalsleutel: {key}",
"empty": "Vul een vertaling in voor {key} of verwerp deze wijziging.",
"invalid": "Ongeldige berichtsyntaxis in {key}. Controleer accolades en tekstopmaaktags.",
"variables": "Behoud dezelfde variabelenamen en tekstopmaaktags als de Engelse bron in {key}."
}
},
"hubs": {
+5 -5
View File
@@ -1708,7 +1708,7 @@
"noNowPlayingHint": "ingen API-sett som nå spilles",
"noListenersHint": "ingen lyttere API satt",
"endpointDiagnostics": "Endepunktdiagnostikk",
"diagnosticsLegend": "<span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-green-600/20 text-[var(--color-text)]\">OK</span> tilgjengelig · <span class=\"inline-block text-[0.72rem] font-bold px-2 py-full. bg-[var(--color-primary)]/18 text-[var(--color-text)]\">ADVARSEL</span> ikke konfigurert · <span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-red-500/22 text-[var(>-farge mislyktes] / ER-farge mislyktes ELLER.",
"diagnosticsLegend": "OK: tilgjengelig · WARN: ikke konfigurert · ERROR: forespørselen mislyktes eller fikk tidsavbrudd.",
"streamUrl": "Strøm URL",
"unreachable": "uoppnåelig",
"refresh": "Oppdater"
@@ -1767,7 +1767,7 @@
"noStreamUrlText": "Det er ingen radio_stream_url satt, så det er ingenting å bygge inn ennå.",
"goToSettings": "Gå til Radioinnstillinger",
"livePreview": "Live forhåndsvisning",
"livePreviewText": "Dette er den nakne <audio>-spilleren slik besøkende vil se den. Trykk på play for å bekrefte at strømmen er tilgjengelig.",
"livePreviewText": "Dette er den nakne audio-spilleren slik besøkende vil se den. Trykk på play for å bekrefte at strømmen er tilgjengelig.",
"source": "Kilde",
"audioSnippet": "Utdrag av lydspiller",
"audioSnippetDesc": "Drop-in HTML5-lydspiller pekte rett på strømmen. Fungerer overalt hvor vanlig HTML er tillatt.",
@@ -1998,7 +1998,7 @@
"title": "Varsler",
"subtitle": "Send varsler for hele hotellet og se nylige varsler",
"sendAlert": "Send hotellvarsel",
"recentAlerts": "Nylige varsler",
"recentAlerts": "Nylige varsler ({count})",
"noRecentAlerts": "Ingen nylige varsler",
"form": {
"title": "Varseltittel",
@@ -2209,7 +2209,7 @@
"tabTest": "Test",
"tabAudit": "Audit log",
"importPermissions": "Importer Tillatelser",
"importHint": "Paste a JSON array of Tillatelse objects. Format: [{ \"permission\": \"...\", \"minRank\": 7, \"description\": \"...\", \"groupName\": \"...\", \"dependsOn\": \"...\" }]",
"importHint": "Paste a JSON array of Tillatelse objects. Format: ['{' \"permission\": \"...\", \"minRank\": 7, \"description\": \"...\", \"groupName\": \"...\", \"dependsOn\": \"...\" '}']",
"importPlaceholder": "Paste JSON here…",
"import": "Importer",
"exportTitle": "Export Tillatelser",
@@ -2346,7 +2346,7 @@
"presetsTitle": "Forhåndsinnstillinger for tema",
"presetsSubtitle": "Velg et forhåndsbygd fargevalg",
"applyPreset": "Søk",
"presetApplied": "Forhåndsinnstilling brukt",
"presetApplied": "Forhåndsinnstilling brukt: {preset}",
"customiseTitle": "Egendefinerte farger",
"primary": "Primær",
"secondary": "Sekundær",
+5 -5
View File
@@ -1709,7 +1709,7 @@
"noNowPlayingHint": "brak aktualnie odtwarzanego zestawu API",
"noListenersHint": "brak zestawu API odbiorników",
"endpointDiagnostics": "Diagnostyka punktów końcowych",
"diagnosticsLegend": "<span class=\"inline-block tekst-[0.72rem] czcionka-bold px-2 py-0.5 zaokrąglona-pełna bg-green-600/20 tekst-[var(--color-text)]\">OK</span> osiągalna · <span class=\"inline-block tekst-[0.72rem] czcionka-bold px-2 py-0.5 zaokrąglona-pełna bg-[var(--color-primary)]/18 tekst-[var(--color-text)]\">WARN</span> nie skonfigurowano · <span class=\"inline-block text-[0.72rem] Font-bold px-2 py-0.5 zaokrąglony-pełny bg-red-500/22 tekst-[var(--color-text)]\">BŁĄD</span> pobieranie nie powiodło się / przekroczyło czas na zewnątrz.",
"diagnosticsLegend": "OK: dostępne · WARN: nieskonfigurowane · ERROR: błąd żądania lub przekroczony limit czasu.",
"streamUrl": "Adres URL strumienia",
"unreachable": "nieosiągalny",
"refresh": "Odśwież"
@@ -1768,7 +1768,7 @@
"noStreamUrlText": "Nie ma ustawionego radio_stream_url, więc nie ma jeszcze czego osadzać.",
"goToSettings": "Przejdź do Ustawień radia",
"livePreview": "Podgląd na żywo",
"livePreviewText": "To jest goły odtwarzacz <audio>, jaki zobaczą odwiedzający. Naciśnij przycisk odtwarzania, aby potwierdzić, że strumień jest osiągalny.",
"livePreviewText": "To jest goły odtwarzacz audio, jaki zobaczą odwiedzający. Naciśnij przycisk odtwarzania, aby potwierdzić, że strumień jest osiągalny.",
"source": "Źródło",
"audioSnippet": "Fragment odtwarzacza audio",
"audioSnippetDesc": "Wpuszczany odtwarzacz audio HTML5 skierowany bezpośrednio na strumień. Działa wszędzie tam, gdzie dozwolony jest zwykły HTML.",
@@ -1999,7 +1999,7 @@
"title": "Alerty",
"subtitle": "Wysyłaj alerty dotyczące całego hotelu i przeglądaj najnowsze alerty",
"sendAlert": "Wyślij alert hotelowy",
"recentAlerts": "Ostatnie alerty",
"recentAlerts": "Ostatnie alerty ({count})",
"noRecentAlerts": "Brak ostatnich alertów",
"form": {
"title": "Tytuł alertu",
@@ -2210,7 +2210,7 @@
"tabTest": "Test",
"tabAudit": "Audit log",
"importPermissions": "Importuj Uprawnienia",
"importHint": "Paste a JSON array of Pozwolenie objects. Format: [{ \"permission\": \"...\", \"minRank\": 7, \"description\": \"...\", \"groupName\": \"...\", \"dependsOn\": \"...\" }]",
"importHint": "Paste a JSON array of Pozwolenie objects. Format: ['{' \"permission\": \"...\", \"minRank\": 7, \"description\": \"...\", \"groupName\": \"...\", \"dependsOn\": \"...\" '}']",
"importPlaceholder": "Paste JSON here…",
"import": "Importuj",
"exportTitle": "Export Uprawnienia",
@@ -2347,7 +2347,7 @@
"presetsTitle": "Wstępne ustawienia motywu",
"presetsSubtitle": "Wybierz gotowy schemat kolorów",
"applyPreset": "Zastosuj",
"presetApplied": "Zastosowano ustawienie wstępne",
"presetApplied": "Zastosowano ustawienie wstępne: {preset}",
"customiseTitle": "Kolory niestandardowe",
"primary": "Podstawowy",
"secondary": "Drugorzędne",
+5 -5
View File
@@ -1709,7 +1709,7 @@
"noNowPlayingHint": "nenhum conjunto de API em execução",
"noListenersHint": "nenhuma API de ouvinte definida",
"endpointDiagnostics": "Diagnóstico de endpoint",
"diagnosticsLegend": "<span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 round-full bg-green-600/20 text-[var(--color-text)]\">OK</span> alcançável · <span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 arredondado-completo bg-[var(--color-primary)]/18 text-[var(--color-text)]\">WARN</span> não configurado · <span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 round-full bg-red-500/22 text-[var(--color-text)]\">ERROR</span> busca falhou/expirou o tempo limite.",
"diagnosticsLegend": "OK: acessível · WARN: não configurado · ERROR: falha no pedido ou tempo esgotado.",
"streamUrl": "URL de transmissão",
"unreachable": "inacessível",
"refresh": "Atualizar"
@@ -1768,7 +1768,7 @@
"noStreamUrlText": "Não há radio_stream_url definido, então não há nada para incorporar ainda.",
"goToSettings": "Vá para configurações de rádio",
"livePreview": "Visualização ao vivo",
"livePreviewText": "Este é o player <audio> simples, como os visitantes o verão. Pressione play para confirmar que o stream está acessível.",
"livePreviewText": "Este é o player audio simples, como os visitantes o verão. Pressione play para confirmar que o stream está acessível.",
"source": "Fonte",
"audioSnippet": "Snippet do player de áudio",
"audioSnippetDesc": "Reprodutor de áudio HTML5 drop-in apontado diretamente para o stream. Funciona em qualquer lugar onde HTML simples seja permitido.",
@@ -1999,7 +1999,7 @@
"title": "Alertas",
"subtitle": "Envie alertas para todo o hotel e visualize alertas recentes",
"sendAlert": "Enviar alerta de hotel",
"recentAlerts": "Alertas recentes",
"recentAlerts": "Alertas recentes ({count})",
"noRecentAlerts": "Nenhum alerta recente",
"form": {
"title": "Título do alerta",
@@ -2210,7 +2210,7 @@
"tabTest": "Test",
"tabAudit": "Audit log",
"importPermissions": "Importar Permissões",
"importHint": "Paste a JSON array of Permissão objects. Format: [{ \"permission\": \"...\", \"minRank\": 7, \"description\": \"...\", \"groupName\": \"...\", \"dependsOn\": \"...\" }]",
"importHint": "Paste a JSON array of Permissão objects. Format: ['{' \"permission\": \"...\", \"minRank\": 7, \"description\": \"...\", \"groupName\": \"...\", \"dependsOn\": \"...\" '}']",
"importPlaceholder": "Paste JSON here…",
"import": "Importar",
"exportTitle": "Export Permissões",
@@ -2347,7 +2347,7 @@
"presetsTitle": "Predefinições de tema",
"presetsSubtitle": "Escolha um esquema de cores pré-construído",
"applyPreset": "Aplicar",
"presetApplied": "Predefinição aplicada",
"presetApplied": "Predefinição aplicada: {preset}",
"customiseTitle": "Cores personalizadas",
"primary": "Primário",
"secondary": "Secundário",
+5 -5
View File
@@ -1708,7 +1708,7 @@
"noNowPlayingHint": "nici un set API care se joacă acum",
"noListenersHint": "nu se setează API pentru ascultători",
"endpointDiagnostics": "Diagnosticarea punctului final",
"diagnosticsLegend": "<span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-green-600/20 text-[var(--color-text)]\">OK</span> accesibil · <span class=\"inline-block text-[0.72rem] font-bold-full-pxl.5 bg-[var(--color-primary)]/18 text-[var(--color-text)]\">AVERTISMENT</span> neconfigurat · <span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-red-500/22 text-[var(]> text-[var](---> text-[var](--) eșuat-[var (---> text-[var](--)",
"diagnosticsLegend": "OK: accesibil · WARN: neconfigurat · ERROR: cerere eșuată sau timp expirat.",
"streamUrl": "Adresa URL a fluxului",
"unreachable": "de neatins",
"refresh": "Reîmprospătați"
@@ -1767,7 +1767,7 @@
"noStreamUrlText": "Nu există nicio setare radio_stream_url, așa că încă nu există nimic de încorporat.",
"goToSettings": "Accesați Setările radio",
"livePreview": "Previzualizare live",
"livePreviewText": "Acesta este playerul <audio> gol, așa cum îl vor vedea vizitatorii. Apăsați pe redare pentru a confirma că fluxul este accesibil.",
"livePreviewText": "Acesta este playerul audio gol, așa cum îl vor vedea vizitatorii. Apăsați pe redare pentru a confirma că fluxul este accesibil.",
"source": "Sursa",
"audioSnippet": "Fragment de player audio",
"audioSnippetDesc": "Playerul audio HTML5 integrat a îndreptat direct spre flux. Funcționează oriunde este permis HTML simplu.",
@@ -1998,7 +1998,7 @@
"title": "Alerte",
"subtitle": "Trimiteți alerte la nivel de hotel și vizualizați alertele recente",
"sendAlert": "Trimiteți o alertă la hotel",
"recentAlerts": "Alerte recente",
"recentAlerts": "Alerte recente ({count})",
"noRecentAlerts": "Nu există alerte recente",
"form": {
"title": "Titlul alertei",
@@ -2209,7 +2209,7 @@
"tabTest": "Test",
"tabAudit": "Audit log",
"importPermissions": "Import Permisiuni",
"importHint": "Paste a JSON array of Permisiune objects. Format: [{ \"permission\": \"...\", \"minRank\": 7, \"description\": \"...\", \"groupName\": \"...\", \"dependsOn\": \"...\" }]",
"importHint": "Paste a JSON array of Permisiune objects. Format: ['{' \"permission\": \"...\", \"minRank\": 7, \"description\": \"...\", \"groupName\": \"...\", \"dependsOn\": \"...\" '}']",
"importPlaceholder": "Paste JSON here…",
"import": "Import",
"exportTitle": "Export Permisiuni",
@@ -2346,7 +2346,7 @@
"presetsTitle": "presetări teme",
"presetsSubtitle": "Alegeți o schemă de culori prefabricată",
"applyPreset": "Aplicați",
"presetApplied": "Presetarea aplicată",
"presetApplied": "Presetarea aplicată: {preset}",
"customiseTitle": "Culori personalizate",
"primary": "Primar",
"secondary": "Secundar",
+5 -5
View File
@@ -1707,7 +1707,7 @@
"noNowPlayingHint": "no now-playing API set",
"noListenersHint": "no listeners API set",
"endpointDiagnostics": "Endpoint diagnostics",
"diagnosticsLegend": "<span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-green-600/20 text-[var(--color-text)]\">OK</span> reachable · <span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-[var(--color-primary)]/18 text-[var(--color-text)]\">WARN</span> not configured · <span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-red-500/22 text-[var(--color-text)]\">ERROR</span> fetch failed / timed out.",
"diagnosticsLegend": "OK: доступно · WARN: не настроено · ERROR: ошибка запроса или истекло время ожидания.",
"streamUrl": "Stream URL",
"unreachable": "unreachable",
"refresh": "Refresh"
@@ -1766,7 +1766,7 @@
"noStreamUrlText": "There is no radio_stream_url set, so there is nothing to embed yet.",
"goToSettings": "Go to Radio Settings",
"livePreview": "Live preview",
"livePreviewText": "This is the bare <audio> player as visitors will see it. Press play to confirm the stream is reachable.",
"livePreviewText": "This is the bare audio player as visitors will see it. Press play to confirm the stream is reachable.",
"source": "Source",
"audioSnippet": "Audio player snippet",
"audioSnippetDesc": "Drop-in HTML5 audio player pointed straight at the stream. Works anywhere plain HTML is allowed.",
@@ -1997,7 +1997,7 @@
"title": "Alerts",
"subtitle": "Send hotel-wide alerts and view recent alerts",
"sendAlert": "Send hotel alert",
"recentAlerts": "Recent alerts",
"recentAlerts": "Recent alerts ({count})",
"noRecentAlerts": "No recent alerts",
"form": {
"title": "Alert title",
@@ -2208,7 +2208,7 @@
"tabTest": "Test",
"tabAudit": "Audit log",
"importPermissions": "Импорт Разрешения",
"importHint": "Paste a JSON array of permission objects. Format: [{ \"permission\": \"...\", \"minRank\": 7, \"description\": \"...\", \"groupName\": \"...\", \"dependsOn\": \"...\" }]",
"importHint": "Paste a JSON array of permission objects. Format: ['{' \"permission\": \"...\", \"minRank\": 7, \"description\": \"...\", \"groupName\": \"...\", \"dependsOn\": \"...\" '}']",
"importPlaceholder": "Paste JSON here…",
"import": "Импорт",
"exportTitle": "Export Разрешения",
@@ -2345,7 +2345,7 @@
"presetsTitle": "Theme presets",
"presetsSubtitle": "Choose a pre-built colour scheme",
"applyPreset": "Apply",
"presetApplied": "Preset applied",
"presetApplied": "Preset applied: {preset}",
"customiseTitle": "Custom colours",
"primary": "Primary",
"secondary": "Secondary",
+5 -5
View File
@@ -1708,7 +1708,7 @@
"noNowPlayingHint": "nie je teraz nastavené rozhranie API",
"noListenersHint": "nie je nastavené žiadne rozhranie API poslucháčov",
"endpointDiagnostics": "Diagnostika koncového bodu",
"diagnosticsLegend": "<span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-green-600/20 text-[var(--color-text)]\">OK</span> dosiahnuteľné · <span class=\"inline-block text-[0.72rem] font-bold px-2 py-primaryl]-1 bg-8 rounded text-[var(--color-text)]\">UPOZORNENIE</span> nie je nakonfigurované · <span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-red-500/22 text-[var(--color-text)]\">CHYBA</span> načítanie zlyhalo / vypršal časový limit.",
"diagnosticsLegend": "OK: dostupné · WARN: nenastavené · ERROR: požiadavka zlyhala alebo vypršal časový limit.",
"streamUrl": "Webová adresa streamu",
"unreachable": "nedosiahnuteľný",
"refresh": "Obnoviť"
@@ -1767,7 +1767,7 @@
"noStreamUrlText": "Nie je nastavená žiadna adresa_url rádia, takže zatiaľ nie je čo vkladať.",
"goToSettings": "Prejdite do časti Nastavenia rádia",
"livePreview": "Živá ukážka",
"livePreviewText": "Toto je holý <audio> prehrávač, ako ho uvidia návštevníci. Stlačením tlačidla prehrávania potvrďte, že stream je dostupný.",
"livePreviewText": "Toto je holý audio prehrávač, ako ho uvidia návštevníci. Stlačením tlačidla prehrávania potvrďte, že stream je dostupný.",
"source": "Zdroj",
"audioSnippet": "Útržok zvukového prehrávača",
"audioSnippetDesc": "Zvukový prehrávač HTML5 nasmerovaný priamo na stream. Funguje všade, kde je povolený obyčajný HTML.",
@@ -1998,7 +1998,7 @@
"title": "Upozornenia",
"subtitle": "Odosielajte upozornenia pre celý hotel a zobrazujte posledné upozornenia",
"sendAlert": "Odoslať upozornenie na hotel",
"recentAlerts": "Nedávne upozornenia",
"recentAlerts": "Nedávne upozornenia ({count})",
"noRecentAlerts": "Žiadne nedávne upozornenia",
"form": {
"title": "Názov upozornenia",
@@ -2209,7 +2209,7 @@
"tabTest": "Test",
"tabAudit": "Audit log",
"importPermissions": "Importovať Povolenia",
"importHint": "Paste a JSON array of Povolenie objects. Format: [{ \"permission\": \"...\", \"minRank\": 7, \"description\": \"...\", \"groupName\": \"...\", \"dependsOn\": \"...\" }]",
"importHint": "Paste a JSON array of Povolenie objects. Format: ['{' \"permission\": \"...\", \"minRank\": 7, \"description\": \"...\", \"groupName\": \"...\", \"dependsOn\": \"...\" '}']",
"importPlaceholder": "Paste JSON here…",
"import": "Importovať",
"exportTitle": "Export Povolenia",
@@ -2346,7 +2346,7 @@
"presetsTitle": "Prednastavenia tém",
"presetsSubtitle": "Vyberte si vopred zostavenú farebnú schému",
"applyPreset": "Použiť",
"presetApplied": "Predvoľba bola použitá",
"presetApplied": "Predvoľba bola použitá: {preset}",
"customiseTitle": "Vlastné farby",
"primary": "Primárne",
"secondary": "Sekundárne",
+5 -5
View File
@@ -1708,7 +1708,7 @@
"noNowPlayingHint": "нема скупа АПИ-ја који се тренутно репродукује",
"noListenersHint": "није постављен АПИ слушалаца",
"endpointDiagnostics": "Ендпоинт диагностицс",
"diagnosticsLegend": "<span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-green-600/20 text-[var(--color-text)]\">ОК</span> доступан · <span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-[var(--color-primary)]/18 text-[var(--color-text)]\">УПОЗОРЕЊЕ</span> није конфигурисано · <span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-red-500/22 text-[var(--color-text)]\">ERROR</span> преузимање није успело / време је истекло.",
"diagnosticsLegend": "OK: доступно · WARN: није подешено · ERROR: захтев није успео или је истекао.",
"streamUrl": "УРЛ стрима",
"unreachable": "недостижан",
"refresh": "Освежи"
@@ -1767,7 +1767,7 @@
"noStreamUrlText": "Радио_стреам_урл није постављен, тако да још нема шта да се угради.",
"goToSettings": "Идите на Подешавања радија",
"livePreview": "Преглед уживо",
"livePreviewText": "Ово је голи <audio> плејер како ће га посетиоци видети. Притисните плаи да бисте потврдили да је стрим доступан.",
"livePreviewText": "Ово је голи audio плејер како ће га посетиоци видети. Притисните плаи да бисте потврдили да је стрим доступан.",
"source": "Извор",
"audioSnippet": "Исечак аудио плејера",
"audioSnippetDesc": "Улазни ХТМЛ5 аудио плејер усмерен право на ток. Ради свуда где је обичан ХТМЛ дозвољен.",
@@ -1998,7 +1998,7 @@
"title": "Алертс",
"subtitle": "Шаљите упозорења широм хотела и погледајте недавна упозорења",
"sendAlert": "Пошаљите хотелско обавештење",
"recentAlerts": "Недавна упозорења",
"recentAlerts": "Недавна упозорења ({count})",
"noRecentAlerts": "Нема недавних упозорења",
"form": {
"title": "Наслов упозорења",
@@ -2209,7 +2209,7 @@
"tabTest": "Test",
"tabAudit": "Audit log",
"importPermissions": "Увоз Дозволе",
"importHint": "Paste a JSON array of Дозвола objects. Format: [{ \"permission\": \"...\", \"minRank\": 7, \"description\": \"...\", \"groupName\": \"...\", \"dependsOn\": \"...\" }]",
"importHint": "Paste a JSON array of Дозвола objects. Format: ['{' \"permission\": \"...\", \"minRank\": 7, \"description\": \"...\", \"groupName\": \"...\", \"dependsOn\": \"...\" '}']",
"importPlaceholder": "Paste JSON here…",
"import": "Увоз",
"exportTitle": "Export Дозволе",
@@ -2346,7 +2346,7 @@
"presetsTitle": "Унапред подешене теме",
"presetsSubtitle": "Choose a pre-built colour scheme",
"applyPreset": "Пријавите се",
"presetApplied": "Пресет је примењен",
"presetApplied": "Пресет је примењен: {preset}",
"customiseTitle": "Прилагођене боје",
"primary": "Примарни",
"secondary": "Секундарни",
+5 -5
View File
@@ -1708,7 +1708,7 @@
"noNowPlayingHint": "ingen nu-spelande API-uppsättning",
"noListenersHint": "ingen lyssnars API inställd",
"endpointDiagnostics": "Slutpunktsdiagnostik",
"diagnosticsLegend": "<span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-green-600/20 text-[var(--color-text)]\">OK</span> nås · <span class=\"inline-block text-[0.72rem] font-bold px-2 py-full. bg-[var(--color-primary)]/18 text-[var(--color-text)]\">VARNING</span> inte konfigurerad · <span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-red-500/22 text-[var(--color-text) /hämtad tid utebliven text-[var(--färg-R-text])",
"diagnosticsLegend": "OK: tillgänglig · WARN: inte konfigurerad · ERROR: begäran misslyckades eller tog för lång tid.",
"streamUrl": "Streama URL",
"unreachable": "oåtkomlig",
"refresh": "Uppdatera"
@@ -1767,7 +1767,7 @@
"noStreamUrlText": "Det finns ingen radio_stream_url inställd, så det finns inget att bädda in ännu.",
"goToSettings": "Gå till Radioinställningar",
"livePreview": "Live förhandsvisning",
"livePreviewText": "Detta är den blotta <audio>-spelaren som besökare kommer att se den. Tryck på play för att bekräfta att streamen är tillgänglig.",
"livePreviewText": "Detta är den blotta audio-spelaren som besökare kommer att se den. Tryck på play för att bekräfta att streamen är tillgänglig.",
"source": "Källa",
"audioSnippet": "Utdrag från ljudspelare",
"audioSnippetDesc": "Drop-in HTML5-ljudspelare pekade rakt mot strömmen. Fungerar överallt där vanlig HTML är tillåtet.",
@@ -1998,7 +1998,7 @@
"title": "Varningar",
"subtitle": "Skicka varningar för hela hotellet och visa de senaste varningarna",
"sendAlert": "Skicka hotellvarning",
"recentAlerts": "Senaste varningar",
"recentAlerts": "Senaste varningar ({count})",
"noRecentAlerts": "Inga senaste varningar",
"form": {
"title": "Varningsrubrik",
@@ -2209,7 +2209,7 @@
"tabTest": "Test",
"tabAudit": "Audit log",
"importPermissions": "Importera Behörigheter",
"importHint": "Paste a JSON array of Tillstånd objects. Format: [{ \"permission\": \"...\", \"minRank\": 7, \"description\": \"...\", \"groupName\": \"...\", \"dependsOn\": \"...\" }]",
"importHint": "Paste a JSON array of Tillstånd objects. Format: ['{' \"permission\": \"...\", \"minRank\": 7, \"description\": \"...\", \"groupName\": \"...\", \"dependsOn\": \"...\" '}']",
"importPlaceholder": "Paste JSON here…",
"import": "Importera",
"exportTitle": "Export Behörigheter",
@@ -2346,7 +2346,7 @@
"presetsTitle": "Förinställda tema",
"presetsSubtitle": "Välj ett förbyggt färgschema",
"applyPreset": "Ansök",
"presetApplied": "Förinställning tillämpas",
"presetApplied": "Förinställning tillämpas: {preset}",
"customiseTitle": "Anpassade färger",
"primary": "Primär",
"secondary": "Sekundär",
+5 -5
View File
@@ -1709,7 +1709,7 @@
"noNowPlayingHint": "şu anda oynatılan API seti yok",
"noListenersHint": "dinleyici API seti yok",
"endpointDiagnostics": "Uç nokta teşhisi",
"diagnosticsLegend": "<span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-green-600/20 text-[var(--color-text)]\">Tamam</span> ulaşılabilir · <span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-[var(--color-primary)]/18 text-[var(--color-text)]\">WARN</span> yapılandırılmadı · <span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-red-500/22 text-[var(--color-text)]\">ERROR</span> getirme başarısız oldu / zaman aşımına uğradı.",
"diagnosticsLegend": "OK: erişilebilir · WARN: yapılandırılmamış · ERROR: istek başarısız veya zaman aşımına uğradı.",
"streamUrl": "Akış URL'si",
"unreachable": "ulaşılamaz",
"refresh": "Yenile"
@@ -1768,7 +1768,7 @@
"noStreamUrlText": "Radio_stream_url kümesi yok, bu nedenle henüz eklenecek bir şey yok.",
"goToSettings": "Radyo Ayarlarına Git",
"livePreview": "Canlı önizleme",
"livePreviewText": "Bu, ziyaretçilerin göreceği şekliyle çıplak <audio> oynatıcısıdır. Akışın erişilebilir olduğunu onaylamak için oynat tuşuna basın.",
"livePreviewText": "Bu, ziyaretçilerin göreceği şekliyle çıplak audio oynatıcısıdır. Akışın erişilebilir olduğunu onaylamak için oynat tuşuna basın.",
"source": "Kaynak",
"audioSnippet": "Ses oynatıcı snippet'i",
"audioSnippetDesc": "Açılan HTML5 ses oynatıcısı doğrudan akışa işaret ediyordu. Düz HTML'ye izin verilen her yerde çalışır.",
@@ -1999,7 +1999,7 @@
"title": "Uyarılar",
"subtitle": "Otel genelinde uyarılar gönderin ve son uyarıları görüntüleyin",
"sendAlert": "Otel uyarısı gönder",
"recentAlerts": "Son uyarılar",
"recentAlerts": "Son uyarılar ({count})",
"noRecentAlerts": "Yeni uyarı yok",
"form": {
"title": "Uyarı başlığı",
@@ -2210,7 +2210,7 @@
"tabTest": "Test",
"tabAudit": "Audit log",
"importPermissions": "İthalat İzinler",
"importHint": "Paste a JSON array of İzin objects. Format: [{ \"permission\": \"...\", \"minRank\": 7, \"description\": \"...\", \"groupName\": \"...\", \"dependsOn\": \"...\" }]",
"importHint": "Paste a JSON array of İzin objects. Format: ['{' \"permission\": \"...\", \"minRank\": 7, \"description\": \"...\", \"groupName\": \"...\", \"dependsOn\": \"...\" '}']",
"importPlaceholder": "Paste JSON here…",
"import": "İthalat",
"exportTitle": "Export İzinler",
@@ -2347,7 +2347,7 @@
"presetsTitle": "Tema ön ayarları",
"presetsSubtitle": "Önceden oluşturulmuş bir renk şeması seçin",
"applyPreset": "Uygula",
"presetApplied": "Ön ayar uygulandı",
"presetApplied": "Ön ayar uygulandı: {preset}",
"customiseTitle": "Özel renkler",
"primary": "Birincil",
"secondary": "İkincil",
+5 -5
View File
@@ -1708,7 +1708,7 @@
"noNowPlayingHint": "не налаштовано API поточного відтворення",
"noListenersHint": "API слухачів не встановлено",
"endpointDiagnostics": "Діагностика кінцевої точки",
"diagnosticsLegend": "<span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-green-600/20 text-[var(--color-text)]\">OK</span> доступно · <span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-[var(--color-primary)]/18 text-[var(--color-text)]\">WARN</span> не налаштовано · <span class=\"inline-block text-[0.72rem] font-bold px-2 py-0.5 rounded-full bg-red-500/22 text-[var(--color-text)]\">ПОМИЛКА</span> вибірка не вдалася / час очікування минув.",
"diagnosticsLegend": "OK: доступно · WARN: не налаштовано · ERROR: помилка запиту або вичерпано час очікування.",
"streamUrl": "URL-адреса потоку",
"unreachable": "недоступний",
"refresh": "Оновити"
@@ -1767,7 +1767,7 @@
"noStreamUrlText": "Немає параметра radio_stream_url, тому поки що немає чого вставляти.",
"goToSettings": "Перейдіть до налаштувань радіо",
"livePreview": "Попередній перегляд",
"livePreviewText": "Це простий програвач <audio>, як його побачать відвідувачі. Натисніть кнопку відтворення, щоб підтвердити, що потік доступний.",
"livePreviewText": "Це простий програвач audio, як його побачать відвідувачі. Натисніть кнопку відтворення, щоб підтвердити, що потік доступний.",
"source": "Джерело",
"audioSnippet": "Фрагмент аудіоплеєра",
"audioSnippetDesc": "Вставний аудіопрогравач HTML5 спрямований прямо на потік. Працює скрізь, де дозволено простий HTML.",
@@ -1998,7 +1998,7 @@
"title": "Оповіщення",
"subtitle": "Надсилайте сповіщення для всього готелю та переглядайте останні сповіщення",
"sendAlert": "Надіслати сповіщення готелю",
"recentAlerts": "Останні сповіщення",
"recentAlerts": "Останні сповіщення ({count})",
"noRecentAlerts": "Немає останніх сповіщень",
"form": {
"title": "Назва сповіщення",
@@ -2209,7 +2209,7 @@
"tabTest": "Test",
"tabAudit": "Audit log",
"importPermissions": "Імпорт Дозволи",
"importHint": "Paste a JSON array of Дозвіл objects. Format: [{ \"permission\": \"...\", \"minRank\": 7, \"description\": \"...\", \"groupName\": \"...\", \"dependsOn\": \"...\" }]",
"importHint": "Paste a JSON array of Дозвіл objects. Format: ['{' \"permission\": \"...\", \"minRank\": 7, \"description\": \"...\", \"groupName\": \"...\", \"dependsOn\": \"...\" '}']",
"importPlaceholder": "Paste JSON here…",
"import": "Імпорт",
"exportTitle": "Export Дозволи",
@@ -2346,7 +2346,7 @@
"presetsTitle": "Попередні налаштування теми",
"presetsSubtitle": "Виберіть попередньо створену колірну схему",
"applyPreset": "Застосувати",
"presetApplied": "Попереднє налаштування застосовано",
"presetApplied": "Попереднє налаштування застосовано: {preset}",
"customiseTitle": "Нестандартні кольори",
"primary": "Первинний",
"secondary": "Вторинний",