Internationalization
Edit pageLast modified: 06 December 2024To enable IntelliJ-based IDEs and plugins for National Language Support (NLS), all String
instances in the code can be split into three categories:
NLS strings - strings which are shown to users in the UI: texts in dialogs, menu items, descriptions of inspections, error messages, etc.
non-NLS strings - strings which are used internally and aren't shown in UI: attributes in configuration files, keys in indices and caches, etc.
NLS-safe strings - strings which don't need to be localized but can be shown in UI: strings written by users (e.g., parts of program code, file names, URLs, etc.), names of frameworks (in some cases names of frameworks may be translated, e.g., if they consist of multiple words), etc.
By default, a string is considered as non-NLS.
NLS Annotations
There is a set of NLS-related annotations, which can be used for annotating strings in the IDE or plugin code. Annotating strings enables inspecting NLS string content correctness.
For example, if an API method parameter is annotated as an NLS string, any hardcoded string passed as a value will be reported and extracting it to a message bundle will be proposed as a quick fix. Another example is inspecting whether a given string value is properly capitalized for the usage context.
tip
Internationalization-related inspections can be enabled in Settings | Editor | Inspections in the following groups (some inspections require enabling Java Internationalization bundled plugin):
Java | Internationalization
Java | Properties files
Properties files
Consider using the following annotations:
@Nls
- for NLS strings. Thecapitalization
attribute allows to specify required capitalization.@NonNls
- for non-NLS strings@NlsSafe
- for NLS-safe strings
NLS Context Annotations
NLS context annotations are semantic annotations describing the context where the annotated strings are intended to be used. For example, @InspectionMessage
should be used for strings displayed as messages reported by inspections.
NLS context annotations must be annotated with @Nls
and they can define:
capitalization requirement - via
@Nls.capitalization
attribute@NlsContext
- specifies default prefix and suffix for property keys, which will be suggested by the I18nize hardcoded string literal quick fix provided by Java | Internationalization | Hardcoded strings inspection
The IntelliJ Platform provides NLS context annotations, including:
general contexts:
NlsContexts
nested annotationsaction contexts:
NlsActions
nested annotationsmiscellaneous contexts:
@InspectionMessage
,@IntentionFamilyName
,@IntentionName
,@GutterName
,@TooltipTitle
To find all available annotations, search for @NlsContext
usages in the intellij-community source code.
If the provided set of NLS context annotations are not sufficient, create custom annotations.
A Single NLS Category Restriction
It is important not to use the same string instance for both NLS and non-NLS categories. If it is required to use some class as a key in configuration files and present it in the UI, create two separate methods (getId
, getDisplayName
) even if they return the same value in the default locale.
Avoiding Non-NLS Strings in Message Bundle
To enable localization, all NLS strings must be placed in resource bundle files. It is important to avoid adding non-NLS strings to a resource bundle. At best, this will add unnecessary work for translators, but it is also quite possible that if such a string is translated, it could break some features.
Message Bundles
All NLS strings from a module should be added to a *.properties file. A standard location of message files in JAR is
warning
A standard convention for naming a message bundle properties file is *Bundle.properties.
If a plugin project is multi-module, and it combines resources into a single JAR, make sure that all bundle files have unique names or paths. Otherwise, only the last packed bundle file will exist in the distribution package.
A corresponding bundle class should be used to access the strings from the code.
tip
Enable Plugin DevKit | Plugin descriptor | Plugin.xml i18n verification inspection for reporting hardcoded texts in plugin descriptor files.
Message Bundle Class
The recommended approach to create a bundle class is to delegate getting messages to DynamicBundle
, e.g.:
@NonNls
private const val BUNDLE = "messages.ExampleBundle"
internal object ExampleBundle {
private val INSTANCE = DynamicBundle(ExampleBundle::class.java, BUNDLE)
fun message(
key: @PropertyKey(resourceBundle = BUNDLE) String,
vararg params: Any
): @Nls String {
return INSTANCE.getMessage(key, *params)
}
fun lazyMessage(
@PropertyKey(resourceBundle = BUNDLE) key: String,
vararg params: Any
): Supplier<@Nls String> {
return INSTANCE.getLazyMessage(key, *params)
}
}
final class ExampleBundle {
@NonNls
private static final String BUNDLE = "messages.ExampleBundle";
private static final DynamicBundle INSTANCE =
new DynamicBundle(ExampleBundle.class, BUNDLE);
private ExampleBundle() {}
public static @NotNull @Nls String message(
@NotNull @PropertyKey(resourceBundle = BUNDLE) String key,
Object @NotNull ... params
) {
return INSTANCE.getMessage(key, params);
}
public static Supplier<@Nls String> lazyMessage(
@NotNull @PropertyKey(resourceBundle = BUNDLE) String key,
Object @NotNull ... params
) {
return INSTANCE.getLazyMessage(key, params);
}
}
Do not extend DynamicBundle
in bundle classes.
Annotating message key parameter with @PropertyKey
adds the IDE support in the client code, e.g., reporting unresolved message keys.
Moving Strings to Message Bundles
tip
Make sure the encoding for bundle files is set to UTF-8 in Settings | Editor | File Encodings | Properties Files (*.properties).
IntelliJ IDEA provides inspections with fixes which help with moving strings to message bundles, e.g. Editor | Inspections | Java | Internationalization | Hardcoded strings for Java and Kotlin code.
It is possible to move multiple hardcoded strings to a message bundle in batch mode.
Moving Hardcoded Strings to a Message Bundle
Invoke the Code | Analyse Code | Run Inspection by Name... action.
Select the Hardcoded strings inspection.
Select the inspection scope and run the inspection.
In the Problems tool window, select the items to internationalize.
Invoke the I18nize hardcoded string literal quick fix.
Provide the message bundle file and resource bundle expression.
Review the internationalized messages. It is possible to delete items and adjust their keys.
Click the OK button.
Internationalization Tips
Property Keys
It is important to specify a prefix for the property key describing the context in which UI string is used, especially for short strings.
For example, set=Set
property is hard to translate as the message usage context is unclear. It is required to find usages of such a message to properly translate it, which is a very time-consuming process. Also, other developers may reuse the same property for a different meaning of the word, and after that it will be impossible to translate it correctly.
The simplest way to specify the prefix is annotating the parameter with one of @NlsContext
annotations. It will cause the IDE to generate prefix and suffix automatically when the string is moved to a message bundle.
Using &
in Messages
The &
symbol in message bundles is used to specify mnemonic characters for buttons and labels. To use &
in a value, escape it by a backslash (note that you also need to escape the backslash symbol):
section.title=Drag \\& Drop
Long Values
To wrap a long value in a message bundle, put a backslash at the end of the line, and continue the value on the next line with an indent. Starting spaces on the next line are ignored, so if a space character is required before the word in the next line, add a space before the backslash:
key=very, very long \
description
In the above example, message("key")
evaluates to very, very long description
.
Note that the backslash at the end of a line doesn't add a line break into the resulting value. For line breaks, use \n
.
Avoid Programmatic String Transformations
They include but are not limited to:
capitalization (e.g., "usage" and "Usage")
pluralization (e.g., "child" and "children")
grammatical casing ("das Projekt" and "dem Projekt" - "the project" in German used for different grammatical cases: "the project" and "of the project" accordingly)
gender-based modification (e.g., "nuevo" and "nueva" - "new" in Spanish used for a masculine and feminine subject accordingly)
Such transformations make the incorrect assumption about:
existence of a transformation in another language (e.g., there might be no casing in some language)
rules of transformation in another language (e.g., capitalization rules may differ)
context of the transformed string (e.g., the same word might be used in different contexts)
In all above cases, it is better to put the result string into the bundle. Example:
Wrong | Correct |
---|---|
Message bundle:
Code:
| Message bundle:
Code:
|
Internationalization of String Concatenations
Concatenation of Localized Strings and User Input
If an NLS string is not a simple literal but obtained as a concatenation of several values, always use MessageFormat pattern to extract it to a message bundle.
For example, to internationalize "Delete Class " + className
add the following property:
title.delete.class=Delete Class {0}
and access it with message("title.delete.class", className)
in the client code.
Patterns should be used even if the changing part of the string is the last or the first element in the concatenation, because this may change when the message is translated to a language with a different grammar.
tip
Escaping Single Quotes in MessageFormat PatternsNote that single quotes are used for escaping in
MessageFormat
patterns, and to use'
character it is required to double it:''
.For example, below are the correct sentences:
# single quote in the value below is not escaped, # because MessageFormat pattern is not used: checkbox.text.do.not.show.again=Don't show again # single quote in the value below is escaped, # because MessageFormat pattern is used (notice "{0}"): error.message.file.does.not.exist=File {0} doesn''t exist
Avoid Composing NLS Strings from Parts
If it is required to include a dynamically obtained string in a message, it is necessary to use message patterns as described above. However, in other cases, it is better to avoid composing NLS strings from smaller parts stored in separate properties:
the concatenation of words in the sentence incorrectly assumes that the translated sentence will have the same words in the same order and form in other languages
it is harder to properly translate messages
The following example won't be translated correctly into the languages which use word cases:
English messages:
dialog.title.add.0=Add {0}
dialog.title.edit.0=Edit {0}
dialog.title.name.0.1={0} ''{1}''
concept.library=Library
concept.dependency=Dependency
Polish translations:
dialog.title.add.0=Dodaj {0}
dialog.title.add.0=Edytuj {0}
concept.library=Bibilioteka
concept.dependency=Zależność
Correct result:
message("dialog.title.name.0.1", message("concept.library"), "foo")
// Biblioteka 'foo'
Wrong result:
message("dialog.title.add.0", message("concept.library"))
// Dodaj Biblioteka
// wrong case and capitalization (correct: "Dodaj bibliotekę")
If several localized strings (non-user input) are used to concatenate the string, then the following techniques can be used (in order of preference):
Consider reworking the UI to avoid the string concatenation (consult the UX expert if your organization has any, or check IntelliJ Platform UI Guidelines).
Put string parts into different UI elements.
Remove a UI element which shows the concatenated string.
Rephrase the string.
Use a generic term:
"Select " + term + " to preview"
where term is"usage"
or"occurrence"
or"match"
→"Select result to preview"
.Don't use the term at all:
"Preview " + term
→"Preview"
The UI might still need a slight rework to be clear what will be previewed (consult the UX expert if possible).
In some cases, it might be correct to use the sentence concatenation:
guidelines.browser.tab.name={0} | IntelliJ Platform UI Guidelines google.docs.browser.tab.name={0} - Google Docs
Provide the whole string for each context and term pair:
dialog.title.add.library=Add Library dialog.title.edit.library=Edit Library dialog.title.name.library.0=Library ''{0}'' dialog.title.add.dependency=Add Dependency dialog.title.edit.dependency=Edit Dependency dialog.title.name.dependency.0=Dependency ''{0}''
This approach is the least preferred:
Given X terms and Y contexts, it will result in X*Y strings. It is acceptable to have several strings for small X and Y. For more cases, the translations may become unmaintainable.
If plugins provide terms via an extension point, the extension point API would require a change to provide full strings.
Messages Depending on Numbers
Sometimes it is required to change a message depending on a number, for example, to pluralize a noun or use a proper form for a verb. In that case, use ChoiceFormat patterns.
It starts with an index of the argument which will be used for choosing a variant, then the choice
word follows, and the rest specifies pairs of numbers and corresponding values separated by |
.
For example, for pattern:
title.selected.files=Selected {0,choice,1#File|2#Files}
the expression message("title.selected.files", count)
will evaluate to:
"Selected File"
ifcount
is1
or less"Selected Files"
ifcount
is2
or greater
The number before #
characters does not specify the exact value but start of the range. The corresponding variant will be used if the argument is greater or equal to the number in this clause and less than the number in the next clause. For example, for pattern:
label.selected.files={0,choice,0#No files are|1#One file is|2#A few files are|10#Many files are} selected
the expression message("label.selected.files", 6)
will evaluate to "A few files are selected"
.
If an argument is referred in one of the choice's variants, it becomes a message pattern itself, and this adds additional layer of escaping of single quotes, so to add '
character in such a variant, add: ''''
. For example, below is the correct sentence:
warning.message={0,choice,1#One person doesn''t|2#{0} people don''''t} like MessageFormat
In some cases, the ordinal format can be useful. It adds an appropriate ending to numbers, so it turns into 1st
, 2nd
, 3rd
, 10th
, and so on:
parameter.cast.fix=Cast {0,number,ordinal} parameter to {1}
Formatting Non-Textual Values
Different countries and languages display numbers, dates and times, durations, file sizes, and other values, in a localized way. The IntelliJ Platform provides some utils to display them properly.
Date and Time
DateFormatUtil
(IDE's locale is supported since 2024.1)
Durations
File Sizes
Formats.formatFileSize()
(the units are not localized and the default JVM locale is used instead of the IDE's locale)
note
Something missing?If a topic you are interested in is not covered in the above sections, let us know via the Was this page helpful? feedback form below or other channels.
Please be specific about the topics and reasons for adding them, and leave your email in case we need more details. Thanks for your feedback!
Thanks for your feedback!