Improve documentation for translations API endpoints

I tinkered quite a bit with Kirby’s API and also the KQL plugin, often reading about the API internals and trying out different requests in Insomnia (an API tester).

Now I hit quite a dead end with the translations API (/api/translations), how it works and how it was meant to be used, and I believe these two pages about the two translation endpoints need some improvement.

I can create custom language variables in e.g. de.php and use them in Kirby templates. Also, I can call /translations/de to retrieve the translations, based on the documentation. Yet, this endpoint doesn’t return my variables defined in something like de.php, but all translation variables of the CMS/Panel. The same goes for the /translations endpoint, which returns, I assume, all available translations of the CMS/panel. Conclusion: Either doesn’t work the way I understand it from the documentation, or I’m missing something that the documentation isn’t telling me. Based on the documentation and the API results, I have no clue how to retrieve my custom language variables.

Then, there are several parameters that help me to define what kind of translations I want to have returned. Yet, those parameters are not really documented or linked to a documentation in the documentation page itself. For example, there is an author field, but I have actually no clue what this is and where it is coming from. Could I retrieve my very own translation variables by submitting the correct author? How am I supposed to know the author of a translation? Where is this defined?

Instead of simply answering those questions, I believe best would be to improve this part of the documentation, yet as I seem not to understand the endpoint, maybe someone would like to take that over.

If you feel like I’m totally wrong on that, feel free to relabel this post as a question.

I think there is no disagreement that the API docs can be improved certainly.

Nevertheless, as I am not sure when we will get to that, I’ll try to answer your questions here already.

The endpoint, e.g. translations/de should always get you all translation/i18n strings for that language - so not only our core strings for the Panel and error exceptions, but also your custom ones as well as all strings from all plugins. Are you sure that your strings aren’t included? In that case it would be interesting to see how you are registering them.

And what you are referring to as parameters (author etc.) aren’t parameters but fields in the API response. These special fields are specific translation keys in our translation files that contain some additional information about the translation instead of being real i18n strings, e.g. https://github.com/getkirby/kirby/blob/master/i18n/translations/de.json#L470-L473

For the translations/de endpoint (as an example) there are only two available parameters: pretty for getting pretty-printed response (for debugging) and select which lets you select the fields that the response should have (e.g. only direction and id). To be fair I think in case of this endpoint, the select parameter is less relevant than for other endpoints, but it is supported.

The endpoint, e.g. translations/de should always get you all translation/i18n strings for that language - so not only our core strings for the Panel and error exceptions, but also your custom ones as well as all strings from all plugins. Are you sure that your strings aren’t included? In that case it would be interesting to see how you are registering them.

I’m quite sure they are not there. But, I’m also aware that I might not understand this endpoint or might not register it correctly for API use. I followed the guides and at least in the Kirby templates, they seem to work.

That’s the way I register them (site/languages/de.php):


<?php

return [
    'code' => 'de',
    'default' => true,
    'direction' => 'ltr',
    'locale' => [
        'LC_ALL' => 'de'
    ],
    'name' => 'Deutsch',
    'translations' => [
        'labelname' => 'Name',
        'labelemail' => 'E-Mail',
        'labelresidence' => 'Wohnort',
        'labelquestion' => 'Frage stellen'
    ],
    'url' => ''
];

And when I send a request to api/translations/de (without any language header), I get this:

