Localisation updates from https://translatewiki.net.
[mediawiki.git] / resources / src / vue / i18n.js
blob2fa08b2ce915f1b7175a776cb51572ff1c0acd1b
1 /*!
2  * i18n plugin for Vue that connects to MediaWiki's i18n system.
3  *
4  * Based on https://github.com/santhoshtr/vue-banana-i18n , but modified to connect to mw.message()
5  * instead of banana-i18n.
6  */
8 module.exports = {
9         install: function ( app ) {
10                 /**
11                  * Adds an `$i18n()` instance method that can be used in all components. This method is a
12                  * proxy to {@link mw.message}.
13                  *
14                  * Usage:
15                  * ```
16                  * <p>{{ $i18n( 'my-message-key', param1, param2 ) }}</p>
17                  * ```
18                  * or
19                  * ```
20                  * <p>{{ $i18n( 'my-message-key' ).params( [ param1, param2 ] ) }}</p>
21                  * ```
22                  *
23                  * Note that this method only works for messages that return text. For messages that
24                  * need to be parsed to HTML, use the `v-i18n-html` directive.
25                  *
26                  * @param {string} key Key of message to get
27                  * @param {...any} parameters Values for $N replacements
28                  * @return {mw.Message}
29                  * @memberof module:vue.prototype
30                  */
31                 function $i18n( key, ...parameters ) {
32                         // eslint-disable-next-line mediawiki/msg-doc
33                         return mw.message( key, ...parameters );
34                 }
36                 // Make $i18n available as a global property
37                 app.config.globalProperties.$i18n = $i18n;
38                 // Also make $i18n available in setup() functions through inject()
39                 app.provide( 'i18n', $i18n );
41                 function renderI18nHtml( el, binding ) {
42                         /* eslint-disable mediawiki/msg-doc */
43                         let message;
45                         if ( Array.isArray( binding.value ) ) {
46                                 if ( binding.arg === undefined ) {
47                                         // v-i18n-html="[ ...params ]" (error)
48                                         throw new Error( 'v-i18n-html used with parameter array but without message key' );
49                                 }
50                                 // v-i18n-html:messageKey="[ ...params ]"
51                                 message = mw.message( binding.arg ).params( binding.value );
52                         } else if ( binding.value instanceof mw.Message ) {
53                                 // v-i18n-html="mw.message( '...' ).params( [ ... ] )"
54                                 message = binding.value;
55                         } else {
56                                 // v-i18n-html:foo or v-i18n-html="'foo'"
57                                 message = mw.message( binding.arg || binding.value );
58                         }
59                         /* eslint-enable mediawiki/msg-doc */
61                         el.innerHTML = message.parse();
62                 }
64                 /*
65                  * Add a custom v-i18n-html directive. This is used to inject parsed i18n message contents.
66                  *
67                  * <div v-i18n-html:my-message-key />
68                  *     Parses the my-message-key message and injects the parsed HTML into the div.
69                  *     Equivalent to v-html="mw.message( 'my-message-key' ).parse()"
70                  *
71                  * <div v-i18n-html="msgKey" />
72                  *     Looks in the msgKey variable for the message name, and parses that message.
73                  *     Equivalent to v-html="mw.message( msgKey ).parse()"
74                  *
75                  * <div v-i18n-html="'my-message-key'" />
76                  *     Parses the message named my-message-key. Note the nested quotes!
77                  *     Equivalent to v-html="mw.message( 'my-message-key' ).parse()"
78                  *
79                  * <div v-i18n-html:my-message-key="[ param1, param2 ]" />
80                  *     Parses the my-message-key message, passing parameters param1 and param2
81                  *     Equivalent to v-html="mw.message( 'my-message-key' ).params( [ param1, param2 ] ).parse()"
82                  *
83                  * <div v-i18n-html:my-message-key="[ param1 ]" />
84                  *     Parses the my-message-key message, passing only one parameter. Note the array brackets!
85                  *     Equivalent to v-html="mw.message( 'my-message-key' ).params( [ param1 ] ).parse()"
86                  *
87                  * <div v-i18n-html="$i18n( 'my-message-key' ).params( [ param1, param2 ] )" />
88                  *     If a mw.Message object is passed in, .parse() will be called on it.
89                  *     Equivalent to v-html="mw.message( 'my-message-key' ).params( [ param1, param2 ] ).parse()"
90                  *     This is only recommended for when you have a Message object coming from a
91                  *     computed property or a method, or for when you can't use any of the other calling
92                  *     styles (e.g. because the message key is dynamic, or contains unusual characters).
93                  *     Note that you can use mw.message() in computed properties, but in template attributes
94                  *     you have to use $i18n() instead as demonstrated above.
95                  *
96                  * WARNING: Do not use dynamic argument syntax, like <div v-i18n-html:[msgKeyVariable] />
97                  *          If you do this, the message will not update when msgKeyVariable changes, due to
98                  *          limitations in Vue's directives API. Instead, use the $i18n style described
99                  *          above if you need a dynamic message key.
100                  */
101                 app.directive( 'i18n-html', {
102                         mounted: renderI18nHtml,
103                         updated( el, binding ) {
104                                 // This function is invoked often, every time anything in the component changes.
105                                 // We don't want to rerender unnecessarily, because that's wasteful and can cause
106                                 // strange issues like T327229. For each possible type of binding.value, compare it
107                                 // to binding.oldValue, and abort if they're equal. This does not account for
108                                 // changes in binding.arg; we can't detect those, so there's a warning in the
109                                 // documentation above explaining that using a dynamic argument is not supported.
111                                 const areArraysEqual = ( arr1, arr2 ) => Array.isArray( arr1 ) && Array.isArray( arr2 ) &&
112                                         arr1.length === arr2.length &&
113                                         arr1.every( ( val, index ) => arr2[ index ] === val );
114                                 const areMessagesEqual = ( msg1, msg2 ) => msg1 instanceof mw.Message && msg2 instanceof mw.Message &&
115                                         msg1.key === msg2.key &&
116                                         areArraysEqual( msg1.parameters, msg2.parameters );
118                                 if (
119                                         binding.value === binding.oldValue ||
120                                         areArraysEqual( binding.value, binding.oldValue ) ||
121                                         areMessagesEqual( binding.value, binding.oldValue )
122                                 ) {
123                                         return;
124                                 }
126                                 renderI18nHtml( el, binding );
127                         }
128                 } );
129         }