Our implementation is based on the "classic" Jenkins approach of locale. This means we need to store the translations in a specific format and follow naming conventions.
This means you have to store the translations in the src/main/resources
folder of your Jenkins plugin. To identify the resources you should choose a unique path within the resource directory. This path will become namespace
of the keys of your plugin.
e.g. ./jenkins/plugins/blueocean/dashboard/Messages.properties
leads to following namespace jenkins.plugins.blueocean.dashboard.Messages
. This is true for all resources store in Jenkins. e.g. core resource jenkins/management/Messages.properties
has namespace jenkins.management.Messages
You can submit a new PR for french (fr
) like following
# with aliases:
# g=git
# and git aliases
# co=checkout
# nb=checkout -b
# You may have to fork and use that fork
git clone [email protected]:jenkinsci/blueocean-plugin.git
g co JENKINS-35845
g nb JENKINS-39225_fr
Then create a locale version for your translation. In our case we will create fr
cp ./blueocean-dashboard/src/main/resources/jenkins/plugins/blueocean/dashboard/Messages.properties ./blueocean-dashboard/src/main/resources/jenkins/plugins/blueocean/dashboard/Messages_fr.properties
cp ./blueocean-web/src/main/resources/jenkins/plugins/blueocean/web/Messages.properties ./blueocean-web/src/main/resources/jenkins/plugins/blueocean/web/Messages_fr.properties
Then do your translations and create a new PR in this repository. Please link to JENKINS-39225
in that PR and state which locale you are submitting.
Since java properties are supporting only iso-8859-1
(at least in Jenkins core) we need to ensure that all special characters like ñ
are getting translated into their corresponding utf code \u00F1
. There are multiple tools to convert UTF-8 to valid ISO we recommend native2ascii
which comes in each JDK.
native2ascii Messages_fr.properties Messages_fr.properties.new
# control that everything is still fine
mv Messages_fr.properties.new Messages_fr.properties
We are using traditional java properties based locale as described in Internationalization: Understanding Locale in the Java Platform you can create region/country and variants codes.
If we want to follow the example of above page and create a new translation for language: "en", country; "US" and variant: "SiliconValley"). We could create a file named:
Messages_en_US_SiliconValley.properties
However best practise is to create first the "common" language and then the country specific one.
Messages_en_au.properties
Messages_en_US_SiliconValley.properties
Should be
Messages_en.properties
Messages_en_au.properties
Messages_en_US_SiliconValley.properties
where Messages_en.properties
is where the general translations are kept and in e.g. Messages_en_au.properties
only overrides/adds some keys
Quoting the mozilla wiki page: Locale Codes for the identifiers a browser uses to negotiate languages:
The basic for of our new locale identifiers is
<language>-<region>-<dialect>
, where the region and dialect parts are optional.
Further information see W3C Internationalization
Our client code is based on i18next so basically you can do everything described there. However since we are using properties and not json format, our keys are flat and cannot be nested.
Further to maintain java compatible we are using numeric arguments for substitutions {0}
in our standard components.
const text = i18n.t('toast.run.stopping', {
0: name,
1: runId,
});
with properties:
toast.run.stopping=Stopping "{0}" #{1}...
If you do not care about java you can use as well named parameters #~name~#
.
const text = i18n.t('toast.run.stopping', {
name,
runId,
interpolation: {
prefix: '~#',
suffix: '~#',
}
});
with properties:
toast.run.stopping=Stopping "~#name~#" #~#runId~#...
Note: in case you want to use i18next plural you actually have to use the last example and the param you have to use is
count
key.count=__count__ item
key.count_plural=__count__ items
and in your code
const interpolation = {prefix: '__', suffix: '__'};
const options = (number = 0) => {return {count: number, interpolation}};
console.log(translate('key.count', options()))
console.log(translate('key.count', options(1)))
console.log(translate('key.count', options(2)))
console.log(translate('key.count', options(3)))
There is as well a way to have plural without using interpolation. Basically we "trick" the library by passing the count
but not using it.
key.count={0} item
key.count_plural={0} items
key.count.second={1} item
key.count.second_plural={1} items
and in your code
const options = (number = 0, position = 0) => {
const pluralized = { count: number }
pluralized[position] = number
return pluralized
}
// then
console.log(translate('key.count', options()))
console.log(translate('key.count', options(1)))
console.log(translate('key.count', options(2)))
console.log(translate('key.count', options(3)))
console.log(translate('key.count.second', options(0, 1)))
console.log(translate('key.count.second', options(1, 1)))
console.log(translate('key.count.second', options(2, 1)))
console.log(translate('key.count.second', options(3, 1)))
This way you can still use the keys in your java code.
In case you are having lager paragraph which need to contain some kind of markup, you can use markdown grammar in the value and later parse that.
<Markdown>
{t('EmptyState.branches.notSupported')}
</Markdown>
with properties:
EmptyState.branches.notSupported=# Ramas no soportadas\nLa construcci\u00f3n de ramas (Branch) solo funciona con el tipo de job _Multibranch Pipeline_. Esto es solo uno de las muchas razones para cambiar a "Jenkins Pipeline".\n\n[M\u00e1s motivos](https://jenkins.io/doc/book/pipeline-as-code/)
[FIX JENKINS-39225/JENKINS-35845] Internationalisation for Blue Ocean and JDL