Using how-to about translation overrides maybe i found an issue. I created _/user/languages/cs.yaml_ with simple content:
PLUGIN_ADMIN.TITLE: my translation
but "my translation" will not replace the original translation even if i delete cache. In settings i have _cs_ used as default language.
PLUGIN_ADMIN:
TITLE: 'my translation'
should work
Just tested, and I can confirm that PLUGIN_ADMIN.TITLE: does not work, while @flaviocopes 's fix does work.
I've fixed the docs to reflect this. Thanks.
Yes, its working this way, but if i create such file and insert translation, its translated but the rest of the admin falls back to english.
Recreated, looking to solve
I'm not sure we can really solve this. The thing is this is YAML, and if you define:
PLUGIN_ADMIN.TITLE: my translation
You are really saying this key is "PLUGIN_ADMIN.TITLE", however when you request this, the string is treated as a "dot-seperated" key, that means it's broken up into:
[PLUGIN_ADMIN][TITLE]
So a nested array, where it expects to find the value in YAML in this format:
PLUGIN_ADMIN:
TITLE: 'my translation'
So you simply must nest your YAML and your keys can't have periods in them.
Yes, the problem is that you're overriding TITLE but all the other keys defined in the Admin language file are not untranslated:

so there is no way to override a single language string of a plugin, you need to copy them all into your custom lang file.
Should we reopen this?
It seems counter-intuitive to me that the other language strings do not fallback to their original language files when a custom override/translation is made.
Seems unnecessary that in order to get, for example, the Maintenance page provided by the Maintenance plugin to say "Under Construction" instead of "Site Offline" that I have to copy the entire contents of the plugins languages.yaml file into my custom language file.
Yes I agree
Been debugging this, in https://github.com/getgrav/grav/blob/develop/system/src/Grav/Common/Config/CompiledLanguages.php we have
/**
* Load single configuration file and append it to the correct position.
*
* @param string $name Name of the position.
* @param string $filename File to be loaded.
*/
protected function loadFile($name, $filename)
{
$file = CompiledYamlFile::instance($filename);
if (preg_match('|languages\.yaml$|', $filename)) {
$this->object->mergeRecursive($file->content());
} else {
$this->object->join($name, $file->content(), '/');
}
$file->free();
}
The user language file goes through the else, so calls https://github.com/getgrav/grav/blob/develop/system/src/Grav/Common/Data/Data.php
/**
* Join nested values together by using blueprints.
*
* @param string $name Dot separated path to the requested value.
* @param mixed $value Value to be joined.
* @param string $separator Separator, defaults to '.'
* @return $this
* @throws \RuntimeException
*/
public function join($name, $value, $separator = '.')
{
$old = $this->get($name, null, $separator);
if ($old !== null) {
if (!is_array($old)) {
throw new \RuntimeException('Value ' . $old);
}
if (is_object($value)) {
$value = (array) $value;
} elseif (!is_array($value)) {
throw new \RuntimeException('Value ' . $value);
}
$value = $this->blueprints()->mergeData($old, $value, $name, $separator);
}
$this->set($name, $value, $separator);
return $this;
}
The $value = $this->blueprints()->mergeData($old, $value, $name, $separator); is the key, if $old is for example
PLUGIN_FORM:
NOT_VALIDATED: "Form not validated. One or more required fields are missing."
NONCE_NOT_VALIDATED: "Oops there was a problem, please check your input and submit the form again."
FILES: "Files Upload"
ALLOW_MULTIPLE: "Allow More than one file"
ALLOW_MULTIPLE_HELP: "Allows to select more than one file for upload."
DESTINATION: "Destination"
DESTINATION_HELP: "The location where the files should be uploaded to"
ACCEPT: "Allowed MIME Types"
ACCEPT_HELP: "A list of MIME Types that are allowed for upload"
ERROR_VALIDATING_CAPTCHA: "Error validating the Captcha"
DATA_SUMMARY: "Here is the summary of what you wrote to us:"
NO_FORM_DATA: "No form data available"
RECAPTCHA: "ReCaptcha"
RECAPTCHA_SITE_KEY: "Site key"
RECAPTCHA_SITE_KEY_HELP: "For more info visit https://developers.google.com/recaptcha"
RECAPTCHA_SECRET_KEY: "Secret key"
RECAPTCHA_SECRET_KEY_HELP: "For more info visit https://developers.google.com/recaptcha"
GENERAL: "General"
USE_BUILT_IN_CSS: "Use built-in CSS"
FILEUPLOAD_PREVENT_SELF: 'Cannot use "%s" outside of pages.'
FILEUPLOAD_UNABLE_TO_UPLOAD: 'Unable to upload file %s: %s'
FILEUPLOAD_UNABLE_TO_MOVE: 'Unable to move file %s to "%s"'
DROPZONE_CANCEL_UPLOAD: 'Cancel upload'
DROPZONE_CANCEL_UPLOAD_CONFIRMATION: 'Are you sure you want to cancel this upload?'
DROPZONE_DEFAULT_MESSAGE: 'Drop your files here or <strong>click in this area</strong>'
DROPZONE_FALLBACK_MESSAGE: 'Your browser does not support drag and drop file uploads.'
DROPZONE_FALLBACK_TEXT: 'Please use the fallback form below to upload your files like in the olden days.'
DROPZONE_FILE_TOO_BIG: 'File is too big ({{filesize}}MiB). Max filesize: {{maxFilesize}}MiB.'
DROPZONE_INVALID_FILE_TYPE: "You can't upload files of this type."
DROPZONE_MAX_FILES_EXCEEDED: "You can not upload any more files."
DROPZONE_REMOVE_FILE: "Remove file"
DROPZONE_REMOVE_FILE_CONFIRMATION: 'Are you sure you want to delete this file?'
DROPZONE_RESPONSE_ERROR: "Server responded with {{statusCode}} code."
and $new is
PLUGIN_FORM:
NOT_VALIDATED: "Form not validated. One or more required fields are missing."
the return value is
PLUGIN_FORM:
NOT_VALIDATED: "Form not validated. One or more required fields are missing."
so it's not actually merging the data, but putting $new instead of $old.
The actual inner workings are too abstract for me, maybe @mahagr has an idea.
I don't think that blueprint should be used for language files. They are meant to merge complex data, not just recursively add missing items (which blueprint doesn't do).
Maybe we could add support for multiple merge strategies to the blueprints..
I'm surely missing something, but couldn't the custom language files be made to use mergeRecursive() instead of join() like the plugin language files do? Since the files are located in (e.g.) user/languages/en.yaml, I'm guessing the preg_match in CompiledLanguages.php could be updated to also look for user/languages/*.yaml in the file path/name, making it a minimal code change?
Most helpful comment
It seems counter-intuitive to me that the other language strings do not fallback to their original language files when a custom override/translation is made.
Seems unnecessary that in order to get, for example, the Maintenance page provided by the Maintenance plugin to say "Under Construction" instead of "Site Offline" that I have to copy the entire contents of the plugins languages.yaml file into my custom language file.