Help:Extension:Translate/Validators

Translatable strings often contain markup that should be retained as-is in the translation. Typing that markup can be slow and difficult because special characters are common. Translate extension can provide translators a button clicking on which inserts the piece of markup into the translation to the current cursor position. In addition, if a translation is missing that specific markup, the Translate extension can either warn the translator or simply reject the translation, since such markup is usually mandatory to display the messages properly to the end user.

For example in the string,

Adapted by %{name} from a work by %{original}

there are two insertables - %{name} and %{original}.

If the translator does not add them to their translation, the end user using the software will not see a proper message.

The MessageValidator framework has been added with the intent of helping with validating translations. Validators run on the translated message and based on the configuration, a warning or error message is shown to the translator. Translations with warnings can still be saved, but ones that have error cannot. Only a user with translate-manage permission can save translations that have errors.

When configuring a validator, a regex is defined to identify markup that is mandatory. The validator can also be marked as insertable, in which case a button will be displayed to the translator to add that markup into the translation.

Adding a custom validators is still possible and will be needed for more specialized validations.

Configuration edit

Following is a summarized validator configuration,

VALIDATORS:
    # Example 1
    - id: InsertableRegex
      enforce: true
      insertable: true
      params: /\$[a-z0-9]+/
      keymatch:
        - 'untranslated' # Matches key untranslated directly
        - 
          type: 'wildcard'
          pattern: '*translated*' # Matches any key that has the translated in it
    # Example 2
    - id: InsertableRegex
      insertable: true
      params:
          regex: /(?<pre>\[)[^]]+(?<post>\]\([^)]+\))/
          display: $pre $post
          pre: $pre
          post: $post
    # Example 3
    - class: MathJaxMessageValidator
      enforce: true
    # Example 4
    - id: BraceBalance

In the example above,

  1. InsertableRegex is a bundled validator that can accept a custom regex and run validations.
  2. MathJaxMessageValidator is a custom validator class.
  3. BraceBalance is another bundled validator.

VALIDATORS uses an array format. Lets look at the various parameters being used here in each array item,

Parameters edit

Property Type Description
id string Incase a bundled / pre-provided validator is being used, the ID of the validator. Required if class is not specified.
class string If a custom validator is being used, then use this option instead of id. Specifies the name of the validator class. See example #3 in the above config. The AUTOLOAD option can be used to load the class. Required if id is not specified.
enforce boolean Whether the validator should be enforced. If set to true, and a translation fails validation, an error will be displayed which must be fixed in order to save the translation.
insertable boolean Whether the validator should also be an insertable.
keymatch array With this option it is possible to limit certain validations to certain messages. Keymatch is an array with each option being either a string or a prototype. If it is a string, a direct comparison with the message key is done. See example #1 in the above config.
keymatch[i].type string Type is either regex or wildcard. This is the approach that will be used to check if the message key matches a given pattern.
keymatch[i].pattern string Pattern is a string that will be used for matching.
params string / associative array If params is specified as a string, it is used as the regex. See example #1

In this case if insertable is true,

  1. display is the first value from the regex match.
  2. pre is also the first value from the regex match.
  3. post is left empty.

If params is specified as an associative array (see example #2), see below for further details.

params.regex string The regex to be used for validator. Must use named captures. When specifying named captures, do not use the $ symbol in the name.

In example #2, two named captures are used - pre and post.

params.display string Mandatory value. The display value for the insertable. Named captures prefixed with $ are used here. See example #2.
params.pre string The pre value for the insertable. Value inserted before the cursor position. Named captures prefixed with $ are used here. If not specified, is set to the display value. See example #2.
params.post string The post value for the insertable. Value inserted after the cursor position. Named captures prefixed with $ are used here. See example #2. If not specified, defaults to an empty string.

Pre-provided / Bundled validators edit

Following is a list of bundled validators,

BraceBalance edit

ID: BraceBalance

Ensures that the number of open braces / brackets, matches the number of closed braces / brackets in the translation.

For example, the following translations would pass,

  • {{ }}
  • [ ]

whereas, the following would fail,

  • [ ]]
  • {{ }

This validator cannot be marked as insertable.

EscapeCharacter edit

ID: EscapeCharacter

The validator ensures that only the specified escape character are present in a translation.

The allowed escape characters can be specified when adding the validator, and can only include,

  • \t
  • \n
  • \'
  • \"
  • \f
  • \r
  • \a
  • \b
  • \\

This validator is not insertable.

GettextNewline edit

ID: GettextNewline

This works specifically for GetText based message groups.

Ensures that the translation has the same number of newlines as the source message at the beginning and end of the string.

GettextPlural edit

ID: GettextPlural

This works specifically on GetText based message groups.

Ensures that if the source / definition contains a plural in the format - foo {{PLURAL:GETTEXT|one|many}} bar, the translation must contain it as well. Based on the language this also checks if the translation has the correct number of plural forms. For example, English has two, but Hebrew has four.

InsertableRegex edit

ID: InsertableRegex

A generic reusable validator that can be used to specify custom validations and insertables.

For example, take the following configuration where the validator is marked as insertable and enforced,

- id: InsertableRegex
  enforce: true
  insertable: true
  params: "/\$[a-zA-Z0-9]+/"

Given the following source message - Hello $name. My name is $myName. that is being translated, the translation must have the parameters - $name and $myName. They will also be displayed as insertables to make it easier for translators to use them in the translation. An absence of these parameters will cause an error to be displayed to the translator.

InsertableRubyVariable edit

ID: InsertableRubyVariable

This is a validator that matches ruby variables in the translations. Internally it extends InsertableRegexValidator and uses the following regex - %{[a-zA-Z_]+}. This validator is insertable.

Example: %{abc}

IosVariable edit

ID: IosVariable

An insertable variable validator for Ios. Regex is used from this Rubustrings source. This validator is insertable.

Example: %@

MatchSet edit

ID: MatchSet

Ensures that the translation is present in the list of values. Also takes a parameter - caseSensitive that can be either true (default) or false.

For example the following configuration, the validator will validate the message with key - html.dir and ensure that the values for it can either be ltr or rtl. Note that LTR or RTL will not be valid values, since caseSensitive is true by default.

  - id: MatchSet
    enforce: true
    keymatch:
      - html.dir
    params:
      values:
        - ltr
        - rtl

MediaWikiLink edit

ID: MediaWikiLink

Checks if the translation uses links that are discouraged. Valid links are those that link to Special: pages, {{ns:special}}: or project pages trough MediaWiki messages like {{MediaWiki:helppage-url}}:. Also links in the definition are allowed.

MediaWikiPageName edit

ID: MediaWikiPageName

Ensures that if the source / definition contains a namespace such as {{ns:project}}:hello the translations made do not try to translate the namespaces itself.

MediaWikiParameter edit

ID: MediaWikiParameter

This is a validator that matches wiki parameters in the translations. Internally it extends InsertableRegexValidator and uses the following regex - /\$[1-9]/. This validator is insertable.

Example: $1, $2.

MediaWikiPlural edit

ID: MediaWikiPlural

Ensures that if the source / definition contains a {{PLURAL:$1|message|messages}}, the translation should also have it. It can also be used as an insertable. Based on the language this also checks if the translation has the correct number of plural forms. For example, English has two, but Hebrew has three.

MediaWikiTimeList edit

ID: MediaWikiTimeList

Provides validations for expiry options and IP block options specified in the MediaWiki core. These are usually in the format,

indefinite:indefinite,3 hours:3 hours,12 hours:12 hours,24 hours:24 hours,31 hours:31 hours,36 hours:36 hours,48 hours:48 hours,60 hours:60 hours,72 hours:72 hours,1 week:1 week,2 weeks:2 weeks,1 month:1 month,3 months:3 months,6 months:6 months,1 year:1 year,2 years:2 years,3 years:3 years,infinite:indefinite

The validations ensure that the translations have the exact same number of key-value pairs. These validations are run only on messages with keys,

  1. protect-expiry-options
  2. ipboptions

Newline edit

ID: Newline

Ensures that the translation has the same number of newlines as the source / definition message at the beginning of the string. This validator is not insertable.

NotEmpty edit

ID: NotEmpty

Ensures that the translation has some content, and that content is not just whitespace. This validator is not insertable.

NumericalParameter edit

ID: NumericalParameter

This validator matches numerical parameters by using the following regex: /\$\d+/. This validator is insertable.

Example: $33, $1 etc.

Printf edit

ID: Printf

This validator checks for missing and unknown printf formatting characters in translations. This validator is insertable.

Example: %2$f, %d etc.

PythonInterpolation edit

ID: PythonInterpolation

This validator matches python string interpolation variables by using the following regex: /\%(?:\([a-zA-Z0-9]*?\))?[diouxXeEfFgGcrs]/U. This validator is insertable.

Example: %s, %(name)s

Replacement edit

ID: Replacement

Checks if a translation is using the search string, and instead suggests the translator to use the string mentioned under replacement. This validator is not insertable.

  - id: Replacement
    enforce: true
    params:
      search: '{{PLURAL:'
      replace: '{PLURAL:'

SmartFormatPlural edit

ID: SmartFormatPlural

This works specifically on SmartFormat based message groups.

Ensures that if the source / definition contains a plural in the format - {1:test|tests}{0:message|messages}, the translation must contain it as well. Based on the language this also checks if the translation has the correct number of plural forms. For example, English has two, but Hebrew has four.

UnicodePlural edit

ID: UnicodePlural

Ensures that if the source / definition contains a plural in the format - foo {{PLURAL|one=one|many}} bar, the translation must contain it as well. Based on the language this also checks if the translation has the correct number of plural forms. For example, English has two, but Hebrew has three.

User interface edit

The user interface has been updated to differentiate between errors and warnings.

 
A warning and error shown on top of a translation

During translation, if an error is noticed with the translation, the Save translation button is disabled unless the user who is translating has translate-manage permission.

Additionally validation is also done on the server when the user is saving the translation. This will still allow users who have the translate-manage permission to save the translation even if it has errors.

Custom validators edit

Certain complicated validations might still require a custom validator to be written. Custom validators must implement the MediaWiki\Extensions\Translate\Validation\MessageValidator interface [1].

Below is an example of a custom validator,

<?php
// Filename: Validator.php
use MediaWiki\Extensions\Translate\Validation\MessageValidator;
use MediaWiki\Extensions\Translate\Validation\ValidationIssue;
use MediaWiki\Extensions\Translate\Validation\ValidationIssues;

/**
 * My Custom Validator
 */
class MyCustomValidator implements MessageValidator {
	
	public function getIssues( TMessage $message, string $targetLanguage ): ValidationIssues {
		$issues = new ValidationIssues();
	    
	    // Validation code goes here. Push ValidationIssue into the ValidationIssues. E.g.:
	    $issue = new ValidationIssue(
			'value-not-present',                                // type
		    'invalid',                                          // sub-type
			'translate-checks-value-not-present',               // message key
			[                                                   // message params
				[ 'PLAIN-PARAMS', $this->possibleValues ],
				[ 'COUNT', count( $this->possibleValues ) ]
			]
		);

		$issues->add( $issue );
	    
	    return $issues;
	}
}

Also see the following classes,

  1. ValidationIssues - https://gerrit.wikimedia.org/r/plugins/gitiles/mediawiki/extensions/Translate/+/master/src/Validation/ValidationIssues.php
  2. ValidationIssue - https://gerrit.wikimedia.org/r/plugins/gitiles/mediawiki/extensions/Translate/+/master/src/Validation/ValidationIssue.php

The add the custom validator in the configuration file,

VALIDATORS:
  - class: MyCustomValidator
    enforce: true

AUTOLOAD:
  MyCustomValidator: Validator.php