aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMáté Szabó <mszabo@wikimedia.org>2025-03-10 16:35:29 +0100
committerMáté Szabó <mszabo@wikimedia.org>2025-03-10 16:51:33 +0100
commitff7b937f5276e97ef0db2707843635ea1601b84b (patch)
treeb4c7353d96bdc93877e949c76ea4683d2c6dc3c9
parente0c9c34cdf723a0671fce193198f3d2fca6eea7e (diff)
downloadmediawikicore-ff7b937f5276e97ef0db2707843635ea1601b84b.tar.gz
mediawikicore-ff7b937f5276e97ef0db2707843635ea1601b84b.zip
language: fix mw.language.convertGrammar with word-specific casing rules
Why: - mw.language.convertGrammar is responsible for implementing grammatical transformations for inflected languages, similar to its backend counterpart Language::convertGrammar. - The default implementation may be overridden by language-specific implementations loaded dynamically based on the user language. - Both the default implementation and all language-specific implementations suffer from a bug where requesting a case transformation for which word-specific casing rules are defined will return "undefined" if the case transformation was requested for a word for which no word-specific casing rule exists. - There are QUnit tests for the logic, but they only run if the user language matches the language being tested. This means they never run in CI and cannot even be run locally, since Special:JavaScriptTest has been forcing the user language to "qqx" for more than five years. What: - Place language-specific convertGrammar overrides into a new convertGrammarMapping property keyed by language code to allow loading more than one simultaneously. - Introduce and use a new 'mediawiki.language.grammar.testdata' test-only RL module that loads every language-specific convertGrammar override. - Introduce and use a new 'mediawiki.language.testdata' test-only RL module that loads language-specific rules for languages that we have QUnit tests for. Since this only covers less than two dozen languages, there'd be no value in loading this data for all 300+ languages supported by MediaWiki. - Update QUnit tests for convertGrammar to override the user language and language rules before each case. - Ensure word-specific casing rules always take precedence over language-specific convertGrammar implementations to avoid repeated boilerplate in language-specific code. - Don't attempt to use word-specific casing rules for words that do not have one. - As we're here, add matching PHPUnit tests for Language* subclasses that had preexisting QUnit tests but no PHPUnit tests. Bug: T388370 Change-Id: I3f2432f5f801c2a7e4390c2ff2038363a36e2ed9
-rw-r--r--resources/src/mediawiki.language/languages/bs.js6
-rw-r--r--resources/src/mediawiki.language/languages/dsb.js7
-rw-r--r--resources/src/mediawiki.language/languages/fi.js7
-rw-r--r--resources/src/mediawiki.language/languages/ga.js6
-rw-r--r--resources/src/mediawiki.language/languages/hsb.js6
-rw-r--r--resources/src/mediawiki.language/languages/hu.js6
-rw-r--r--resources/src/mediawiki.language/languages/hy.js7
-rw-r--r--resources/src/mediawiki.language/languages/la.js6
-rw-r--r--resources/src/mediawiki.language/languages/os.js6
-rw-r--r--resources/src/mediawiki.language/languages/sl.js6
-rw-r--r--resources/src/mediawiki.language/mediawiki.language.js14
-rw-r--r--tests/phpunit/includes/languages/LanguageBsTest.php13
-rw-r--r--tests/phpunit/includes/languages/LanguageDsbTest.php13
-rw-r--r--tests/phpunit/includes/languages/LanguageFiTest.php40
-rw-r--r--tests/phpunit/includes/languages/LanguageGaTest.php19
-rw-r--r--tests/phpunit/includes/languages/LanguageHsbTest.php13
-rw-r--r--tests/phpunit/includes/languages/LanguageHuTest.php25
-rw-r--r--tests/phpunit/includes/languages/LanguageHyTest.php16
-rw-r--r--tests/phpunit/includes/languages/LanguageLaTest.php71
-rw-r--r--tests/phpunit/includes/languages/LanguageOsTest.php47
-rw-r--r--tests/phpunit/includes/languages/LanguageSlTest.php13
-rw-r--r--tests/qunit/QUnitTestResources.php78
-rw-r--r--tests/qunit/resources/mediawiki.jqueryMsg.test.js2
-rw-r--r--tests/qunit/resources/mediawiki.language.test.js51
24 files changed, 388 insertions, 90 deletions
diff --git a/resources/src/mediawiki.language/languages/bs.js b/resources/src/mediawiki.language/languages/bs.js
index 1ade9a773156..f261d4fb27ef 100644
--- a/resources/src/mediawiki.language/languages/bs.js
+++ b/resources/src/mediawiki.language/languages/bs.js
@@ -2,11 +2,7 @@
* Bosnian (bosanski) language functions
*/
-mw.language.convertGrammar = function ( word, form ) {
- const grammarForms = mw.language.getData( 'bs', 'grammarForms' );
- if ( grammarForms && grammarForms[ form ] ) {
- return grammarForms[ form ][ word ];
- }
+mw.language.convertGrammarMapping.bs = function ( word, form ) {
switch ( form ) {
case 'instrumental': // instrumental
word = 's ' + word;
diff --git a/resources/src/mediawiki.language/languages/dsb.js b/resources/src/mediawiki.language/languages/dsb.js
index ff30168c30c1..e0035cecf512 100644
--- a/resources/src/mediawiki.language/languages/dsb.js
+++ b/resources/src/mediawiki.language/languages/dsb.js
@@ -1,12 +1,7 @@
/*!
* Lower Sorbian (Dolnoserbski) language functions
*/
-
-mw.language.convertGrammar = function ( word, form ) {
- const grammarForms = mw.language.getData( 'dsb', 'grammarForms' );
- if ( grammarForms && grammarForms[ form ] ) {
- return grammarForms[ form ][ word ];
- }
+mw.language.convertGrammarMapping.dsb = function ( word, form ) {
switch ( form ) {
case 'instrumental': // instrumental
word = 'z ' + word;
diff --git a/resources/src/mediawiki.language/languages/fi.js b/resources/src/mediawiki.language/languages/fi.js
index a9d512e84e1c..77adb6da7628 100644
--- a/resources/src/mediawiki.language/languages/fi.js
+++ b/resources/src/mediawiki.language/languages/fi.js
@@ -3,12 +3,7 @@
* @author Santhosh Thottingal
*/
-mw.language.convertGrammar = function ( word, form ) {
- const grammarForms = mw.language.getData( 'fi', 'grammarForms' );
- if ( grammarForms && grammarForms[ form ] ) {
- return grammarForms[ form ][ word ];
- }
-
+mw.language.convertGrammarMapping.fi = function ( word, form ) {
// vowel harmony flag
let aou = /[aou][^äöy]*$/i.test( word );
const origWord = word;
diff --git a/resources/src/mediawiki.language/languages/ga.js b/resources/src/mediawiki.language/languages/ga.js
index b83f89813f83..05c54fe656a1 100644
--- a/resources/src/mediawiki.language/languages/ga.js
+++ b/resources/src/mediawiki.language/languages/ga.js
@@ -2,11 +2,7 @@
* Irish (Gaeilge) language functions
*/
-mw.language.convertGrammar = function ( word, form ) {
- const grammarForms = mw.language.getData( 'ga', 'grammarForms' );
- if ( grammarForms && grammarForms[ form ] ) {
- return grammarForms[ form ][ word ];
- }
+mw.language.convertGrammarMapping.ga = function ( word, form ) {
switch ( form ) {
case 'ainmlae':
switch ( word ) {
diff --git a/resources/src/mediawiki.language/languages/hsb.js b/resources/src/mediawiki.language/languages/hsb.js
index c7c6b03082fb..ffc1bcd8e255 100644
--- a/resources/src/mediawiki.language/languages/hsb.js
+++ b/resources/src/mediawiki.language/languages/hsb.js
@@ -2,11 +2,7 @@
* Upper Sorbian (Hornjoserbsce) language functions
*/
-mw.language.convertGrammar = function ( word, form ) {
- const grammarForms = mw.language.getData( 'hsb', 'grammarForms' );
- if ( grammarForms && grammarForms[ form ] ) {
- return grammarForms[ form ][ word ];
- }
+mw.language.convertGrammarMapping.hsb = function ( word, form ) {
switch ( form ) {
case 'instrumental': // instrumental
word = 'z ' + word;
diff --git a/resources/src/mediawiki.language/languages/hu.js b/resources/src/mediawiki.language/languages/hu.js
index 74847feabce9..4ca2ed5bd8ed 100644
--- a/resources/src/mediawiki.language/languages/hu.js
+++ b/resources/src/mediawiki.language/languages/hu.js
@@ -3,11 +3,7 @@
* @author Santhosh Thottingal
*/
-mw.language.convertGrammar = function ( word, form ) {
- const grammarForms = mw.language.getData( 'hu', 'grammarForms' );
- if ( grammarForms && grammarForms[ form ] ) {
- return grammarForms[ form ][ word ];
- }
+mw.language.convertGrammarMapping.hu = function ( word, form ) {
switch ( form ) {
case 'rol':
word += 'ról';
diff --git a/resources/src/mediawiki.language/languages/hy.js b/resources/src/mediawiki.language/languages/hy.js
index 69dce79926a5..6f4bc136129e 100644
--- a/resources/src/mediawiki.language/languages/hy.js
+++ b/resources/src/mediawiki.language/languages/hy.js
@@ -2,12 +2,7 @@
* Armenian (Հայերեն) language functions
*/
-mw.language.convertGrammar = function ( word, form ) {
- const grammarForms = mw.language.getData( 'hy', 'grammarForms' );
- if ( grammarForms && grammarForms[ form ] ) {
- return grammarForms[ form ][ word ];
- }
-
+mw.language.convertGrammarMapping.hy = function ( word, form ) {
// These rules are not perfect, but they are currently only used for site names so it doesn't
// matter if they are wrong sometimes. Just add a special case for your site name if necessary.
diff --git a/resources/src/mediawiki.language/languages/la.js b/resources/src/mediawiki.language/languages/la.js
index cc7c95f0de6e..4fa08e00e3a1 100644
--- a/resources/src/mediawiki.language/languages/la.js
+++ b/resources/src/mediawiki.language/languages/la.js
@@ -3,11 +3,7 @@
* @author Santhosh Thottingal
*/
-mw.language.convertGrammar = function ( word, form ) {
- const grammarForms = mw.language.getData( 'la', 'grammarForms' );
- if ( grammarForms && grammarForms[ form ] ) {
- return grammarForms[ form ][ word ];
- }
+mw.language.convertGrammarMapping.la = function ( word, form ) {
switch ( form ) {
case 'genitive':
// only a few declensions, and even for those mostly the singular only
diff --git a/resources/src/mediawiki.language/languages/os.js b/resources/src/mediawiki.language/languages/os.js
index fb3a15a4322a..9e8859cada87 100644
--- a/resources/src/mediawiki.language/languages/os.js
+++ b/resources/src/mediawiki.language/languages/os.js
@@ -3,8 +3,7 @@
* @author Santhosh Thottingal
*/
-mw.language.convertGrammar = function ( word, form ) {
- const grammarForms = mw.language.getData( 'os', 'grammarForms' );
+mw.language.convertGrammarMapping.os = function ( word, form ) {
// Ending for allative case
let endAllative = 'мæ',
// Variable for 'j' beetwen vowels
@@ -14,9 +13,6 @@ mw.language.convertGrammar = function ( word, form ) {
// Variable for ending
ending = '';
- if ( grammarForms && grammarForms[ form ] ) {
- return grammarForms[ form ][ word ];
- }
// Checking if the $word is in plural form
if ( /тæ$/i.test( word ) ) {
word = word.slice( 0, -1 );
diff --git a/resources/src/mediawiki.language/languages/sl.js b/resources/src/mediawiki.language/languages/sl.js
index 7dc63dc663c4..fd2afd45c29f 100644
--- a/resources/src/mediawiki.language/languages/sl.js
+++ b/resources/src/mediawiki.language/languages/sl.js
@@ -2,11 +2,7 @@
* Slovenian (Slovenščina) language functions
*/
-mw.language.convertGrammar = function ( word, form ) {
- const grammarForms = mw.language.getData( 'sl', 'grammarForms' );
- if ( grammarForms && grammarForms[ form ] ) {
- return grammarForms[ form ][ word ];
- }
+mw.language.convertGrammarMapping.sl = function ( word, form ) {
switch ( form ) {
case 'mestnik': // locative
word = 'o ' + word;
diff --git a/resources/src/mediawiki.language/mediawiki.language.js b/resources/src/mediawiki.language/mediawiki.language.js
index 618fb29fc121..13bee34e5fed 100644
--- a/resources/src/mediawiki.language/mediawiki.language.js
+++ b/resources/src/mediawiki.language/mediawiki.language.js
@@ -72,6 +72,11 @@
},
/**
+ * Map of language-specific convertGrammar() implementations keyed by language code.
+ */
+ convertGrammarMapping: {},
+
+ /**
* Grammatical transformations, needed for inflected languages.
* Invoked by putting `{{grammar:case|word}}` in a message.
*
@@ -85,11 +90,18 @@
convertGrammar: function ( word, form ) {
const userLanguage = mw.config.get( 'wgUserLanguage' );
+ // Word-specific casing rules have the highest precedence.
const forms = mw.language.getData( userLanguage, 'grammarForms' );
- if ( forms && forms[ form ] ) {
+ if ( forms && forms[ form ] && forms[ form ][ word ] ) {
return forms[ form ][ word ];
}
+ // If no word-specific casing rule exists, prefer to use a language-specific
+ // convertGrammar() implementation if one is available.
+ if ( Object.prototype.hasOwnProperty.call( mw.language.convertGrammarMapping, userLanguage ) ) {
+ return mw.language.convertGrammarMapping[ userLanguage ]( word, form );
+ }
+
const transformations = mw.language.getData( userLanguage, 'grammarTransformations' );
if ( !( transformations && transformations[ form ] ) ) {
diff --git a/tests/phpunit/includes/languages/LanguageBsTest.php b/tests/phpunit/includes/languages/LanguageBsTest.php
index 8d46d33126ba..7333113f5de9 100644
--- a/tests/phpunit/includes/languages/LanguageBsTest.php
+++ b/tests/phpunit/includes/languages/LanguageBsTest.php
@@ -44,4 +44,17 @@ class LanguageBsTest extends LanguageClassesTestCase {
[ 'other', 200 ],
];
}
+
+ /**
+ * @dataProvider provideConvertGrammar
+ */
+ public function testConvertGrammar( string $word, string $case, string $expected ): void {
+ $this->assertSame( $expected, $this->getLang()->convertGrammar( $word, $case ) );
+ }
+
+ public static function provideConvertGrammar(): iterable {
+ yield [ 'word', 'instrumental', 's word' ];
+ yield [ 'word', 'lokativ', 'o word' ];
+ yield [ 'word', 'nominativ', 'word' ];
+ }
}
diff --git a/tests/phpunit/includes/languages/LanguageDsbTest.php b/tests/phpunit/includes/languages/LanguageDsbTest.php
index 3087426767c9..7d0714988d5e 100644
--- a/tests/phpunit/includes/languages/LanguageDsbTest.php
+++ b/tests/phpunit/includes/languages/LanguageDsbTest.php
@@ -41,4 +41,17 @@ class LanguageDsbTest extends LanguageClassesTestCase {
[ 'other', 555 ],
];
}
+
+ /**
+ * @dataProvider provideConvertGrammar
+ */
+ public function testConvertGrammar( string $word, string $case, string $expected ): void {
+ $this->assertSame( $expected, $this->getLang()->convertGrammar( $word, $case ) );
+ }
+
+ public static function provideConvertGrammar(): iterable {
+ yield [ 'word', 'nominatiw', 'word' ];
+ yield [ 'word', 'instrumental', 'wo z word' ];
+ yield [ 'word', 'lokatiw', 'wo word' ];
+ }
}
diff --git a/tests/phpunit/includes/languages/LanguageFiTest.php b/tests/phpunit/includes/languages/LanguageFiTest.php
new file mode 100644
index 000000000000..fb9efd76e493
--- /dev/null
+++ b/tests/phpunit/includes/languages/LanguageFiTest.php
@@ -0,0 +1,40 @@
+<?php
+declare( strict_types=1 );
+
+/**
+ * @group Language
+ * @covers LanguageFi
+ */
+class LanguageFiTest extends LanguageClassesTestCase {
+ /**
+ * @dataProvider provideConvertGrammar
+ */
+ public function testConvertGrammar( string $word, string $case, string $expected ): void {
+ $this->assertSame( $expected, $this->getLang()->convertGrammar( $word, $case ) );
+ }
+
+ public static function provideConvertGrammar(): iterable {
+ $wordCaseMappings = [
+ 'talo' => [
+ 'genitive' => 'talon',
+ 'elative' => 'talosta',
+ 'partitive' => 'taloa',
+ 'illative' => 'taloon',
+ 'inessive' => 'talossa',
+ ],
+ 'pastöroitu' => [
+ 'partitive' => 'pastöroitua',
+ ],
+ 'Wikipedia' => [
+ 'elative' => 'Wikipediasta',
+ 'partitive' => 'Wikipediaa',
+ ],
+ ];
+
+ foreach ( $wordCaseMappings as $word => $caseMappings ) {
+ foreach ( $caseMappings as $case => $expected ) {
+ yield "$word $case" => [ (string)$word, $case, $expected ];
+ }
+ }
+ }
+}
diff --git a/tests/phpunit/includes/languages/LanguageGaTest.php b/tests/phpunit/includes/languages/LanguageGaTest.php
index fc6a5ac3a236..08cd052e072f 100644
--- a/tests/phpunit/includes/languages/LanguageGaTest.php
+++ b/tests/phpunit/includes/languages/LanguageGaTest.php
@@ -37,4 +37,23 @@ class LanguageGaTest extends LanguageClassesTestCase {
[ 'other', 200 ],
];
}
+
+ /**
+ * @dataProvider provideConvertGrammar
+ */
+ public function testConvertGrammar( string $word, string $case, string $expected ): void {
+ $this->assertSame( $expected, $this->getLang()->convertGrammar( $word, $case ) );
+ }
+
+ public static function provideConvertGrammar(): iterable {
+ yield [ 'an Domhnach', 'ainmlae', 'Dé Domhnaigh' ];
+ yield [ 'an Luan', 'ainmlae', 'Dé Luain' ];
+ yield [ 'an Mháirt', 'ainmlae', 'Dé Mháirt' ];
+ yield [ 'an Chéadaoin', 'ainmlae', 'Dé Chéadaoin' ];
+ yield [ 'an Déardaoin', 'ainmlae', 'Déardaoin' ];
+ yield [ 'an Aoine', 'ainmlae', 'Dé hAoine' ];
+ yield [ 'an Satharn', 'ainmlae', 'Dé Sathairn' ];
+
+ yield [ 'an Domhnach', 'other', 'an Domhnach' ];
+ }
}
diff --git a/tests/phpunit/includes/languages/LanguageHsbTest.php b/tests/phpunit/includes/languages/LanguageHsbTest.php
index c90983daab17..728496438b8e 100644
--- a/tests/phpunit/includes/languages/LanguageHsbTest.php
+++ b/tests/phpunit/includes/languages/LanguageHsbTest.php
@@ -41,4 +41,17 @@ class LanguageHsbTest extends LanguageClassesTestCase {
[ 'other', 555 ],
];
}
+
+ /**
+ * @dataProvider provideConvertGrammar
+ */
+ public function testConvertGrammar( string $word, string $case, string $expected ): void {
+ $this->assertSame( $expected, $this->getLang()->convertGrammar( $word, $case ) );
+ }
+
+ public static function provideConvertGrammar(): iterable {
+ yield [ 'word', 'nominatiw', 'word' ];
+ yield [ 'word', 'instrumental', 'z word' ];
+ yield [ 'word', 'lokatiw', 'wo word' ];
+ }
}
diff --git a/tests/phpunit/includes/languages/LanguageHuTest.php b/tests/phpunit/includes/languages/LanguageHuTest.php
index a8b0a578840e..445d164a65f5 100644
--- a/tests/phpunit/includes/languages/LanguageHuTest.php
+++ b/tests/phpunit/includes/languages/LanguageHuTest.php
@@ -35,4 +35,29 @@ class LanguageHuTest extends LanguageClassesTestCase {
[ 'other', 200 ],
];
}
+
+ /**
+ * @dataProvider provideConvertGrammar
+ */
+ public function testConvertGrammar( string $word, string $case, string $expected ): void {
+ $this->assertSame( $expected, $this->getLang()->convertGrammar( $word, $case ) );
+ }
+
+ public static function provideConvertGrammar(): iterable {
+ $wordCaseMappings = [
+ 'kocsmafal' => [
+ 'rol' => 'kocsmafalról',
+ 'ba' => 'kocsmafalba',
+ ],
+ 'Bevezető' => [
+ 'k' => 'Bevezetők',
+ ],
+ ];
+
+ foreach ( $wordCaseMappings as $word => $caseMappings ) {
+ foreach ( $caseMappings as $case => $expected ) {
+ yield "$word $case" => [ (string)$word, $case, $expected ];
+ }
+ }
+ }
}
diff --git a/tests/phpunit/includes/languages/LanguageHyTest.php b/tests/phpunit/includes/languages/LanguageHyTest.php
index 01e8be7a17e0..44548b69cd93 100644
--- a/tests/phpunit/includes/languages/LanguageHyTest.php
+++ b/tests/phpunit/includes/languages/LanguageHyTest.php
@@ -37,4 +37,20 @@ class LanguageHyTest extends LanguageClassesTestCase {
[ 'other', 200 ],
];
}
+
+ /**
+ * @dataProvider provideConvertGrammar
+ */
+ public function testConvertGrammar( string $word, string $case, string $expected ): void {
+ $this->assertSame( $expected, $this->getLang()->convertGrammar( $word, $case ) );
+ }
+
+ public static function provideConvertGrammar(): iterable {
+ yield [ 'Մաունա', 'genitive', 'Մաունայի' ];
+ yield [ 'հետո', 'genitive', 'հետոյի' ];
+ yield [ 'գիրք', 'genitive', 'գրքի' ];
+ yield [ 'ժամանակի', 'genitive', 'ժամանակիի' ];
+
+ yield [ 'Մաունա', 'dative', 'Մաունա' ];
+ }
}
diff --git a/tests/phpunit/includes/languages/LanguageLaTest.php b/tests/phpunit/includes/languages/LanguageLaTest.php
new file mode 100644
index 000000000000..6b8f895c1664
--- /dev/null
+++ b/tests/phpunit/includes/languages/LanguageLaTest.php
@@ -0,0 +1,71 @@
+<?php
+declare( strict_types=1 );
+
+/**
+ * @group Language
+ * @covers LanguageLa
+ */
+class LanguageLaTest extends LanguageClassesTestCase {
+ /**
+ * @dataProvider provideConvertGrammar
+ */
+ public function testConvertGrammar( string $word, string $case, string $expected ): void {
+ $this->assertSame( $expected, $this->getLang()->convertGrammar( $word, $case ) );
+ }
+
+ public static function provideConvertGrammar(): iterable {
+ $wordCaseMappings = [
+ 'translatio' => [
+ 'genitive' => 'translationis',
+ 'accusative' => 'translationem',
+ 'ablative' => 'translatione',
+ ],
+ 'ursus' => [
+ 'genitive' => 'ursi',
+ 'accusative' => 'ursum',
+ 'ablative' => 'urso',
+ ],
+ 'gens' => [
+ 'genitive' => 'gentis',
+ 'accusative' => 'gentem',
+ 'ablative' => 'gente',
+ ],
+ 'bellum' => [
+ 'genitive' => 'belli',
+ 'accusative' => 'bellum',
+ 'ablative' => 'bello',
+ ],
+ 'communia' => [
+ 'genitive' => 'communium',
+ 'accusative' => 'communia',
+ 'ablative' => 'communibus',
+ ],
+ 'libri' => [
+ 'genitive' => 'librorum',
+ 'accusative' => 'libros',
+ 'ablative' => 'libris',
+ ],
+ 'dies' => [
+ 'genitive' => 'diei',
+ 'accusative' => 'diem',
+ 'ablative' => 'die',
+ ],
+ 'declinatio' => [
+ 'genitive' => 'declinationis',
+ 'accusative' => 'declinationem',
+ 'ablative' => 'declinatione',
+ ],
+ 'vanitas' => [
+ 'genitive' => 'vanitatis',
+ 'accusative' => 'vanitatem',
+ 'ablative' => 'vanitate',
+ ],
+ ];
+
+ foreach ( $wordCaseMappings as $word => $caseMappings ) {
+ foreach ( $caseMappings as $case => $expected ) {
+ yield "$word $case" => [ (string)$word, $case, $expected ];
+ }
+ }
+ }
+}
diff --git a/tests/phpunit/includes/languages/LanguageOsTest.php b/tests/phpunit/includes/languages/LanguageOsTest.php
new file mode 100644
index 000000000000..dc4bb44c8309
--- /dev/null
+++ b/tests/phpunit/includes/languages/LanguageOsTest.php
@@ -0,0 +1,47 @@
+<?php
+declare( strict_types=1 );
+
+/**
+ * @group Language
+ * @covers LanguageOs
+ */
+class LanguageOsTest extends LanguageClassesTestCase {
+ /**
+ * @dataProvider provideConvertGrammar
+ */
+ public function testConvertGrammar( string $word, string $case, string $expected ): void {
+ $this->assertSame( $expected, $this->getLang()->convertGrammar( $word, $case ) );
+ }
+
+ public static function provideConvertGrammar(): iterable {
+ $wordCaseMappings = [
+ 'бæстæ' => [
+ 'genitive' => 'бæсты',
+ 'allative' => 'бæстæм',
+ 'dative' => 'бæстæн',
+ 'ablative' => 'бæстæй',
+ 'inessive' => 'бæст',
+ 'superessive' => 'бæстыл',
+ ],
+
+ 'лæппу' => [
+ 'genitive' => 'лæппуйы',
+ 'allative' => 'лæппумæ',
+ 'dative' => 'лæппуйæн',
+ 'ablative' => 'лæппуйæ',
+ 'inessive' => 'лæппу',
+ 'superessive' => 'лæппуйыл',
+ ],
+
+ '2011' => [
+ 'equative' => '2011-ау',
+ ],
+ ];
+
+ foreach ( $wordCaseMappings as $word => $caseMappings ) {
+ foreach ( $caseMappings as $case => $expected ) {
+ yield "$word $case" => [ (string)$word, $case, $expected ];
+ }
+ }
+ }
+}
diff --git a/tests/phpunit/includes/languages/LanguageSlTest.php b/tests/phpunit/includes/languages/LanguageSlTest.php
index ab5d0efa7afd..f99445a7db85 100644
--- a/tests/phpunit/includes/languages/LanguageSlTest.php
+++ b/tests/phpunit/includes/languages/LanguageSlTest.php
@@ -43,4 +43,17 @@ class LanguageSlTest extends LanguageClassesTestCase {
[ 'one', 201 ],
];
}
+
+ /**
+ * @dataProvider provideConvertGrammar
+ */
+ public function testConvertGrammar( string $word, string $case, string $expected ): void {
+ $this->assertSame( $expected, $this->getLang()->convertGrammar( $word, $case ) );
+ }
+
+ public static function provideConvertGrammar(): iterable {
+ yield [ 'word', 'mestnik', 'o word' ];
+ yield [ 'word', 'orodnik', 'z word' ];
+ yield [ 'word', 'imenovalnik', 'word' ];
+ }
}
diff --git a/tests/qunit/QUnitTestResources.php b/tests/qunit/QUnitTestResources.php
index 2ca510a2f117..65cad8e887d7 100644
--- a/tests/qunit/QUnitTestResources.php
+++ b/tests/qunit/QUnitTestResources.php
@@ -1,5 +1,7 @@
<?php
// These modules are only registered when $wgEnableJavaScriptTest is true
+use MediaWiki\Html\Html;
+use MediaWiki\MediaWikiServices;
use MediaWiki\ResourceLoader\FilePath;
return [
@@ -21,23 +23,71 @@ return [
],
],
+ // Test module exposing language-specific rules for mediawiki.language.test.js.
'mediawiki.language.testdata' => [
- 'localBasePath' => "{$GLOBALS['IP']}/resources/src/mediawiki.language/languages",
- 'remoteBasePath' => "{$GLOBALS['wgResourceBasePath']}/resources/src/mediawiki.language/languages",
+ 'packageFiles' => [
+ [
+ 'name' => 'mediawiki.language.testdata.js',
+ 'callback' => static function () {
+ // Optimization: Only compute and load data for languages that we have tests for.
+ $langCodes = [
+ 'bs',
+ 'he',
+ 'hsb',
+ 'dsb',
+ 'hy',
+ 'fi',
+ 'ka',
+ 'ru',
+ 'hu',
+ 'ga',
+ 'mn',
+ 'uk',
+ 'sl',
+ 'os',
+ 'la',
+ ];
+
+ $languageFactory = MediaWikiServices::getInstance()->getLanguageFactory();
+
+ $data = [];
+
+ foreach ( $langCodes as $langCode ) {
+ $data[$langCode] = $languageFactory->getLanguage( $langCode )->getJsData();
+ }
+
+ return 'module.exports = ' . Html::encodeJsVar( $data ) . ';';
+ },
+ ]
+ ],
+ ],
+
+ 'mediawiki.language.jqueryMsg.testdata' => [
+ 'localBasePath' => __DIR__ . '/data',
'packageFiles' => [
new FilePath( 'mediawiki.jqueryMsg.testdata.js', __DIR__ . '/data' ),
new FilePath( 'mediawiki.jqueryMsg.data.json', __DIR__ . '/data' ),
- 'bs.js',
- 'dsb.js',
- 'fi.js',
- 'ga.js',
- 'hsb.js',
- 'hu.js',
- 'hy.js',
- 'la.js',
- 'os.js',
- 'sl.js',
- ]
+ ],
+ ],
+
+ // Test module loading all language-specific convertGrammar() implementations.
+ 'mediawiki.language.grammar.testdata' => [
+ 'localBasePath' => "{$GLOBALS['IP']}/resources/src/mediawiki.language/languages",
+ // Automatically discover and load every language-specific convertGrammar() implementation.
+ 'scripts' => ( static function () {
+ $basePath = "{$GLOBALS['IP']}/resources/src/mediawiki.language/languages";
+ $scripts = [];
+
+ foreach ( new DirectoryIterator( $basePath ) as $file ) {
+ /** @var DirectoryIterator $file */
+ if ( $file->isFile() && $file->getExtension() === 'js' ) {
+ $scripts[] = $file->getBasename();
+ }
+ }
+
+ return $scripts;
+ } )(),
+ 'dependencies' => [ 'mediawiki.language' ],
],
'test.MediaWiki' => [
@@ -133,6 +183,8 @@ return [
'mediawiki.rcfilters.filters.ui',
'mediawiki.language',
'mediawiki.language.testdata',
+ 'mediawiki.language.grammar.testdata',
+ 'mediawiki.language.jqueryMsg.testdata',
'mediawiki.cldr',
'mediawiki.cookie',
'mediawiki.deflate',
diff --git a/tests/qunit/resources/mediawiki.jqueryMsg.test.js b/tests/qunit/resources/mediawiki.jqueryMsg.test.js
index 5f1a75b530d4..1423d4a1ad9c 100644
--- a/tests/qunit/resources/mediawiki.jqueryMsg.test.js
+++ b/tests/qunit/resources/mediawiki.jqueryMsg.test.js
@@ -4,7 +4,7 @@
/* eslint-disable camelcase */
let formatText, formatParse, specialCharactersPageName, expectedListUsers,
expectedListUsersSitename, expectedLinkPagenamee, expectedEntrypoints;
- const testData = require( 'mediawiki.language.testdata' ),
+ const testData = require( 'mediawiki.language.jqueryMsg.testdata' ),
phpParserData = testData.phpParserData;
// When the expected result is the same in both modes
diff --git a/tests/qunit/resources/mediawiki.language.test.js b/tests/qunit/resources/mediawiki.language.test.js
index de9e2d285263..bdeb0c4a9dd8 100644
--- a/tests/qunit/resources/mediawiki.language.test.js
+++ b/tests/qunit/resources/mediawiki.language.test.js
@@ -3,11 +3,13 @@
QUnit.module( 'mediawiki.language', QUnit.newMwEnvironment( {
beforeEach: function () {
+ this.userLang = mw.config.get( 'wgUserLanguage' );
this.liveLangData = mw.language.data;
mw.language.data = {};
},
afterEach: function () {
mw.language.data = this.liveLangData;
+ mw.config.set( 'wgUserLanguage', this.userLang );
},
messages: {
// mw.language.listToText test
@@ -77,21 +79,6 @@
assert.strictEqual( mw.language.convertNumber( '1.200', true ), 1200, 'unformat plain (digit transform disabled)' );
} );
- function grammarTest( langCode, test ) {
- // The test works only if the content language is opt.language
- // because it requires [lang].js to be loaded.
- QUnit.test( 'Grammar test for lang=' + langCode, ( assert ) => {
- let i;
- for ( i = 0; i < test.length; i++ ) {
- assert.strictEqual(
- mw.language.convertGrammar( test[ i ].word, test[ i ].grammarForm ),
- test[ i ].expected,
- test[ i ].description
- );
- }
- } );
- }
-
// These tests run only for the current UI language.
const grammarTests = {
bs: [
@@ -449,7 +436,7 @@
{
word: 'Викитека',
grammarForm: 'accusative',
- expected: 'Викитека',
+ expected: 'Викитеку',
description: 'Grammar test for accusative case, Викитека -> Викитеку'
},
{
@@ -644,7 +631,7 @@
{
word: 'Викитолийн',
grammarForm: 'genitive',
- expected: 'Викитоль',
+ expected: 'Викитолийн',
description: 'Grammar test for genitive case'
}
],
@@ -758,11 +745,31 @@
]
};
- // eslint-disable-next-line no-jquery/no-each-util
- $.each( grammarTests, ( langCode, test ) => {
- if ( langCode === mw.config.get( 'wgUserLanguage' ) ) {
- grammarTest( langCode, test );
- }
+ const languageTestData = require( 'mediawiki.language.testdata' );
+
+ Object.keys( grammarTests ).forEach( ( langCode ) => {
+ QUnit.test( `Language data for lang=${ langCode }`, ( assert ) => {
+ assert.strictEqual(
+ Object.prototype.hasOwnProperty.call( languageTestData, langCode ),
+ true,
+ `Missing test data for lang=${ langCode }.` +
+ 'Update the definition of the "mediawiki.language.testdata" module.'
+ );
+ } );
+
+ QUnit.test( `Grammar test for lang=${ langCode }`, ( assert ) => {
+ mw.config.set( 'wgUserLanguage', langCode );
+ mw.language.setData( langCode, languageTestData[ langCode ] );
+
+ const test = grammarTests[ langCode ];
+ for ( let i = 0; i < test.length; i++ ) {
+ assert.strictEqual(
+ mw.language.convertGrammar( test[ i ].word, test[ i ].grammarForm ),
+ test[ i ].expected,
+ test[ i ].description
+ );
+ }
+ } );
} );
QUnit.test( 'List to text test', ( assert ) => {