Translations: settings schema overlays (ru/de/fr/es), diagnostics, update steps, companion sizes, friends notes; dev mock for settings pages; docs/TRANSLATIONS.md

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ДмитрийandClaude Sonnet 5.5 committed 2026-09-30 21:32:15 +05:00
1 parent a4b9ae81c7
commit 38b5b86be1
22 files changed
+2552 -33

No files matched your search

+5 -3
View File
@@ -484,9 +484,11 @@ The reference install classifies as **Healthy (customised)**: repack shape, bina
translations are reviewed by native speakers before release, with an English fallback for missing keys and a CI check
that every locale has every key. Do this before the first public release so strings are not retro-fitted.
**Status (2026-09-30):** catalogue + picker + all screens translated (en/ru/de/fr/es drafts, `tools/check-i18n.mjs` checks
keys/placeholders). Still English: settings-schema titles/descriptions/presets, companion size names, diagnostics check
texts and update step names coming from the backend (need `schemas/i18n/<locale>.json` overlays and code-keyed backend strings).
**Status (2026-09-30, updated):** catalogue + picker + every screen translated (en/ru/de/fr/es drafts); settings-schema titles,
descriptions, categories, presets, options and units are translated through overlays in `src/i18n/schema/`; diagnostics
checks, update steps, companion sizes and friends notes are recognised and translated in the UI. `tools/check-i18n.mjs`
verifies keys, placeholders and overlay coverage. Still English: free-form backend technical messages. Native-speaker
review is still needed (see `docs/TRANSLATIONS.md`).
* **Language choice on the very first screen** (requested 2026-09-30): the welcome screen must offer the language picker
right away (before any other text is read), preselected from the OS language; the choice is persisted and shared with
+36
View File
@@ -0,0 +1,36 @@
# Translations
The Manager interface exists in English (source), Russian, German, French and Spanish. Everything except English is a
**draft written by an AI assistant and needs review by a native speaker** before it is treated as final.
## Where the texts are
| What | File | Notes |
|---|---|---|
| Interface texts | `src/i18n/locales/<lang>.ts` | flat `"key": "text"` lists; `en.ts` is the source of truth, other languages fall back to it |
| Setting titles, descriptions, categories, presets, units, option labels | `src/i18n/schema/<lang>.json` | generated once from `schemas/*.json`; edit the JSON directly |
| Backend error messages | keys `err.<code>.title` / `err.<code>.message` in the locale files | codes come from `crates/coa-core/src/error.rs` |
`{name}` placeholders must stay exactly as they are. Plural forms use the suffixes `_one`, `_few`, `_many`, `_other`
(the browser picks the right one for the language).
## Checking your work
```
node tools/check-i18n.mjs # unknown keys, changed placeholders, coverage, schema overlays complete
npx tsc --noEmit # types
npx vite # UI with mock data in a browser; pick the language on the welcome screen
```
## Adding a language
1. Copy `src/i18n/locales/en.ts` to `<lang>.ts` (keep the keys), translate, and register it in `src/i18n/index.tsx`
(`Locale`, `LOCALES`, `DICTS`).
2. Copy `src/i18n/schema/ru.json` to `<lang>.json`, translate the values, and register it in `SCHEMA`.
3. Run the check above.
## Known gaps
* Free-form technical messages produced deep inside the backend (`Error::Invalid("...")`) stay in English; they are
shown as "technical details".
* Texts inside the game itself (client, server data) are not part of the Manager.
+11 -1
View File
@@ -1,4 +1,8 @@
// Dev-only: lets the UI run in a plain browser for visual review. Never bundled in production builds.
import botsSchema from "../../schemas/bots.json";
import serverSchema from "../../schemas/server.json";
import botsPresets from "../../schemas/presets/bots.json";
import serverPresets from "../../schemas/presets/server.json";
export {};
(() => {
@@ -14,7 +18,7 @@ export {};
const svc=(n: string,p: number,st: string)=>({name:n,state:st,pid:st==="running"?100:null,port:p,port_ready:st==="running",conflict:null,uptime_secs:st==="running"?4206:null});
(window as any).__TAURI_INTERNALS__ = {
transformCallback: (cb: unknown)=>cb, unregisterCallback(){}, convertFileSrc:(x: unknown)=>x,
invoke: async (cmd: string)=>{
invoke: async (cmd: string, args?: any)=>{
if(cmd==="list_servers") return added?[{id:"1",name:"CoA-Repack",path:report.path}]:[];
if(cmd==="scan_server") return report;
if(cmd==="add_server"){added=true;return {id:"1",name:"CoA-Repack",path:report.path};}
@@ -22,6 +26,12 @@ export {};
if(cmd==="start_server"){await new Promise((r: any)=>setTimeout(r,1500));running=true;return {ok:true,exit_code:0,code:null,human:null,output:""};}
if(cmd==="stop_server"){await new Promise((r: any)=>setTimeout(r,1500));running=false;return {ok:true,exit_code:0,code:null,human:null,output:""};}
if(cmd.startsWith("plugin:dialog")) return "C:\games\CoA-Repack";
if(cmd==="get_settings"){const sc=args?.scope==="server"?serverSchema:botsSchema;return {scope:args?.scope,categories:sc.categories,settings:(sc.settings as any[]).map(x=>({...x,options:x.options??[],value:x.default,is_default:true,present:true,problem:null,drift:false})),unknown_keys:3,drift_keys:[],files:[]};}
if(cmd==="list_presets"){const pr=args?.scope==="server"?serverPresets:botsPresets;return (pr.presets as any[]).map(p=>({id:p.id,title:p.title,description:p.description}));}
if(cmd==="preview_preset"){const a=args;const sc=a.scope==="server"?serverSchema:botsSchema;const pr=(a.scope==="server"?serverPresets:botsPresets).presets as any[];const p=pr.find(x=>x.id===a.preset)??{id:a.preset??"defaults",title:"Recommended defaults",description:"Every setting returns to its default value."};const st=(sc.settings as any[]).filter(x=>x.type==="int"||x.type==="float"||x.type==="enum").slice(0,3);return {id:p.id,title:p.title,description:p.description,changes:st.map(x=>({key:x.key,title:x.title,from:x.default,to:x.type==="enum"?(x.options?.[1]?.value??x.default):(Number(x.default)||1)*2,dangerous:false}))};}
if(cmd==="companion_sizes") return {hardware:{cores:16,ram_gb:31,free_ram_gb:12},sizes:[{id:"small",title:"Small",bots:50,warning:null},{id:"medium",title:"Medium",bots:250,warning:null},{id:"large",title:"Large",bots:500,warning:"500 companions may need more CPU and memory than this computer has (8 cores, 16 GB)."}]};
if(cmd==="get_population") return null;
if(cmd==="run_diagnostics") return {problems:2,checks:[{id:"files",title:"Server files",level:"ok",detail:"All expected server parts were found."},{id:"world",title:"World server",level:"warn",detail:"Not running."},{id:"disk",title:"Free disk space",level:"warn",detail:"Only 3 GB free; backups and updates need room."},{id:"exposure",title:"Private services",level:"ok",detail:"Database and server console are not reachable from the network."}]};
if(cmd==="plugin:event|listen") return 1;
if(cmd==="plugin:event|unlisten") return null;
throw new Error("unmocked "+cmd);
+30
View File
@@ -4,6 +4,10 @@ import ru from "./locales/ru";
import de from "./locales/de";
import fr from "./locales/fr";
import es from "./locales/es";
import ruSchema from "./schema/ru.json";
import deSchema from "./schema/de.json";
import frSchema from "./schema/fr.json";
import esSchema from "./schema/es.json";
export type Locale = "en" | "ru" | "de" | "fr" | "es";
export type Key = keyof typeof en;
@@ -83,6 +87,29 @@ export function I18nProvider({ children }: { children: ReactNode }) {
export const useI18n = () => useContext(I18n);
/** Translated text of the settings schemas (titles, descriptions, categories, presets, units). English comes from the backend. */
interface SchemaOverlay {
categories: Record<string, string>;
units: Record<string, string>;
settings: Record<string, { t: string; d: string }>;
options: Record<string, string>;
presets: Record<string, { t: string; d: string }>;
}
const SCHEMA: Partial<Record<Locale, SchemaOverlay>> = { ru: ruSchema, de: deSchema, fr: frSchema, es: esSchema };
export function useSchemaText() {
const { locale } = useContext(I18n);
const o = SCHEMA[locale];
return {
title: (s: { key: string; title: string }) => o?.settings[s.key]?.t ?? s.title,
description: (s: { key: string; description: string }) => o?.settings[s.key]?.d ?? s.description,
category: (scope: string, c: { id: string; title: string }) => o?.categories[`${scope}.${c.id}`] ?? c.title,
option: (key: string, opt: { value: unknown; label: string }) => o?.options[`${key}.${String(opt.value)}`] ?? opt.label,
unit: (u: string) => o?.units[u] ?? u,
preset: (p: { id: string; title: string; description: string }) => ({ title: o?.presets[p.id]?.t ?? p.title, description: o?.presets[p.id]?.d ?? p.description }),
};
}
/** Translated title/message for a backend error; falls back to the backend's English text for unknown codes. */
export function useHuman() {
const { t } = useContext(I18n);
@@ -93,3 +120,6 @@ export function useHuman() {
};
}
export const useT = () => useContext(I18n).t;
/** True when `key` exists in the catalogue (backend-supplied texts are translated only when a key is defined for them). */
export const hasKey = (key: string): key is Key => key in en;
+64
View File
@@ -433,6 +433,70 @@ const de: Partial<Record<keyof typeof en, string>> = {
// Friends line
"fr.copyLine": "Zeile kopieren",
// Diagnostics checks
"dg.title.files": "Serverdateien",
"dg.title.missing_parts": "Fehlende Teile",
"dg.title.mysql": "Datenbank",
"dg.title.auth": "Anmeldeserver",
"dg.title.world": "Weltserver",
"dg.title.configs": "Konfiguration",
"dg.title.config_values": "Einstellungswerte",
"dg.title.disk": "Freier Speicherplatz",
"dg.title.permissions": "Berechtigungen",
"dg.title.client": "Spielclient",
"dg.title.exposure": "Private Dienste",
"dg.files.ok": "Alle erwarteten Serverteile wurden gefunden.",
"dg.files.partial": "Einige Serverteile fehlen.",
"dg.files.custom": "Dies ist ein angepasster Server-Build; einige Funktionen sind eingeschränkt.",
"dg.files.bad": "Dieser Ordner sieht nicht wie ein CoA-Server aus.",
"dg.svc.running": "Läuft",
"dg.svc.port": "Port {port} wird von einem anderen Programm verwendet (Prozess {pid}).",
"dg.svc.starting": "Startet, antwortet noch nicht.",
"dg.svc.stopped": "Läuft nicht.",
"dg.configs.ok": "Konfigurationsdateien können gelesen werden.",
"dg.configs.bad": "Nicht lesbar: {files}",
"dg.values.bad": "Unbrauchbare Werte bei: {keys}",
"dg.disk.ok": "{gb} GB frei",
"dg.disk.low": "Nur {gb} GB frei; Sicherungen und Updates brauchen Platz.",
"dg.perm.ok": "Der Serverordner ist beschreibbar.",
"dg.perm.bad": "Der Manager kann nicht in den Serverordner schreiben (wähle einen anderen Ort oder starte einmal als Administrator).",
"dg.client.missing": "Der gespeicherte Spielordner wurde nicht gefunden; wähle ihn in den Einstellungen erneut.",
"dg.exposure.ok": "Datenbank und Serverkonsole sind aus dem Netzwerk nicht erreichbar.",
"dg.exposure.bad": "Aus dem Netzwerk erreichbar: {what}.",
// Update steps
"ustep.Starting": "Start",
"ustep.Checking the update": "Update wird geprüft",
"ustep.Downloading the update": "Update wird heruntergeladen",
"ustep.Verifying the update": "Update wird verifiziert",
"ustep.Making sure the server is stopped": "Es wird sichergestellt, dass der Server gestoppt ist",
"ustep.Saving a recovery point": "Wiederherstellungspunkt wird gespeichert",
"ustep.Applying the update": "Update wird angewendet",
"ustep.Updating the database": "Datenbank wird aktualisiert",
"ustep.Starting the updated server": "Aktualisierter Server wird gestartet",
"ustep.Finishing": "Abschluss",
"ustep.Undoing the update": "Update wird rückgängig gemacht",
"ustep.Done": "Fertig",
// Companion sizes
"comp.size.small": "Klein",
"comp.size.medium": "Mittel",
"comp.size.large": "Groß",
"comp.warnHardware": "{n} Begleiter brauchen möglicherweise mehr CPU und Speicher, als dieser Computer hat ({cores} Kerne, {ram} GB).",
"comp.warnMemory": "Gib zuerst etwas Speicher frei; andere Programme belegen den größten Teil.",
// Friends notes
"fr.note.forwarded": "Dein Router wurde gebeten, die Spielports weiterzuleiten.",
"fr.note.manual": "Dein Router unterstützt keine automatische Einrichtung; leite die beiden Spielports manuell weiter oder nutze das private Netzwerk.",
// Import labels and placeholder
"scan.label.worldserver": "Weltserver",
"scan.label.authserver": "Anmeldeserver",
"scan.label.database_content": "Datenbanken für Anmeldung, Charaktere und Welt",
"scan.label.companions": "CoA-Begleiter (Bots)",
"scan.label.client": "Spielclient",
"placeholder.soon": "Dieser Bereich entsteht in einer späteren Version.",
};
export default de;
+64
View File
@@ -433,6 +433,70 @@ const en = {
// Friends line
"fr.copyLine": "Copy line",
// Diagnostics checks
"dg.title.files": "Server files",
"dg.title.missing_parts": "Missing parts",
"dg.title.mysql": "Database",
"dg.title.auth": "Login server",
"dg.title.world": "World server",
"dg.title.configs": "Configuration",
"dg.title.config_values": "Setting values",
"dg.title.disk": "Free disk space",
"dg.title.permissions": "Permissions",
"dg.title.client": "Game client",
"dg.title.exposure": "Private services",
"dg.files.ok": "All expected server parts were found.",
"dg.files.partial": "Some server parts are missing.",
"dg.files.custom": "This is a custom server build; some features are limited.",
"dg.files.bad": "This folder does not look like a CoA server.",
"dg.svc.running": "Running",
"dg.svc.port": "Port {port} is used by another program (process {pid}).",
"dg.svc.starting": "Starting, not answering yet.",
"dg.svc.stopped": "Not running.",
"dg.configs.ok": "Configuration files can be read.",
"dg.configs.bad": "Cannot read: {files}",
"dg.values.bad": "Unusable values for: {keys}",
"dg.disk.ok": "{gb} GB free",
"dg.disk.low": "Only {gb} GB free; backups and updates need room.",
"dg.perm.ok": "The server folder is writable.",
"dg.perm.bad": "The Manager cannot write to the server folder (try another location or run once as administrator).",
"dg.client.missing": "The saved game folder was not found; choose it again in Settings.",
"dg.exposure.ok": "Database and server console are not reachable from the network.",
"dg.exposure.bad": "Reachable from the network: {what}.",
// Update steps
"ustep.Starting": "Starting",
"ustep.Checking the update": "Checking the update",
"ustep.Downloading the update": "Downloading the update",
"ustep.Verifying the update": "Verifying the update",
"ustep.Making sure the server is stopped": "Making sure the server is stopped",
"ustep.Saving a recovery point": "Saving a recovery point",
"ustep.Applying the update": "Applying the update",
"ustep.Updating the database": "Updating the database",
"ustep.Starting the updated server": "Starting the updated server",
"ustep.Finishing": "Finishing",
"ustep.Undoing the update": "Undoing the update",
"ustep.Done": "Done",
// Companion sizes
"comp.size.small": "Small",
"comp.size.medium": "Medium",
"comp.size.large": "Large",
"comp.warnHardware": "{n} companions may need more CPU and memory than this computer has ({cores} cores, {ram} GB).",
"comp.warnMemory": "Free some memory first; other programs are using most of it.",
// Friends notes
"fr.note.forwarded": "Your router was asked to forward the game ports.",
"fr.note.manual": "Your router does not support automatic setup; forward the two game ports by hand or use the private network.",
// Import labels and placeholder
"scan.label.worldserver": "World server",
"scan.label.authserver": "Auth server",
"scan.label.database_content": "Auth / Characters / World databases",
"scan.label.companions": "CoA Companions (bots)",
"scan.label.client": "Game client",
"placeholder.soon": "This section is being built in a later release.",
} as const;
export default en;
+64
View File
@@ -433,6 +433,70 @@ const es: Partial<Record<keyof typeof en, string>> = {
// Friends line
"fr.copyLine": "Copiar línea",
// Diagnostics checks
"dg.title.files": "Archivos del servidor",
"dg.title.missing_parts": "Partes que faltan",
"dg.title.mysql": "Base de datos",
"dg.title.auth": "Servidor de acceso",
"dg.title.world": "Servidor del mundo",
"dg.title.configs": "Configuración",
"dg.title.config_values": "Valores de los ajustes",
"dg.title.disk": "Espacio libre en disco",
"dg.title.permissions": "Permisos",
"dg.title.client": "Cliente del juego",
"dg.title.exposure": "Servicios privados",
"dg.files.ok": "Se encontraron todas las partes esperadas del servidor.",
"dg.files.partial": "Faltan algunas partes del servidor.",
"dg.files.custom": "Esta es una versión personalizada del servidor; algunas funciones están limitadas.",
"dg.files.bad": "Esta carpeta no parece un servidor de CoA.",
"dg.svc.running": "En marcha",
"dg.svc.port": "El puerto {port} lo usa otro programa (proceso {pid}).",
"dg.svc.starting": "Iniciándose, aún no responde.",
"dg.svc.stopped": "No está en marcha.",
"dg.configs.ok": "Los archivos de configuración se pueden leer.",
"dg.configs.bad": "No se puede leer: {files}",
"dg.values.bad": "Valores inutilizables en: {keys}",
"dg.disk.ok": "{gb} GB libres",
"dg.disk.low": "Solo {gb} GB libres; las copias y las actualizaciones necesitan espacio.",
"dg.perm.ok": "La carpeta del servidor admite escritura.",
"dg.perm.bad": "El Manager no puede escribir en la carpeta del servidor (prueba otra ubicación o ejecútalo una vez como administrador).",
"dg.client.missing": "No se encontró la carpeta del juego guardada; elígela de nuevo en Ajustes.",
"dg.exposure.ok": "La base de datos y la consola del servidor no son accesibles desde la red.",
"dg.exposure.bad": "Accesible desde la red: {what}.",
// Update steps
"ustep.Starting": "Iniciando",
"ustep.Checking the update": "Comprobando la actualización",
"ustep.Downloading the update": "Descargando la actualización",
"ustep.Verifying the update": "Verificando la actualización",
"ustep.Making sure the server is stopped": "Comprobando que el servidor está detenido",
"ustep.Saving a recovery point": "Guardando un punto de recuperación",
"ustep.Applying the update": "Aplicando la actualización",
"ustep.Updating the database": "Actualizando la base de datos",
"ustep.Starting the updated server": "Iniciando el servidor actualizado",
"ustep.Finishing": "Finalizando",
"ustep.Undoing the update": "Deshaciendo la actualización",
"ustep.Done": "Hecho",
// Companion sizes
"comp.size.small": "Pequeño",
"comp.size.medium": "Mediano",
"comp.size.large": "Grande",
"comp.warnHardware": "{n} compañeros pueden necesitar más CPU y memoria de las que tiene este equipo ({cores} núcleos, {ram} GB).",
"comp.warnMemory": "Libera primero algo de memoria; otros programas usan la mayor parte.",
// Friends notes
"fr.note.forwarded": "Se pidió a tu router que redirigiera los puertos del juego.",
"fr.note.manual": "Tu router no admite la configuración automática; redirige los dos puertos del juego a mano o usa la red privada.",
// Import labels and placeholder
"scan.label.worldserver": "Servidor del mundo",
"scan.label.authserver": "Servidor de acceso",
"scan.label.database_content": "Bases de datos de acceso, personajes y mundo",
"scan.label.companions": "Compañeros de CoA (bots)",
"scan.label.client": "Cliente del juego",
"placeholder.soon": "Esta sección se está desarrollando para una versión posterior.",
};
export default es;
+64
View File
@@ -433,6 +433,70 @@ const fr: Partial<Record<keyof typeof en, string>> = {
// Friends line
"fr.copyLine": "Copier la ligne",
// Diagnostics checks
"dg.title.files": "Fichiers du serveur",
"dg.title.missing_parts": "Parties manquantes",
"dg.title.mysql": "Base de données",
"dg.title.auth": "Serveur de connexion",
"dg.title.world": "Serveur de monde",
"dg.title.configs": "Configuration",
"dg.title.config_values": "Valeurs des paramètres",
"dg.title.disk": "Espace disque libre",
"dg.title.permissions": "Autorisations",
"dg.title.client": "Client du jeu",
"dg.title.exposure": "Services privés",
"dg.files.ok": "Toutes les parties attendues du serveur ont été trouvées.",
"dg.files.partial": "Certaines parties du serveur manquent.",
"dg.files.custom": "Il s’agit d’une version personnalisée du serveur ; certaines fonctions sont limitées.",
"dg.files.bad": "Ce dossier ne ressemble pas à un serveur CoA.",
"dg.svc.running": "En cours",
"dg.svc.port": "Le port {port} est utilisé par un autre programme (processus {pid}).",
"dg.svc.starting": "Démarrage, ne répond pas encore.",
"dg.svc.stopped": "Non démarré.",
"dg.configs.ok": "Les fichiers de configuration sont lisibles.",
"dg.configs.bad": "Lecture impossible : {files}",
"dg.values.bad": "Valeurs inutilisables pour : {keys}",
"dg.disk.ok": "{gb} Go libres",
"dg.disk.low": "Seulement {gb} Go libres ; les sauvegardes et mises à jour ont besoin de place.",
"dg.perm.ok": "Le dossier du serveur est accessible en écriture.",
"dg.perm.bad": "Le Manager ne peut pas écrire dans le dossier du serveur (essayez un autre emplacement ou lancez-le une fois en administrateur).",
"dg.client.missing": "Le dossier de jeu enregistré est introuvable ; choisissez-le à nouveau dans les Paramètres.",
"dg.exposure.ok": "La base de données et la console du serveur ne sont pas joignables depuis le réseau.",
"dg.exposure.bad": "Joignable depuis le réseau : {what}.",
// Update steps
"ustep.Starting": "Démarrage",
"ustep.Checking the update": "Vérification de la mise à jour",
"ustep.Downloading the update": "Téléchargement de la mise à jour",
"ustep.Verifying the update": "Vérification de la mise à jour",
"ustep.Making sure the server is stopped": "Vérification que le serveur est arrêté",
"ustep.Saving a recovery point": "Enregistrement d’un point de restauration",
"ustep.Applying the update": "Application de la mise à jour",
"ustep.Updating the database": "Mise à jour de la base de données",
"ustep.Starting the updated server": "Démarrage du serveur mis à jour",
"ustep.Finishing": "Finalisation",
"ustep.Undoing the update": "Annulation de la mise à jour",
"ustep.Done": "Terminé",
// Companion sizes
"comp.size.small": "Petit",
"comp.size.medium": "Moyen",
"comp.size.large": "Grand",
"comp.warnHardware": "{n} compagnons peuvent demander plus de processeur et de mémoire que cet ordinateur n’en a ({cores} cœurs, {ram} Go).",
"comp.warnMemory": "Libérez d’abord de la mémoire ; d’autres programmes en utilisent la plus grande partie.",
// Friends notes
"fr.note.forwarded": "Votre routeur a été invité à rediriger les ports du jeu.",
"fr.note.manual": "Votre routeur ne prend pas en charge la configuration automatique ; redirigez les deux ports du jeu à la main ou utilisez le réseau privé.",
// Import labels and placeholder
"scan.label.worldserver": "Serveur de monde",
"scan.label.authserver": "Serveur de connexion",
"scan.label.database_content": "Bases de données connexion / personnages / monde",
"scan.label.companions": "Compagnons CoA (bots)",
"scan.label.client": "Client du jeu",
"placeholder.soon": "Cette section sera développée dans une prochaine version.",
};
export default fr;
+64
View File
@@ -433,6 +433,70 @@ const ru: Partial<Record<keyof typeof en, string>> = {
// Friends line
"fr.copyLine": "Скопировать строку",
// Diagnostics checks
"dg.title.files": "Файлы сервера",
"dg.title.missing_parts": "Отсутствующие части",
"dg.title.mysql": "База данных",
"dg.title.auth": "Сервер входа",
"dg.title.world": "Игровой мир",
"dg.title.configs": "Конфигурация",
"dg.title.config_values": "Значения настроек",
"dg.title.disk": "Свободное место на диске",
"dg.title.permissions": "Права доступа",
"dg.title.client": "Клиент игры",
"dg.title.exposure": "Закрытые службы",
"dg.files.ok": "Найдены все ожидаемые части сервера.",
"dg.files.partial": "Некоторых частей сервера не хватает.",
"dg.files.custom": "Это нестандартная сборка сервера; часть функций ограничена.",
"dg.files.bad": "Эта папка не похожа на сервер CoA.",
"dg.svc.running": "Работает",
"dg.svc.port": "Порт {port} занят другой программой (процесс {pid}).",
"dg.svc.starting": "Запускается, пока не отвечает.",
"dg.svc.stopped": "Не запущено.",
"dg.configs.ok": "Файлы конфигурации читаются.",
"dg.configs.bad": "Не удаётся прочитать: {files}",
"dg.values.bad": "Недопустимые значения у: {keys}",
"dg.disk.ok": "Свободно {gb} ГБ",
"dg.disk.low": "Свободно только {gb} ГБ; для резервных копий и обновлений нужно место.",
"dg.perm.ok": "В папку сервера можно записывать.",
"dg.perm.bad": "Менеджер не может записывать в папку сервера (выберите другое место или один раз запустите от имени администратора).",
"dg.client.missing": "Сохранённая папка игры не найдена; выберите её заново в Настройках.",
"dg.exposure.ok": "База данных и консоль сервера недоступны из сети.",
"dg.exposure.bad": "Доступно из сети: {what}.",
// Update steps
"ustep.Starting": "Запуск",
"ustep.Checking the update": "Проверка обновления",
"ustep.Downloading the update": "Загрузка обновления",
"ustep.Verifying the update": "Проверка подлинности обновления",
"ustep.Making sure the server is stopped": "Убеждаюсь, что сервер остановлен",
"ustep.Saving a recovery point": "Сохранение точки восстановления",
"ustep.Applying the update": "Применение обновления",
"ustep.Updating the database": "Обновление базы данных",
"ustep.Starting the updated server": "Запуск обновлённого сервера",
"ustep.Finishing": "Завершение",
"ustep.Undoing the update": "Откат обновления",
"ustep.Done": "Готово",
// Companion sizes
"comp.size.small": "Малый",
"comp.size.medium": "Средний",
"comp.size.large": "Большой",
"comp.warnHardware": "Для {n} компаньонов может не хватить процессора и памяти этого компьютера (ядер — {cores}, памяти — {ram} ГБ).",
"comp.warnMemory": "Сначала освободите память: большую её часть занимают другие программы.",
// Friends notes
"fr.note.forwarded": "Роутеру отправлена просьба пробросить игровые порты.",
"fr.note.manual": "Ваш роутер не поддерживает автоматическую настройку; пробросьте два игровых порта вручную или используйте частную сеть.",
// Import labels and placeholder
"scan.label.worldserver": "Игровой сервер (мир)",
"scan.label.authserver": "Сервер входа",
"scan.label.database_content": "Базы данных входа, персонажей и мира",
"scan.label.companions": "Компаньоны CoA (боты)",
"scan.label.client": "Клиент игры",
"placeholder.soon": "Этот раздел появится в одном из следующих выпусков.",
};
export default ru;
+513
View File
@@ -0,0 +1,513 @@
{
"categories": {
"bots.general": "Allgemein",
"bots.population": "Population",
"bots.login": "Anmeldung",
"bots.roles": "Rollen",
"bots.openworld": "Offene Welt",
"bots.quests": "Quests",
"bots.combat": "Kampf",
"bots.dungeon": "Dungeonsuche",
"bots.battlegrounds": "Schlachtfelder",
"bots.social": "Soziales Verhalten",
"bots.advanced": "Erweitert",
"server.gameplay": "Spielablauf",
"server.rates": "Raten",
"server.characters": "Charaktere",
"server.world": "Welt",
"server.network": "Netzwerk",
"server.accounts": "Konten",
"server.logging": "Protokolle",
"server.performance": "Leistung",
"server.advanced": "Erweitert"
},
"units": {
"ms": "ms",
"%": "%",
"yards": "Yards",
"levels": "Stufen",
"x": "×",
"min": "Min.",
"copper": "Kupfer"
},
"settings": {
"CoaBots.RandomSpawn.DefaultCount": {
"t": "Bots pro Erstellungsbefehl",
"d": "Wie viele Bots der Erstellungsbefehl anlegt, wenn du keine Zahl angibst."
},
"CoaBots.RandomSpawn.MaxCount": {
"t": "Größte einzelne Erstellung",
"d": "Sicherheitsgrenze für einen Erstellungsbefehl. Sehr große Mengen können den Server kurz einfrieren lassen; erhöhe den Wert daher nur, wenn sonst niemand spielt."
},
"CoaBots.LeveledSpawn.Brackets": {
"t": "Stufenmix neuer Bots",
"d": "Wie neue Level-Bots auf Stufen verteilt werden, als Bereiche mit Gewichten (1-20:40 bedeutet 40 Teile Bots zwischen Stufe 1 und 20)."
},
"CoaBots.RandomSpawn.AutoLogin": {
"t": "Neue Bots sofort online bringen",
"d": "Neu erstellte Bots melden sich sofort an, statt in der Datenbank zu warten."
},
"CoaBots.RandomSpawn.AccountPrefix": {
"t": "Namenspräfix der Bot-Konten",
"d": "Bots leben auf Konten, deren Name mit diesem Präfix beginnt. Wird es geändert, melden sich vorhandene Bots nicht mehr automatisch an."
},
"CoaBots.RandomSpawn.BatchSize": {
"t": "Bots pro Erstellungsschritt",
"d": "Die Erstellung wird zeitlich gestreckt, damit der Server flüssig bleibt. Höhere Werte sind schneller, können aber Ruckler verursachen."
},
"CoaBots.RandomSpawn.BatchIntervalMs": {
"t": "Pause zwischen Erstellungsschritten",
"d": "Zeit zwischen den Erstellungsschritten. Niedrigere Werte sind schneller, können aber Ruckler verursachen."
},
"CoaBots.AutoLoginOnStartup": {
"t": "Automatische Bot-Anmeldung",
"d": "Vorhandene Bots melden sich bei jedem Serverstart selbst an."
},
"CoaBots.AutoLogin.MaxCount": {
"t": "Maximale Bots online",
"d": "Die höchste Anzahl an Bots, die sich beim Start automatisch anmelden."
},
"CoaBots.AutoLogin.BatchSize": {
"t": "Bots pro Anmeldeschritt",
"d": "Anmeldungen werden zeitlich gestreckt. Höhere Werte bringen Bots schneller online, belasten den Server aber stärker."
},
"CoaBots.AutoLogin.BatchIntervalMs": {
"t": "Pause zwischen Anmeldeschritten",
"d": "Zeit zwischen den Anmeldeschritten."
},
"CoaBots.SpecBalance.TankTargetPct": {
"t": "Zielanteil an Tanks",
"d": "Anteil der Bots, die Tanks sein sollen."
},
"CoaBots.SpecBalance.HealerTargetPct": {
"t": "Zielanteil an Heilern",
"d": "Anteil der Bots, die Heiler sein sollen."
},
"CoaBots.TalentBuildsPath": {
"t": "Talentdatendatei",
"d": "Wo die Talent-Builds der Community gespeichert sind. Leer lassen, um die eingebaute Suche zu nutzen."
},
"CoaBots.World.Enable": {
"t": "Bots streifen durch die Welt",
"d": "Untätige Bots erledigen Besorgungen: Sie besuchen Stadtdienste, reisen zu Sammel- oder Grind-Gebieten oder wandern ein wenig umher."
},
"CoaBots.WorldBrain.Enable": {
"t": "Quest-KI",
"d": "Bots nehmen Quests selbstständig an und erledigen sie. Wenn ausgeschaltet, sammeln, angeln, grinden und wandern Bots nur."
},
"CoaBots.World.ServiceRadius": {
"t": "Suchradius für Stadtdienste",
"d": "Wie weit ein Bot nach Händlern und Briefkästen sucht."
},
"CoaBots.World.AreaRadius": {
"t": "Radius des Besorgungsgebiets",
"d": "Wie weit ein Bot nach Sammel- oder Grind-Gebieten sucht."
},
"CoaBots.World.ThinkIntervalMs": {
"t": "Prüfintervall für Besorgungen",
"d": "Wie oft jeder Bot seine Besorgung neu überdenkt."
},
"CoaBots.World.IdleBeforeErrandMs": {
"t": "Leerlaufzeit vor einer Besorgung",
"d": "Wie lange ein Bot nichts zu tun finden muss, bevor er eine Besorgung beginnt."
},
"CoaBots.World.VerboseLog": {
"t": "Ausführliches Besorgungsprotokoll",
"d": "Schreibt jeden Schritt einer Besorgung ins Protokoll. Nützlich zur Fehlersuche, bei vielen Bots aber sehr laut."
},
"CoaBots.Combat.VerboseLog": {
"t": "Ausführliches Kampfprotokoll",
"d": "Schreibt Kampfentscheidungen ins Protokoll. Sehr laut; nur einschalten, wenn du ein bestimmtes Problem suchst."
},
"CoaBots.Profile.IntentMinMs": {
"t": "Kürzeste Aktivität",
"d": "Die kürzeste Zeit, die ein Bot bei einer Art von Aktivität bleibt."
},
"CoaBots.Profile.IntentMaxMs": {
"t": "Längste Aktivität",
"d": "Die längste Zeit, die ein Bot bei einer Art von Aktivität bleibt."
},
"CoaBots.Grind.SearchRadius": {
"t": "Suchradius fürs Grinden",
"d": "Wie weit ein Bot nach Monstern zum Grinden sucht."
},
"CoaBots.Grind.MaxLevelAbove": {
"t": "Höchste Monsterstufe fürs Grinden",
"d": "Um wie viele Stufen über sich selbst ein Bot beim Grinden kämpft."
},
"CoaBots.Healer.CriticalHealPct": {
"t": "Schwelle für Notheilung",
"d": "Heiler wechseln zu ihren stärksten Heilungen, wenn die Gesundheit eines Verbündeten darunter fällt."
},
"CoaBots.WorldBrain.SearchRadius": {
"t": "Suchradius für Ziele",
"d": "Wie weit ein Bot innerhalb eines Zielgebiets nach Questzielen sucht."
},
"CoaBots.WorldBrain.EngageRange": {
"t": "Angriffsentfernung",
"d": "Entfernung, ab der ein Bot einen Quest-Gegner bekämpft."
},
"CoaBots.WorldBrain.MountDistanceMin": {
"t": "Aufsitzen ab (Min.)",
"d": "Jeder Bot sitzt bei Reisen auf, die länger sind als eine persönliche Entfernung, die zwischen diesem Wert und dem Maximum gewählt wird."
},
"CoaBots.WorldBrain.MountDistanceMax": {
"t": "Aufsitzen ab (Max.)",
"d": "Oberes Ende der persönlichen Aufsitzentfernung."
},
"CoaBots.WorldBrain.TaxiMinDistance": {
"t": "Flugrouten nutzen ab",
"d": "Reisen, die länger sind, nutzen eine Flugroute, wenn der Bot eine kennt."
},
"CoaBots.WorldBrain.SessionBreaks": {
"t": "Pausen einlegen",
"d": "Bots wechseln Questen mit ein paar Minuten Stadtleben ab, wie ein Spieler."
},
"CoaBots.WorldBrain.GatherDetours": {
"t": "Kräuter und Erz in der Nähe sammeln",
"d": "Sammler weichen vom Weg ab, wenn direkt daneben ein Vorkommen liegt."
},
"CoaBots.WorldBrain.MaxActiveQuests": {
"t": "Maximale aktive Quests",
"d": "Wie viele Quests ein Bot gleichzeitig trägt."
},
"CoaBots.WorldBrain.AcceptEliteQuests": {
"t": "Elite-Quests annehmen",
"d": "Einzelne Bots übernehmen Elite-Quests. Standardmäßig aus, weil sie diese allein meist nicht schaffen."
},
"CoaBots.WorldBrain.QuestLevelBelow": {
"t": "Quests unter der eigenen Stufe",
"d": "Um wie viele Stufen unter sich selbst ein Bot noch Quests annimmt."
},
"CoaBots.WorldBrain.QuestLevelAbove": {
"t": "Quests über der eigenen Stufe",
"d": "Um wie viele Stufen über sich selbst ein Bot noch Quests annimmt."
},
"CoaBots.WorldBrain.MaxMobLevelAbove": {
"t": "Höchste Stufe von Quest-Monstern",
"d": "Wie weit über seiner Stufe ein Quest-Monster sein darf."
},
"CoaBots.WorldBrain.MaxObjectiveDistance": {
"t": "Am weitesten entferntes Questziel",
"d": "Quests, deren Ziele weiter entfernt sind, werden nicht angenommen."
},
"CoaBots.WorldBrain.GiverSearchRadius": {
"t": "Suchradius für Questgeber",
"d": "Wie weit ein Bot nach Questgebern sucht."
},
"CoaBots.WorldBrain.MaxBadAreasPerObjective": {
"t": "Versuchte Gebiete pro Ziel",
"d": "Wie viele Gebiete ein Bot aufgibt, bevor er die Quest zurückstellt."
},
"CoaBots.WorldBrain.QuestSuspendMs": {
"t": "Zurückstellzeit (kurz)",
"d": "Wie lange eine Quest zurückgestellt wird, nachdem der Bot sie aufgegeben hat."
},
"CoaBots.WorldBrain.QuestSuspendMaxMs": {
"t": "Zurückstellzeit (lang)",
"d": "Die längste Zeit, die eine Quest nach wiederholtem Scheitern zurückgestellt bleiben kann."
},
"CoaBots.LfgFill.Enable": {
"t": "Bot-Auffüllung der Dungeonsuche",
"d": "Wenn du allein für einen Dungeon anstehst, übernehmen Bots die fehlenden Rollen Tank, Heiler und Schaden."
},
"CoaBots.AutoQueueDungeon.Enable": {
"t": "Bots stellen sich selbst für Dungeons an",
"d": "Experimentell: Untätige Tank-Bots stellen sich selbst für einen Dungeon an, und die Auffüllung oben vervollständigt die Gruppe."
},
"CoaBots.BGFill.Enable": {
"t": "Bot-Auffüllung für Schlachtfelder",
"d": "Wenn jemand für ein Schlachtfeld ansteht, werden beide Teams mit Bots aufgefüllt, damit das Spiel sofort beginnt."
},
"CoaBots.BGFill.TargetPlayersPerTeam": {
"t": "Teamgröße auf Schlachtfeldern",
"d": "Bis zu wie vielen Bots pro Team aufgefüllt wird. 0 bedeutet ein volles Team für dieses Schlachtfeld."
},
"CoaBots.WorldBrain.HelpOthers": {
"t": "Bots helfen Spielern in der Nähe",
"d": "Ein vorbeikommender Bot steigt in einen Kampf ein, den ein befreundeter Spieler verliert, ohne dessen Beute oder Anrechnung zu nehmen."
},
"CoaBots.WorldBrain.HelpRadius": {
"t": "Hilfsentfernung",
"d": "Wie nah ein Bot sein muss, um in einen Kampf einzugreifen."
},
"CoaBots.WorldBrain.HelpHealthPct": {
"t": "Helfen unter Gesundheit",
"d": "Bots helfen, wenn die Gesundheit des kämpfenden Spielers darunter liegt."
},
"CoaBots.WorldBrain.ResurrectOthers": {
"t": "Bots beleben gefallene Spieler",
"d": "Bots, die einen Wiederbelebungszauber kennen, beleben befreundete Leichen."
},
"CoaBots.WorldBrain.TemporaryParties": {
"t": "Vorübergehende Bot-Gruppen",
"d": "Bots ähnlicher Stufe mit derselben Quest tun sich kurz zusammen."
},
"CoaBots.WorldBrain.PartyMaxSize": {
"t": "Größe vorübergehender Gruppen",
"d": "Größte vorübergehende Gruppe."
},
"CoaBots.WorldBrain.PartyRadius": {
"t": "Entfernung zur Gruppenbildung",
"d": "Wie nah Bots sein müssen, um sich zusammenzutun."
},
"CoaBots.WorldBrain.PartyMinMinutes": {
"t": "Kürzeste Gruppendauer",
"d": "Mindestdauer einer vorübergehenden Gruppe."
},
"CoaBots.WorldBrain.PartyMaxMinutes": {
"t": "Längste Gruppendauer",
"d": "Höchstdauer einer vorübergehenden Gruppe."
},
"CoaBots.WorldBrain.PlannerMinMs": {
"t": "Planerintervall (Min.)",
"d": "Kürzeste Zeit zwischen den Planungsdurchläufen eines Bots."
},
"CoaBots.WorldBrain.PlannerMaxMs": {
"t": "Planerintervall (Max.)",
"d": "Längste Zeit zwischen den Planungsdurchläufen eines Bots."
},
"CoaBots.WorldBrain.ScanMinMs": {
"t": "Zielsuchintervall (Min.)",
"d": "Kürzeste Zeit zwischen Zielsuchen."
},
"CoaBots.WorldBrain.ScanMaxMs": {
"t": "Zielsuchintervall (Max.)",
"d": "Längste Zeit zwischen Zielsuchen."
},
"CoaBots.WorldBrain.SearchTimeoutMs": {
"t": "Zeitlimit der Gebietssuche",
"d": "Wie lange ein Bot ein leeres Gebiet absucht, bevor er ein anderes versucht."
},
"CoaBots.WorldBrain.ApproachTimeoutMs": {
"t": "Zeitlimit der Annäherung",
"d": "Wie lange ein Bot zu einem beanspruchten Ziel läuft."
},
"CoaBots.WorldBrain.TravelTimeoutMs": {
"t": "Zeitlimit der Reise",
"d": "Wie lange eine ganze Reise in ein Gebiet dauern darf."
},
"CoaBots.WorldBrain.Weight.TravelPer100Yards": {
"t": "Präferenzgewicht: TravelPer100Yards",
"d": "Wie stark Bots diesen Faktor gewichten, wenn sie entscheiden, was sie als Nächstes tun. Nur ändern, wenn du den Planer kennst."
},
"CoaBots.WorldBrain.Weight.CrowdPerBot": {
"t": "Präferenzgewicht: CrowdPerBot",
"d": "Wie stark Bots diesen Faktor gewichten, wenn sie entscheiden, was sie als Nächstes tun. Nur ändern, wenn du den Planer kennst."
},
"CoaBots.WorldBrain.Weight.Overlap": {
"t": "Präferenzgewicht: Overlap",
"d": "Wie stark Bots diesen Faktor gewichten, wenn sie entscheiden, was sie als Nächstes tun. Nur ändern, wenn du den Planer kennst."
},
"CoaBots.WorldBrain.Weight.TurnIn": {
"t": "Präferenzgewicht: TurnIn",
"d": "Wie stark Bots diesen Faktor gewichten, wenn sie entscheiden, was sie als Nächstes tun. Nur ändern, wenn du den Planer kennst."
},
"CoaBots.WorldBrain.Weight.Accept": {
"t": "Präferenzgewicht: Accept",
"d": "Wie stark Bots diesen Faktor gewichten, wenn sie entscheiden, was sie als Nächstes tun. Nur ändern, wenn du den Planer kennst."
},
"CoaBots.WorldBrain.Weight.AreaOccupancy": {
"t": "Präferenzgewicht: AreaOccupancy",
"d": "Wie stark Bots diesen Faktor gewichten, wenn sie entscheiden, was sie als Nächstes tun. Nur ändern, wenn du den Planer kennst."
},
"CoaBots.WorldBrain.Weight.TargetReserved": {
"t": "Präferenzgewicht: TargetReserved",
"d": "Wie stark Bots diesen Faktor gewichten, wenn sie entscheiden, was sie als Nächstes tun. Nur ändern, wenn du den Planer kennst."
},
"Rate.XP.Kill": {
"t": "Erfahrung durch Kills",
"d": "Multiplikator für Erfahrung durch das Töten von Monstern. 1 ist normal."
},
"Rate.XP.Quest": {
"t": "Erfahrung durch Quests",
"d": "Multiplikator für Erfahrung aus Quests."
},
"Rate.XP.Quest.DF": {
"t": "Erfahrung durch tägliche Quests / Dungeonsuche",
"d": "Multiplikator für Erfahrung aus Quests der Dungeonsuche."
},
"Rate.XP.Explore": {
"t": "Erfahrung durch Erkunden",
"d": "Multiplikator für Erfahrung beim Entdecken neuer Gebiete."
},
"Rate.Drop.Item.Normal": {
"t": "Gegenstands-Droprate",
"d": "Multiplikator dafür, wie oft Gegenstände normaler Qualität fallen."
},
"Rate.Drop.Money": {
"t": "Gold-Droprate",
"d": "Multiplikator für das Gold, das Monster fallen lassen."
},
"Rate.Honor": {
"t": "Ehregewinn",
"d": "Multiplikator für verdiente Ehrenpunkte."
},
"Rate.Reputation.Gain": {
"t": "Rufgewinn",
"d": "Multiplikator für verdienten Ruf."
},
"Rate.Rest.InGame": {
"t": "Ruhebonus beim Spielen",
"d": "Geschwindigkeit, mit der Ruhe-Erfahrung beim Spielen aufgebraucht wird."
},
"Rate.Creature.Normal.Damage": {
"t": "Monsterschaden",
"d": "Multiplikator für den Schaden normaler Monster. Verändert den Schwierigkeitsgrad stark."
},
"Rate.Creature.Normal.HP": {
"t": "Monstergesundheit",
"d": "Multiplikator für die Gesundheit normaler Monster. Verändert den Schwierigkeitsgrad stark."
},
"PlayerLimit": {
"t": "Maximale Spielerzahl",
"d": "Wie viele Spieler gleichzeitig online sein können. 0 bedeutet kein Limit."
},
"GameType": {
"t": "Realmtyp",
"d": "Der in der Realmliste angezeigte Realmtyp."
},
"RealmZone": {
"t": "Realmzone",
"d": "Die Sprach- bzw. Regionsgruppe des Realms, die Clients angezeigt wird."
},
"PlayerSaveInterval": {
"t": "Autospeicher-Intervall",
"d": "Wie oft Spielercharaktere gespeichert werden, solange sie online sind."
},
"CharactersPerAccount": {
"t": "Charaktere pro Konto",
"d": "Maximale Zahl an Charakteren, die ein Konto über alle Realms hat."
},
"CharactersPerRealm": {
"t": "Charaktere pro Realm",
"d": "Maximale Zahl an Charakteren, die ein Konto auf diesem Realm hat."
},
"StartPlayerLevel": {
"t": "Startstufe",
"d": "Stufe, mit der neue Charaktere beginnen."
},
"StartPlayerMoney": {
"t": "Startgold",
"d": "Kupfer, mit dem neue Charaktere beginnen (10000 Kupfer = 1 Gold)."
},
"MaxPlayerLevel": {
"t": "Maximale Stufe",
"d": "Höchste Stufe, die ein Charakter erreichen kann. Der Spielclient unterstützt nur 80."
},
"SkipCinematics": {
"t": "Intro-Videos überspringen",
"d": "Ob neue Charaktere ihr Intro-Video sehen."
},
"DurabilityLoss.OnDeath": {
"t": "Haltbarkeitsverlust beim Tod",
"d": "Prozent der Haltbarkeit von Gegenständen, die beim Tod verloren gehen."
},
"Death.SicknessLevel": {
"t": "Wiederbelebungsschwäche bis Stufe",
"d": "Charaktere über dieser Stufe erhalten Wiederbelebungsschwäche. -10 schaltet sie aus."
},
"LeaveGroupOnLogout.Enabled": {
"t": "Gruppe beim Abmelden verlassen",
"d": "Spieler werden aus ihrer Gruppe entfernt, wenn sie sich abmelden."
},
"GM.LoginState": {
"t": "GM-Modus bei der Anmeldung",
"d": "Ob Gamemaster mit eingeschaltetem GM-Modus starten."
},
"AllowTwoSide.Accounts": {
"t": "Beide Fraktionen auf einem Konto",
"d": "Ein Konto darf Allianz- und Horde-Charaktere haben."
},
"AllowTwoSide.Interaction.Chat": {
"t": "Fraktionsübergreifender Chat",
"d": "Allianz- und Horde-Spieler können miteinander sprechen."
},
"AutoBroadcast.On": {
"t": "Automatische Ankündigungen",
"d": "Sendet die in der Datenbank gespeicherten Nachrichten in regelmäßigen Abständen."
},
"AutoBroadcast.Timer": {
"t": "Ankündigungsintervall",
"d": "Zeit zwischen automatischen Ankündigungen."
},
"Warden.Enabled": {
"t": "Anti-Cheat (Warden)",
"d": "Aktiviert die Anti-Cheat-Prüfung des Clients. Auf einem privaten Server meist unnötig."
},
"MapUpdateInterval": {
"t": "Karten-Aktualisierungsintervall",
"d": "Wie oft die Welt Karten aktualisiert. Niedriger ist flüssiger, braucht aber mehr CPU."
},
"MinWorldUpdateTime": {
"t": "Minimale Weltaktualisierungszeit",
"d": "Kürzeste Zeit zwischen Weltaktualisierungen. Erhöhe sie, um die CPU-Last zu senken."
},
"ThreadPool": {
"t": "Arbeitsthreads",
"d": "Anzahl der Hintergrund-Arbeitsthreads. Mehr hilft bei großen Spielerzahlen."
},
"Network.Threads": {
"t": "Netzwerk-Threads",
"d": "Threads, die Spielerverbindungen verarbeiten. Für kleine Server genügt einer."
},
"Ra.Enable": {
"t": "Remote-Konsole",
"d": "Erlaubt dem Manager und Skripten, Befehle an den Server zu senden. Sie lauscht nur auf diesem Computer; lass es dabei."
},
"Ra.IP": {
"t": "Adresse der Remote-Konsole",
"d": "Adresse, auf der die Remote-Konsole lauscht. Behalte 127.0.0.1, damit sie nicht aus dem Internet erreichbar ist."
},
"Updates.EnableDatabases": {
"t": "Automatische Datenbank-Updates",
"d": "Erlaubt dem Server, seine Datenbanken beim Start selbst zu ändern. Lass es auf 0: Der Manager wendet Datenbank-Updates sicher an und verfolgt sie."
}
},
"options": {
"GameType.0": "Normal",
"GameType.1": "Spieler gegen Spieler",
"GameType.6": "Rollenspiel",
"GameType.8": "Rollenspiel + PvP",
"SkipCinematics.0": "Alle zeigen",
"SkipCinematics.1": "Für alle außer dem ersten Charakter überspringen",
"SkipCinematics.2": "Alle überspringen",
"GM.LoginState.0": "Aus",
"GM.LoginState.1": "An",
"GM.LoginState.2": "Wie beim letzten Mal"
},
"presets": {
"balanced": {
"t": "Ausgewogen",
"d": "Die empfohlene Alltagseinstellung: Quests, Dungeon- und Schlachtfeld-Auffüllung an, ruhige Protokolle."
},
"solo-friendly": {
"t": "Solo-freundlich",
"d": "Bots, die dir helfen und deine Gruppen auffüllen, mit einer kleinen, ruhigen Population."
},
"living-world": {
"t": "Lebendige Welt",
"d": "Eine belebtere Welt: mehr Bots, Pausen, Sammel-Abstecher und vorübergehende Gruppen."
},
"performance": {
"t": "Leistung",
"d": "Weniger Bots und weniger Bot-Denken, für schwächere Computer."
},
"vanilla": {
"t": "Standardraten",
"d": "Alles in normaler Geschwindigkeit (×1)."
},
"relaxed": {
"t": "Entspannt",
"d": "Schnelleres Leveln und mehr Beute für Gelegenheitsspieler (×2 / ×3)."
},
"fast": {
"t": "Schnell",
"d": "Schneller Fortschritt für Tests oder kurze Sitzungen (×5)."
},
"defaults": {
"t": "Empfohlene Standardwerte",
"d": "Alle Einstellungen kehren zu ihren Standardwerten zurück."
}
}
}
+513
View File
@@ -0,0 +1,513 @@
{
"categories": {
"bots.general": "General",
"bots.population": "Población",
"bots.login": "Inicio de sesión",
"bots.roles": "Roles",
"bots.openworld": "Mundo abierto",
"bots.quests": "Misiones",
"bots.combat": "Combate",
"bots.dungeon": "Buscador de mazmorras",
"bots.battlegrounds": "Campos de batalla",
"bots.social": "Comportamiento social",
"bots.advanced": "Avanzado",
"server.gameplay": "Jugabilidad",
"server.rates": "Tasas",
"server.characters": "Personajes",
"server.world": "Mundo",
"server.network": "Red",
"server.accounts": "Cuentas",
"server.logging": "Registros",
"server.performance": "Rendimiento",
"server.advanced": "Avanzado"
},
"units": {
"ms": "ms",
"%": "%",
"yards": "yardas",
"levels": "niveles",
"x": "×",
"min": "min",
"copper": "cobre"
},
"settings": {
"CoaBots.RandomSpawn.DefaultCount": {
"t": "Bots creados por comando",
"d": "Cuántos bots crea el comando de creación cuando no indicas un número."
},
"CoaBots.RandomSpawn.MaxCount": {
"t": "Mayor creación individual",
"d": "Límite de seguridad para un comando de creación. Los lotes muy grandes pueden congelar el servidor un momento, así que súbelo solo cuando nadie más esté jugando."
},
"CoaBots.LeveledSpawn.Brackets": {
"t": "Reparto de niveles de los bots nuevos",
"d": "Cómo se reparten por niveles los nuevos bots que suben de nivel, como rangos con pesos (1-20:40 significa 40 partes de bots entre los niveles 1 y 20)."
},
"CoaBots.RandomSpawn.AutoLogin": {
"t": "Conectar enseguida a los bots nuevos",
"d": "Los bots recién creados inician sesión de inmediato en lugar de esperar en la base de datos."
},
"CoaBots.RandomSpawn.AccountPrefix": {
"t": "Prefijo de los nombres de cuenta de bots",
"d": "Los bots viven en cuentas cuyo nombre empieza por este prefijo. Cambiarlo hace que los bots existentes dejen de conectarse automáticamente."
},
"CoaBots.RandomSpawn.BatchSize": {
"t": "Bots creados por paso",
"d": "La creación se reparte en el tiempo para que el servidor vaya fluido. Valores altos son más rápidos, pero pueden causar tirones."
},
"CoaBots.RandomSpawn.BatchIntervalMs": {
"t": "Pausa entre pasos de creación",
"d": "Tiempo entre los pasos de creación. Valores bajos son más rápidos, pero pueden causar tirones."
},
"CoaBots.AutoLoginOnStartup": {
"t": "Conexión automática de bots",
"d": "Los bots existentes inician sesión solos cada vez que arranca el servidor."
},
"CoaBots.AutoLogin.MaxCount": {
"t": "Máximo de bots conectados",
"d": "El mayor número de bots que inician sesión automáticamente al arrancar."
},
"CoaBots.AutoLogin.BatchSize": {
"t": "Bots que se conectan por paso",
"d": "Las conexiones se reparten en el tiempo. Valores altos conectan bots más rápido, pero cargan más el servidor."
},
"CoaBots.AutoLogin.BatchIntervalMs": {
"t": "Pausa entre pasos de conexión",
"d": "Tiempo entre los pasos de conexión."
},
"CoaBots.SpecBalance.TankTargetPct": {
"t": "Proporción objetivo de tanques",
"d": "Proporción de bots que deben ser tanques."
},
"CoaBots.SpecBalance.HealerTargetPct": {
"t": "Proporción objetivo de sanadores",
"d": "Proporción de bots que deben ser sanadores."
},
"CoaBots.TalentBuildsPath": {
"t": "Archivo de datos de talentos",
"d": "Dónde se guardan las builds de talentos de la comunidad. Déjalo vacío para usar la búsqueda integrada."
},
"CoaBots.World.Enable": {
"t": "Los bots vagan por el mundo",
"d": "Los bots inactivos hacen recados: visitan servicios de la ciudad, viajan a zonas de recolección o de farmeo o deambulan un poco."
},
"CoaBots.WorldBrain.Enable": {
"t": "IA de misiones",
"d": "Los bots aceptan y completan misiones por su cuenta. Si se apaga, los bots solo recolectan, pescan, farmean y deambulan."
},
"CoaBots.World.ServiceRadius": {
"t": "Radio de búsqueda de servicios de la ciudad",
"d": "Hasta dónde busca un bot vendedores y buzones."
},
"CoaBots.World.AreaRadius": {
"t": "Radio de la zona de recados",
"d": "Hasta dónde busca un bot zonas de recolección o de farmeo."
},
"CoaBots.World.ThinkIntervalMs": {
"t": "Intervalo de revisión de recados",
"d": "Con qué frecuencia cada bot reconsidera su recado."
},
"CoaBots.World.IdleBeforeErrandMs": {
"t": "Inactividad antes de un recado",
"d": "Cuánto tiempo debe un bot no encontrar nada que hacer antes de empezar un recado."
},
"CoaBots.World.VerboseLog": {
"t": "Registro detallado de recados",
"d": "Escribe cada paso de los recados en el registro. Útil para depurar, ruidoso con muchos bots."
},
"CoaBots.Combat.VerboseLog": {
"t": "Registro detallado de combate",
"d": "Escribe las decisiones de combate en el registro. Muy ruidoso; actívalo solo mientras buscas un problema concreto."
},
"CoaBots.Profile.IntentMinMs": {
"t": "Actividad más corta",
"d": "El menor tiempo que un bot se mantiene en un mismo tipo de actividad."
},
"CoaBots.Profile.IntentMaxMs": {
"t": "Actividad más larga",
"d": "El mayor tiempo que un bot se mantiene en un mismo tipo de actividad."
},
"CoaBots.Grind.SearchRadius": {
"t": "Radio de búsqueda para farmear",
"d": "Hasta dónde busca un bot monstruos para farmear."
},
"CoaBots.Grind.MaxLevelAbove": {
"t": "Nivel máximo de monstruo para farmear",
"d": "Cuántos niveles por encima del suyo combate un bot al farmear."
},
"CoaBots.Healer.CriticalHealPct": {
"t": "Umbral de curación de emergencia",
"d": "Los sanadores pasan a sus curas más fuertes cuando la salud de un aliado cae por debajo de este valor."
},
"CoaBots.WorldBrain.SearchRadius": {
"t": "Radio de búsqueda de objetivos",
"d": "Hasta dónde examina un bot en busca de objetivos de misión dentro de una zona de objetivo."
},
"CoaBots.WorldBrain.EngageRange": {
"t": "Distancia de enfrentamiento",
"d": "Distancia a la que un bot empieza a combatir a un monstruo de misión."
},
"CoaBots.WorldBrain.MountDistanceMin": {
"t": "Montar a partir de (mín.)",
"d": "Cada bot monta en viajes más largos que una distancia personal elegida entre este valor y el máximo."
},
"CoaBots.WorldBrain.MountDistanceMax": {
"t": "Montar a partir de (máx.)",
"d": "Extremo superior de la distancia personal para montar."
},
"CoaBots.WorldBrain.TaxiMinDistance": {
"t": "Usar rutas de vuelo a partir de",
"d": "Los viajes más largos usan una ruta de vuelo si el bot conoce alguna."
},
"CoaBots.WorldBrain.SessionBreaks": {
"t": "Hacer descansos",
"d": "Los bots alternan las misiones con unos minutos de vida en la ciudad, como haría un jugador."
},
"CoaBots.WorldBrain.GatherDetours": {
"t": "Recolectar hierbas y minerales cercanos",
"d": "Los recolectores se desvían de su camino por un nodo justo al lado."
},
"CoaBots.WorldBrain.MaxActiveQuests": {
"t": "Máximo de misiones activas",
"d": "Cuántas misiones lleva un bot a la vez."
},
"CoaBots.WorldBrain.AcceptEliteQuests": {
"t": "Aceptar misiones de élite",
"d": "Los bots en solitario aceptan misiones de élite. Desactivado por defecto porque normalmente no pueden completarlas solos."
},
"CoaBots.WorldBrain.QuestLevelBelow": {
"t": "Misiones por debajo de su nivel",
"d": "Cuántos niveles por debajo del suyo sigue aceptando misiones un bot."
},
"CoaBots.WorldBrain.QuestLevelAbove": {
"t": "Misiones por encima de su nivel",
"d": "Cuántos niveles por encima del suyo sigue aceptando misiones un bot."
},
"CoaBots.WorldBrain.MaxMobLevelAbove": {
"t": "Nivel máximo del monstruo de misión",
"d": "Cuánto por encima de su nivel puede estar un monstruo de misión."
},
"CoaBots.WorldBrain.MaxObjectiveDistance": {
"t": "Objetivo de misión más lejano",
"d": "No se aceptan las misiones cuyos objetivos estén más lejos."
},
"CoaBots.WorldBrain.GiverSearchRadius": {
"t": "Radio de búsqueda de dadores de misiones",
"d": "Hasta dónde busca un bot a quienes dan misiones."
},
"CoaBots.WorldBrain.MaxBadAreasPerObjective": {
"t": "Zonas probadas por objetivo",
"d": "Cuántas zonas descarta un bot antes de dejar la misión aparcada."
},
"CoaBots.WorldBrain.QuestSuspendMs": {
"t": "Tiempo aparcada (corto)",
"d": "Cuánto tiempo se aparca una misión después de que el bot renuncie a ella."
},
"CoaBots.WorldBrain.QuestSuspendMaxMs": {
"t": "Tiempo aparcada (largo)",
"d": "El máximo tiempo que una misión puede quedar aparcada tras fallos repetidos."
},
"CoaBots.LfgFill.Enable": {
"t": "Relleno con bots del Buscador de mazmorras",
"d": "Cuando te pones en cola solo para una mazmorra, los bots ocupan los roles de tanque, sanador y daño que falten."
},
"CoaBots.AutoQueueDungeon.Enable": {
"t": "Los bots se ponen en cola para mazmorras solos",
"d": "Experimental: los bots tanque inactivos se ponen en cola para una mazmorra por su cuenta y el relleno anterior completa el grupo."
},
"CoaBots.BGFill.Enable": {
"t": "Relleno con bots de campos de batalla",
"d": "Cuando alguien se pone en cola para un campo de batalla, ambos equipos se completan con bots para que la partida empiece enseguida."
},
"CoaBots.BGFill.TargetPlayersPerTeam": {
"t": "Tamaño de equipo en campos de batalla",
"d": "Bots por equipo hasta los que rellenar. 0 significa un equipo completo para ese campo de batalla."
},
"CoaBots.WorldBrain.HelpOthers": {
"t": "Los bots ayudan a jugadores cercanos",
"d": "Un bot que pasa por allí se une a un combate que un jugador amigo está perdiendo, sin quedarse con su botín ni su crédito."
},
"CoaBots.WorldBrain.HelpRadius": {
"t": "Distancia de ayuda",
"d": "Lo cerca que debe estar un bot para meterse en un combate."
},
"CoaBots.WorldBrain.HelpHealthPct": {
"t": "Ayudar por debajo de salud",
"d": "Los bots ayudan cuando la salud del jugador que combate está por debajo de este valor."
},
"CoaBots.WorldBrain.ResurrectOthers": {
"t": "Los bots resucitan a jugadores caídos",
"d": "Los bots que conocen un hechizo de resurrección reviven cadáveres amigos."
},
"CoaBots.WorldBrain.TemporaryParties": {
"t": "Grupos temporales de bots",
"d": "Bots de nivel parecido con la misma misión se unen brevemente."
},
"CoaBots.WorldBrain.PartyMaxSize": {
"t": "Tamaño del grupo temporal",
"d": "El mayor tamaño de un grupo temporal."
},
"CoaBots.WorldBrain.PartyRadius": {
"t": "Distancia para formar grupo",
"d": "Lo cerca que deben estar los bots para unirse."
},
"CoaBots.WorldBrain.PartyMinMinutes": {
"t": "Duración mínima del grupo",
"d": "Duración mínima de un grupo temporal."
},
"CoaBots.WorldBrain.PartyMaxMinutes": {
"t": "Duración máxima del grupo",
"d": "Duración máxima de un grupo temporal."
},
"CoaBots.WorldBrain.PlannerMinMs": {
"t": "Intervalo del planificador (mín.)",
"d": "El menor tiempo entre las pasadas de planificación de un bot."
},
"CoaBots.WorldBrain.PlannerMaxMs": {
"t": "Intervalo del planificador (máx.)",
"d": "El mayor tiempo entre las pasadas de planificación de un bot."
},
"CoaBots.WorldBrain.ScanMinMs": {
"t": "Intervalo de búsqueda de objetivos (mín.)",
"d": "El menor tiempo entre búsquedas de objetivos."
},
"CoaBots.WorldBrain.ScanMaxMs": {
"t": "Intervalo de búsqueda de objetivos (máx.)",
"d": "El mayor tiempo entre búsquedas de objetivos."
},
"CoaBots.WorldBrain.SearchTimeoutMs": {
"t": "Límite de tiempo de búsqueda en una zona",
"d": "Cuánto tiempo rastrea un bot una zona vacía antes de probar otra."
},
"CoaBots.WorldBrain.ApproachTimeoutMs": {
"t": "Límite de tiempo de aproximación",
"d": "Cuánto tiempo camina un bot hacia un objetivo reservado."
},
"CoaBots.WorldBrain.TravelTimeoutMs": {
"t": "Límite de tiempo de viaje",
"d": "Cuánto puede durar todo un viaje a una zona."
},
"CoaBots.WorldBrain.Weight.TravelPer100Yards": {
"t": "Peso de preferencia: TravelPer100Yards",
"d": "Cuánto peso dan los bots a este factor al elegir qué hacer a continuación. Cámbialo solo si conoces el planificador."
},
"CoaBots.WorldBrain.Weight.CrowdPerBot": {
"t": "Peso de preferencia: CrowdPerBot",
"d": "Cuánto peso dan los bots a este factor al elegir qué hacer a continuación. Cámbialo solo si conoces el planificador."
},
"CoaBots.WorldBrain.Weight.Overlap": {
"t": "Peso de preferencia: Overlap",
"d": "Cuánto peso dan los bots a este factor al elegir qué hacer a continuación. Cámbialo solo si conoces el planificador."
},
"CoaBots.WorldBrain.Weight.TurnIn": {
"t": "Peso de preferencia: TurnIn",
"d": "Cuánto peso dan los bots a este factor al elegir qué hacer a continuación. Cámbialo solo si conoces el planificador."
},
"CoaBots.WorldBrain.Weight.Accept": {
"t": "Peso de preferencia: Accept",
"d": "Cuánto peso dan los bots a este factor al elegir qué hacer a continuación. Cámbialo solo si conoces el planificador."
},
"CoaBots.WorldBrain.Weight.AreaOccupancy": {
"t": "Peso de preferencia: AreaOccupancy",
"d": "Cuánto peso dan los bots a este factor al elegir qué hacer a continuación. Cámbialo solo si conoces el planificador."
},
"CoaBots.WorldBrain.Weight.TargetReserved": {
"t": "Peso de preferencia: TargetReserved",
"d": "Cuánto peso dan los bots a este factor al elegir qué hacer a continuación. Cámbialo solo si conoces el planificador."
},
"Rate.XP.Kill": {
"t": "Experiencia por muertes",
"d": "Multiplicador de la experiencia ganada al matar monstruos. 1 es lo normal."
},
"Rate.XP.Quest": {
"t": "Experiencia por misiones",
"d": "Multiplicador de la experiencia ganada con las misiones."
},
"Rate.XP.Quest.DF": {
"t": "Experiencia por misiones diarias / Buscador de mazmorras",
"d": "Multiplicador de la experiencia de las misiones del Buscador de mazmorras."
},
"Rate.XP.Explore": {
"t": "Experiencia por explorar",
"d": "Multiplicador de la experiencia por descubrir nuevas zonas."
},
"Rate.Drop.Item.Normal": {
"t": "Tasa de botín de objetos",
"d": "Multiplicador de la frecuencia con que caen objetos de calidad normal."
},
"Rate.Drop.Money": {
"t": "Tasa de botín de oro",
"d": "Multiplicador del oro que sueltan los monstruos."
},
"Rate.Honor": {
"t": "Ganancia de honor",
"d": "Multiplicador de los puntos de honor obtenidos."
},
"Rate.Reputation.Gain": {
"t": "Ganancia de reputación",
"d": "Multiplicador de la reputación obtenida."
},
"Rate.Rest.InGame": {
"t": "Bonificación de descanso al jugar",
"d": "Velocidad a la que se consume la experiencia de descanso mientras juegas."
},
"Rate.Creature.Normal.Damage": {
"t": "Daño de los monstruos",
"d": "Multiplicador del daño de los monstruos normales. Cambia mucho la dificultad."
},
"Rate.Creature.Normal.HP": {
"t": "Salud de los monstruos",
"d": "Multiplicador de la salud de los monstruos normales. Cambia mucho la dificultad."
},
"PlayerLimit": {
"t": "Máximo de jugadores",
"d": "Cuántos jugadores pueden estar conectados a la vez. 0 significa sin límite."
},
"GameType": {
"t": "Tipo de reino",
"d": "El tipo de reino que se muestra en la lista de reinos."
},
"RealmZone": {
"t": "Zona del reino",
"d": "El grupo de idioma / región del reino que se muestra a los clientes."
},
"PlayerSaveInterval": {
"t": "Intervalo de autoguardado",
"d": "Con qué frecuencia se guardan los personajes de los jugadores mientras están conectados."
},
"CharactersPerAccount": {
"t": "Personajes por cuenta",
"d": "Máximo de personajes que puede tener una cuenta entre todos los reinos."
},
"CharactersPerRealm": {
"t": "Personajes por reino",
"d": "Máximo de personajes que puede tener una cuenta en este reino."
},
"StartPlayerLevel": {
"t": "Nivel inicial",
"d": "Nivel con el que empiezan los personajes nuevos."
},
"StartPlayerMoney": {
"t": "Oro inicial",
"d": "Cobre con el que empiezan los personajes nuevos (10000 de cobre = 1 de oro)."
},
"MaxPlayerLevel": {
"t": "Nivel máximo",
"d": "El nivel más alto que puede alcanzar un personaje. El cliente del juego solo admite 80."
},
"SkipCinematics": {
"t": "Omitir cinemáticas de introducción",
"d": "Si los personajes nuevos ven su película de introducción."
},
"DurabilityLoss.OnDeath": {
"t": "Durabilidad perdida al morir",
"d": "Porcentaje de durabilidad de los objetos que se pierde al morir."
},
"Death.SicknessLevel": {
"t": "Enfermedad de resurrección hasta el nivel",
"d": "Los personajes por encima de este nivel sufren la enfermedad de resurrección. -10 la desactiva."
},
"LeaveGroupOnLogout.Enabled": {
"t": "Dejar el grupo al desconectarse",
"d": "Los jugadores salen de su grupo cuando cierran sesión."
},
"GM.LoginState": {
"t": "Modo GM al iniciar sesión",
"d": "Si los maestros del juego empiezan con el modo GM activado."
},
"AllowTwoSide.Accounts": {
"t": "Ambas facciones en una cuenta",
"d": "Una cuenta puede tener personajes de la Alianza y de la Horda."
},
"AllowTwoSide.Interaction.Chat": {
"t": "Chat entre facciones",
"d": "Los jugadores de la Alianza y de la Horda pueden hablar entre sí."
},
"AutoBroadcast.On": {
"t": "Anuncios automáticos",
"d": "Difunde a intervalos regulares los mensajes guardados en la base de datos."
},
"AutoBroadcast.Timer": {
"t": "Intervalo de anuncios",
"d": "Tiempo entre los anuncios automáticos."
},
"Warden.Enabled": {
"t": "Antitrampas (Warden)",
"d": "Activa la comprobación antitrampas del cliente. Normalmente innecesaria en un servidor privado."
},
"MapUpdateInterval": {
"t": "Intervalo de actualización de mapas",
"d": "Con qué frecuencia el mundo actualiza los mapas. Más bajo es más fluido, pero usa más CPU."
},
"MinWorldUpdateTime": {
"t": "Tiempo mínimo de actualización del mundo",
"d": "El menor tiempo entre actualizaciones del mundo. Súbelo para reducir el uso de CPU."
},
"ThreadPool": {
"t": "Hilos de trabajo",
"d": "Número de hilos de trabajo en segundo plano. Más ayuda con muchos jugadores."
},
"Network.Threads": {
"t": "Hilos de red",
"d": "Hilos que gestionan las conexiones de los jugadores. Uno basta para servidores pequeños."
},
"Ra.Enable": {
"t": "Consola remota",
"d": "Permite que el Manager y los scripts envíen comandos al servidor. Solo escucha en este equipo; déjalo así."
},
"Ra.IP": {
"t": "Dirección de la consola remota",
"d": "Dirección en la que escucha la consola remota. Mantén 127.0.0.1 para que no sea accesible desde internet."
},
"Updates.EnableDatabases": {
"t": "Actualizaciones automáticas de bases de datos",
"d": "Permite que el servidor cambie él mismo sus bases de datos al arrancar. Déjalo en 0: el Manager aplica y registra las actualizaciones de bases de datos de forma segura."
}
},
"options": {
"GameType.0": "Normal",
"GameType.1": "Jugador contra jugador",
"GameType.6": "Juego de rol",
"GameType.8": "Juego de rol + JcJ",
"SkipCinematics.0": "Mostrar todas",
"SkipCinematics.1": "Omitir salvo para el primer personaje",
"SkipCinematics.2": "Omitir todas",
"GM.LoginState.0": "Apagado",
"GM.LoginState.1": "Encendido",
"GM.LoginState.2": "Igual que la última vez"
},
"presets": {
"balanced": {
"t": "Equilibrado",
"d": "La configuración recomendada para el día a día: misiones, relleno de mazmorras y de campos de batalla activados, registros discretos."
},
"solo-friendly": {
"t": "Apto para jugar solo",
"d": "Bots que te ayudan y completan tus grupos, con una población pequeña y tranquila."
},
"living-world": {
"t": "Mundo vivo",
"d": "Un mundo más animado: más bots, descansos, desvíos para recolectar y grupos temporales."
},
"performance": {
"t": "Rendimiento",
"d": "Menos bots y menos cálculo de los bots, para equipos más modestos."
},
"vanilla": {
"t": "Tasas estándar",
"d": "Todo a velocidad normal (×1)."
},
"relaxed": {
"t": "Relajado",
"d": "Subida de nivel más rápida y más botín para jugar sin prisa (×2 / ×3)."
},
"fast": {
"t": "Rápido",
"d": "Progreso rápido para pruebas o sesiones cortas (×5)."
},
"defaults": {
"t": "Valores recomendados",
"d": "Todos los ajustes vuelven a su valor predeterminado."
}
}
}
+513
View File
@@ -0,0 +1,513 @@
{
"categories": {
"bots.general": "Général",
"bots.population": "Population",
"bots.login": "Connexion",
"bots.roles": "Rôles",
"bots.openworld": "Monde ouvert",
"bots.quests": "Quêtes",
"bots.combat": "Combat",
"bots.dungeon": "Recherche de donjon",
"bots.battlegrounds": "Champs de bataille",
"bots.social": "Comportement social",
"bots.advanced": "Avancé",
"server.gameplay": "Jeu",
"server.rates": "Taux",
"server.characters": "Personnages",
"server.world": "Monde",
"server.network": "Réseau",
"server.accounts": "Comptes",
"server.logging": "Journaux",
"server.performance": "Performances",
"server.advanced": "Avancé"
},
"units": {
"ms": "ms",
"%": "%",
"yards": "yards",
"levels": "niveaux",
"x": "×",
"min": "min",
"copper": "cuivre"
},
"settings": {
"CoaBots.RandomSpawn.DefaultCount": {
"t": "Bots créés par commande",
"d": "Nombre de bots que la commande de création crée quand vous n’indiquez pas de nombre."
},
"CoaBots.RandomSpawn.MaxCount": {
"t": "Plus grande création en une fois",
"d": "Limite de sécurité pour une commande de création. De très gros lots peuvent figer brièvement le serveur ; n’augmentez donc cette valeur que si personne d’autre ne joue."
},
"CoaBots.LeveledSpawn.Brackets": {
"t": "Répartition des niveaux des nouveaux bots",
"d": "Comment les nouveaux bots à faire progresser sont répartis par niveau, sous forme de plages pondérées (1-20:40 signifie 40 parts de bots entre les niveaux 1 et 20)."
},
"CoaBots.RandomSpawn.AutoLogin": {
"t": "Connecter tout de suite les nouveaux bots",
"d": "Les bots tout juste créés se connectent immédiatement au lieu d’attendre dans la base de données."
},
"CoaBots.RandomSpawn.AccountPrefix": {
"t": "Préfixe des noms de comptes de bots",
"d": "Les bots vivent sur des comptes dont le nom commence par ce préfixe. Le modifier empêche les bots existants de se connecter automatiquement."
},
"CoaBots.RandomSpawn.BatchSize": {
"t": "Bots créés par étape",
"d": "La création est étalée dans le temps pour garder le serveur fluide. Des valeurs plus élevées sont plus rapides mais peuvent causer des saccades."
},
"CoaBots.RandomSpawn.BatchIntervalMs": {
"t": "Pause entre les étapes de création",
"d": "Temps entre les étapes de création. Des valeurs plus basses sont plus rapides mais peuvent causer des saccades."
},
"CoaBots.AutoLoginOnStartup": {
"t": "Connexion automatique des bots",
"d": "Les bots existants se connectent eux-mêmes à chaque démarrage du serveur."
},
"CoaBots.AutoLogin.MaxCount": {
"t": "Maximum de bots en ligne",
"d": "Le plus grand nombre de bots qui se connectent automatiquement au démarrage."
},
"CoaBots.AutoLogin.BatchSize": {
"t": "Bots connectés par étape",
"d": "Les connexions sont étalées dans le temps. Des valeurs plus élevées mettent les bots en ligne plus vite mais chargent davantage le serveur."
},
"CoaBots.AutoLogin.BatchIntervalMs": {
"t": "Pause entre les étapes de connexion",
"d": "Temps entre les étapes de connexion."
},
"CoaBots.SpecBalance.TankTargetPct": {
"t": "Part visée de tanks",
"d": "Part des bots qui doivent être des tanks."
},
"CoaBots.SpecBalance.HealerTargetPct": {
"t": "Part visée de soigneurs",
"d": "Part des bots qui doivent être des soigneurs."
},
"CoaBots.TalentBuildsPath": {
"t": "Fichier de données de talents",
"d": "Emplacement des builds de talents de la communauté. Laissez vide pour utiliser la recherche intégrée."
},
"CoaBots.World.Enable": {
"t": "Les bots se promènent dans le monde",
"d": "Les bots inactifs font des courses : ils visitent les services de la ville, vont vers des zones de récolte ou de farm, ou se promènent un peu."
},
"CoaBots.WorldBrain.Enable": {
"t": "IA de quêtes",
"d": "Les bots prennent et accomplissent des quêtes seuls. Désactivée, les bots ne font que récolter, pêcher, farmer et se promener."
},
"CoaBots.World.ServiceRadius": {
"t": "Rayon de recherche des services de la ville",
"d": "Jusqu’où un bot cherche des marchands et des boîtes aux lettres."
},
"CoaBots.World.AreaRadius": {
"t": "Rayon de la zone de courses",
"d": "Jusqu’où un bot cherche des zones de récolte ou de farm."
},
"CoaBots.World.ThinkIntervalMs": {
"t": "Intervalle de vérification des courses",
"d": "Fréquence à laquelle chaque bot reconsidère sa course."
},
"CoaBots.World.IdleBeforeErrandMs": {
"t": "Inactivité avant une course",
"d": "Durée pendant laquelle un bot ne doit rien trouver à faire avant de commencer une course."
},
"CoaBots.World.VerboseLog": {
"t": "Journal détaillé des courses",
"d": "Écrit chaque étape des courses dans le journal. Utile pour le débogage, bruyant avec beaucoup de bots."
},
"CoaBots.Combat.VerboseLog": {
"t": "Journal détaillé des combats",
"d": "Écrit les décisions de combat dans le journal. Très bruyant ; à activer seulement pour traquer un problème précis."
},
"CoaBots.Profile.IntentMinMs": {
"t": "Activité la plus courte",
"d": "La durée la plus courte pendant laquelle un bot reste sur un même type d’activité."
},
"CoaBots.Profile.IntentMaxMs": {
"t": "Activité la plus longue",
"d": "La durée la plus longue pendant laquelle un bot reste sur un même type d’activité."
},
"CoaBots.Grind.SearchRadius": {
"t": "Rayon de recherche pour le farm",
"d": "Jusqu’où un bot cherche des monstres à farmer."
},
"CoaBots.Grind.MaxLevelAbove": {
"t": "Niveau maximal des monstres à farmer",
"d": "De combien de niveaux au-dessus de lui un bot accepte de combattre en farmant."
},
"CoaBots.Healer.CriticalHealPct": {
"t": "Seuil de soin d’urgence",
"d": "Les soigneurs passent à leurs soins les plus puissants quand la vie d’un allié tombe sous cette valeur."
},
"CoaBots.WorldBrain.SearchRadius": {
"t": "Rayon de recherche des cibles",
"d": "Jusqu’où un bot cherche des cibles de quête dans une zone d’objectif."
},
"CoaBots.WorldBrain.EngageRange": {
"t": "Distance d’engagement",
"d": "Distance à laquelle un bot commence à combattre un monstre de quête."
},
"CoaBots.WorldBrain.MountDistanceMin": {
"t": "Monter en selle au-delà de (min.)",
"d": "Chaque bot monte pour les trajets plus longs qu’une distance personnelle tirée entre cette valeur et le maximum."
},
"CoaBots.WorldBrain.MountDistanceMax": {
"t": "Monter en selle au-delà de (max.)",
"d": "Borne supérieure de la distance personnelle de monte."
},
"CoaBots.WorldBrain.TaxiMinDistance": {
"t": "Utiliser les trajets aériens au-delà de",
"d": "Les trajets plus longs utilisent un trajet aérien quand le bot connaît un itinéraire."
},
"CoaBots.WorldBrain.SessionBreaks": {
"t": "Faire des pauses",
"d": "Les bots alternent quêtes et quelques minutes de vie en ville, comme un joueur."
},
"CoaBots.WorldBrain.GatherDetours": {
"t": "Récolter herbes et minerai à proximité",
"d": "Les récolteurs quittent leur chemin pour un filon juste à côté."
},
"CoaBots.WorldBrain.MaxActiveQuests": {
"t": "Maximum de quêtes actives",
"d": "Nombre de quêtes qu’un bot porte en même temps."
},
"CoaBots.WorldBrain.AcceptEliteQuests": {
"t": "Accepter les quêtes d’élite",
"d": "Les bots solo prennent des quêtes d’élite. Désactivé par défaut car ils ne peuvent généralement pas les finir seuls."
},
"CoaBots.WorldBrain.QuestLevelBelow": {
"t": "Quêtes sous son propre niveau",
"d": "De combien de niveaux en dessous de lui un bot accepte encore des quêtes."
},
"CoaBots.WorldBrain.QuestLevelAbove": {
"t": "Quêtes au-dessus de son propre niveau",
"d": "De combien de niveaux au-dessus de lui un bot accepte encore des quêtes."
},
"CoaBots.WorldBrain.MaxMobLevelAbove": {
"t": "Niveau maximal des monstres de quête",
"d": "De combien un monstre de quête peut dépasser le niveau du bot."
},
"CoaBots.WorldBrain.MaxObjectiveDistance": {
"t": "Objectif de quête le plus éloigné",
"d": "Les quêtes dont les objectifs sont plus loin ne sont pas prises."
},
"CoaBots.WorldBrain.GiverSearchRadius": {
"t": "Rayon de recherche des donneurs de quêtes",
"d": "Jusqu’où un bot cherche des donneurs de quêtes."
},
"CoaBots.WorldBrain.MaxBadAreasPerObjective": {
"t": "Zones essayées par objectif",
"d": "Nombre de zones qu’un bot abandonne avant de mettre la quête de côté."
},
"CoaBots.WorldBrain.QuestSuspendMs": {
"t": "Durée de mise de côté (courte)",
"d": "Durée pendant laquelle une quête est mise de côté après l’abandon du bot."
},
"CoaBots.WorldBrain.QuestSuspendMaxMs": {
"t": "Durée de mise de côté (longue)",
"d": "La durée maximale pendant laquelle une quête peut rester de côté après des échecs répétés."
},
"CoaBots.LfgFill.Enable": {
"t": "Complétion par bots de la recherche de donjon",
"d": "Quand vous vous inscrivez seul pour un donjon, des bots comblent les rôles manquants de tank, soigneur et dégâts."
},
"CoaBots.AutoQueueDungeon.Enable": {
"t": "Les bots s’inscrivent seuls aux donjons",
"d": "Expérimental : des bots tanks inactifs s’inscrivent seuls à un donjon et la complétion ci-dessus termine le groupe."
},
"CoaBots.BGFill.Enable": {
"t": "Complétion par bots des champs de bataille",
"d": "Quand quelqu’un s’inscrit à un champ de bataille, les deux équipes sont complétées par des bots pour que la partie commence tout de suite."
},
"CoaBots.BGFill.TargetPlayersPerTeam": {
"t": "Taille d’équipe en champ de bataille",
"d": "Nombre de bots par équipe jusqu’auquel compléter. 0 signifie une équipe complète pour ce champ de bataille."
},
"CoaBots.WorldBrain.HelpOthers": {
"t": "Les bots aident les joueurs proches",
"d": "Un bot de passage rejoint un combat qu’un joueur ami est en train de perdre, sans prendre son butin ni son crédit."
},
"CoaBots.WorldBrain.HelpRadius": {
"t": "Distance d’aide",
"d": "À quelle distance un bot doit être pour rejoindre un combat."
},
"CoaBots.WorldBrain.HelpHealthPct": {
"t": "Aider sous une vie de",
"d": "Les bots aident quand la vie du joueur qui combat est inférieure à cette valeur."
},
"CoaBots.WorldBrain.ResurrectOthers": {
"t": "Les bots ressuscitent les joueurs tombés",
"d": "Les bots qui connaissent un sort de résurrection relèvent les cadavres amis."
},
"CoaBots.WorldBrain.TemporaryParties": {
"t": "Groupes de bots temporaires",
"d": "Des bots de niveau proche avec la même quête s’unissent brièvement."
},
"CoaBots.WorldBrain.PartyMaxSize": {
"t": "Taille des groupes temporaires",
"d": "Plus grand groupe temporaire."
},
"CoaBots.WorldBrain.PartyRadius": {
"t": "Distance de formation de groupe",
"d": "À quelle distance les bots doivent être pour s’unir."
},
"CoaBots.WorldBrain.PartyMinMinutes": {
"t": "Durée minimale de groupe",
"d": "Durée minimale d’un groupe temporaire."
},
"CoaBots.WorldBrain.PartyMaxMinutes": {
"t": "Durée maximale de groupe",
"d": "Durée maximale d’un groupe temporaire."
},
"CoaBots.WorldBrain.PlannerMinMs": {
"t": "Intervalle du planificateur (min.)",
"d": "Temps le plus court entre deux passes de planification d’un bot."
},
"CoaBots.WorldBrain.PlannerMaxMs": {
"t": "Intervalle du planificateur (max.)",
"d": "Temps le plus long entre deux passes de planification d’un bot."
},
"CoaBots.WorldBrain.ScanMinMs": {
"t": "Intervalle de recherche de cibles (min.)",
"d": "Temps le plus court entre deux recherches de cibles."
},
"CoaBots.WorldBrain.ScanMaxMs": {
"t": "Intervalle de recherche de cibles (max.)",
"d": "Temps le plus long entre deux recherches de cibles."
},
"CoaBots.WorldBrain.SearchTimeoutMs": {
"t": "Limite de temps de recherche de zone",
"d": "Durée pendant laquelle un bot fouille une zone vide avant d’en essayer une autre."
},
"CoaBots.WorldBrain.ApproachTimeoutMs": {
"t": "Limite de temps d’approche",
"d": "Durée pendant laquelle un bot marche vers une cible réservée."
},
"CoaBots.WorldBrain.TravelTimeoutMs": {
"t": "Limite de temps de trajet",
"d": "Durée maximale d’un trajet complet vers une zone."
},
"CoaBots.WorldBrain.Weight.TravelPer100Yards": {
"t": "Poids de préférence : TravelPer100Yards",
"d": "Importance que les bots accordent à ce facteur pour choisir ce qu’ils font ensuite. Ne modifiez que si vous connaissez le planificateur."
},
"CoaBots.WorldBrain.Weight.CrowdPerBot": {
"t": "Poids de préférence : CrowdPerBot",
"d": "Importance que les bots accordent à ce facteur pour choisir ce qu’ils font ensuite. Ne modifiez que si vous connaissez le planificateur."
},
"CoaBots.WorldBrain.Weight.Overlap": {
"t": "Poids de préférence : Overlap",
"d": "Importance que les bots accordent à ce facteur pour choisir ce qu’ils font ensuite. Ne modifiez que si vous connaissez le planificateur."
},
"CoaBots.WorldBrain.Weight.TurnIn": {
"t": "Poids de préférence : TurnIn",
"d": "Importance que les bots accordent à ce facteur pour choisir ce qu’ils font ensuite. Ne modifiez que si vous connaissez le planificateur."
},
"CoaBots.WorldBrain.Weight.Accept": {
"t": "Poids de préférence : Accept",
"d": "Importance que les bots accordent à ce facteur pour choisir ce qu’ils font ensuite. Ne modifiez que si vous connaissez le planificateur."
},
"CoaBots.WorldBrain.Weight.AreaOccupancy": {
"t": "Poids de préférence : AreaOccupancy",
"d": "Importance que les bots accordent à ce facteur pour choisir ce qu’ils font ensuite. Ne modifiez que si vous connaissez le planificateur."
},
"CoaBots.WorldBrain.Weight.TargetReserved": {
"t": "Poids de préférence : TargetReserved",
"d": "Importance que les bots accordent à ce facteur pour choisir ce qu’ils font ensuite. Ne modifiez que si vous connaissez le planificateur."
},
"Rate.XP.Kill": {
"t": "Expérience des morts",
"d": "Multiplicateur de l’expérience gagnée en tuant des monstres. 1 est normal."
},
"Rate.XP.Quest": {
"t": "Expérience des quêtes",
"d": "Multiplicateur de l’expérience gagnée avec les quêtes."
},
"Rate.XP.Quest.DF": {
"t": "Expérience des quêtes quotidiennes / recherche de donjon",
"d": "Multiplicateur de l’expérience des quêtes de la recherche de donjon."
},
"Rate.XP.Explore": {
"t": "Expérience d’exploration",
"d": "Multiplicateur de l’expérience de la découverte de nouvelles zones."
},
"Rate.Drop.Item.Normal": {
"t": "Taux de butin d’objets",
"d": "Multiplicateur de la fréquence de chute des objets de qualité normale."
},
"Rate.Drop.Money": {
"t": "Taux de butin d’or",
"d": "Multiplicateur de l’or laissé par les monstres."
},
"Rate.Honor": {
"t": "Gain d’honneur",
"d": "Multiplicateur des points d’honneur gagnés."
},
"Rate.Reputation.Gain": {
"t": "Gain de réputation",
"d": "Multiplicateur de la réputation gagnée."
},
"Rate.Rest.InGame": {
"t": "Bonus de repos en jeu",
"d": "Vitesse à laquelle l’expérience de repos est consommée pendant que vous jouez."
},
"Rate.Creature.Normal.Damage": {
"t": "Dégâts des monstres",
"d": "Multiplicateur des dégâts des monstres normaux. Change beaucoup la difficulté."
},
"Rate.Creature.Normal.HP": {
"t": "Vie des monstres",
"d": "Multiplicateur de la vie des monstres normaux. Change beaucoup la difficulté."
},
"PlayerLimit": {
"t": "Nombre maximal de joueurs",
"d": "Combien de joueurs peuvent être en ligne en même temps. 0 signifie aucune limite."
},
"GameType": {
"t": "Type de royaume",
"d": "Le type de royaume affiché dans la liste des royaumes."
},
"RealmZone": {
"t": "Zone du royaume",
"d": "Le groupe de langue / de région du royaume montré aux clients."
},
"PlayerSaveInterval": {
"t": "Intervalle de sauvegarde automatique",
"d": "Fréquence de sauvegarde des personnages des joueurs pendant qu’ils sont en ligne."
},
"CharactersPerAccount": {
"t": "Personnages par compte",
"d": "Nombre maximal de personnages qu’un compte peut avoir sur tous les royaumes."
},
"CharactersPerRealm": {
"t": "Personnages par royaume",
"d": "Nombre maximal de personnages qu’un compte peut avoir sur ce royaume."
},
"StartPlayerLevel": {
"t": "Niveau de départ",
"d": "Niveau auquel commencent les nouveaux personnages."
},
"StartPlayerMoney": {
"t": "Or de départ",
"d": "Cuivre avec lequel commencent les nouveaux personnages (10000 cuivre = 1 or)."
},
"MaxPlayerLevel": {
"t": "Niveau maximal",
"d": "Niveau le plus élevé qu’un personnage peut atteindre. Le client du jeu ne gère que 80."
},
"SkipCinematics": {
"t": "Ignorer les cinématiques d’introduction",
"d": "Si les nouveaux personnages voient leur film d’introduction."
},
"DurabilityLoss.OnDeath": {
"t": "Durabilité perdue à la mort",
"d": "Pourcentage de durabilité des objets perdu à la mort."
},
"Death.SicknessLevel": {
"t": "Maladie de résurrection jusqu’au niveau",
"d": "Les personnages au-dessus de ce niveau reçoivent la maladie de résurrection. -10 la désactive."
},
"LeaveGroupOnLogout.Enabled": {
"t": "Quitter le groupe à la déconnexion",
"d": "Les joueurs sont retirés de leur groupe quand ils se déconnectent."
},
"GM.LoginState": {
"t": "Mode MJ à la connexion",
"d": "Si les maîtres de jeu démarrent avec le mode MJ activé."
},
"AllowTwoSide.Accounts": {
"t": "Les deux factions sur un compte",
"d": "Un compte peut avoir des personnages de l’Alliance et de la Horde."
},
"AllowTwoSide.Interaction.Chat": {
"t": "Discussion entre factions",
"d": "Les joueurs de l’Alliance et de la Horde peuvent se parler."
},
"AutoBroadcast.On": {
"t": "Annonces automatiques",
"d": "Diffuse à intervalles réguliers les messages stockés dans la base de données."
},
"AutoBroadcast.Timer": {
"t": "Intervalle des annonces",
"d": "Temps entre les annonces automatiques."
},
"Warden.Enabled": {
"t": "Anti-triche (Warden)",
"d": "Active la vérification anti-triche du client. Généralement inutile sur un serveur privé."
},
"MapUpdateInterval": {
"t": "Intervalle de mise à jour des cartes",
"d": "Fréquence à laquelle le monde met à jour les cartes. Plus bas est plus fluide mais utilise plus de processeur."
},
"MinWorldUpdateTime": {
"t": "Temps minimal de mise à jour du monde",
"d": "Temps le plus court entre deux mises à jour du monde. Augmentez-le pour réduire l’utilisation du processeur."
},
"ThreadPool": {
"t": "Threads de travail",
"d": "Nombre de threads de travail en arrière-plan. Plus aide avec beaucoup de joueurs."
},
"Network.Threads": {
"t": "Threads réseau",
"d": "Threads qui gèrent les connexions des joueurs. Un seul suffit pour les petits serveurs."
},
"Ra.Enable": {
"t": "Console distante",
"d": "Permet au Manager et aux scripts d’envoyer des commandes au serveur. Elle n’écoute que cet ordinateur ; laissez-la ainsi."
},
"Ra.IP": {
"t": "Adresse de la console distante",
"d": "Adresse sur laquelle écoute la console distante. Gardez 127.0.0.1 pour qu’elle ne soit pas accessible depuis Internet."
},
"Updates.EnableDatabases": {
"t": "Mises à jour automatiques des bases de données",
"d": "Permet au serveur de modifier lui-même ses bases de données au démarrage. Gardez 0 : le Manager applique et suit les mises à jour de base de données en toute sécurité."
}
},
"options": {
"GameType.0": "Normal",
"GameType.1": "Joueur contre joueur",
"GameType.6": "Jeu de rôle",
"GameType.8": "Jeu de rôle + JcJ",
"SkipCinematics.0": "Tout afficher",
"SkipCinematics.1": "Ignorer sauf pour le premier personnage",
"SkipCinematics.2": "Tout ignorer",
"GM.LoginState.0": "Désactivé",
"GM.LoginState.1": "Activé",
"GM.LoginState.2": "Comme la dernière fois"
},
"presets": {
"balanced": {
"t": "Équilibré",
"d": "La configuration recommandée au quotidien : quêtes, complétion des donjons et des champs de bataille activées, journaux discrets."
},
"solo-friendly": {
"t": "Adapté au solo",
"d": "Des bots qui vous aident et complètent vos groupes, avec une petite population tranquille."
},
"living-world": {
"t": "Monde vivant",
"d": "Un monde plus animé : plus de bots, pauses, détours de récolte et groupes temporaires."
},
"performance": {
"t": "Performances",
"d": "Moins de bots et moins de réflexion des bots, pour les ordinateurs plus modestes."
},
"vanilla": {
"t": "Taux standard",
"d": "Tout à vitesse normale (×1)."
},
"relaxed": {
"t": "Détendu",
"d": "Progression plus rapide et plus de butin pour un jeu décontracté (×2 / ×3)."
},
"fast": {
"t": "Rapide",
"d": "Progression rapide pour des tests ou de courtes sessions (×5)."
},
"defaults": {
"t": "Valeurs recommandées",
"d": "Tous les paramètres reviennent à leur valeur par défaut."
}
}
}
+513
View File
@@ -0,0 +1,513 @@
{
"categories": {
"bots.general": "Общее",
"bots.population": "Население",
"bots.login": "Вход",
"bots.roles": "Роли",
"bots.openworld": "Открытый мир",
"bots.quests": "Задания",
"bots.combat": "Бой",
"bots.dungeon": "Поиск подземелий",
"bots.battlegrounds": "Поля боя",
"bots.social": "Общение и помощь",
"bots.advanced": "Дополнительно",
"server.gameplay": "Игровой процесс",
"server.rates": "Множители",
"server.characters": "Персонажи",
"server.world": "Мир",
"server.network": "Сеть",
"server.accounts": "Учётные записи",
"server.logging": "Журналы",
"server.performance": "Производительность",
"server.advanced": "Дополнительно"
},
"units": {
"ms": "мс",
"%": "%",
"yards": "ярдов",
"levels": "уровней",
"x": "×",
"min": "мин",
"copper": "меди"
},
"settings": {
"CoaBots.RandomSpawn.DefaultCount": {
"t": "Ботов за одну команду создания",
"d": "Сколько ботов создаёт команда создания, если вы не указали число."
},
"CoaBots.RandomSpawn.MaxCount": {
"t": "Наибольшая единовременная партия",
"d": "Ограничение безопасности для одной команды создания. Очень большие партии могут ненадолго подвесить сервер, поэтому повышайте значение, только когда больше никто не играет."
},
"CoaBots.LeveledSpawn.Brackets": {
"t": "Распределение уровней новых ботов",
"d": "Как новые прокачиваемые боты распределяются по уровням: диапазоны с весами (1-20:40 означает 40 частей ботов с уровнями от 1 до 20)."
},
"CoaBots.RandomSpawn.AutoLogin": {
"t": "Сразу выводить новых ботов в сеть",
"d": "Только что созданные боты входят в игру сразу, а не ждут в базе данных."
},
"CoaBots.RandomSpawn.AccountPrefix": {
"t": "Префикс имён учётных записей ботов",
"d": "Боты живут на учётных записях, имена которых начинаются с этого префикса. Если его изменить, существующие боты перестанут входить автоматически."
},
"CoaBots.RandomSpawn.BatchSize": {
"t": "Ботов за один шаг создания",
"d": "Создание растягивается во времени, чтобы сервер работал плавно. Большие значения быстрее, но могут вызывать подвисания."
},
"CoaBots.RandomSpawn.BatchIntervalMs": {
"t": "Пауза между шагами создания",
"d": "Время между шагами создания. Меньшие значения быстрее, но могут вызывать подвисания."
},
"CoaBots.AutoLoginOnStartup": {
"t": "Автоматический вход ботов",
"d": "Существующие боты сами входят в игру при каждом запуске сервера."
},
"CoaBots.AutoLogin.MaxCount": {
"t": "Максимум ботов в сети",
"d": "Наибольшее число ботов, входящих автоматически при запуске."
},
"CoaBots.AutoLogin.BatchSize": {
"t": "Ботов за один шаг входа",
"d": "Входы растягиваются во времени. Большие значения быстрее выводят ботов в сеть, но сильнее нагружают сервер."
},
"CoaBots.AutoLogin.BatchIntervalMs": {
"t": "Пауза между шагами входа",
"d": "Время между шагами входа."
},
"CoaBots.SpecBalance.TankTargetPct": {
"t": "Целевая доля танков",
"d": "Какая доля ботов должна быть танками."
},
"CoaBots.SpecBalance.HealerTargetPct": {
"t": "Целевая доля целителей",
"d": "Какая доля ботов должна быть целителями."
},
"CoaBots.TalentBuildsPath": {
"t": "Файл с данными талантов",
"d": "Где хранятся билды талантов от сообщества. Оставьте пустым, чтобы использовать встроенный поиск."
},
"CoaBots.World.Enable": {
"t": "Боты бродят по миру",
"d": "Бездействующие боты занимаются делами: посещают городские службы, отправляются в места сбора ресурсов или гринда либо немного бродят."
},
"CoaBots.WorldBrain.Enable": {
"t": "ИИ заданий",
"d": "Боты сами берут и выполняют задания. Если выключить, боты только собирают ресурсы, ловят рыбу, гриндят и бродят."
},
"CoaBots.World.ServiceRadius": {
"t": "Радиус поиска городских служб",
"d": "Как далеко бот ищет торговцев и почтовые ящики."
},
"CoaBots.World.AreaRadius": {
"t": "Радиус области дел",
"d": "Как далеко бот ищет места для сбора ресурсов или гринда."
},
"CoaBots.World.ThinkIntervalMs": {
"t": "Интервал выбора дела",
"d": "Как часто каждый бот пересматривает своё дело."
},
"CoaBots.World.IdleBeforeErrandMs": {
"t": "Простой до начала дела",
"d": "Сколько времени бот должен не находить себе занятия, прежде чем начнёт дело."
},
"CoaBots.World.VerboseLog": {
"t": "Подробный журнал дел",
"d": "Записывает в журнал каждый шаг дел. Полезно для отладки, но при большом числе ботов создаёт много шума."
},
"CoaBots.Combat.VerboseLog": {
"t": "Подробный журнал боя",
"d": "Записывает в журнал решения в бою. Очень шумно; включайте только при поиске конкретной проблемы."
},
"CoaBots.Profile.IntentMinMs": {
"t": "Кратчайшее занятие",
"d": "Наименьшее время, в течение которого бот держится одного вида занятий."
},
"CoaBots.Profile.IntentMaxMs": {
"t": "Самое длинное занятие",
"d": "Наибольшее время, в течение которого бот держится одного вида занятий."
},
"CoaBots.Grind.SearchRadius": {
"t": "Радиус поиска для гринда",
"d": "Как далеко бот ищет монстров для гринда."
},
"CoaBots.Grind.MaxLevelAbove": {
"t": "Наибольший уровень монстра для гринда",
"d": "На сколько уровней выше себя бот согласен сражаться при гринде."
},
"CoaBots.Healer.CriticalHealPct": {
"t": "Порог экстренного лечения",
"d": "Целители переключаются на самые сильные исцеления, когда здоровье союзника падает ниже этого значения."
},
"CoaBots.WorldBrain.SearchRadius": {
"t": "Радиус поиска целей",
"d": "Как далеко бот высматривает цели заданий внутри области задачи."
},
"CoaBots.WorldBrain.EngageRange": {
"t": "Дистанция начала боя",
"d": "Расстояние, на котором бот начинает бой с монстром из задания."
},
"CoaBots.WorldBrain.MountDistanceMin": {
"t": "Садиться на транспорт при пути от (мин.)",
"d": "Каждый бот садится на ездовое животное в поездках длиннее личного расстояния, выбираемого между этим значением и максимумом."
},
"CoaBots.WorldBrain.MountDistanceMax": {
"t": "Садиться на транспорт при пути от (макс.)",
"d": "Верхняя граница личного расстояния для посадки на ездовое животное."
},
"CoaBots.WorldBrain.TaxiMinDistance": {
"t": "Летать по маршрутам при пути от",
"d": "Поездки длиннее этого значения выполняются на маршрутах перелёта, если бот знает подходящий."
},
"CoaBots.WorldBrain.SessionBreaks": {
"t": "Делать перерывы",
"d": "Боты чередуют задания с несколькими минутами городской жизни, как игрок."
},
"CoaBots.WorldBrain.GatherDetours": {
"t": "Собирать травы и руду поблизости",
"d": "Собиратели сходят с пути ради ресурса, лежащего совсем рядом."
},
"CoaBots.WorldBrain.MaxActiveQuests": {
"t": "Максимум активных заданий",
"d": "Сколько заданий бот несёт одновременно."
},
"CoaBots.WorldBrain.AcceptEliteQuests": {
"t": "Брать элитные задания",
"d": "Одиночные боты берутся за элитные задания. По умолчанию выключено, так как в одиночку они обычно их не выполняют."
},
"CoaBots.WorldBrain.QuestLevelBelow": {
"t": "Задания ниже своего уровня",
"d": "На сколько уровней ниже себя бот всё ещё берёт задания."
},
"CoaBots.WorldBrain.QuestLevelAbove": {
"t": "Задания выше своего уровня",
"d": "На сколько уровней выше себя бот всё ещё берёт задания."
},
"CoaBots.WorldBrain.MaxMobLevelAbove": {
"t": "Наибольший уровень монстра задания",
"d": "Насколько выше уровня бота может быть монстр из задания."
},
"CoaBots.WorldBrain.MaxObjectiveDistance": {
"t": "Самая далёкая цель задания",
"d": "Задания, цели которых дальше этого расстояния, не берутся."
},
"CoaBots.WorldBrain.GiverSearchRadius": {
"t": "Радиус поиска раздающих задания",
"d": "Как далеко бот ищет тех, кто раздаёт задания."
},
"CoaBots.WorldBrain.MaxBadAreasPerObjective": {
"t": "Областей на одну цель",
"d": "От скольких областей бот отказывается, прежде чем отложить задание."
},
"CoaBots.WorldBrain.QuestSuspendMs": {
"t": "Время откладывания (короткое)",
"d": "Как долго задание откладывается после того, как бот от него отказался."
},
"CoaBots.WorldBrain.QuestSuspendMaxMs": {
"t": "Время откладывания (длинное)",
"d": "Наибольший срок, на который задание можно отложить после повторных неудач."
},
"CoaBots.LfgFill.Enable": {
"t": "Дополнение группы ботами в Поиске подземелий",
"d": "Если вы встаёте в очередь в подземелье в одиночку, боты занимают недостающие роли танка, целителя и бойцов урона."
},
"CoaBots.AutoQueueDungeon.Enable": {
"t": "Боты сами встают в очередь в подземелья",
"d": "Экспериментально: бездействующие боты-танки сами встают в очередь в подземелье, а дополнение выше собирает группу."
},
"CoaBots.BGFill.Enable": {
"t": "Дополнение поля боя ботами",
"d": "Когда кто-то встаёт в очередь на поле боя, обе команды пополняются ботами, чтобы матч начался сразу."
},
"CoaBots.BGFill.TargetPlayersPerTeam": {
"t": "Размер команды на поле боя",
"d": "Сколько ботов в команде добавлять. 0 означает полную команду для этого поля боя."
},
"CoaBots.WorldBrain.HelpOthers": {
"t": "Боты помогают игрокам поблизости",
"d": "Проходящий мимо бот вступает в бой, который проигрывает дружественный игрок, не забирая его добычу и не претендуя на зачёт."
},
"CoaBots.WorldBrain.HelpRadius": {
"t": "Дистанция помощи",
"d": "Как близко бот должен находиться, чтобы вступить в бой."
},
"CoaBots.WorldBrain.HelpHealthPct": {
"t": "Помогать при здоровье ниже",
"d": "Боты помогают, когда здоровье сражающегося игрока ниже этого значения."
},
"CoaBots.WorldBrain.ResurrectOthers": {
"t": "Боты воскрешают павших игроков",
"d": "Боты, знающие заклинание воскрешения, поднимают дружественные тела."
},
"CoaBots.WorldBrain.TemporaryParties": {
"t": "Временные группы ботов",
"d": "Боты близкого уровня с одинаковым заданием ненадолго объединяются."
},
"CoaBots.WorldBrain.PartyMaxSize": {
"t": "Размер временной группы",
"d": "Наибольший размер временной группы."
},
"CoaBots.WorldBrain.PartyRadius": {
"t": "Расстояние для создания группы",
"d": "Как близко должны быть боты, чтобы объединиться."
},
"CoaBots.WorldBrain.PartyMinMinutes": {
"t": "Кратчайшее время группы",
"d": "Минимальная длительность временной группы."
},
"CoaBots.WorldBrain.PartyMaxMinutes": {
"t": "Наибольшее время группы",
"d": "Максимальная длительность временной группы."
},
"CoaBots.WorldBrain.PlannerMinMs": {
"t": "Интервал планировщика (мин.)",
"d": "Кратчайшее время между проходами планирования бота."
},
"CoaBots.WorldBrain.PlannerMaxMs": {
"t": "Интервал планировщика (макс.)",
"d": "Наибольшее время между проходами планирования бота."
},
"CoaBots.WorldBrain.ScanMinMs": {
"t": "Интервал поиска целей (мин.)",
"d": "Кратчайшее время между поисками целей."
},
"CoaBots.WorldBrain.ScanMaxMs": {
"t": "Интервал поиска целей (макс.)",
"d": "Наибольшее время между поисками целей."
},
"CoaBots.WorldBrain.SearchTimeoutMs": {
"t": "Лимит времени поиска в области",
"d": "Как долго бот обыскивает пустую область, прежде чем попробовать другую."
},
"CoaBots.WorldBrain.ApproachTimeoutMs": {
"t": "Лимит времени подхода",
"d": "Как долго бот идёт к выбранной цели."
},
"CoaBots.WorldBrain.TravelTimeoutMs": {
"t": "Лимит времени пути",
"d": "Сколько может занять вся поездка в область."
},
"CoaBots.WorldBrain.Weight.TravelPer100Yards": {
"t": "Вес предпочтения: TravelPer100Yards",
"d": "Насколько сильно боты учитывают этот фактор при выборе следующего дела. Меняйте, только если разбираетесь в планировщике."
},
"CoaBots.WorldBrain.Weight.CrowdPerBot": {
"t": "Вес предпочтения: CrowdPerBot",
"d": "Насколько сильно боты учитывают этот фактор при выборе следующего дела. Меняйте, только если разбираетесь в планировщике."
},
"CoaBots.WorldBrain.Weight.Overlap": {
"t": "Вес предпочтения: Overlap",
"d": "Насколько сильно боты учитывают этот фактор при выборе следующего дела. Меняйте, только если разбираетесь в планировщике."
},
"CoaBots.WorldBrain.Weight.TurnIn": {
"t": "Вес предпочтения: TurnIn",
"d": "Насколько сильно боты учитывают этот фактор при выборе следующего дела. Меняйте, только если разбираетесь в планировщике."
},
"CoaBots.WorldBrain.Weight.Accept": {
"t": "Вес предпочтения: Accept",
"d": "Насколько сильно боты учитывают этот фактор при выборе следующего дела. Меняйте, только если разбираетесь в планировщике."
},
"CoaBots.WorldBrain.Weight.AreaOccupancy": {
"t": "Вес предпочтения: AreaOccupancy",
"d": "Насколько сильно боты учитывают этот фактор при выборе следующего дела. Меняйте, только если разбираетесь в планировщике."
},
"CoaBots.WorldBrain.Weight.TargetReserved": {
"t": "Вес предпочтения: TargetReserved",
"d": "Насколько сильно боты учитывают этот фактор при выборе следующего дела. Меняйте, только если разбираетесь в планировщике."
},
"Rate.XP.Kill": {
"t": "Опыт за убийства",
"d": "Множитель опыта за убийство монстров. 1 — обычное значение."
},
"Rate.XP.Quest": {
"t": "Опыт за задания",
"d": "Множитель опыта за задания."
},
"Rate.XP.Quest.DF": {
"t": "Опыт за ежедневные задания и Поиск подземелий",
"d": "Множитель опыта за задания Поиска подземелий."
},
"Rate.XP.Explore": {
"t": "Опыт за исследование",
"d": "Множитель опыта за открытие новых областей."
},
"Rate.Drop.Item.Normal": {
"t": "Шанс выпадения предметов",
"d": "Множитель частоты выпадения предметов обычного качества."
},
"Rate.Drop.Money": {
"t": "Шанс выпадения золота",
"d": "Множитель золота, выпадающего с монстров."
},
"Rate.Honor": {
"t": "Получение чести",
"d": "Множитель получаемых очков чести."
},
"Rate.Reputation.Gain": {
"t": "Получение репутации",
"d": "Множитель получаемой репутации."
},
"Rate.Rest.InGame": {
"t": "Бонус отдыха во время игры",
"d": "Скорость, с которой расходуется накопленный отдых во время игры."
},
"Rate.Creature.Normal.Damage": {
"t": "Урон монстров",
"d": "Множитель урона обычных монстров. Сильно меняет сложность."
},
"Rate.Creature.Normal.HP": {
"t": "Здоровье монстров",
"d": "Множитель здоровья обычных монстров. Сильно меняет сложность."
},
"PlayerLimit": {
"t": "Максимум игроков",
"d": "Сколько игроков может быть в сети одновременно. 0 означает без ограничений."
},
"GameType": {
"t": "Тип мира",
"d": "Тип мира, отображаемый в списке миров."
},
"RealmZone": {
"t": "Зона мира",
"d": "Языковая или региональная группа мира, показываемая клиентам."
},
"PlayerSaveInterval": {
"t": "Интервал автосохранения",
"d": "Как часто персонажи игроков сохраняются, пока они в сети."
},
"CharactersPerAccount": {
"t": "Персонажей на учётную запись",
"d": "Наибольшее число персонажей на учётной записи во всех мирах."
},
"CharactersPerRealm": {
"t": "Персонажей в мире",
"d": "Наибольшее число персонажей одной учётной записи в этом мире."
},
"StartPlayerLevel": {
"t": "Начальный уровень",
"d": "Уровень, с которого начинают новые персонажи."
},
"StartPlayerMoney": {
"t": "Начальное золото",
"d": "Сколько меди получают новые персонажи (10000 меди = 1 золото)."
},
"MaxPlayerLevel": {
"t": "Максимальный уровень",
"d": "Наивысший уровень, которого может достичь персонаж. Клиент игры поддерживает только 80."
},
"SkipCinematics": {
"t": "Пропуск вступительных роликов",
"d": "Показывать ли новым персонажам вступительный ролик."
},
"DurabilityLoss.OnDeath": {
"t": "Потеря прочности при смерти",
"d": "Процент прочности предметов, теряемый при смерти."
},
"Death.SicknessLevel": {
"t": "Болезнь воскрешения до уровня",
"d": "Персонажи выше этого уровня получают болезнь воскрешения. Значение -10 отключает её."
},
"LeaveGroupOnLogout.Enabled": {
"t": "Покидать группу при выходе",
"d": "Игроки исключаются из группы, когда выходят из игры."
},
"GM.LoginState": {
"t": "Режим ГМ при входе",
"d": "Включён ли режим ГМ у игровых мастеров при входе."
},
"AllowTwoSide.Accounts": {
"t": "Обе фракции на одной учётной записи",
"d": "На одной учётной записи можно иметь персонажей Альянса и Орды."
},
"AllowTwoSide.Interaction.Chat": {
"t": "Общий чат фракций",
"d": "Игроки Альянса и Орды могут общаться друг с другом."
},
"AutoBroadcast.On": {
"t": "Автоматические объявления",
"d": "Регулярно рассылать сообщения, хранящиеся в базе данных."
},
"AutoBroadcast.Timer": {
"t": "Интервал объявлений",
"d": "Время между автоматическими объявлениями."
},
"Warden.Enabled": {
"t": "Античит (Warden)",
"d": "Включает проверку античита на стороне клиента. На частном сервере обычно не нужна."
},
"MapUpdateInterval": {
"t": "Интервал обновления карт",
"d": "Как часто мир обновляет карты. Меньше — плавнее, но больше нагрузка на процессор."
},
"MinWorldUpdateTime": {
"t": "Минимальное время обновления мира",
"d": "Наименьшее время между обновлениями мира. Увеличьте, чтобы снизить нагрузку на процессор."
},
"ThreadPool": {
"t": "Рабочие потоки",
"d": "Число фоновых рабочих потоков. Больше помогает при большом числе игроков."
},
"Network.Threads": {
"t": "Сетевые потоки",
"d": "Потоки, обрабатывающие подключения игроков. Для небольших серверов хватает одного."
},
"Ra.Enable": {
"t": "Удалённая консоль",
"d": "Позволяет менеджеру и скриптам отправлять серверу команды. Она слушает только этот компьютер; оставьте так."
},
"Ra.IP": {
"t": "Адрес удалённой консоли",
"d": "Адрес, на котором слушает удалённая консоль. Оставьте 127.0.0.1, чтобы она не была доступна из интернета."
},
"Updates.EnableDatabases": {
"t": "Автоматические обновления баз данных",
"d": "Позволяет серверу самому менять свои базы данных при запуске. Оставьте 0: менеджер безопасно применяет и учитывает обновления баз данных."
}
},
"options": {
"GameType.0": "Обычный",
"GameType.1": "PvP",
"GameType.6": "Ролевой",
"GameType.8": "Ролевой + PvP",
"SkipCinematics.0": "Показывать все",
"SkipCinematics.1": "Пропускать для всех, кроме первого персонажа",
"SkipCinematics.2": "Пропускать все",
"GM.LoginState.0": "Выключен",
"GM.LoginState.1": "Включён",
"GM.LoginState.2": "Как в прошлый раз"
},
"presets": {
"balanced": {
"t": "Сбалансированный",
"d": "Рекомендуемая повседневная настройка: задания, дополнение групп в подземельях и на полях боя включены, журналы тихие."
},
"solo-friendly": {
"t": "Для одиночной игры",
"d": "Боты, которые помогают вам и дополняют ваши группы, небольшое спокойное население."
},
"living-world": {
"t": "Живой мир",
"d": "Более оживлённый мир: больше ботов, перерывы, отклонения ради сбора ресурсов и временные группы."
},
"performance": {
"t": "Производительность",
"d": "Меньше ботов и меньше вычислений на ботов — для слабых компьютеров."
},
"vanilla": {
"t": "Стандартные множители",
"d": "Всё на обычной скорости (×1)."
},
"relaxed": {
"t": "Спокойный",
"d": "Быстрая прокачка и больше добычи для неспешной игры (×2 / ×3)."
},
"fast": {
"t": "Быстрый",
"d": "Быстрое развитие для тестов или коротких сессий (×5)."
},
"defaults": {
"t": "Рекомендуемые значения",
"d": "Все настройки возвращаются к значениям по умолчанию."
}
}
}
+10 -3
View File
@@ -3,7 +3,7 @@ import { Loader2 } from "lucide-react";
import { api, asUiError, type CompanionSizes, type Population, type UiError } from "@/lib/api";
import { Button } from "@/components/ui/button";
import { Card } from "@/components/ui/card";
import { useHuman, useT } from "@/i18n";
import { hasKey, useHuman, useT, type Key } from "@/i18n";
export function CompanionsCard({ serverId }: { serverId: string }) {
const t = useT();
@@ -35,8 +35,15 @@ export function CompanionsCard({ serverId }: { serverId: string }) {
};
}, [serverId]);
// The backend describes a size's warning in English; show the translated text for the two known kinds.
const warningFor = (size?: { bots: number; warning: string | null }) => {
if (!size?.warning) return null;
return size.warning.startsWith(`${size.bots} companions`)
? t("comp.warnHardware", { n: size.bots, cores: sizes?.hardware.cores ?? "?", ram: sizes ? sizes.hardware.ram_gb.toFixed(0) : "?" })
: t("comp.warnMemory");
};
const count = choice === "custom" ? Number(custom) : (sizes?.sizes.find((s) => s.id === choice)?.bots ?? 0);
const warning = choice === "custom" ? (count > 500 ? t("comp.largeWarning") : null) : (sizes?.sizes.find((s) => s.id === choice)?.warning ?? null);
const warning = choice === "custom" ? (count > 500 ? t("comp.largeWarning") : null) : (warningFor(sizes?.sizes.find((s) => s.id === choice)));
const valid = Number.isInteger(count) && count >= 1 && count <= 2000;
async function add() {
@@ -70,7 +77,7 @@ export function CompanionsCard({ serverId }: { serverId: string }) {
{sizes?.sizes.map((s) => (
<label key={s.id} className={`cursor-pointer rounded-md border px-3 py-2 text-sm ${choice === s.id ? "border-gold bg-gold/10" : "border-line hover:bg-white/5"}`}>
<input type="radio" name="size" className="sr-only" checked={choice === s.id} onChange={() => setChoice(s.id)} />
{s.title} <span className="text-muted">· {s.bots}</span>
{hasKey(`comp.size.${s.id}`) ? t(`comp.size.${s.id}` as Key) : s.title} <span className="text-muted">· {s.bots}</span>
</label>
))}
<label className={`cursor-pointer rounded-md border px-3 py-2 text-sm ${choice === "custom" ? "border-gold bg-gold/10" : "border-line hover:bg-white/5"}`}>
+36 -3
View File
@@ -3,12 +3,45 @@ import { Check, Loader2, TriangleAlert, X } from "lucide-react";
import { api, asUiError, type DiagCheck, type UiError } from "@/lib/api";
import { Button } from "@/components/ui/button";
import { Card } from "@/components/ui/card";
import { useHuman, useI18n } from "@/i18n";
import { hasKey, useHuman, useI18n, type Key } from "@/i18n";
const ICON = { ok: Check, warn: TriangleAlert, fail: X } as const;
const TONE = { ok: "text-ok", warn: "text-warn", fail: "text-bad" } as const;
const WORD = { ok: "diag.word.ok", warn: "diag.word.warn", fail: "diag.word.fail" } as const;
type T = (k: Key, v?: Record<string, string | number>) => string;
/** The backend writes each check in English; recognise the known ones and show them in the current language. */
function checkText(t: T, c: DiagCheck): { title: string; detail: string } {
const title = hasKey(`dg.title.${c.id}`) ? t(`dg.title.${c.id}` as Key) : c.title;
const d = c.detail;
const rules: [RegExp, (m: RegExpMatchArray) => string][] = [
[/^All expected server parts were found\.$/, () => t("dg.files.ok")],
[/^Some server parts are missing\.$/, () => t("dg.files.partial")],
[/^This is a custom server build; some features are limited\.$/, () => t("dg.files.custom")],
[/^This folder does not look like a CoA server\.$/, () => t("dg.files.bad")],
[/^Running$/, () => t("dg.svc.running")],
[/^Port (\d+) is used by another program \(process (\d+)\)\.$/, (m) => t("dg.svc.port", { port: m[1], pid: m[2] })],
[/^Starting, not answering yet\.$/, () => t("dg.svc.starting")],
[/^Not running\.$/, () => t("dg.svc.stopped")],
[/^Configuration files can be read\.$/, () => t("dg.configs.ok")],
[/^Cannot read: (.*)$/, (m) => t("dg.configs.bad", { files: m[1] })],
[/^Unusable values for: (.*)$/, (m) => t("dg.values.bad", { keys: m[1] })],
[/^(\d+) GB free$/, (m) => t("dg.disk.ok", { gb: m[1] })],
[/^Only (\d+) GB free; backups and updates need room\.$/, (m) => t("dg.disk.low", { gb: m[1] })],
[/^The server folder is writable\.$/, () => t("dg.perm.ok")],
[/^The Manager cannot write to the server folder/, () => t("dg.perm.bad")],
[/^The saved game folder was not found; choose it again in Settings\.$/, () => t("dg.client.missing")],
[/^Database and server console are not reachable from the network\.$/, () => t("dg.exposure.ok")],
[/^Reachable from the network: (.*)\.$/, (m) => t("dg.exposure.bad", { what: m[1] })],
];
for (const [re, f] of rules) {
const m = d.match(re);
if (m) return { title, detail: f(m) };
}
return { title, detail: d };
}
export function DiagnosticsCard({ serverId }: { serverId: string }) {
const { t, tn } = useI18n();
const human = useHuman();
@@ -60,9 +93,9 @@ export function DiagnosticsCard({ serverId }: { serverId: string }) {
<li key={c.id + c.title} className="flex items-start gap-3 py-2">
<Icon className={`mt-0.5 h-4 w-4 shrink-0 ${TONE[c.level]}`} aria-hidden />
<div>
<span className="font-medium">{c.title}</span>
<span className="font-medium">{checkText(t, c).title}</span>
<span className="sr-only"> — {t(WORD[c.level])}</span>
<p className="text-muted">{c.detail}</p>
<p className="text-muted">{checkText(t, c).detail}</p>
</div>
</li>
);
+1 -1
View File
@@ -86,7 +86,7 @@ export function FriendsPage({ serverId }: { serverId: string }) {
run(`enable-${mode}`, async () => {
const r = await api.friendsEnable(serverId, mode, net?.public_ip ?? undefined, mode === "direct" && !!net?.router_found);
setNeedsRestart(r.restart_required);
return [r.restart_required ? t("fr.restartToApply") : null, r.note].filter(Boolean).join(" ") || t("fr.ready");
return [r.restart_required ? t("fr.restartToApply") : null, r.note ? (r.note.startsWith("Your router was asked") ? t("fr.note.forwarded") : r.note.startsWith("Your router does not support") ? t("fr.note.manual") : r.note) : null].filter(Boolean).join(" ") || t("fr.ready");
});
if (!st) return <p className="text-muted">{error ? human(error.human).message : t("fr.looking")}</p>;
+2 -2
View File
@@ -4,7 +4,7 @@ import { AlertTriangle, Check, Minus } from "lucide-react";
import { api, asUiError, type Classification, type ScanReport, type ServerSummary, type UiError } from "@/lib/api";
import { Button } from "@/components/ui/button";
import { Card } from "@/components/ui/card";
import { useHuman, useT, type Key } from "@/i18n";
import { hasKey, useHuman, useT, type Key } from "@/i18n";
const HEADLINE: Record<Classification, { title: Key; tone: string; text: Key }> = {
healthy: { title: "import.healthy.title", tone: "text-ok", text: "import.healthy.text" },
@@ -101,7 +101,7 @@ export function ImportServer(props: {
<ul className="mt-4 divide-y divide-line text-sm">
{report.items.map((it) => (
<li key={it.key} className="flex items-center justify-between py-2">
<span>{it.label}</span>
<span>{hasKey(`scan.label.${it.key}`) ? t(`scan.label.${it.key}` as Key) : it.label}</span>
<span className="flex items-center gap-2 text-muted">
{it.detail && <span className="text-xs">{it.detail}</span>}
{it.status === "found" ? (
+4 -1
View File
@@ -1,10 +1,13 @@
import { useT } from "@/i18n";
export function Placeholder({ title, question }: { title: string; question: string }) {
const t = useT();
return (
<div className="max-w-xl">
<h1 className="text-2xl font-semibold">{title}</h1>
<p className="mt-1 text-muted">{question}</p>
<p className="mt-8 rounded-card border border-line bg-card/70 p-5 text-sm text-muted">
This section is being built in a later release.
{t("placeholder.soon")}
</p>
</div>
);
+2 -2
View File
@@ -8,7 +8,7 @@ import { Card } from "@/components/ui/card";
import { ClientCard } from "@/screens/ClientCard";
import { DiagnosticsCard } from "@/screens/DiagnosticsCard";
import { AboutCard } from "@/screens/AboutCard";
import { useHuman, useI18n, type Key } from "@/i18n";
import { hasKey, useHuman, useI18n, type Key } from "@/i18n";
import { LanguagePicker } from "@/components/LanguagePicker";
function mb(bytes: number) {
@@ -192,7 +192,7 @@ export function SettingsHome({ serverId }: { serverId: string }) {
{busy === "update" && progress && (
<div className="mt-5">
<div className="flex justify-between text-sm">
<span role="status">{progress.step}</span>
<span role="status">{hasKey(`ustep.${progress.step}`) ? t(`ustep.${progress.step}` as Key) : progress.step}</span>
<span className="text-muted">{progress.percent}%</span>
</div>
<div className="mt-2 h-2 overflow-hidden rounded-full bg-white/10" role="progressbar" aria-valuenow={progress.percent} aria-valuemin={0} aria-valuemax={100}>
+25 -17
View File
@@ -17,16 +17,21 @@ import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import { Card } from "@/components/ui/card";
import { CompanionsCard } from "@/screens/CompanionsCard";
import { useHuman, useI18n, useT, type Key } from "@/i18n";
import { useHuman, useI18n, useSchemaText, useT, type Key } from "@/i18n";
type T = (k: Key, v?: Record<string, string | number>) => string;
const RESTART_LABEL = { runtime: "set.restart.runtime", world: "set.restart.world", full: "set.restart.full" } as const;
function fmt(t: T, v: JsonValue, s: { type: string; unit?: string | null; options: { value: JsonValue; label: string }[] }): string {
type SX = ReturnType<typeof useSchemaText>;
function fmt(t: T, sx: SX, v: JsonValue, s: { key: string; type: string; unit?: string | null; options: { value: JsonValue; label: string }[] }): string {
if (typeof v === "boolean") return v ? t("set.on") : t("set.off");
if (s.type === "enum") return s.options.find((o) => o.value === v)?.label ?? String(v);
return s.unit ? `${v} ${s.unit}` : String(v);
if (s.type === "enum") {
const o = s.options.find((x) => x.value === v);
return o ? sx.option(s.key, o) : String(v);
}
return s.unit ? `${v} ${sx.unit(s.unit)}` : String(v);
}
function clientCheck(t: T, s: SettingView, v: JsonValue): string | null {
@@ -42,6 +47,7 @@ function clientCheck(t: T, s: SettingView, v: JsonValue): string | null {
function Field(props: { s: SettingView; value: JsonValue; error: string | null; onChange: (v: JsonValue) => void }) {
const { s, value, error, onChange } = props;
const t = useT();
const sx = useSchemaText();
const id = `f-${s.key}`;
const base = "rounded-md border bg-bg px-3 py-2 text-sm outline-none focus:border-gold";
if (s.type === "bool") {
@@ -68,7 +74,7 @@ function Field(props: { s: SettingView; value: JsonValue; error: string | null;
>
{s.options.map((o) => (
<option key={String(o.value)} value={String(o.value)}>
{o.label}
{sx.option(s.key, o)}
</option>
))}
</select>
@@ -88,7 +94,7 @@ function Field(props: { s: SettingView; value: JsonValue; error: string | null;
onChange(numeric ? (raw.trim() === "" || Number.isNaN(Number(raw)) ? (raw as unknown as number) : Number(raw)) : raw);
}}
/>
{s.unit && <span className="text-xs text-muted">{s.unit}</span>}
{s.unit && <span className="text-xs text-muted">{sx.unit(s.unit)}</span>}
</div>
);
}
@@ -96,12 +102,13 @@ function Field(props: { s: SettingView; value: JsonValue; error: string | null;
function Row(props: { s: SettingView; value: JsonValue; error: string | null; onChange: (v: JsonValue) => void; onReset: () => void }) {
const { s, value, error } = props;
const t = useT();
const sx = useSchemaText();
const changed = value !== s.value;
return (
<div className="grid grid-cols-[1fr_auto] items-start gap-x-6 gap-y-1 py-3.5">
<div>
<label htmlFor={`f-${s.key}`} className="flex flex-wrap items-center gap-2 font-medium">
{s.title}
{sx.title(s)}
{s.dangerous && (
<span className="inline-flex items-center gap-1 rounded bg-warn/15 px-1.5 py-0.5 text-[11px] font-normal text-warn">
<AlertTriangle className="h-3 w-3" aria-hidden /> {t("set.careful")}
@@ -109,9 +116,9 @@ function Row(props: { s: SettingView; value: JsonValue; error: string | null; on
)}
{changed && <span className="rounded bg-gold/15 px-1.5 py-0.5 text-[11px] font-normal text-gold">{t("set.changed")}</span>}
</label>
<p className="mt-0.5 max-w-xl text-sm text-muted">{s.description}</p>
<p className="mt-0.5 max-w-xl text-sm text-muted">{sx.description(s)}</p>
<p className="mt-1 text-xs text-muted/80">
{t("set.default", { v: fmt(t, s.default, s) })}
{t("set.default", { v: fmt(t, sx, s.default, s) })}
{s.min !== undefined && s.max !== undefined && s.type !== "bool" ? ` · ${t("set.range", { min: s.min, max: s.max })}` : ""} · {t(RESTART_LABEL[s.restartRequired])}
</p>
{s.problem && <p className="mt-1 text-xs text-warn">{t("set.badValue", { problem: s.problem })}</p>}
@@ -126,7 +133,7 @@ function Row(props: { s: SettingView; value: JsonValue; error: string | null; on
<Field s={s} value={value} error={error} onChange={props.onChange} />
<button
title={t("set.reset")}
aria-label={t("set.resetItem", { name: s.title })}
aria-label={t("set.resetItem", { name: sx.title(s) })}
onClick={props.onReset}
disabled={value === s.default}
className="cursor-pointer rounded p-1.5 text-muted hover:bg-white/5 hover:text-ink disabled:opacity-25"
@@ -141,6 +148,7 @@ function Row(props: { s: SettingView; value: JsonValue; error: string | null; on
export function SettingsPage(props: { serverId: string; scope: Scope; title: string; question: string }) {
const { serverId, scope } = props;
const { t, tn } = useI18n();
const sx = useSchemaText();
const human = useHuman();
const [view, setView] = useState<SettingsView | null>(null);
const [draft, setDraft] = useState<Record<string, JsonValue>>({});
@@ -266,8 +274,8 @@ export function SettingsPage(props: { serverId: string; scope: Scope; title: str
<div className="mt-5 flex flex-wrap items-center gap-2">
<span className="text-sm text-muted">{t("set.presets")}</span>
{presets.map((p) => (
<Button key={p.id} size="sm" title={p.description} onClick={() => void openPreset(p.id)}>
{p.title}
<Button key={p.id} size="sm" title={sx.preset(p).description} onClick={() => void openPreset(p.id)}>
{sx.preset(p).title}
</Button>
))}
<Button size="sm" variant="ghost" onClick={() => void openPreset("defaults")}>
@@ -277,8 +285,8 @@ export function SettingsPage(props: { serverId: string; scope: Scope; title: str
{preview && (
<Card className="mt-4 border-gold/40 p-5">
<p className="font-medium">{preview.title}</p>
<p className="text-sm text-muted">{preview.description}</p>
<p className="font-medium">{sx.preset({ id: preview.id, title: preview.title, description: preview.description }).title}</p>
<p className="text-sm text-muted">{sx.preset({ id: preview.id, title: preview.title, description: preview.description }).description}</p>
{preview.changes.length === 0 ? (
<p className="mt-3 text-sm">{t("set.matches")}</p>
) : (
@@ -291,9 +299,9 @@ export function SettingsPage(props: { serverId: string; scope: Scope; title: str
const s = view.settings.find((x) => x.key === c.key)!;
return (
<li key={c.key} className="flex justify-between gap-4 py-1.5">
<span>{c.title}</span>
<span>{sx.title({ key: c.key, title: c.title })}</span>
<span className="text-muted">
{fmt(t, c.from, s)} → <span className="text-ink">{fmt(t, c.to, s)}</span>
{fmt(t, sx, c.from, s)} → <span className="text-ink">{fmt(t, sx, c.to, s)}</span>
</span>
</li>
);
@@ -367,7 +375,7 @@ export function SettingsPage(props: { serverId: string; scope: Scope; title: str
category === c.id ? "border-gold text-ink" : "border-transparent text-muted hover:text-ink",
)}
>
{c.title}
{sx.category(scope, c)}
</button>
))}
</div>
+17
View File
@@ -38,4 +38,21 @@ for (const loc of ["ru", "de", "fr", "es"]) {
const pct = ((100 * (en.size - missing)) / en.size).toFixed(1);
console.log(`${loc}: ${en.size - missing}/${en.size} translated (${pct}%), ${missing} fall back to English`);
}
// Settings-schema overlays (src/i18n/schema/<locale>.json) must cover every setting, category, option and preset.
const root = join(dirname(fileURLToPath(import.meta.url)), "..");
const schemas = ["bots", "server"].map((sc) => ({ sc, d: JSON.parse(readFileSync(join(root, "schemas", `${sc}.json`), "utf8")), p: JSON.parse(readFileSync(join(root, "schemas", "presets", `${sc}.json`), "utf8")).presets }));
for (const loc of ["ru", "de", "fr", "es"]) {
const o = JSON.parse(readFileSync(join(root, "src", "i18n", "schema", `${loc}.json`), "utf8"));
let miss = 0;
for (const { sc, d, p } of schemas) {
for (const c of d.categories) if (!o.categories[`${sc}.${c.id}`]) miss++;
for (const s of d.settings) {
if (!o.settings[s.key]) miss++;
for (const opt of s.options ?? []) if (!o.options[`${s.key}.${opt.value}`]) miss++;
}
for (const x of p) if (!o.presets[x.id]) miss++;
}
console.log(`${loc}: schema overlay ${miss === 0 ? "complete" : miss + " entries missing"}`);
if (miss) errors++;
}
process.exit(errors ? 1 : 0);
+1
View File
@@ -15,6 +15,7 @@
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,
"isolatedModules": true,
"resolveJsonModule": true,
"noEmit": true,
"types": [
"node"