{
  "code": 200,
  "data": {
    "author": "Kirby Team",
    "data": {
      "add": "Hinzufügen",
      "avatar": "Profilbild",
      "back": "Zurück",
      "cancel": "Abbrechen",
      "change": "Ändern",
      "close": "Schließen",
      "confirm": "OK",
      "collapse": "Zusammenklappen",
      "collapse.all": "Alle zusammenklappen",
      "copy": "Kopieren",
      "create": "Erstellen",
      "date": "Datum",
      "date.select": "Datum auswählen",
      "day": "Tag",
      "days.fri": "Fr",
      "days.mon": "Mo",
      "days.sat": "Sa",
      "days.sun": "So",
      "days.thu": "Do",
      "days.tue": "Di",
      "days.wed": "Mi",
      "delete": "Löschen",
      "delete.all": "Alle löschen",
      "dimensions": "Maße",
      "disabled": "Gesperrt",
      "discard": "Verwerfen",
      "download": "Download",
      "duplicate": "Duplizieren",
      "edit": "Bearbeiten",
      "expand": "Aufklappen",
      "expand.all": "Alle aufklappen",
      "dialog.files.empty": "Keine verfügbaren Dateien",
      "dialog.pages.empty": "Keine verfügbaren Seiten",
      "dialog.users.empty": "Keine verfügbaren Accounts",
      "email": "E-Mail",
      "email.placeholder": "mail@beispiel.de",
      "error.access.code": "Ungültiger Code",
      "error.access.login": "Ungültige Zugangsdaten",
      "error.access.panel": "Du hast keinen Zugang zum Panel",
      "error.access.view": "Du hast keinen Zugriff auf diesen Teil des Panels",
      "error.avatar.create.fail": "Das Profilbild konnte nicht hochgeladen werden",
      "error.avatar.delete.fail": "Das Profilbild konnte nicht gelöscht werden",
      "error.avatar.dimensions.invalid": "Bitte lade ein Profilbild hoch, das nicht breiter oder höher als 3000 Pixel ist.",
      "error.avatar.mime.forbidden": "Das Profilbild muss vom Format JPEG oder PNG sein",
      "error.blueprint.notFound": "Das Blueprint \"{name}\" konnte nicht geladen werden.",
      "error.blocks.max.plural": "Bitte füge nicht mehr als {max} Blöcke hinzu",
      "error.blocks.max.singular": "Bitte füge nicht mehr als einen Block hinzu",
      "error.blocks.min.plural": "Bitte füge mindestens {min} Blöcke hinzu",
      "error.blocks.min.singular": "Bitte füge mindestens einen Block hinzu",
      "error.blocks.validation": "Fehler in Block {index}",
      "error.email.preset.notFound": "Die E-Mailvorlage \"{name}\" wurde nicht gefunden",
      "error.field.converter.invalid": "Ungültiger Konverter:  \"{converter}\"",
      "error.file.changeName.empty": "Bitte gib einen Namen an",
      "error.file.changeName.permission": "Du darfst den Dateinamen von \"{filename}\" nicht ändern",
      "error.file.duplicate": "Eine Datei mit dem Dateinamen \"{filename}\" besteht bereits",
      "error.file.extension.forbidden": "Verbotene Dateiendung \"{extension}\"",
      "error.file.extension.invalid": "Verbotene Dateiendung \"{extension}\"",
      "error.file.extension.missing": "Du kannst keine Dateien ohne Dateiendung hochladen",
      "error.file.maxheight": "Die Bildhöhe darf {height} Pixel nicht überschreiten",
      "error.file.maxsize": "Die Datei ist zu groß",
      "error.file.maxwidth": "Die Bildbreite darf {height} Pixel nicht überschreiten",
      "error.file.mime.differs": "Die Datei muss den Medientyp \"{mime}\" haben.",
      "error.file.mime.forbidden": "Der Medientyp \"{mime}\"  ist nicht erlaubt",
      "error.file.mime.invalid": "Ungültiger Dateityp: {mime}",
      "error.file.mime.missing": "Der Medientyp für \"{filename}\" konnte nicht erkannt werden",
      "error.file.minheight": "Die Bildhöhe muss mindestens {height} Pixel betragen",
      "error.file.minsize": "Die Datei ist zu klein",
      "error.file.minwidth": "Die Bildbreite muss mindestens {height} Pixel betragen",
      "error.file.name.missing": "Bitte gib einen Dateinamen an",
      "error.file.notFound": "Die Datei \"{filename}\" konnte nicht gefunden werden",
      "error.file.orientation": "Das Bildformat ist ungültig. Erwartetes Format: \"{orientation}\"",
      "error.file.type.forbidden": "Du kannst keinen {type}-Dateien hochladen",
      "error.file.type.invalid": "Ungültiger Dateityp: {mime}",
      "error.file.undefined": "Die Datei konnte nicht gefunden werden",
      "error.form.incomplete": "Bitte behebe alle Fehler …",
      "error.form.notSaved": "Das Formular konnte nicht gespeichert werden",
      "error.language.code": "Bitte gib einen gültigen Code für die Sprache an",
      "error.language.duplicate": "Die Sprache besteht bereits",
      "error.language.name": "Bitte gib einen gültigen Namen für die Sprache an",
      "error.layout.validation.block": "Fehler in Block {blockindex} in Layout {layoutIndex}",
      "error.layout.validation.settings": "Fehler in den Einstellungen von Layout {index}",
      "error.license.format": "Bitte gib einen gültigen Lizenzschlüssel ein",
      "error.license.email": "Bitte gib eine gültige E-Mailadresse an",
      "error.license.verification": "Die Lizenz konnte nicht verifiziert werden",
      "error.page.changeSlug.permission": "Du darfst die URL der Seite \"{slug}\" nicht ändern",
      "error.page.changeStatus.incomplete": "Die Seite ist nicht vollständig und kann daher nicht veröffentlicht werden",
      "error.page.changeStatus.permission": "Der Status der Seite kann nicht geändert werden",
      "error.page.changeStatus.toDraft.invalid": "Die Seite \"{slug}\" kann nicht in einen Entwurf umgewandelt werden",
      "error.page.changeTemplate.invalid": "Die Vorlage für die Seite \"{slug}\" kann nicht geändert werden",
      "error.page.changeTemplate.permission": "Du kannst die Vorlage für die Seite \"{slug}\" nicht ändern",
      "error.page.changeTitle.empty": "Bitte gib einen Titel an",
      "error.page.changeTitle.permission": "Du kannst den Titel für die Seite \"{slug}\" nicht ändern",
      "error.page.create.permission": "Du kannst die Seite \"{slug}\" nicht anlegen",
      "error.page.delete": "Die Seite \"{slug}\" kann nicht gelöscht werden",
      "error.page.delete.confirm": "Bitte gib zur Bestätigung den Seitentitel ein",
      "error.page.delete.hasChildren": "Die Seite hat Unterseiten und kann nicht gelöscht werden",
      "error.page.delete.permission": "Du kannst die Seite \"{slug}\" nicht löschen",
      "error.page.draft.duplicate": "Ein Entwurf mit dem URL-Kürzel \"{slug}\" besteht bereits",
      "error.page.duplicate": "Eine Seite mit dem URL-Kürzel \"{slug}\" besteht bereits",
      "error.page.duplicate.permission": "Du kannst die Seite \"{slug}\" nicht duplizieren",
      "error.page.notFound": "Die Seite \"{slug}\" konnte nicht gefunden werden",
      "error.page.num.invalid": "Bitte gib eine gültige Sortierungszahl an. Negative Zahlen sind nicht erlaubt.",
      "error.page.slug.invalid": "Bitte gib ein gültiges URL-Kürzel an",
      "error.page.slug.maxlength": "Die Pfadlänge darf {length} Zeichen nicht überschreiten",
      "error.page.sort.permission": "Die Seite \"{slug}\" kann nicht umsortiert werden",
      "error.page.status.invalid": "Bitte gib einen gültigen Seitenstatus an",
      "error.page.undefined": "Die Seite konnte nicht gefunden werden",
      "error.page.update.permission": "Du kannst die Seite \"{slug}\" nicht editieren",
      "error.section.files.max.plural": "Bitte füge nicht mehr als {max} Dateien zum Bereich \"{section}\" hinzu",
      "error.section.files.max.singular": "Bitte füge nicht mehr als eine Datei zum Bereich \"{section}\" hinzu",
      "error.section.files.min.plural": "Der Bereich \"{section}\" benötigt mindestens {min} Dateien",
      "error.section.files.min.singular": "Der Bereich \"{section}\" benötigt mindestens eine Datei",
      "error.section.pages.max.plural": "Bitte füge nicht mehr als {max} Seiten zum Bereich \"{section}\" hinzu",
      "error.section.pages.max.singular": "Bitte füge nicht mehr als eine Seite zum Bereich \"{section}\" hinzu",
      "error.section.pages.min.plural": "Der Bereich \"{section}\" benötigt mindestens {min} Seiten",
      "error.section.pages.min.singular": "Der Bereich \"{section}\" benötigt mindestens eine Seite",
      "error.section.notLoaded": "Der Bereich \"{name}\" konnte nicht geladen werden",
      "error.section.type.invalid": "Der Bereichstyp \"{type}\" ist nicht gültig",
      "error.site.changeTitle.empty": "Bitte gib einen Titel an",
      "error.site.changeTitle.permission": "Du kannst den Titel der Seite nicht ändern",
      "error.site.update.permission": "Du darfst die Seite nicht bearbeiten",
      "error.template.default.notFound": "Die \"Default\"-Vorlage existiert nicht",
      "error.user.changeEmail.permission": "Du kannst die E-Mailadresse für den Account \"{name}\" nicht ändern",
      "error.user.changeLanguage.permission": "Du kannst die Sprache für den Account \"{name}\" nicht ändern",
      "error.user.changeName.permission": "Du kannst den Namen für den Account \"{name}\" nicht ändern",
      "error.user.changePassword.permission": "Du kannst das Passwort für den Account \"{name}\" nicht ändern",
      "error.user.changeRole.lastAdmin": "Die Rolle des letzten Accounts mit Administrationsrechten kann nicht geändert werden",
      "error.user.changeRole.permission": "Du kannst die Rolle für den Benutzer \"{name}\" nicht ändern",
      "error.user.changeRole.toAdmin": "Du darfst die Admin-Rolle nicht an andere Accounts vergeben",
      "error.user.create.permission": "Du darfst diesen Account nicht anlegen",
      "error.user.delete": "Der Account \"{name}\" konnte nicht gelöscht werden",
      "error.user.delete.lastAdmin": "Du kannst den letzten Account mit Administrationsrechten nicht löschen",
      "error.user.delete.lastUser": "Der letzte Account kann nicht gelöscht werden",
      "error.user.delete.permission": "Du darfst den Account \"{name}\" nicht löschen",
      "error.user.duplicate": "Ein Account mit der E-Mailadresse \"{email}\" besteht bereits",
      "error.user.email.invalid": "Bitte gib eine gültige E-Mailadresse an",
      "error.user.language.invalid": "Bitte gib eine gültige Sprache an",
      "error.user.notFound": "Der Account \"{name}\" wurde nicht gefunden",
      "error.user.password.invalid": "Bitte gib ein gültiges Passwort ein. Passwörter müssen mindestens 8 Zeichen lang sein.",
      "error.user.password.notSame": "Die Passwörter stimmen nicht überein",
      "error.user.password.undefined": "Der Account hat kein Passwort",
      "error.user.role.invalid": "Bitte gib eine gültige Rolle an",
      "error.user.update.permission": "Du darfst den den Account \"{name}\" nicht bearbeiten",
      "error.validation.accepted": "Bitte bestätige",
      "error.validation.alpha": "Bitte gib nur Zeichen zwischen A und Z ein",
      "error.validation.alphanum": "Bitte gib nur Zeichen zwischen A und Z und Zahlen zwischen 0 und 9 ein",
      "error.validation.between": "Bitte gib einen Wert zwischen \"{min}\" und \"{max}\" ein",
      "error.validation.boolean": "Bitte bestätige oder lehne ab",
      "error.validation.contains": "Bitte gib einen Wert ein, der \"{needle}\" enthält",
      "error.validation.date": "Bitte gib ein gültiges Datum ein",
      "error.validation.date.after": "Bitte gib ein Datum nach dem {date} ein",
      "error.validation.date.before": "Bitte gib ein Datum vor dem {date} ein",
      "error.validation.date.between": "Bitte gib ein Datum zwischen dem {min} und dem {max} ein",
      "error.validation.denied": "Bitte lehne die Eingabe ab",
      "error.validation.different": "Der Wert darf nicht \"{other}\" sein",
      "error.validation.email": "Bitte gib eine gültige E-Mailadresse an",
      "error.validation.endswith": "Der Wert muss auf \"{end}\" enden",
      "error.validation.filename": "Bitte gib einen gültigen Dateinamen ein",
      "error.validation.in": "Bitte gib einen der folgenden Werte ein: ({in})",
      "error.validation.integer": "Bitte gib eine ganze Zahl ein",
      "error.validation.ip": "Bitte gib eine gültige IP Adresse ein",
      "error.validation.less": "Bitte gib einen Wert kleiner als {max} ein",
      "error.validation.match": "Der Wert entspricht nicht dem erwarteten Muster",
      "error.validation.max": "Bitte gib einen Wert ein, der nicht größer als {max} ist",
      "error.validation.maxlength": "Bitte gib einen kürzeren Text ein (max. {max} Zeichen)",
      "error.validation.maxwords": "Bitte nutze nicht mehr als {max} Wort(e)",
      "error.validation.min": "Bitte gib einen Wert ein, der nicht kleiner als {min} ist",
      "error.validation.minlength": "Bitte gib einen längeren Text ein. (min. {min} Zeichen)",
      "error.validation.minwords": "Bitte nutze mindestens {min} Wort(e)",
      "error.validation.more": "Bitte gib einen größeren Wert als {min} ein",
      "error.validation.notcontains": "Bitte gib einen Wert ein, der nicht \"{needle}\" enthält",
      "error.validation.notin": "Bitte gib keinen der folgenden Werte ein: ({notIn})",
      "error.validation.option": "Bitte wähle eine gültige Option aus",
      "error.validation.num": "Bitte gib eine gültige Zahl an",
      "error.validation.required": "Bitte gib etwas ein",
      "error.validation.same": "Bitte gib \"{other}\" ein",
      "error.validation.size": "Die Größe des Wertes muss \"{size}\" sein",
      "error.validation.startswith": "Der Wert muss mit \"{start}\" beginnen",
      "error.validation.time": "Bitte gib eine gültige Uhrzeit ein",
      "error.validation.time.after": "Bitte gib eine Zeit nach {time} ein",
      "error.validation.time.before": "Bitte gib eine Zeit vor {time} ein",
      "error.validation.time.between": "Bitte gib eine Zeit zwischen {min} und {max} ein",
      "error.validation.url": "Bitte gib eine gültige URL ein",
      "field.required": "Das Feld ist Pflicht",
      "field.blocks.changeType": "Blocktyp ändern",
      "field.blocks.code.name": "Code",
      "field.blocks.code.language": "Sprache",
      "field.blocks.code.placeholder": "Code …",
      "field.blocks.delete.confirm": "Willst du diesen Block wirklich löschen?",
      "field.blocks.delete.confirm.all": "Willst du wirklich alle Blöcke löschen?",
      "field.blocks.delete.confirm.selected": "Willst du wirklich die ausgewählten Blöcke löschen?",
      "field.blocks.empty": "Keine Blöcke",
      "field.blocks.fieldsets.label": "Bitte wähle einen Blocktyp aus …",
      "field.blocks.gallery.name": "Galerie",
      "field.blocks.gallery.images.empty": "Keine Bilder",
      "field.blocks.gallery.images.label": "Bilder",
      "field.blocks.heading.level": "Ebene",
      "field.blocks.heading.name": "Überschrift",
      "field.blocks.heading.text": "Text",
      "field.blocks.heading.placeholder": "Überschrift …",
      "field.blocks.image.alt": "Alternativer Text",
      "field.blocks.image.caption": "Bildunterschrift",
      "field.blocks.image.crop": "Beschneiden",
      "field.blocks.image.link": "Link",
      "field.blocks.image.location": "Ort",
      "field.blocks.image.name": "Bild",
      "field.blocks.image.placeholder": "Bild auswählen",
      "field.blocks.image.ratio": "Seitenverhältnis",
      "field.blocks.list.name": "Liste",
      "field.blocks.markdown.name": "Markdown",
      "field.blocks.markdown.label": "Text",
      "field.blocks.markdown.placeholder": "Markdown …",
      "field.blocks.quote.name": "Zitat",
      "field.blocks.quote.text.label": "Text",
      "field.blocks.quote.text.placeholder": "Zitat …",
      "field.blocks.quote.citation.label": "Quelle",
      "field.blocks.quote.citation.placeholder": "Quelle …",
      "field.blocks.text.name": "Text",
      "field.blocks.text.placeholder": "Text …",
      "field.blocks.video.caption": "Bildunterschrift",
      "field.blocks.video.name": "Video",
      "field.blocks.video.placeholder": "Video-URL eingeben",
      "field.blocks.video.url.label": "Video-URL",
      "field.blocks.video.url.placeholder": "https:\/\/youtube.com\/?v=",
      "field.files.empty": "Keine Dateien ausgewählt",
      "field.layout.delete": "Layout löschen",
      "field.layout.delete.confirm": "Willst du dieses Layout wirklich löschen?",
      "field.layout.empty": "Keine Layouts",
      "field.layout.select": "Layout auswählen",
      "field.pages.empty": "Keine Seiten ausgewählt",
      "field.structure.delete.confirm": "Willst du diesen Eintrag wirklich löschen?",
      "field.structure.empty": "Es bestehen keine Einträge.",
      "field.users.empty": "Keine Accounts ausgewählt",
      "file.blueprint": "Du kannst zusätzliche Felder und Bereiche für diese Datei in <strong>\/site\/blueprints\/{template}.yml<\/strong> anlegen",
      "file.delete.confirm": "Willst du die Datei <strong>{filename}<\/strong> <br>wirklich löschen?",
      "file.sort": "Position ändern",
      "files": "Dateien",
      "files.empty": "Keine Dateien",
      "hide": "Verbergen",
      "hour": "Stunde",
      "insert": "Einfügen",
      "insert.after": "Danach einfügen",
      "insert.before": "Davor einfügen",
      "install": "Installieren",
      "installation": "Installation",
      "installation.completed": "Das Panel wurde installiert",
      "installation.disabled": "Die Panel-Installation ist auf öffentlichen Servern automatisch deaktiviert. Bitte installiere das Panel auf einem lokalen Server oder aktiviere die Installation gezielt mit der <code>panel.install<\/code> Option. ",
      "installation.issues.accounts": "<code>\/site\/accounts<\/code> ist nicht beschreibbar",
      "installation.issues.content": "<code>\/content<\/code> existiert nicht oder ist nicht beschreibbar",
      "installation.issues.curl": "Die <code>CURL<\/code> Erweiterung wird benötigt",
      "installation.issues.headline": "Das Panel kann nicht installiert werden",
      "installation.issues.mbstring": "Die <code>MB String<\/code> Erweiterung wird benötigt",
      "installation.issues.media": "Der <code>\/media<\/code> Ordner ist nicht beschreibbar",
      "installation.issues.php": "Bitte verwende <code>PHP 7+<\/code>",
      "installation.issues.server": "Kirby benötigt <code>Apache<\/code>, <code>Nginx<\/code> or <code>Caddy<\/code>",
      "installation.issues.sessions": "<code>\/site\/sessions<\/code> ist nicht beschreibbar",
      "language": "Sprache",
      "language.code": "Code",
      "language.convert": "Als Standard auswählen",
      "language.convert.confirm": "<p>Willst du <strong>{name}<\/strong> wirklich in die Standardsprache umwandeln? Dieser Schritt kann nicht rückgängig gemacht werden.<\/p><p>Wenn <strong>{name}<\/strong> unübersetzte Felder hat, gibt es keine gültigen Standardwerte für diese Felder und Inhalte könnten verloren gehen.<\/p>",
      "language.create": "Neue Sprache anlegen",
      "language.delete.confirm": "Willst du <strong>{name}<\/strong> inklusive aller Übersetzungen wirklich löschen? Dieser Schritt kann nicht rückgängig gemacht werden!",
      "language.deleted": "Die Sprache wurde gelöscht",
      "language.direction": "Leserichtung",
      "language.direction.ltr": "Von links nach rechts",
      "language.direction.rtl": "Von rechts nach links",
      "language.locale": "PHP locale string",
      "language.locale.warning": "Du nutzt ein angepasstes Setup for PHP Locales. Bitte bearbeite dieses direkt in der entsprechenden Sprachdatei in \/site\/languages",
      "language.name": "Name",
      "language.updated": "Die Sprache wurde gespeichert",
      "languages": "Sprachen",
      "languages.default": "Standardsprache",
      "languages.empty": "Noch keine Sprachen",
      "languages.secondary": "Sekundäre Sprachen",
      "languages.secondary.empty": "Noch keine sekundären Sprachen",
      "license": "Lizenz",
      "license.buy": "Kaufe eine Lizenz",
      "license.register": "Registrieren",
      "license.register.help": "Den Lizenzcode findest du in der Bestätigungsmail zu deinem Kauf. Bitte kopiere und füge ihn ein, um Kirby zu registrieren.",
      "license.register.label": "Bitte gib deinen Lizenzcode ein",
      "license.register.success": "Vielen Dank für deine Unterstützung",
      "license.unregistered": "Dies ist eine unregistrierte Kirby-Demo",
      "link": "Link",
      "link.text": "Linktext",
      "loading": "Laden",
      "lock.unsaved": "Ungespeicherte Änderungen",
      "lock.unsaved.empty": "Keine ungespeicherten Änderungen",
      "lock.isLocked": "Ungespeicherte Änderungen von <strong>{email}<\/strong>",
      "lock.file.isLocked": "Die Datei wird von {email} bearbeitet und kann nicht geändert werden.",
      "lock.page.isLocked": "Die Seite wird von {email} bearbeitet und kann nicht geändert werden.",
      "lock.unlock": "Entsperren",
      "lock.isUnlocked": "Deine ungespeicherten Änderungen wurden von einem anderen Account überschrieben. Du kannst sie herunterladen, um sie manuell einzufügen. ",
      "login": "Anmelden",
      "login.code.label.login": "Anmeldecode",
      "login.code.label.password-reset": "Anmeldecode",
      "login.code.placeholder.email": "000 000",
      "login.code.text.email": "Wenn deine E-Mail-Adresse registriert ist, wurde der angeforderte Code per E-Mail versendet.",
      "login.email.login.body": "Hallo {user.nameOrEmail},\n\ndu hast gerade einen Anmeldecode für das Kirby Panel angefordert.\nDer folgende Anmeldecode ist für die nächsten {timeout} Minuten gültig:\n\n{code}\n\nWenn du keinen Anmeldecode angefordert hast, ignoriere bitte diese E-Mail oder kontaktiere bei Fragen deinen Administrator.\nBitte leite diese E-Mail aus Sicherheitsgründen NICHT weiter.",
      "login.email.login.subject": "Dein Anmeldecode",
      "login.email.password-reset.body": "Hallo {user.nameOrEmail},\n\ndu hast gerade einen Anmeldecode für das Kirby Panel angefordert.\nDer folgende Anmeldecode ist für die nächsten {timeout} Minuten gültig:\n\n{code}\n\nWenn du keinen Anmeldecode angefordert hast, ignoriere bitte diese E-Mail oder kontaktiere bei Fragen deinen Administrator.\nBitte leite diese E-Mail aus Sicherheitsgründen NICHT weiter.",
      "login.email.password-reset.subject": "Dein Anmeldecode",
      "login.remember": "Angemeldet bleiben",
      "login.reset": "Passwort zurücksetzen",
      "login.toggleText.code.email": "Anmelden über E-Mail",
      "login.toggleText.code.email-password": "Anmelden mit Passwort",
      "login.toggleText.password-reset.email": "Passwort vergessen?",
      "login.toggleText.password-reset.email-password": "← Zurück zur Anmeldung",
      "logout": "Abmelden",
      "menu": "Menü",
      "meridiem": "AM\/PM",
      "mime": "Medientyp",
      "minutes": "Minuten",
      "month": "Monat",
      "months.april": "April",
      "months.august": "August",
      "months.december": "Dezember",
      "months.february": "Februar",
      "months.january": "Januar",
      "months.july": "Juli",
      "months.june": "Juni",
      "months.march": "März",
      "months.may": "Mai",
      "months.november": "November",
      "months.october": "Oktober",
      "months.september": "September",
      "more": "Mehr",
      "name": "Name",
      "next": "Nächster Eintrag",
      "off": "aus",
      "on": "an",
      "open": "Öffnen",
      "options": "Optionen",
      "options.none": "Keine Optionen",
      "orientation": "Ausrichtung",
      "orientation.landscape": "Querformat",
      "orientation.portrait": "Hochformat",
      "orientation.square": "Quadratisch",
      "page.blueprint": "Du kannst zusätzliche Felder und Bereiche für diese Seite in <strong>\/site\/blueprints\/{template}.yml<\/strong> anlegen",
      "page.changeSlug": "URL ändern",
      "page.changeSlug.fromTitle": "Aus Titel erzeugen",
      "page.changeStatus": "Status ändern",
      "page.changeStatus.position": "Bitte wähle eine Position aus",
      "page.changeStatus.select": "Wähle einen neuen Status aus",
      "page.changeTemplate": "Vorlage ändern",
      "page.delete.confirm": "Willst du die Seite <strong>{title}<\/strong> wirklich löschen?",
      "page.delete.confirm.subpages": "<strong>Diese Seite hat Unterseiten<\/strong>. <br>Alle Unterseiten werden ebenfalls gelöscht.",
      "page.delete.confirm.title": "Gib zur Bestätigung den Seitentitel ein",
      "page.draft.create": "Entwurf anlegen",
      "page.duplicate.appendix": "Kopie",
      "page.duplicate.files": "Dateien kopieren",
      "page.duplicate.pages": "Seiten kopieren",
      "page.sort": "Position ändern",
      "page.status": "Status",
      "page.status.draft": "Entwurf",
      "page.status.draft.description": "Die Seite ist im Entwurfsmodus und ist nur nach Anmeldung oder über den geheimen Link sichtbar",
      "page.status.listed": "Öffentlich",
      "page.status.listed.description": "Die Seite ist öffentlich für alle",
      "page.status.unlisted": "Ungelistet",
      "page.status.unlisted.description": "Die Seite kann nur über die URL aufgerufen werden",
      "pages": "Seiten",
      "pages.empty": "Keine Seiten",
      "pages.status.draft": "Entwürfe",
      "pages.status.listed": "Veröffentlicht",
      "pages.status.unlisted": "Ungelistet",
      "pagination.page": "Seite",
      "password": "Passwort",
      "pixel": "Pixel",
      "prev": "Vorheriger Eintrag",
      "preview": "Vorschau",
      "remove": "Entfernen",
      "rename": "Umbenennen",
      "replace": "Ersetzen",
      "retry": "Wiederholen",
      "revert": "Verwerfen",
      "revert.confirm": "Willst du wirklich alle ungespeicherten Änderungen verwerfen? ",
      "role": "Rolle",
      "role.admin.description": "Admins haben alle Rechte",
      "role.admin.title": "Admin",
      "role.all": "Alle",
      "role.empty": "Keine Accounts mit dieser Rolle",
      "role.description.placeholder": "Keine Beschreibung",
      "role.nobody.description": "Dies ist die Platzhalterrolle ohne Rechte",
      "role.nobody.title": "Niemand",
      "save": "Speichern",
      "search": "Suchen",
      "search.min": "Gib mindestens {min}  Zeichen ein, um zu suchen",
      "search.all": "Alles zeigen",
      "search.results.none": "Keine Ergebnisse",
      "section.required": "Der Bereich ist Pflicht",
      "select": "Auswählen",
      "settings": "Einstellungen",
      "show": "Anzeigen",
      "size": "Größe",
      "slug": "URL-Anhang",
      "sort": "Sortieren",
      "title": "Titel",
      "template": "Vorlage",
      "today": "Heute",
      "site.blueprint": "Du kannst zusätzliche Felder und Bereiche für die Seite in <strong>\/site\/blueprints\/site.yml<\/strong> anlegen",
      "toolbar.button.code": "Code",
      "toolbar.button.bold": "Fetter Text",
      "toolbar.button.email": "E-Mail",
      "toolbar.button.headings": "Überschriften",
      "toolbar.button.heading.1": "Überschrift 1",
      "toolbar.button.heading.2": "Überschrift 2",
      "toolbar.button.heading.3": "Überschrift 3",
      "toolbar.button.italic": "Kursiver Text",
      "toolbar.button.file": "Datei",
      "toolbar.button.file.select": "Datei auswählen",
      "toolbar.button.file.upload": "Datei hochladen",
      "toolbar.button.link": "Link",
      "toolbar.button.ol": "Geordnete Liste",
      "toolbar.button.ul": "Ungeordnete Liste",
      "translation.author": "Kirby Team",
      "translation.direction": "ltr",
      "translation.name": "Deutsch",
      "translation.locale": "de_DE",
      "upload": "Hochladen",
      "upload.error.cantMove": "Die Datei konnte nicht an ihren Zielort bewegt werden",
      "upload.error.cantWrite": "Die Datei konnte nicht auf der Festplatte gespeichert werden",
      "upload.error.default": "Die Datei konnte nicht hochgeladen werden",
      "upload.error.extension": "Der Dateiupload wurde durch eine Erweiterung verhindert",
      "upload.error.formSize": "Die Datei ist größer als die MAX_FILE_SIZE Einstellung im Formular",
      "upload.error.iniPostSize": "Die Datei ist größer als die post_max_size Einstellung in der php.ini",
      "upload.error.iniSize": "Die Datei ist größer als die upload_max_filesize Einstellung in der php.ini",
      "upload.error.noFile": "Es wurde keine Datei hochgeladen",
      "upload.error.noFiles": "Es wurden keine Dateien hochgeladen",
      "upload.error.partial": "Die Datei wurde nur teilweise hochgeladen",
      "upload.error.tmpDir": "Der temporäre Ordner für den Dateiupload existiert leider nicht",
      "upload.errors": "Fehler",
      "upload.progress": "Hochladen …",
      "url": "Url",
      "url.placeholder": "https:\/\/beispiel.de",
      "user": "Account",
      "user.blueprint": "Du kannst zusätzliche Felder und Bereiche für diese Rolle in <strong>\/site\/blueprints\/users\/{role}.yml<\/strong> anlegen",
      "user.changeEmail": "E-Mail ändern",
      "user.changeLanguage": "Sprache ändern",
      "user.changeName": "Account umbenennen",
      "user.changePassword": "Passwort ändern",
      "user.changePassword.new": "Neues Passwort",
      "user.changePassword.new.confirm": "Wiederhole das Passwort …",
      "user.changeRole": "Rolle ändern",
      "user.changeRole.select": "Neue Rolle auswählen",
      "user.create": "Neuen Account anlegen",
      "user.delete": "Account löschen",
      "user.delete.confirm": "Willst du den Account <br><strong>{email}<\/strong> wirklich löschen?",
      "users": "Accounts",
      "version": "Version",
      "view.account": "Dein Account",
      "view.installation": "Installation",
      "view.resetPassword": "Passwort zurücksetzen",
      "view.settings": "Einstellungen",
      "view.site": "Seite",
      "view.users": "Accounts",
      "welcome": "Willkommen",
      "year": "Jahr",
      "uniform-filled-potty": "Es wurde das Feld ausgefüllt, das leer bleiben sollte. Falls Sie kein Spam-Bot sind, versuchen Sie es bitte erneut ohne das Feld auszufüllen.",
      "uniform-calc-incorrect": "Bitte lösen Sie die Rechenaufgabe.",
      "uniform-email-subject": "Nachricht über das Formular",
      "uniform-email-error": "Es ist ein Fehler beim Senden aufgetreten",
      "uniform-email-copy": "Kopie:",
      "uniform-calc-plus": "plus",
      "uniform-log-error": "Beim Schreiben in die Log-Datei ist ein Fehler aufgetreten.",
      "uniform-login-error": "Benutzername oder Passwort falsch.",
      "uniform-webhook-error": "Beim Aufruf des Webhook ist ein Fehler aufgetreten: ",
      "uniform-email-select-error": "Ungültiger Empfänger.",
      "uniform-upload-mkdir-fail": "Zielverzeichnis konnte nicht erstellt werden.",
      "uniform-upload-exists": "Die Datei existiert bereits.",
      "uniform-upload-failed": "Die Datei konnte nicht hochgeladen werden.",
      "trevor.heading": "Translations",
      "trevor.startinfo": "Select a variable on the left and start translating.",
      "trevor.addkey": "Enter the variable name",
      "trevor.payoff": "nobody trenslates voriables better."
    },
    "direction": "ltr",
    "id": "de",
    "name": "Deutsch"
  },
  "status": "ok",
  "type": "model"
}

Edited: Added the full response.

From looking at the source code, $kirby->translations() load the default translations plus registered translation extensions.

So that doesn’t look like translations defined in the language files are taken into account. You’d probably have to define your translations in a plugin for this to work as expected.

Don’t know if this can be considered a bug or a missing implementation.

1 Like

That does indeed sound like a bug possibly. Would you mind opening a bug issue for it pease? https://github.com/getkirby/kirby/issues

1 Like

Sure, will do. Thanks, everyone, for looking into it.

For reference: