From: Brad Jorsch Date: Thu, 29 Aug 2019 20:52:27 +0000 (-0400) Subject: libs/Message: Improve documentation X-Git-Tag: 1.34.0-rc.0~363^2 X-Git-Url: http://git.cyclocoop.org/?a=commitdiff_plain;h=ce79d607c76029e7076672f23d713d1bc1e4fac5;p=lhc%2Fweb%2Fwiklou.git libs/Message: Improve documentation Among other things, this removed mention of MediaWiki classes from the library and adds a README.md that attempts to define constraints for interoperability between libraries producing MessageValues and formatter implementations that are expected to handle them. This also renames "TextParam" to "ScalarParam", as that seems a more accurate name for the class. Change-Id: I264dd4de394d734a87929cf4740779e7b7d0e04a --- diff --git a/includes/libs/Message/ITextFormatter.php b/includes/libs/Message/ITextFormatter.php index 00f6e99007..c433e47fdc 100644 --- a/includes/libs/Message/ITextFormatter.php +++ b/includes/libs/Message/ITextFormatter.php @@ -3,16 +3,7 @@ namespace Wikimedia\Message; /** - * ITextFormatter is a simplified interface to the Message class. It converts - * MessageValue message specifiers to localized text in a certain language. - * - * MessageValue supports message keys, and parameters with a wide variety of - * types. It does not expose any details of how messages are retrieved from - * storage or what format they are stored in. - * - * Thus, TextFormatter supports single message keys, but not the concept of - * presence or absence of a key from storage. So it does not support - * fallback sequences of multiple keys. + * Converts MessageValue message specifiers to localized plain text in a certain language. * * The caller cannot modify the details of message translation, such as which * of multiple sources the message is taken from. Any such flags may be injected diff --git a/includes/libs/Message/ListParam.php b/includes/libs/Message/ListParam.php index c6a9c65d57..d550fec51a 100644 --- a/includes/libs/Message/ListParam.php +++ b/includes/libs/Message/ListParam.php @@ -3,19 +3,17 @@ namespace Wikimedia\Message; /** - * The class for list parameters + * Value object representing a message parameter that consists of a list of values. + * + * Message parameter classes are pure value objects and are safely newable. */ class ListParam extends MessageParam { private $listType; /** - * @param string $listType One of the ListType constants: - * - ListType::COMMA: A comma-separated list - * - ListType::SEMICOLON: A semicolon-separated list - * - ListType::PIPE: A pipe-separated list - * - ListType::TEXT: A natural language list, separated by commas and - * the word "and". - * @param (MessageParam|string)[] $elements An array of parameters + * @param string $listType One of the ListType constants. + * @param (MessageParam|MessageValue|string|int|float)[] $elements Values in the list. + * Values that are not instances of MessageParam are wrapped using ParamType::TEXT. */ public function __construct( $listType, array $elements ) { $this->type = ParamType::LIST; @@ -25,7 +23,7 @@ class ListParam extends MessageParam { if ( $element instanceof MessageParam ) { $this->value[] = $element; } elseif ( is_scalar( $element ) ) { - $this->value[] = new TextParam( ParamType::TEXT, $element ); + $this->value[] = new ScalarParam( ParamType::TEXT, $element ); } else { throw new \InvalidArgumentException( 'ListParam elements must be MessageParam or scalar' ); diff --git a/includes/libs/Message/ListType.php b/includes/libs/Message/ListType.php index 60f3a82233..f846464daa 100644 --- a/includes/libs/Message/ListType.php +++ b/includes/libs/Message/ListType.php @@ -4,8 +4,7 @@ namespace Wikimedia\Message; /** * The constants used to specify list types. The values of the constants are an - * unstable implementation detail and correspond to the names of the list types - * in the Message class. + * unstable implementation detail. */ class ListType { /** A comma-separated list */ diff --git a/includes/libs/Message/MessageParam.php b/includes/libs/Message/MessageParam.php index 8162212c06..b6475a78e5 100644 --- a/includes/libs/Message/MessageParam.php +++ b/includes/libs/Message/MessageParam.php @@ -3,7 +3,9 @@ namespace Wikimedia\Message; /** - * The base class for message parameters. + * Value object representing a message parameter that consists of a list of values. + * + * Message parameter classes are pure value objects and are safely newable. */ abstract class MessageParam { protected $type; @@ -21,7 +23,7 @@ abstract class MessageParam { /** * Get the input value of the parameter * - * @return int|float|string|array + * @return mixed */ public function getValue() { return $this->value; diff --git a/includes/libs/Message/MessageValue.php b/includes/libs/Message/MessageValue.php index 13b97f224a..1d80d603fd 100644 --- a/includes/libs/Message/MessageValue.php +++ b/includes/libs/Message/MessageValue.php @@ -3,7 +3,13 @@ namespace Wikimedia\Message; /** - * A MessageValue holds a key and an array of parameters + * Value object representing a message for i18n. + * + * A MessageValue holds a key and an array of parameters. It can be converted + * to a string in a particular language using formatters obtained from an + * IMessageFormatterFactory. + * + * MessageValues are pure value objects and are safely newable. */ class MessageValue { /** @var string */ @@ -14,9 +20,8 @@ class MessageValue { /** * @param string $key - * @param array $params Each element of the parameter array - * may be either a MessageParam or a scalar. If it is a scalar, it is - * converted to a parameter of type TEXT. + * @param (MessageParam|MessageValue|string|int|float)[] $params Values that are not instances + * of MessageParam are wrapped using ParamType::TEXT. */ public function __construct( $key, $params = [] ) { $this->key = $key; @@ -45,7 +50,7 @@ class MessageValue { /** * Chainable mutator which adds text parameters and MessageParam parameters * - * @param mixed ...$values Scalar or MessageParam values + * @param MessageParam|MessageValue|string|int|float ...$values * @return MessageValue */ public function params( ...$values ) { @@ -53,7 +58,7 @@ class MessageValue { if ( $value instanceof MessageParam ) { $this->params[] = $value; } else { - $this->params[] = new TextParam( ParamType::TEXT, $value ); + $this->params[] = new ScalarParam( ParamType::TEXT, $value ); } } return $this; @@ -63,12 +68,12 @@ class MessageValue { * Chainable mutator which adds text parameters with a common type * * @param string $type One of the ParamType constants - * @param mixed ...$values Scalar values + * @param MessageValue|string|int|float ...$values Scalar values * @return MessageValue */ public function textParamsOfType( $type, ...$values ) { foreach ( $values as $value ) { - $this->params[] = new TextParam( $type, $value ); + $this->params[] = new ScalarParam( $type, $value ); } return $this; } @@ -77,7 +82,8 @@ class MessageValue { * Chainable mutator which adds list parameters with a common type * * @param string $listType One of the ListType constants - * @param array ...$values Each value should be an array of list items. + * @param (MessageParam|MessageValue|string|int|float)[] ...$values Each value + * is an array of items suitable to pass as $params to ListParam::__construct() * @return MessageValue */ public function listParamsOfType( $listType, ...$values ) { @@ -88,9 +94,9 @@ class MessageValue { } /** - * Chainable mutator which adds parameters of type text. + * Chainable mutator which adds parameters of type text (ParamType::TEXT). * - * @param string ...$values + * @param MessageValue|string|int|float ...$values * @return MessageValue */ public function textParams( ...$values ) { @@ -98,9 +104,9 @@ class MessageValue { } /** - * Chainable mutator which adds numeric parameters + * Chainable mutator which adds numeric parameters (ParamType::NUM). * - * @param mixed ...$values + * @param int|float ...$values * @return MessageValue */ public function numParams( ...$values ) { @@ -109,8 +115,10 @@ class MessageValue { /** * Chainable mutator which adds parameters which are a duration specified - * in seconds. This is similar to timePeriodParams() except that the result - * will be more verbose. + * in seconds (ParamType::DURATION_LONG). + * + * This is similar to shorDurationParams() except that the result will be + * more verbose. * * @param int|float ...$values * @return MessageValue @@ -120,8 +128,10 @@ class MessageValue { } /** - * Chainable mutator which adds parameters which are a time period in seconds. - * This is similar to durationParams() except that the result will be more + * Chainable mutator which adds parameters which are a duration specified + * in seconds (ParamType::DURATION_SHORT). + * + * This is similar to longDurationParams() except that the result will be more * compact. * * @param int|float ...$values @@ -132,10 +142,10 @@ class MessageValue { } /** - * Chainable mutator which adds parameters which are an expiry timestamp - * as used in the MediaWiki database schema. + * Chainable mutator which adds parameters which are an expiry timestamp (ParamType::EXPIRY). * - * @param string ...$values + * @param string ...$values Timestamp as accepted by the Wikimedia\Timestamp library, + * or "infinity" * @return MessageValue */ public function expiryParams( ...$values ) { @@ -143,7 +153,7 @@ class MessageValue { } /** - * Chainable mutator which adds parameters which are a number of bytes. + * Chainable mutator which adds parameters which are a number of bytes (ParamType::SIZE). * * @param int ...$values * @return MessageValue @@ -154,7 +164,7 @@ class MessageValue { /** * Chainable mutator which adds parameters which are a number of bits per - * second. + * second (ParamType::BITRATE). * * @param int|float ...$values * @return MessageValue @@ -164,9 +174,13 @@ class MessageValue { } /** - * Chainable mutator which adds parameters of type "raw". + * Chainable mutator which adds "raw" parameters (ParamType::RAW). * - * @param mixed ...$values + * Raw parameters are substituted after formatter processing. The caller is responsible + * for ensuring that the value will be safe for the intended output format, and + * documenting what that intended output format is. + * + * @param string ...$values * @return MessageValue */ public function rawParams( ...$values ) { @@ -174,21 +188,27 @@ class MessageValue { } /** - * Chainable mutator which adds parameters of type "plaintext". + * Chainable mutator which adds plaintext parameters (ParamType::PLAINTEXT). + * + * Plaintext parameters are substituted after formatter processing. The value + * will be escaped by the formatter as appropriate for the target output format + * so as to be represented as plain text rather than as any sort of markup. + * + * @param string ...$values + * @return MessageValue */ public function plaintextParams( ...$values ) { return $this->textParamsOfType( ParamType::PLAINTEXT, ...$values ); } /** - * Chainable mutator which adds comma lists. Each comma list is an array of - * list elements, and each list element is either a MessageParam or a - * string. String parameters are converted to parameters of type "text". + * Chainable mutator which adds comma lists (ListType::COMMA). * * The list parameters thus created are formatted as a comma-separated list, * or some local equivalent. * - * @param (MessageParam|string)[] ...$values + * @param (MessageParam|MessageValue|string|int|float)[] ...$values Each value + * is an array of items suitable to pass as $params to ListParam::__construct() * @return MessageValue */ public function commaListParams( ...$values ) { @@ -196,15 +216,13 @@ class MessageValue { } /** - * Chainable mutator which adds semicolon lists. Each semicolon list is an - * array of list elements, and each list element is either a MessageParam - * or a string. String parameters are converted to parameters of type - * "text". + * Chainable mutator which adds semicolon lists (ListType::SEMICOLON). * * The list parameters thus created are formatted as a semicolon-separated * list, or some local equivalent. * - * @param (MessageParam|string)[] ...$values + * @param (MessageParam|MessageValue|string|int|float)[] ...$values Each value + * is an array of items suitable to pass as $params to ListParam::__construct() * @return MessageValue */ public function semicolonListParams( ...$values ) { @@ -212,14 +230,13 @@ class MessageValue { } /** - * Chainable mutator which adds pipe lists. Each pipe list is an array of - * list elements, and each list element is either a MessageParam or a - * string. String parameters are converted to parameters of type "text". + * Chainable mutator which adds pipe lists (ListType::PIPE). * * The list parameters thus created are formatted as a pipe ("|") -separated * list, or some local equivalent. * - * @param (MessageParam|string)[] ...$values + * @param (MessageParam|MessageValue|string|int|float)[] ...$values Each value + * is an array of items suitable to pass as $params to ListParam::__construct() * @return MessageValue */ public function pipeListParams( ...$values ) { @@ -227,9 +244,7 @@ class MessageValue { } /** - * Chainable mutator which adds text lists. Each text list is an array of - * list elements, and each list element is either a MessageParam or a - * string. String parameters are converted to parameters of type "text". + * Chainable mutator which adds natural-language lists (ListType::AND). * * The list parameters thus created, when formatted, are joined as in natural * language. In English, this means a comma-separated list, with the last diff --git a/includes/libs/Message/ParamType.php b/includes/libs/Message/ParamType.php index 890ef38ee4..4db7112342 100644 --- a/includes/libs/Message/ParamType.php +++ b/includes/libs/Message/ParamType.php @@ -4,44 +4,62 @@ namespace Wikimedia\Message; /** * The constants used to specify parameter types. The values of the constants - * are an unstable implementation detail, and correspond to the names of the - * parameter types in the Message class. + * are an unstable implementation detail. + * + * Unless otherwise noted, these should be used with an instance of ScalarParam. */ class ParamType { - /** A simple text parameter */ + /** A simple text string or another MessageValue, not otherwise formatted. */ const TEXT = 'text'; /** A number, to be formatted using local digits and separators */ const NUM = 'num'; - /** A number of seconds, to be formatted as natural language text. */ + /** + * A number of seconds, to be formatted as natural language text. + * The value will be output exactly. + */ const DURATION_LONG = 'duration'; - /** A number of seconds, to be formatted in an abbreviated way. */ + /** + * A number of seconds, to be formatted as natural language text in an abbreviated way. + * The output will be rounded to an appropriate magnitude. + */ const DURATION_SHORT = 'timeperiod'; /** - * An expiry time for a block. The input is either a timestamp in one - * of the formats accepted by the Wikimedia\Timestamp library, or - * "infinity" for an infinite block. + * An expiry time. + * + * The input is either a timestamp in one of the formats accepted by the + * Wikimedia\Timestamp library, or "infinity" if the thing doesn't expire. + * + * The output is a date and time in local format, or a string representing + * an "infinite" expiry. */ const EXPIRY = 'expiry'; - /** A number of bytes. */ + /** A number of bytes. The output will be rounded to an appropriate magnitude. */ const SIZE = 'size'; - /** A number of bits per second. */ + /** A number of bits per second. The output will be rounded to an appropriate magnitude. */ const BITRATE = 'bitrate'; - /** The list type (ListParam) */ + /** A list of values. Must be used with ListParam. */ const LIST = 'list'; /** - * A text parameter which is substituted after preprocessing, and so is - * not available to the preprocessor and cannot be modified by it. + * A text parameter which is substituted after formatter processing. + * + * The creator of the parameter and message is responsible for ensuring + * that the value will be safe for the intended output format, and + * documenting what that intended output format is. */ const RAW = 'raw'; - /** Reserved for future use. */ + /** + * A text parameter which is substituted after formatter processing. + * The output will be escaped as appropriate for the output format so + * as to represent plain text rather than any sort of markup. + */ const PLAINTEXT = 'plaintext'; } diff --git a/includes/libs/Message/README.md b/includes/libs/Message/README.md new file mode 100644 index 0000000000..9f6255a3f4 --- /dev/null +++ b/includes/libs/Message/README.md @@ -0,0 +1,291 @@ +Wikimedia Internationalization Library +====================================== + +This library provides interfaces and value objects for internationalization (i18n) +of applications in PHP. + +It is based on the i18n code used in MediaWiki, and is also intended to be +compatible with [jQuery.i18n], a JavaScript i18n library. + +Concepts +-------- + +Any text string that is needed in an application is a **message**. This might +be something like a button label, a sentence, or a longer text. Each message is +assigned a **message key**, which is used as the identifier in code. + +Each message is translated into various languages, each represented by a +**language code**. The message's text (as translated into each language) can +contain **placeholders**, which represents a place in the message where a +**parameter** is to be inserted, and **formatting commands**. It might be plain +text other than these placeholders and formatting commands, or it might be in a +**markup language** such as wikitext or Markdown. + +A **formatter** is used to convert the message key and parameters into a text +representation in a particular language and **output format**. + +The library itself imposes few restrictions on all of these concepts; this +document contains recommendations to help various implementations operate in +compatible ways. + +Usage +----- + +
+use Wikimedia\Message\MessageValue;
+use Wikimedia\Message\MessageParam;
+use Wikimedia\Message\ParamType;
+
+// Constructor interface
+$message = new MessageValue( 'message-key', [
+    'parameter',
+    new MessageValue( 'another-message' ),
+    new MessageParam( ParamType::NUM, 12345 ),
+] );
+
+// Fluent interface
+$message = ( new MessageValue( 'message-key' ) )
+    ->params( 'parameter', new MessageValue( 'another-message' ) )
+    ->numParams( 12345 );
+
+// Formatting
+$messageFormatter = $serviceContainter->get( 'MessageFormatterFactory' )->getTextFormatter( 'de' );
+$output = $messageFormatter->format( $message );
+
+ +Class Overview +-------------- + +### Messages + +Messages and their parameters are represented by newable value objects. + +**MessageValue** represents an instance of a message, holding the key and any +parameters. It is mutable in that parameters can be added to the object after +creation. + +**MessageParam** is an abstract value class representing a parameter to a message. +It has a type (using constants defined in the **ParamType** class) and a value. It +has two implementations: + +- **ScalarParam** represents a single-valued parameter, such as a text string, a + number, or another message. +- **ListParam** represents a list of values, which will be joined together with + appropriate separators. It has a "list type" (using constants defined in the + **ListType** class) defining the desired separators. + +### Formatters + +A formatter for a particular language is obtained from an implementation of +**IMessageFormatterFactory**. No implementation of this interface is provided by +this library. If an environment needs its formatters to vary behavior on things +other than the language code, for example selecting among multiple sources of +messages or markup language used for processing message texts, it should define +a MessageFormatterFactoryFactory of some sort to provide appropriate +IMessageFormatterFactory implementations. + +There is no one base interface for all formatters; the intent is that type +hinting will ensure that the formatter being used will produce output in the +expected output format. The defined output formats are: + +- **ITextFormatter** produces plain text output. + +No implementation of these interfaces are provided by this library. + +Formatter implementations are expected to perform the following procedure to +generate the output string: + +1. Fetch the message's translation in the formatter's language. Details of this + fetching are unspecified here. + - If no translation is found in the formatter's language, it should attempt + to fall back to appropriate other languages. Details of the fallback are + unspecified here. + - If no translation can be found in any fallback language, a string should + be returned that indicates at minimum the message key that was unable to + be found. +2. Replace placeholders with parameter values. + - Note that placeholders must not be replaced recursively. That is, if a + parameter's value contains text that looks like a placeholder, it must not + be replaced as if it really were a placeholder. + - Certain types of parameters are not substituted directly at this stage. + Instead their placeholders must be replaced with an opaque representation + that will not be misinterpreted during later stages. + - Parameters of type RAW or PLAINTEXT + - TEXT parameters with a MessageValue as the value + - LIST parameters with any late-substituted value as one of their values. +3. Process any formatting commands. +4. Process the source markup language to produce a string in the desired output + format. This may be a no-op, and may be combined with the previous step if + the markup language implements compatible formatting commands. +5. Replace any opaque representations from step 2 with the actual values of + the corresponding parameters. + +Guidelines for Interoperability +------------------------------- + +Besides allowing for libraries to safely supply their own translations for +every app using them, and apps to easily use libraries' translations instead of +having to retranslate everything, following these guidelines will also help +open source projects use [translatewiki.net] for crowdsourced volunteer +translation into many languages. + +### Language codes + +[BCP 47] language tags should be used for language codes. If a supplied +language tag is not recognized, at minimum the corresponding tag with all +optional subtags stripped should be tried as a fallback. + +All messages must have a translation in English (code "en"). All languages +should fall back to English as a last resort. + +The English translations should use `{{PLURAL:...}}` and `{{GENDER:...}}` even +when English doesn't make a grammatical distinction, to signal to translators +that plural/gender support is available. + +Language code "qqq" is reserved for documenting messages. Documentation should +describe the context in which the message is used and the values of all +parameters used with the message. Generally this is written in English. +Attempting to obtain a message formatter for "qqq" should return one for "en" +instead. + +Language code "qqx" is reserved for debugging. Rather than retrieving +translations from some underlying storage, every key should act as if it were +translated as something `(key-name: $1, $2, $3)` with the number of +placeholders depending on how many parameters are included in the +MessageValue. + +### Message keys + +Message keys intended for use with external implementations should follow +certain guidelines for interoperability: + +- Keys should be restricted to the regular expression `/^[a-z][a-z0-9-]*$/`. + That is, it should consist of lowercase ASCII letters, numbers, and hyphen + only, and should begin with a letter. +- Keys should be prefixed to help avoid collisions. For example, a library + named "ApplePicker" should prefix its message keys with "applepicker-". +- Common values needing translation, such as names of months and weekdays, + should not be prefixed by each library. Libraries needing these should use + keys from the [Common Locale Data Repository][CLDR] and document this + requirement, and environments should provide these messages. + +### Message format + +Placeholders are represented by `$1`, `$2`, `$3`, and so on. Text like `$100` +is interpreted as a placeholder for parameter 100 if 100 or more parameters +were supplied, as a placeholder for parameter 10 followed by text "0" if +between ten and 99 parameters were supplied, and as a placeholder for parameter +1 followed by text "00" if between one and nine parameters were supplied. + +All formatting commands look like `{{NAME:$value1|$value2|$value3|...}}`. Braces +are to be balanced, e.g. `{{NAME:foo|{{bar|baz}}}}` has $value1 as "foo" and +$value2 as "{{bar|baz}}". The name is always case-insensitive. + +Anything syntactically resembling a placeholder or formatting command that does +not correspond to an actual paramter or known command should be left unchanged +for processing by the markup language processor. + +Libraries providing messages for use by externally-defined formatters should +generally assume no markup language will be applied, and should avoid +constructs used by common markup languages unless they also make sense when +read as plain text. + +### Formatting commands + +The following formatting commands should be supported. + +#### PLURAL + +`{{PLURAL:$count|$formA|$formB|...}}` is used to produce plurals. + +$count is a number, which may have been formatted with ParamType::NUM. + +The number of forms and which count corresponds to which form depend on the +language, for example English uses `{{PLURAL:$1|one|other}}` while Arabic uses +`{{PLURAL:$1|zero|one|two|few|many|other}}`. Details are defined in +[CLDR][CLDR plurals]. + +It is not possible to "skip" positions while still suppling later ones. If too +few values are supplied, the final form is repeated for subsequent positions. + +If there is an explicit plural form to be given for a specific number, it may +be specified with syntax like `{{PLURAL:$1|one egg|$1 eggs|12=a dozen eggs}}`. + +#### GENDER + +`{{GENDER:$name|$masculine|$feminine|$unspecified}}` is used to handle +grammatical gender, typically when messages refer to user accounts. + +This supports three grammatical genders: "male", "female", and a third option +for cases where the gender is unspecified, unknown, or neither male nor female. +It does not attempt to handle animate-inanimate or [T-V] distinctions. + +$name is a user account name or other similar identifier. If the name given +does not correspond to any known user account, it should probably use the +$unspecified gender. + +If $feminine and/or $unspecified is not specified, the value of $masculine +is normally used in its place. + +#### GRAMMAR + +`{{GRAMMAR:$form|$term}}` converts a term to an appropriate grammatical form. + +If no mapping for $term to $form exists, $term should be returned unchanged. + +See [jQuery.i18n § Grammar][jQuery.i18n grammar] for details. + +#### BIDI + +`{{BIDI:$text}}` applies directional isolation to the wrapped text, to attempt +to avoid errors where directionally-neutral characters are wrongly displayed +when between LTR and RTL content. + +This should output U+202A (left-to-right embedding) or U+202B (right-to-left +embedding) before the text, depending on the directionality of the first +strongly-directional character in $text, and U+202C (pop directional +formatting) after, or do something equivalent for the target output format. + +### Supplying translations + +Code intending its messages to be used by externally-defined formatters should +supply the translations as described by +[jQuery.i18n § Message File Format][jQuery.i18n file format]. + +In brief, the base directory of the library should contain a directory named +"i18n". This directory should contain JSON files named by code such as +"en.json", "de.json", "qqq.json", each with contents like: + +```json +{ + "@metadata": { + "authors": [ + "Alice", + "Bob", + "Carol", + "David" + ], + "last-updated": "2012-09-21" + }, + "appname-title": "Example Application", + "appname-sub-title": "An example application", + "appname-header-introduction": "Introduction", + "appname-about": "About this application", + "appname-footer": "Footer text" +} +``` + +Formatter implementations should be able to consume message data supplied in +this format, either directly via registration of i18n directories to check or +by providing tooling to incorporate it during a build step. + + +--- +[jQuery.i18n]: https://github.com/wikimedia/jquery.i18n +[BCP 47]: https://tools.ietf.org/rfc/bcp/bcp47.txt +[CLDR]: http://cldr.unicode.org/ +[CLDR plurals]: https://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html +[jQuery.i18n grammar]: https://github.com/wikimedia/jquery.i18n#grammar +[jQuery.i18n file format]: https://github.com/wikimedia/jquery.i18n#message-file-format +[translatewiki.net]: https://translatewiki.net/wiki/Translating:New_project +[T-V]: https://en.wikipedia.org/wiki/T%E2%80%93V_distinction diff --git a/includes/libs/Message/ScalarParam.php b/includes/libs/Message/ScalarParam.php new file mode 100644 index 0000000000..c17bc7f149 --- /dev/null +++ b/includes/libs/Message/ScalarParam.php @@ -0,0 +1,30 @@ +type = $type; + $this->value = $value; + } + + public function dump() { + if ( $this->value instanceof MessageValue ) { + $contents = $this->value->dump(); + } else { + $contents = htmlspecialchars( $this->value ); + } + return "<{$this->type}>" . $contents . "type}>"; + } +} diff --git a/includes/libs/Message/TextParam.php b/includes/libs/Message/TextParam.php deleted file mode 100644 index c1a1f089ee..0000000000 --- a/includes/libs/Message/TextParam.php +++ /dev/null @@ -1,37 +0,0 @@ -type = $type; - $this->value = $value; - } - - public function dump() { - return "<{$this->type}>" . htmlspecialchars( $this->value ) . "type}>"; - } -} diff --git a/tests/phpunit/includes/Message/TextFormatterTest.php b/tests/phpunit/includes/Message/TextFormatterTest.php index 233810fe1b..74c56a06a2 100644 --- a/tests/phpunit/includes/Message/TextFormatterTest.php +++ b/tests/phpunit/includes/Message/TextFormatterTest.php @@ -7,13 +7,13 @@ use MediaWikiTestCase; use Message; use Wikimedia\Message\MessageValue; use Wikimedia\Message\ParamType; -use Wikimedia\Message\TextParam; +use Wikimedia\Message\ScalarParam; /** * @covers \MediaWiki\Message\TextFormatter * @covers \Wikimedia\Message\MessageValue * @covers \Wikimedia\Message\ListParam - * @covers \Wikimedia\Message\TextParam + * @covers \Wikimedia\Message\ScalarParam * @covers \Wikimedia\Message\MessageParam */ class TextFormatterTest extends MediaWikiTestCase { @@ -45,7 +45,7 @@ class TextFormatterTest extends MediaWikiTestCase { $formatter = $this->createTextFormatter( 'en' ); $mv = ( new MessageValue( 'test' ) )->commaListParams( [ 'a', - new TextParam( ParamType::BITRATE, 100 ), + new ScalarParam( ParamType::BITRATE, 100 ), ] ); $result = $formatter->format( $mv ); $this->assertSame( 'test a, 100 bps $2', $result ); diff --git a/tests/phpunit/includes/libs/Message/MessageValueTest.php b/tests/phpunit/includes/libs/Message/MessageValueTest.php index 04dfa4e7e9..f0205b8680 100644 --- a/tests/phpunit/includes/libs/Message/MessageValueTest.php +++ b/tests/phpunit/includes/libs/Message/MessageValueTest.php @@ -5,13 +5,13 @@ namespace Wikimedia\Tests\Message; use Wikimedia\Message\ListType; use Wikimedia\Message\MessageValue; use Wikimedia\Message\ParamType; -use Wikimedia\Message\TextParam; +use Wikimedia\Message\ScalarParam; use MediaWikiTestCase; /** * @covers \Wikimedia\Message\MessageValue * @covers \Wikimedia\Message\ListParam - * @covers \Wikimedia\Message\TextParam + * @covers \Wikimedia\Message\ScalarParam * @covers \Wikimedia\Message\MessageParam */ class MessageValueTest extends MediaWikiTestCase { @@ -26,7 +26,7 @@ class MessageValueTest extends MediaWikiTestCase { 'a' ], [ - [ new TextParam( ParamType::BITRATE, 100 ) ], + [ new ScalarParam( ParamType::BITRATE, 100 ) ], '100' ], ]; @@ -46,7 +46,7 @@ class MessageValueTest extends MediaWikiTestCase { public function testParams() { $mv = new MessageValue( 'key' ); $mv->params( 1, 'x' ); - $mv2 = $mv->params( new TextParam( ParamType::BITRATE, 100 ) ); + $mv2 = $mv->params( new ScalarParam( ParamType::BITRATE, 100 ) ); $this->assertSame( '1x100', $mv->dump() ); @@ -76,10 +76,11 @@ class MessageValueTest extends MediaWikiTestCase { public function testTextParams() { $mv = new MessageValue( 'key' ); - $mv2 = $mv->textParams( 'a', 'b' ); + $mv2 = $mv->textParams( 'a', 'b', new MessageValue( 'key2' ) ); $this->assertSame( '' . 'a' . 'b' . + '' . '', $mv->dump() ); $this->assertSame( $mv, $mv2 );