IntelliJ Platform Plugin SDK Help

Internationalization

To 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.

Consider using the following annotations:

  • @Nls - for NLS strings. The capitalization 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:

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 /messages/*.properties. In Gradle-based plugin project sources, message files are located in $MODULE_ROOT$/src/main/resources/messages/*.properties.

A corresponding bundle class should be used to access the strings from the code.

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

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

  1. Invoke the Code | Analyse Code | Run Inspection by Name... action.

  2. Select the Hardcoded strings inspection.

  3. Select the inspection scope and run the inspection.

  4. In the Problems tool window, select the items to internationalize.

  5. Invoke the I18nize hardcoded string literal quick fix.

  6. Provide the message bundle file and resource bundle expression.

  7. Review the internationalized messages. It is possible to delete items and adjust their keys.

  8. 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:

    term.node=node ‎

    Code:

    message("term.node") pluralize(message("term.node"))

    Message bundle:

    term.node=node term.nodes=nodes

    Code:

    message("term.node") message("term.nodes")

    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.

    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):

    1. 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.

    2. 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
    3. 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 terms are provided other by plugins 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" if count is 1 or less

    • "Selected Files" if count is 2 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}
    Last modified: 19 April 2024