summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'MLEB/Translate')
-rw-r--r--MLEB/Translate/.eslintrc.json15
-rw-r--r--MLEB/Translate/.phpcs.xml27
-rw-r--r--MLEB/Translate/.stylelintrc.json9
-rw-r--r--MLEB/Translate/CODE_OF_CONDUCT.md1
-rw-r--r--MLEB/Translate/Gruntfile.js50
-rw-r--r--MLEB/Translate/HISTORY793
-rw-r--r--MLEB/Translate/api/ApiSearchTranslations.php131
-rw-r--r--MLEB/Translate/api/ApiTranslationCheck.php78
-rw-r--r--MLEB/Translate/i18n/api/bcl.json12
-rw-r--r--MLEB/Translate/i18n/api/bg.json27
-rw-r--r--MLEB/Translate/i18n/api/bn.json12
-rw-r--r--MLEB/Translate/i18n/api/bs.json122
-rw-r--r--MLEB/Translate/i18n/api/ckb.json8
-rw-r--r--MLEB/Translate/i18n/api/cs.json12
-rw-r--r--MLEB/Translate/i18n/api/da.json28
-rw-r--r--MLEB/Translate/i18n/api/diq.json27
-rw-r--r--MLEB/Translate/i18n/api/fa.json55
-rw-r--r--MLEB/Translate/i18n/api/fi.json54
-rw-r--r--MLEB/Translate/i18n/api/fr.json139
-rw-r--r--MLEB/Translate/i18n/api/gu.json11
-rw-r--r--MLEB/Translate/i18n/api/hi.json24
-rw-r--r--MLEB/Translate/i18n/api/hu.json107
-rw-r--r--MLEB/Translate/i18n/api/id.json13
-rw-r--r--MLEB/Translate/i18n/api/is.json25
-rw-r--r--MLEB/Translate/i18n/api/ja.json10
-rw-r--r--MLEB/Translate/i18n/api/kab.json37
-rw-r--r--MLEB/Translate/i18n/api/kk-cyrl.json20
-rw-r--r--MLEB/Translate/i18n/api/km.json8
-rw-r--r--MLEB/Translate/i18n/api/ko.json37
-rw-r--r--MLEB/Translate/i18n/api/ku-latn.json14
-rw-r--r--MLEB/Translate/i18n/api/li.json61
-rw-r--r--MLEB/Translate/i18n/api/lki.json10
-rw-r--r--MLEB/Translate/i18n/api/lt.json36
-rw-r--r--MLEB/Translate/i18n/api/my.json27
-rw-r--r--MLEB/Translate/i18n/api/nb.json106
-rw-r--r--MLEB/Translate/i18n/api/ne.json16
-rw-r--r--MLEB/Translate/i18n/api/nn.json8
-rw-r--r--MLEB/Translate/i18n/api/oc.json20
-rw-r--r--MLEB/Translate/i18n/api/pam.json8
-rw-r--r--MLEB/Translate/i18n/api/qu.json8
-rw-r--r--MLEB/Translate/i18n/api/roa-tara.json56
-rw-r--r--MLEB/Translate/i18n/api/sa.json14
-rw-r--r--MLEB/Translate/i18n/api/sah.json11
-rw-r--r--MLEB/Translate/i18n/api/skr-arab.json9
-rw-r--r--MLEB/Translate/i18n/api/sq.json10
-rw-r--r--MLEB/Translate/i18n/api/sr-ec.json9
-rw-r--r--MLEB/Translate/i18n/api/ta.json8
-rw-r--r--MLEB/Translate/i18n/api/th.json17
-rw-r--r--MLEB/Translate/i18n/api/tl.json11
-rw-r--r--MLEB/Translate/i18n/api/tr.json64
-rw-r--r--MLEB/Translate/i18n/api/tt-cyrl.json8
-rw-r--r--MLEB/Translate/i18n/api/vi.json9
-rw-r--r--MLEB/Translate/i18n/api/wa.json8
-rw-r--r--MLEB/Translate/i18n/core/abs.json8
-rw-r--r--MLEB/Translate/i18n/core/ady-cyrl.json8
-rw-r--r--MLEB/Translate/i18n/core/aeb-arab.json8
-rw-r--r--MLEB/Translate/i18n/core/ais.json24
-rw-r--r--MLEB/Translate/i18n/core/ami.json8
-rw-r--r--MLEB/Translate/i18n/core/anp.json8
-rw-r--r--MLEB/Translate/i18n/core/atj.json16
-rw-r--r--MLEB/Translate/i18n/core/bqi.json8
-rw-r--r--MLEB/Translate/i18n/core/btm.json8
-rw-r--r--MLEB/Translate/i18n/core/cak.json8
-rw-r--r--MLEB/Translate/i18n/core/ceb.json8
-rw-r--r--MLEB/Translate/i18n/core/din.json10
-rw-r--r--MLEB/Translate/i18n/core/dty.json18
-rw-r--r--MLEB/Translate/i18n/core/gaa.json8
-rw-r--r--MLEB/Translate/i18n/core/gcr.json8
-rw-r--r--MLEB/Translate/i18n/core/gd.json8
-rw-r--r--MLEB/Translate/i18n/core/glk.json9
-rw-r--r--MLEB/Translate/i18n/core/gor.json8
-rw-r--r--MLEB/Translate/i18n/core/got.json8
-rw-r--r--MLEB/Translate/i18n/core/hif-latn.json8
-rw-r--r--MLEB/Translate/i18n/core/hyw.json8
-rw-r--r--MLEB/Translate/i18n/core/inh.json23
-rw-r--r--MLEB/Translate/i18n/core/jut.json10
-rw-r--r--MLEB/Translate/i18n/core/kbp.json8
-rw-r--r--MLEB/Translate/i18n/core/kjp.json23
-rw-r--r--MLEB/Translate/i18n/core/kum.json8
-rw-r--r--MLEB/Translate/i18n/core/kw.json9
-rw-r--r--MLEB/Translate/i18n/core/lag.json11
-rw-r--r--MLEB/Translate/i18n/core/lfn.json20
-rw-r--r--MLEB/Translate/i18n/core/lij.json9
-rw-r--r--MLEB/Translate/i18n/core/lld.json8
-rw-r--r--MLEB/Translate/i18n/core/luz.json8
-rw-r--r--MLEB/Translate/i18n/core/mni.json8
-rw-r--r--MLEB/Translate/i18n/core/mnw.json8
-rw-r--r--MLEB/Translate/i18n/core/mui.json8
-rw-r--r--MLEB/Translate/i18n/core/mwl.json20
-rw-r--r--MLEB/Translate/i18n/core/mzn.json8
-rw-r--r--MLEB/Translate/i18n/core/nan.json11
-rw-r--r--MLEB/Translate/i18n/core/nys.json8
-rw-r--r--MLEB/Translate/i18n/core/olo.json13
-rw-r--r--MLEB/Translate/i18n/core/sat.json10
-rw-r--r--MLEB/Translate/i18n/core/sd.json54
-rw-r--r--MLEB/Translate/i18n/core/sdh.json8
-rw-r--r--MLEB/Translate/i18n/core/sgs.json20
-rw-r--r--MLEB/Translate/i18n/core/shy-latn.json9
-rw-r--r--MLEB/Translate/i18n/core/skr-arab.json76
-rw-r--r--MLEB/Translate/i18n/core/sty.json8
-rw-r--r--MLEB/Translate/i18n/core/tay.json13
-rw-r--r--MLEB/Translate/i18n/core/tokipona.json10
-rw-r--r--MLEB/Translate/i18n/core/tt-latn.json11
-rw-r--r--MLEB/Translate/i18n/core/tyv.json9
-rw-r--r--MLEB/Translate/i18n/core/udm.json16
-rw-r--r--MLEB/Translate/i18n/core/vro.json8
-rw-r--r--MLEB/Translate/i18n/core/war.json8
-rw-r--r--MLEB/Translate/i18n/core/wo.json8
-rw-r--r--MLEB/Translate/i18n/core/wuu.json10
-rw-r--r--MLEB/Translate/i18n/core/yo.json8
-rw-r--r--MLEB/Translate/i18n/core/zgh.json31
-rw-r--r--MLEB/Translate/i18n/pagetranslation/abs.json8
-rw-r--r--MLEB/Translate/i18n/pagetranslation/ace.json8
-rw-r--r--MLEB/Translate/i18n/pagetranslation/ady-cyrl.json9
-rw-r--r--MLEB/Translate/i18n/pagetranslation/aeb-latn.json8
-rw-r--r--MLEB/Translate/i18n/pagetranslation/ais.json9
-rw-r--r--MLEB/Translate/i18n/pagetranslation/ami.json8
-rw-r--r--MLEB/Translate/i18n/pagetranslation/ang.json8
-rw-r--r--MLEB/Translate/i18n/pagetranslation/anp.json8
-rw-r--r--MLEB/Translate/i18n/pagetranslation/atj.json12
-rw-r--r--MLEB/Translate/i18n/pagetranslation/bho.json8
-rw-r--r--MLEB/Translate/i18n/pagetranslation/bqi.json8
-rw-r--r--MLEB/Translate/i18n/pagetranslation/btm.json8
-rw-r--r--MLEB/Translate/i18n/pagetranslation/cdo.json8
-rw-r--r--MLEB/Translate/i18n/pagetranslation/csb.json8
-rw-r--r--MLEB/Translate/i18n/pagetranslation/din.json8
-rw-r--r--MLEB/Translate/i18n/pagetranslation/dty.json8
-rw-r--r--MLEB/Translate/i18n/pagetranslation/gaa.json8
-rw-r--r--MLEB/Translate/i18n/pagetranslation/gcr.json8
-rw-r--r--MLEB/Translate/i18n/pagetranslation/gd.json8
-rw-r--r--MLEB/Translate/i18n/pagetranslation/glk.json8
-rw-r--r--MLEB/Translate/i18n/pagetranslation/gom-latn.json8
-rw-r--r--MLEB/Translate/i18n/pagetranslation/gor.json8
-rw-r--r--MLEB/Translate/i18n/pagetranslation/got.json8
-rw-r--r--MLEB/Translate/i18n/pagetranslation/grc.json8
-rw-r--r--MLEB/Translate/i18n/pagetranslation/hak.json8
-rw-r--r--MLEB/Translate/i18n/pagetranslation/hif-latn.json8
-rw-r--r--MLEB/Translate/i18n/pagetranslation/hy.json8
-rw-r--r--MLEB/Translate/i18n/pagetranslation/hyw.json8
-rw-r--r--MLEB/Translate/i18n/pagetranslation/ie.json8
-rw-r--r--MLEB/Translate/i18n/pagetranslation/ilo.json8
-rw-r--r--MLEB/Translate/i18n/pagetranslation/inh.json8
-rw-r--r--MLEB/Translate/i18n/pagetranslation/jbo.json15
-rw-r--r--MLEB/Translate/i18n/pagetranslation/jut.json8
-rw-r--r--MLEB/Translate/i18n/pagetranslation/kab.json64
-rw-r--r--MLEB/Translate/i18n/pagetranslation/kbp.json8
-rw-r--r--MLEB/Translate/i18n/pagetranslation/kjp.json8
-rw-r--r--MLEB/Translate/i18n/pagetranslation/krl.json10
-rw-r--r--MLEB/Translate/i18n/pagetranslation/kum.json8
-rw-r--r--MLEB/Translate/i18n/pagetranslation/kw.json8
-rw-r--r--MLEB/Translate/i18n/pagetranslation/lag.json10
-rw-r--r--MLEB/Translate/i18n/pagetranslation/lfn.json11
-rw-r--r--MLEB/Translate/i18n/pagetranslation/li.json8
-rw-r--r--MLEB/Translate/i18n/pagetranslation/lij.json8
-rw-r--r--MLEB/Translate/i18n/pagetranslation/lki.json66
-rw-r--r--MLEB/Translate/i18n/pagetranslation/lo.json10
-rw-r--r--MLEB/Translate/i18n/pagetranslation/luz.json8
-rw-r--r--MLEB/Translate/i18n/pagetranslation/mni.json8
-rw-r--r--MLEB/Translate/i18n/pagetranslation/mnw.json8
-rw-r--r--MLEB/Translate/i18n/pagetranslation/mo.json8
-rw-r--r--MLEB/Translate/i18n/pagetranslation/mwl.json10
-rw-r--r--MLEB/Translate/i18n/pagetranslation/my.json106
-rw-r--r--MLEB/Translate/i18n/pagetranslation/mzn.json8
-rw-r--r--MLEB/Translate/i18n/pagetranslation/nys.json8
-rw-r--r--MLEB/Translate/i18n/pagetranslation/olo.json8
-rw-r--r--MLEB/Translate/i18n/pagetranslation/pnb.json8
-rw-r--r--MLEB/Translate/i18n/pagetranslation/qu.json11
-rw-r--r--MLEB/Translate/i18n/pagetranslation/sat.json9
-rw-r--r--MLEB/Translate/i18n/pagetranslation/sd.json20
-rw-r--r--MLEB/Translate/i18n/pagetranslation/sgs.json8
-rw-r--r--MLEB/Translate/i18n/pagetranslation/sh.json13
-rw-r--r--MLEB/Translate/i18n/pagetranslation/shi.json8
-rw-r--r--MLEB/Translate/i18n/pagetranslation/skr-arab.json25
-rw-r--r--MLEB/Translate/i18n/pagetranslation/sq.json10
-rw-r--r--MLEB/Translate/i18n/pagetranslation/sty.json8
-rw-r--r--MLEB/Translate/i18n/pagetranslation/szl.json8
-rw-r--r--MLEB/Translate/i18n/pagetranslation/tay.json8
-rw-r--r--MLEB/Translate/i18n/pagetranslation/tg-cyrl.json8
-rw-r--r--MLEB/Translate/i18n/pagetranslation/udm.json8
-rw-r--r--MLEB/Translate/i18n/pagetranslation/vro.json8
-rw-r--r--MLEB/Translate/i18n/pagetranslation/war.json8
-rw-r--r--MLEB/Translate/i18n/pagetranslation/wo.json8
-rw-r--r--MLEB/Translate/i18n/pagetranslation/yo.json9
-rw-r--r--MLEB/Translate/i18n/pagetranslation/zgh.json8
-rw-r--r--MLEB/Translate/i18n/sandbox/bs.json52
-rw-r--r--MLEB/Translate/i18n/sandbox/da.json19
-rw-r--r--MLEB/Translate/i18n/sandbox/de-ch.json8
-rw-r--r--MLEB/Translate/i18n/sandbox/el.json18
-rw-r--r--MLEB/Translate/i18n/sandbox/en-gb.json8
-rw-r--r--MLEB/Translate/i18n/sandbox/fy.json9
-rw-r--r--MLEB/Translate/i18n/sandbox/hi.json17
-rw-r--r--MLEB/Translate/i18n/sandbox/hr.json14
-rw-r--r--MLEB/Translate/i18n/sandbox/hu.json45
-rw-r--r--MLEB/Translate/i18n/sandbox/is.json14
-rw-r--r--MLEB/Translate/i18n/sandbox/ka.json38
-rw-r--r--MLEB/Translate/i18n/sandbox/kab.json28
-rw-r--r--MLEB/Translate/i18n/sandbox/kjp.json8
-rw-r--r--MLEB/Translate/i18n/sandbox/kk-cyrl.json37
-rw-r--r--MLEB/Translate/i18n/sandbox/km.json9
-rw-r--r--MLEB/Translate/i18n/sandbox/ku-latn.json12
-rw-r--r--MLEB/Translate/i18n/sandbox/lag.json9
-rw-r--r--MLEB/Translate/i18n/sandbox/lki.json39
-rw-r--r--MLEB/Translate/i18n/sandbox/lt.json46
-rw-r--r--MLEB/Translate/i18n/sandbox/my.json46
-rw-r--r--MLEB/Translate/i18n/sandbox/nb.json53
-rw-r--r--MLEB/Translate/i18n/sandbox/qu.json8
-rw-r--r--MLEB/Translate/i18n/sandbox/sa.json13
-rw-r--r--MLEB/Translate/i18n/sandbox/sah.json8
-rw-r--r--MLEB/Translate/i18n/sandbox/scn.json8
-rw-r--r--MLEB/Translate/i18n/sandbox/sd.json35
-rw-r--r--MLEB/Translate/i18n/sandbox/sk.json12
-rw-r--r--MLEB/Translate/i18n/sandbox/skr-arab.json10
-rw-r--r--MLEB/Translate/i18n/sandbox/sq.json8
-rw-r--r--MLEB/Translate/i18n/sandbox/sr-el.json8
-rw-r--r--MLEB/Translate/i18n/sandbox/ta.json8
-rw-r--r--MLEB/Translate/i18n/sandbox/tcy.json17
-rw-r--r--MLEB/Translate/i18n/sandbox/tg-cyrl.json8
-rw-r--r--MLEB/Translate/i18n/sandbox/tt-cyrl.json9
-rw-r--r--MLEB/Translate/i18n/sandbox/wa.json8
-rw-r--r--MLEB/Translate/i18n/search/af.json8
-rw-r--r--MLEB/Translate/i18n/search/ais.json11
-rw-r--r--MLEB/Translate/i18n/search/as.json8
-rw-r--r--MLEB/Translate/i18n/search/atj.json8
-rw-r--r--MLEB/Translate/i18n/search/be.json8
-rw-r--r--MLEB/Translate/i18n/search/bs.json31
-rw-r--r--MLEB/Translate/i18n/search/ckb.json12
-rw-r--r--MLEB/Translate/i18n/search/eo.json9
-rw-r--r--MLEB/Translate/i18n/search/gom-deva.json8
-rw-r--r--MLEB/Translate/i18n/search/gom-latn.json8
-rw-r--r--MLEB/Translate/i18n/search/hi.json17
-rw-r--r--MLEB/Translate/i18n/search/hr.json30
-rw-r--r--MLEB/Translate/i18n/search/hu.json30
-rw-r--r--MLEB/Translate/i18n/search/ig.json8
-rw-r--r--MLEB/Translate/i18n/search/inh.json11
-rw-r--r--MLEB/Translate/i18n/search/is.json30
-rw-r--r--MLEB/Translate/i18n/search/kab.json29
-rw-r--r--MLEB/Translate/i18n/search/kiu.json8
-rw-r--r--MLEB/Translate/i18n/search/kjp.json19
-rw-r--r--MLEB/Translate/i18n/search/kk-cyrl.json30
-rw-r--r--MLEB/Translate/i18n/search/km.json8
-rw-r--r--MLEB/Translate/i18n/search/krl.json10
-rw-r--r--MLEB/Translate/i18n/search/lag.json10
-rw-r--r--MLEB/Translate/i18n/search/lfn.json8
-rw-r--r--MLEB/Translate/i18n/search/lki.json24
-rw-r--r--MLEB/Translate/i18n/search/lkt.json8
-rw-r--r--MLEB/Translate/i18n/search/lt.json28
-rw-r--r--MLEB/Translate/i18n/search/mwl.json9
-rw-r--r--MLEB/Translate/i18n/search/my.json30
-rw-r--r--MLEB/Translate/i18n/search/ne.json12
-rw-r--r--MLEB/Translate/i18n/search/olo.json10
-rw-r--r--MLEB/Translate/i18n/search/sa.json11
-rw-r--r--MLEB/Translate/i18n/search/scn.json8
-rw-r--r--MLEB/Translate/i18n/search/sd.json10
-rw-r--r--MLEB/Translate/i18n/search/shn.json8
-rw-r--r--MLEB/Translate/i18n/search/shy-latn.json8
-rw-r--r--MLEB/Translate/i18n/search/sl.json30
-rw-r--r--MLEB/Translate/i18n/search/sq.json8
-rw-r--r--MLEB/Translate/i18n/search/sr-el.json9
-rw-r--r--MLEB/Translate/i18n/search/tay.json8
-rw-r--r--MLEB/Translate/i18n/search/tcy.json8
-rw-r--r--MLEB/Translate/i18n/search/te.json12
-rw-r--r--MLEB/Translate/i18n/search/tg-cyrl.json9
-rw-r--r--MLEB/Translate/i18n/search/th.json8
-rw-r--r--MLEB/Translate/i18n/search/tt-cyrl.json9
-rw-r--r--MLEB/Translate/i18n/search/udm.json8
-rw-r--r--MLEB/Translate/i18n/search/wa.json8
-rw-r--r--MLEB/Translate/insertables/CombinedInsertablesSuggester.php26
-rw-r--r--MLEB/Translate/insertables/NumericalParameterInsertablesSuggester.php29
-rw-r--r--MLEB/Translate/package.json16
-rw-r--r--MLEB/Translate/resources/css/ext.translate.groupselector.less142
-rw-r--r--MLEB/Translate/resources/css/ext.translate.legacy.css65
-rw-r--r--MLEB/Translate/resources/css/ext.translate.messagetable.less283
-rw-r--r--MLEB/Translate/resources/css/ext.translate.special.pagepreparation.css11
-rw-r--r--MLEB/Translate/resources/css/ext.translate.tag.languages.css73
-rw-r--r--MLEB/Translate/resources/js/ext.translate.recentgroups.js31
-rw-r--r--MLEB/Translate/resources/js/ext.translate.special.operatorsuggest.js39
-rw-r--r--MLEB/Translate/scripts/TranslateCliLogger.php20
-rw-r--r--MLEB/Translate/scripts/expand-groupspec.php58
-rw-r--r--MLEB/Translate/scripts/test-mt.php92
-rw-r--r--MLEB/Translate/specials/SpecialExportTranslations.php263
-rw-r--r--MLEB/Translate/sql/translate_reviews-patch-01-primary-key.sql3
-rw-r--r--MLEB/Translate/tag/SpecialPageMigration.php80
-rw-r--r--MLEB/Translate/tag/SpecialPagePreparation.php71
-rw-r--r--MLEB/Translate/tag/TranslatablePageMoveJob.php170
-rw-r--r--MLEB/Translate/tag/TranslationsUpdateJob.php127
-rw-r--r--MLEB/Translate/tests/phpunit/TPSectionTest.php123
-rw-r--r--MLEB/Translate/tests/phpunit/TTMServerMessageUpdateJobTest.php441
-rw-r--r--MLEB/Translate/tests/phpunit/TranslatablePageTest.php112
-rw-r--r--MLEB/Translate/tests/phpunit/TranslateYamlTest.php72
-rw-r--r--MLEB/Translate/tests/phpunit/ffs/MediaWikiExtensionsTest.php45
-rw-r--r--MLEB/Translate/tests/phpunit/insertables/CombinedInsertablesSuggesterTest.php90
-rw-r--r--MLEB/Translate/tests/phpunit/insertables/NumericalParameterInsertablesSuggesterTest.php39
-rw-r--r--MLEB/Translate/tests/phpunit/pagetranslation/Inline.ptsource1
-rw-r--r--MLEB/Translate/tests/phpunit/pagetranslation/Whitespace.ptsource19
-rw-r--r--MLEB/Translate/tests/phpunit/tag/PageTranslationHooksTest.php97
-rw-r--r--MLEB/Translate/tests/phpunit/utils/ArrayFlattenerTest.php249
-rw-r--r--MLEB/Translate/tests/phpunit/utils/MessageGroupStatsTest.php36
-rw-r--r--MLEB/Translate/translationaids/QueryAggregatorAwareTranslationAid.php83
-rw-r--r--MLEB/Translate/translationaids/TranslationAidDataProvider.php135
-rw-r--r--MLEB/Translate/ttmserver/CrossLanguageTranslationSearchQuery.php152
-rw-r--r--MLEB/Translate/ttmserver/FuzzyLikeThis.php222
-rw-r--r--MLEB/Translate/utils/ArrayFlattener.php297
-rw-r--r--MLEB/Translate/utils/ExternalMessageSourceStateImporter.php84
-rw-r--r--MLEB/Translate/utils/MessageChangeStorage.php52
-rw-r--r--MLEB/Translate/webservices/CaighdeanWebService.php93
-rw-r--r--MLEB/Translate/webservices/QueryAggregator.php89
-rw-r--r--MLEB/Translate/webservices/QueryAggregatorAware.php17
-rw-r--r--MLEB/Translate/webservices/RESTBaseWebService.php80
-rw-r--r--MLEB/Translate/webservices/TranslationQuery.php105
-rw-r--r--MLEB/Translate/webservices/TranslationQueryResponse.php65
-rw-r--r--MLEB/Translate/webservices/TranslationWebServiceConfigurationException.php18
-rw-r--r--MLEB/Translate/webservices/TranslationWebServiceInvalidInputException.php20
312 files changed, 9810 insertions, 0 deletions
diff --git a/MLEB/Translate/.eslintrc.json b/MLEB/Translate/.eslintrc.json
new file mode 100644
index 00000000..a449bc1a
--- /dev/null
+++ b/MLEB/Translate/.eslintrc.json
@@ -0,0 +1,15 @@
+{
+ "extends": "wikimedia",
+ "env": {
+ "browser": true,
+ "jquery": true,
+ "qunit": true
+ },
+ "globals": {
+ "mediaWiki": false,
+ "OO": false
+ },
+ "rules": {
+ "no-use-before-define": 0
+ }
+}
diff --git a/MLEB/Translate/.phpcs.xml b/MLEB/Translate/.phpcs.xml
new file mode 100644
index 00000000..ad07e67b
--- /dev/null
+++ b/MLEB/Translate/.phpcs.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0"?>
+<ruleset>
+ <rule ref="./vendor/mediawiki/mediawiki-codesniffer/MediaWiki">
+ <exclude name="Generic.Files.OneObjectStructurePerFile.MultipleFound" />
+ <exclude name="MediaWiki.Commenting.FunctionComment.MissingDocumentationProtected"/>
+ <exclude name="MediaWiki.Commenting.FunctionComment.MissingDocumentationPublic"/>
+ <exclude name="MediaWiki.Commenting.FunctionComment.WrongStyle" />
+ <exclude name="MediaWiki.Commenting.MissingCovers.MissingCovers" />
+ <exclude name="MediaWiki.Files.ClassMatchesFilename.NotMatch" />
+ <exclude name="MediaWiki.NamingConventions.LowerCamelFunctionsName.FunctionName" />
+ <exclude name="MediaWiki.Usage.ForbiddenFunctions.escapeshellarg" />
+ <exclude name="MediaWiki.WhiteSpace.SpaceBeforeSingleLineComment.NewLineComment" />
+ <exclude name="Squiz.Scope.MethodScope.Missing" />
+ </rule>
+ <rule ref="Generic.Files.LineLength">
+ <exclude-pattern>Translate\.alias\.php</exclude-pattern>
+ </rule>
+ <rule ref="MediaWiki.NamingConventions.ValidGlobalName">
+ <properties>
+ <property name="ignoreList" type="array" value="$IP" />
+ </properties>
+ </rule>
+ <file>.</file>
+ <arg name="extensions" value="php,php5,inc"/>
+ <arg name="encoding" value="UTF-8"/>
+ <exclude-pattern type="relative">^extensions</exclude-pattern>
+</ruleset>
diff --git a/MLEB/Translate/.stylelintrc.json b/MLEB/Translate/.stylelintrc.json
new file mode 100644
index 00000000..e15d057b
--- /dev/null
+++ b/MLEB/Translate/.stylelintrc.json
@@ -0,0 +1,9 @@
+{
+ "extends": "stylelint-config-wikimedia",
+ "rules": {
+ "selector-max-id": null,
+ "declaration-no-important": null,
+ "no-duplicate-selectors": null,
+ "no-descending-specificity": null
+ }
+}
diff --git a/MLEB/Translate/CODE_OF_CONDUCT.md b/MLEB/Translate/CODE_OF_CONDUCT.md
new file mode 100644
index 00000000..498acf76
--- /dev/null
+++ b/MLEB/Translate/CODE_OF_CONDUCT.md
@@ -0,0 +1 @@
+The development of this software is covered by a [Code of Conduct](https://www.mediawiki.org/wiki/Special:MyLanguage/Code_of_Conduct).
diff --git a/MLEB/Translate/Gruntfile.js b/MLEB/Translate/Gruntfile.js
new file mode 100644
index 00000000..8e8246aa
--- /dev/null
+++ b/MLEB/Translate/Gruntfile.js
@@ -0,0 +1,50 @@
+/* eslint-env node */
+module.exports = function ( grunt ) {
+ 'use strict';
+
+ grunt.loadNpmTasks( 'grunt-eslint' );
+ grunt.loadNpmTasks( 'grunt-jsonlint' );
+ grunt.loadNpmTasks( 'grunt-banana-checker' );
+ grunt.loadNpmTasks( 'grunt-stylelint' );
+
+ grunt.initConfig( {
+ eslint: {
+ all: [
+ '**/*.js',
+ '!node_modules/**',
+ '!extensions/**',
+ '!resources/js/jquery.autosize.js',
+ '!vendor/**'
+ ]
+ },
+ jsonlint: {
+ all: [
+ '**/*.json',
+ '!node_modules/**',
+ '!extensions/**',
+ '!vendor/**'
+ ]
+ },
+ stylelint: {
+ all: [
+ '**/*.css',
+ '**/*.less',
+ '!node_modules/**',
+ '!extensions/**',
+ '!vendor/**'
+ ]
+ },
+ banana: {
+ all: [
+ 'i18n/api',
+ 'i18n/core',
+ 'i18n/pagetranslation',
+ 'i18n/sandbox',
+ 'i18n/search'
+ ]
+ }
+ } );
+
+ grunt.registerTask( 'test', [ 'eslint', 'jsonlint', 'banana', 'stylelint' ] );
+ grunt.registerTask( 'default', 'test' );
+};
diff --git a/MLEB/Translate/HISTORY b/MLEB/Translate/HISTORY
new file mode 100644
index 00000000..8b6916f7
--- /dev/null
+++ b/MLEB/Translate/HISTORY
@@ -0,0 +1,793 @@
+For newer recent changes, please refer the MediaWiki Language Extension Bundle
+release announcements or the git log.
+ https://www.mediawiki.org/wiki/MediaWiki_Language_Extension_Bundle
+
+== Change log ==
+* 2012-11-11
+- Dynamic message groups are now shown in list=messagecollection WebAPI.
+* 2012-11-10
+- Add RecentAdditionsMessageGroup:
+ Like RecentMessageGroup, but instead of proofreading
+ you can translate new message as they come in. Make this
+ view the home page of your browser, for example.
+* 2012-11-09
+- Unbalanced translate tags on translatable pages now fail gracefully again.
+* 2012-11-08
+- MediaWiki 1.19 or later is now required: support for 1.18 was dropped.
+* 2012-11-06
+- Add prop=revision to list=messagecollection WebAPI.
+* 2012-11-05
+- Avoid fatal errors with empty aggregate groups
+* 2012-10-29
+- Add support for Yandex machine translation service.
+* 2012-10-29
+- Sort order of the statistics tables is now included in the URL to make it linkable.
+ Enhances both LanguageStats and MessageGroupStats.
+* 2012-10-24
+- The lists of messages are now rendered faster and with less memory.
+ For a table of 5000 messages in Special:Translate, from 175M to 12M.
+- Better performance also for proofreading and page moves.
+* 2012-10-22
+- Ignored messages are no longer exported.
+ Except for source language or message documentation
+* 2012-10-21
+- Dropdown items on Special:AggregateGroups are now sorted alphabetically.
+* 2012-10-21
+- MediaWiki i18n now with modern support format: MediaWikiExtensionFFS.
+- You should check your config so that the second parameter
+ to the PremadeMediawikiExtensionsGroups constructor is correct.
+- Label and desc of message groups now have $context parameter.
+* 2012-10-18
+- Fixed infinite loop in MediaWikiMessageChecker with unbalanced
+ {{PLURAL}} in translations which gave fatal error on editing.
+* 2012-10-18
+- The page translation languages lists no longer overlaps other elements.
+* 2012-10-18
+- Added framework for key generation algorithms in GettextFFS.
+ Can be invoked in YAML config; backwards compatibility preserved.
+* 2012-10-16
+- Narayam now works in translation editors.
+* 2012-10-10
+- Inline translation editor is now the default.
+* 2012-10-08
+- New JsonFFS and related changes. Useful for jquery.i18n.
+* 2012-10-04
+- Added the concept of events in several places.
+ Useful for future CentralNotice integration.
+* 2012-10-02
+- Page translation is now enabled by default.
+ Still restricted by user group rights, which are not set by default.
+* 2012-09-22
+- Framework for automatic group workflow state changes.
+* 2012-09-21
+- Added collection of stats of proofread messages.
+* 2012-09-20
+- Added checker for too many or redundant plural forms in MediaWiki translations.
+* 2012-09-17
+- New script to create pretty graphs of MediaWiki language fallbacks.
+- Also rewritten plural-comparison.php.
+- Performance fixes to reduce timeouts when translating.
+* 2012-09-07
+- Special:ManageMessageGroups now shows new messages
+ conflicting with existing pages in the wiki as changes.
+* 2012-09-05
+- Completed integration with the new TranslateSvg extension.
+* 2012-08-31
+- Interface usability: for page translation, export tab now
+ shows the wikitext source of the translatable page.
+* 2012-08-19
+- Added support for AndroidXml (Android apps' string.xml).
+* 2012-08-19
+- $wgTranslateRcFilterDefault can be used to show
+ translation changes in the RecentChanges by default.
+* 2012-08-17
+- Fixes to avoid double counting of messages in statistics.
+* 2012-08-07
+- Deleting a translatable page no longer deletes
+ translatable pages which are subpages of it.
+* 2012-08-02
+- Page translation no longer reuses deleted translation unit identifiers.
+* 2012-08-02
+- Special:ManageMessageGroups is now more efficient,
+ able to manage groups with more than 10 000 messages.
+* 2012-08-02
+- GettextFFS now handles messages ending in \ better
+* 2012-07-30
+- Restored compatibility with MediaWiki 1.18.
+* 2012-07-29
+- Dollar signs ($) can now be used in translatable pages without problems.
+* 2012-07-26
+- Introduction of and many improvements to
+ Solr TTMServer (translation memory).
+* 2012-07-09
+- Special:TranslationStats now has a date picker to easily fill
+ starting date field; it replaces the manual input of all the zeros.
+* 2012-07-02
+- Special:MessageGroupStats now doesn't show
+ languages under 2 % of translation by default.
+* 2012-06-27
+- Fixed fatal errors in Special:MessageGroupStats
+* 2012-06-21
+- Fixed recent incompatibility with MW 1.19.
+- Improved hiding of blacklisted groups on Special:LanguageStats.
+* 2012-06-11
+- Allow setting message group state for a group only for specific user right.
+ This redefines $wgTranslateWorkflowStates to hold the rights required for
+ a state transition.
+- Allow black listing and white listing of languages in YAML configuration. YAML
+ files will have new top level section called LANGUAGES. LANGUAGES can have
+ optional subsections whitelist and blacklist, which take an array of language
+ codes. If a subsection is not specified, the white/blacklist value will
+ default to *, meaning all known languages. Whitelist overrides blacklist.
+* 2012-06-04
+- Show the priority languages in Special:Translate.
+* 2012-06-03
+- Option skipgroup added to export.php to filter wildcard groups.
+* 2012-06-02
+- $wgTranslateDisablePreSaveTransform added to make the disabling of PST for
+ message pages configurable, so that mediawiki.org etc. isn't broken.
+* 2012-05-29
+- Implemented and used convertWhiteSpaceToHTML in JavaScript.
+- Dynamic groups are ignored in Special:MessageGroupStats.
+* 2012-05-18
+- Fatal error on Special:AggregateGroups fixed.
+- Sort aggregates and their subgroups on Special:AggregateGroups.
+- Outdated caches were not recreated when file and wiki state matched.
+- Page translation icons are now 264dpi.
+- Unnecessary token check removed from Special:AggregateGroups.
+* 2012-05-14
+- Made link appearance in <languages/> bar saner for edge cases. Links to
+ existing, but untranslated pages are not shown as red anymore.
+- Fixed bug where fields for new aggregate groups on Special:AggregateGroups
+ were sometimes pre-filled.
+- When translation editor is open, a warning is displayed when trying to leave
+ the page.
+- OpenLayersFFS was removed.
+- Administrative pages are now links in tabs.
+* 2012-05-10
+- Fix to prevent page protection from confusing page translation feature.
+* 2012-05-09
+- Decimals on Special:LanguageStats and Special:MessageGroupStats were removed
+ for improved readability.
+* 2012-05-08
+- processMessageChanges was updated to reduce the number of false positives of
+ changed or deleted messages.
+- Deprecated languages are no longer displayed in the language selector on
+ Special:Translate.
+* 2012-05-07
+- FFS.php was split into several files.
+* 2012-05-03
+- $wgTranslateDelayedMessageIndexRebuild was added. If you have lots of message
+ groups, especially file based ones, and the message index rebuilding gets
+ slow, set this to true to delay the rebuilding via JobQueue. This only makes
+ sense if you have configured jobs to be processed outside of requests via cron
+ or similar.
+- API tokens are provided in a saner way now.
+- "ignore" option was added to Special:ManageMessageGroups (regression fixed).
+* 2012-04-30
+- Comparison of fuzzied translations was improved.
+* 2012-04-25
+- Special:ManageMessageGroups was improved so that the update process takes less
+ time. scripts/processMessageChanges.php should be run from the command line.
+ This stages all required changes. After that, the stated changes can be
+ assessed on Special:ManageMessageGroups. Processing takes place using the
+ job queue.
+* 2012-04-16
+- TMessage::setTag renamed to TMessage::addTag.
+* 2012-04-11
+- Option codemaponly added to export.php to so that only code mapped languages
+ can be exported.
+- Script autoexport.php was removed. Use export.php.
+* 2012-04-10
+- New MessageIndex backends: database and cdb.
+- Fixed an out of memory issue with page translation feature on MW 1.18 and
+ older.
+* 2012-04-05
+- JavaFFS was made more robust.
+* 2012-04-03
+- Profiling updates for message index related function calls.
+- Exception on diff pages fixed.
+* 2012-04-02
+- Improved metadata handling when renaming and deleting translatable pages.
+* 2012-03-27
+- Switch "hours" added to export.php.
+- Bug fix for inline editor to remove the "untranslated" class.
+* 2012-03-26
+- Deleting an aggregate group must be confirmed.
+* 2012-03-24
+- list-mwext-i18n-files.php was simplified.
+* 2012-03-22
+- $wgTranslateAddMWExtensionGroups was removed.
+- Messages are loaded using parent::getDefinitions() to allow decoupling
+ MediaWiki message groups from live wiki code.
+* 2012-03-21
+- ext.translate.special.languagestats.js no longer uses onclick.
+- Some issues with overcounting because of aggregate groups were resolved.
+* 2012-03-20
+- ext.translate.special.pagetranslation.js made more reusable: The generic
+ autocompletion funcionality is in ext.translate.multiselectautocomplete.js and
+ ext.translate.special.pagetranslation.js only apply it to
+ Special:PageTranslation.
+* 2012-03-19
+- Group ID prefix for aggregate groups through Special:AggregateGroup was
+ updated from "ag-" to "agg-".
+* 2012-03-16
+- A bug was fixed when renaming a translatable page with priority languages.
+- Plural rules were updates to CLDR 21.0.
+- More validations added to Special:AggregateGroup input.
+* 2012-03-14
+- Adding and removing a group from an aggregate group is now logged.
+* 2012-03-13
+- Removed groupprefix option from export.php and sync-group.php. Use * wildcard
+ with group option.
+- Page translation widget was made more easily reusable.
+- sync-group.php can now find the timestamp for git checkouts.
+- export.php no longer requires definitionFile when using gettext post processing
+ with ppgettext.
+* 2012-03-12
+- Special:LanguageStats group collapsing now supports nested subgroups.
+* 2012-03-11
+- Support for shared TTMServer databases was added.
+- Suggestions from different TTMServers are now grouped.
+- Output of TTMServer api module has changed.
+- TTMServer configs can override the link symbol by adding value for symbol in
+ the config.
+* 2012-03-07
+- $wgTranslateGroupStructure is no longer used. If you are using aggregate
+ message groups, you can remove the old settings and everything still works as
+ expected. If you are not yet using aggregate message groups, you should do
+ that to not confuse users.
+- Gettext file were not shown in translation editor when using recent
+ translations task.
+- New Special page AggregateGroups where translation administrator can group
+ pages into subgroups.
+* 2012-03-06
+- Translation administrator can now suggest languages translatable page should
+ be primarily translated into with a note or preventing other language
+ translations.
+* 2012-03-05
+- Support for using remote TTMServers via API interface added.
+- Support for tmserver was removed. Translate comes with TTMServer enabled by
+ default. To bootstrap it with current translations, run php
+ scripts/ttmserver-export.php.
+- Message documentation was not shown when translating for subgroups nested more
+ than one level deep.
+- Special:TranslationStats can now graph review and reviewer activity.
+* 2012-03-02
+- Encourage/discourage changes of translatable pages are now logged.
+* 2012-03-01
+- Added upper length for translation memory suggestions because of performance
+ concerns.
+* 2012-02-29
+- Configuring wgMainCache is no longer necessary. Translate extension will use
+ any available caching mechanism automatically when needed.
+- Support for Google Translate was removed, as the free service is no more.
+- Special:LanguageStats' default value derived from current interface language
+ now works also for anonymous users (only relevant when using
+ LanguageSelector).
+* 2012-02-24
+- Help link is no longer added twice in Special:SupportedLanguages.
+* 2012-02-21
+- Gettext headers can now be customised with Translate:GettextFFS:headerFields
+ hook. Less headers are modified by default.
+- Message index no longer goes into recursive loop on some cases when creating
+ the index the first time.
+- Improved the position of help links in Monobook skin.
+* 2012-02-19
+- MediaWiki 1.18 or later is now required.
+- Group description of translatable pages can be extended by adding content to
+ [[MediaWiki:Tp-custom-<group id>]].
+* 2012-02-13
+- Updated some deprecated function calls
+- New translation memory called TTMServer comes with the extension and is
+ enabled by default.
+* 2012-02-11
+- Some missing action- messages added.
+* 2012-02-10
+- New tabbed task-based UI on Special:Translate.
+* 2012-02-06
+- API module for message group stats written by Tim Gerundt.
+* 2012-02-01
+- Missing JavaScript dependency added to messagetable.
+- No more logging state changes where the state didn't actually change.
+- Allow syntax like
+ {{Special:MessageGroupStats|group=page-Main/sub|suppresscomplete=1}}.
+* 2012-01-31
+- export.php: switch "grouptrail" renamed to "groupprefix" and switch "groups".
+ was dropped. "group" now takes a comma separated list of groups IDs, too.
+- sync-group.php: switch "groupprefix" added.
+- Double counting of messages when adding message groups and aggregate groups to
+ another aggregate group was partially resolved.
+- Messages for translatable pages for which translation is discouraged, are no
+ longer added to aggregate message groups to avoid mismatches in statistics.
+- Special:MessageGroupStats now works for group IDs with spaces.
+- Updates were made for the translation memory service, to avoid serving
+ incorrect or outdated suggestions.
+* 2012-01-30
+- Statictics issues introduced recently were resolved.
+- "lang" attributes were added to Special:SupportedLanguages.
+- [[Special:Translate/groupname]] links for translatable pages with spaces or
+ colons in them are now possible. For page "Some page" it is:
+ [[Special:Translate/page-Some_page]].
+- The JavaScript translation editor now shows the code browser and gettext
+ comments when translating aggregate message groups.
+- Logging was added group statistics caching to determine if cache purges happen
+ too often.
+* 2012-01-29
+- Update functionality for the translation memory was added.
+* 2012-01-28
+- Code readability improvements were made.
+- A translation memory service was added, including a bootstrap script.
+* 2012-01-22
+- Message groups are no longer forced to implement getBools. getTags is the
+ official way to do this.
+- getDefinitions was added to the MessageGroupInterface.
+- The public $namespaces variable in MessageGroup was removed.
+* 2012-01-20
+- Browser compatibility updates were made in CSS.
+* 2012-01-19
+- Deprecated hook LanguageGetMagic was removed.
+* 2012-01-16
+- Help icons linking to documentation at
+ https://www.mediawiki.org/Help:Extension:Translate or sub pages were added to
+ extension functionality.
+- Backward compatibility with MediaWiki 1.18 was restored.
+* 2012-01-13
+- export.php has a switch "no-fuzzy" that will filter out fuzzy messages for
+ file based message groups.
+- Special:PageTranslation has now better error checking for invalid and
+ duplicate translation unit names.
+- Special pages have now help links.
+- Images were moved to a resources sub folder.
+* 2012-01-11
+- Translate page group id prefix was changed from page| to page-. Some old
+ links need to be updated, some still work.
+* 2012-01-10
+- Translate extension no longer unconditionally suppresses edit intros
+- Fixed compatibility issue with logs on < MW 1.18
+* 2012-01-06
+- Fixes to the sizing of the translation editor dialog
+* 2012-01-05
+- Fixed a fatal error that sometimes occurred when translation page title used
+ GRAMMAR and the page was viewed with English UI.
+* 2012-01-04
+- The summary row in Special:LanguageStats and Special:MessageGroupStats is no
+ longer sorted with rest of the rows.
+- There is now new message group for recent translations intented for reviewing
+ new translations
+- Fixed a bug that prevented changing workflow state of page translation
+ message group
+* 2012-01-02
+- Special:MyLanguage can now be used with language subpage to use that as the
+ default fallback instead of untranslated version
+- The flash of unstylized content effect is reduced
+- FuzzyBot user was not always created, leading to missing log entries
+ for example
+- export.php fixed after it was broken due to recent refactorings
+* 2011-12-26
+- Added {{#translationdialog:title}} for creating a link to the translation
+ dialog
+* 2011-12-25
+- Made the extension work without legacy JavaScript globals
+- PythonSingleFFS now respects codemap
+* 2011-12-23
+- Translatable pages can now be discouraged, meaning that they won't show up in
+ the usually places so that translators don't translate them needlessy.
+* 2011-11-06
+- Rewrote Special:ImportTranslations JavaScript so that it actually works
+- Fixed compatibility with MW 1.17 in JavaScript
+* 2011-11-04
+- The 'no translations' RC filter options was lost when navigating
+- Made MessageCache more robust
+- fuzzy.php can now take optional namespace prefix for each message
+* 2011-10-30
+- The script referenced at 2011-08-26 is now included in the source
+- Fixed compatibility with MW 1.17
+* 2011-10-28
+- New configuration variable $wgTranslatePermissionUrl
+- Message review feature, available to users in translate-proofr group
+- Message collections can now have properties and allow filtering on them
+ This is still work in progress and is likely to get improvements over time
+* 2011-10-14
+- New API module: messagetranslations
+* 2011-10-12
+- Multiple bug fixes and improvements to translatable page moving feature
+* 2011-10-07
+- $wgTranslateNewsletterPreference was introduced (default: false). Setting this
+ to true, will once again add the "Do not send me e-mail newsletters"
+ preference.
+* 2011-10-03
+- MessageIndex can now be stored in object cache (default) or in a file.
+- Fixed an error that sometimes prevented translating messages that had
+ been renamed
+* 2011-10-01
+- Changed index on translate_sections database table
+- Fixed escaping in PythonSingleFFS parser
+- Fixed a bug in getSourceLanguage for certain message group classes
+* 2011-09-29
+- Groups folder was cleaned. See groups/README for more information.
+- Fixed PHP notice when marking page for Translation with MW 1.19
+- Fixed a bug in RubyYamlFFS::unflattenPlural, added unit tests
+* 2011-09-28
+- Special:AdvancedTranslate is not enabled by default anymore
+- Special:TranslationChanges was removed
+- Started taking PHP code out of the groups/ folder anticipating Wikimedia
+ review. Later we will move all content of the groups folder elsewhere to
+ reduce the number of changes of changes to the Translate extension
+ considerably.
+* 2011-09-26
+- Special:SupportedLanguages can now show site specific messages in
+ supportedlanguages-localsummary message
+* 2011-09-23
+- Change log up to date
+* 2011-09-21
+- It is now possible to nest AggregateMessageGroups
+* 2011-09-19
+- New statistics backend considered stable
+- Special:MessageGroupStatistics
+- Translations that were recognized as redirect by MediaWiki no longer cause PHP notices
+- Made groupStatistics.php script more efficient
+* 2011-09-16
+- Languages are now sorted correctly on Special:LanguageStats for groups > 10k messages
+- It is now possible to specify starting point for graphs in Special:TranslationStats
+* 2011-09-15
+- All code is now using the MessageHandle class
+- ArrayMemoryCache removed due to new Statistics backend
+- New database table translate_groupstats
+* 2011-09-14
+- Sql files moved to a subfolder
+* 2011-09-13
+- Magic words exporter is no longer compatible with MediaWiki <=1.15
+* 2011-09-11
+- Log message for translatable page deletion was incorrect
+- Made page translation log compatible with the new logging system
+* 2011-09-06
+- If magic-export.php does not find a header, output a basic header rather than exitting
+* 2011-09-02
+- Fixed table border display issue on chrome for rtl messages
+- Everything converted to use ResoureLoader framework
+- js directory renamed to resources
+- Officially dropped support for MediaWiki 1.16
+* 2011-09-01
+- Message definition changes were not always displayed for fuzzy messages
+- Dropped embedded jQuery ui
+* 2011-08-31
+- Fixed issues with protocol relative urls
+* 2011-08-30
+- Special page TranslationChanges disabled, pending for removal
+- Got rid of MSG constants
+- Introduces TranslateHooks class
+- Removed efTranslateCheckPT() - please make sure you run update.php when needed
+* 2011-08-29
+- Message and rtl related fixes
+* 2011-08-26
+- revtag_type table is now obsolete - run scripts/migrate-schema2.php to update schema
+ the wiki will continue working even if this script is not run immediately
+* 2011-08-22
+- Partial update to CLDR and Gettext plural forms
+* 2011-08-18
+- Made compatible with new fallback method in MW
+* 2011-08-05
+- Translate will use MW page content language feature is available
+* 2011-08-02
+- Do not export TRANSLATE_FUZZY in PythonSingleFFS class
+* 2011-07-23
+- Many rtl related fixes, some only used when MW supports them
+* 2011-07-15
+- Collapsible javascript caused errors with older MWs
+* 2011-07-11
+- Rtl and font related fixes
+- Special:SupportLanguages is now listed on Special:SpecialPages
+- Google suggestions should obey the group source language
+- Update limit selector values. Either people want to show a little by default, or they want to see all of them.
+* 2011-07-10
+- Added some phpunit tests
+* 2011-07-09
+- Rtl fixes
+- Groups can now have source language different from wiki content language
+- action=purge on Special:LanguageStats clears cached stats
+* 2011-07-01
+- Fixed ResourceLoader issue with certain MW versions
+* 2011-06-30
+- Rtl and language tagging fixes
+* 2011-06-26
+- Marking pages for translation the first time works again
+* 2011-06-24
+- Updated bundled spyc library to the latest version
+* 2011-06-22
+- Fixed page translation marking related bug
+* 2011-06-20
+- Improved the UI of Special:ManageMessageGroups somewhat
+* 2011-06-17
+- Page translation now generates hidden markup-less source page versions
+* 2011-06-09
+- Added language cloud to Special:SupportedLanguages
+* 2011-06-08
+- Mark this page for translation link was not always shown
+* 2011-06-07
+- Restored compatibility with MW 1.16
+* 2011-06-05
+- Varios fixes to translatable/translation page deletion
+- Gettext documentation counts against translated messages for message documentation language
+* 2011-06-04
+- Added a special page that allows deleting translatable pages or parts of them.
+- Hack for making Special:MyLanguage links red if target doesn't exists
+* 2011-06-03
+- Handle Chihuahua skin
+* 2011-06-02
+- New hook which tries to do the right thing for subpagelist when viewing translatable pages
+* 2011-04-25
+- Javascript and rtl fixes
+* 2011-04-22
+- With recent MW, the extension now provides form for searching in translations only
+ Can also limit to certain language only
+* 2011-04-18
+- Show the ajax editor also for users without rights, as it contains useful information
+- Fixed compatibility issue with CologneBlue skin
+* 2011-04-16
+- Improve style loading to avoid page flash
+* 2011-04-13
+- Splitted Translate.css into smaller files
+* 2011-04-04
+- Updated gettext plural rule for cy
+- Fixed usability issue with summary field
+- Updated spyc from upstream repo
+- Armenian (hy) needs plural in some cases. MediaWiki already has it, also add to gettext
+- Don't use syck-pecl for dumping, the output is horrible
+- In translation dialog, made input elements grow automatically.
+* 2011-04-03
+- Updated plural data for new CLDR
+* 2011-03-16
+- Added ask question button also in the basic editor (still needs javascript to work :(
+* 2011-03-14
+- Fixed message group caching for single file based message groups.
+- Hooked diff pages to provide more information about the message.
+ Useful for those who follow recent changes feeds in review purposes
+- New class MessageHandle
+* 2011-03-11
+- In Special:ManageMessageGroups - don't expose actions to users who are not allowed to do them
+- Encourage translators to ask for help with a button
+* 2011-03-08
+- Added support for pecl syck, which is magnitudes faster and uses less memory.
+ Like all the other supported yaml implementations, this one has its own bugs that need to be worked around.
+* 2011-03-06
+- Speed and memory improvements to Special:LangugeStats
+* 2011-02-28
+- Special:LanguageStats: Fix IE-bug. Element creation must have valid syntax, does not accept shortcuts
+* 2011-02-01
+- Allow TRANSLATE_FUZZY to be redefined to a nonstandard value in LocalSettings.php
+* 2011-01-24
+- Moved all Special*.php files to specials/ folder. Update autoloader to match
+* 2011-01-22
+- Fixed character escpaing in Python FFS writer.
+* 2011-01-19
+- Improved suport for multi-line messages in OpenLayers FFS
+* 2011-01-17
+- Use the new ArticlePrepareTextForEdit hook to disable pre-save transformation on all message pages except for the message documentation language.
+* 2011-01-15
+- Read authors from python message files.
+* 2011-01-05
+- Use the new jquery.colorUtil to make the brightness 30% higher when cells are hovered on Special:LanguageStats
+
+=== 2010 ===
+
+* 2010-12-31
+- Use new hook to translate message documentation language code
+- Some improvements to Special:Translations: display language name, enable sorting, display count
+* 2010-12-24
+- Last bits of mw-js conventions
+* 2010-12-10
+- Transform translated page titles
+* 2010-11-05
+- Renamed TranslatePage to SpecialTranslate to follow the naming pattern
+- ViewUntranslated task was showing wrong table header
+* 2010-10-24
+- Support for Microsoft Translator
+* 2010-10-06
+- Fixed two bugs in translatable pages parsing which caused non-translatable pages to be considered as tagged
+- Performance improvements and bug fixes to message group cache
+* 2010-10-02
+- Fixed huge text in translation dialog
+* 2010-09-30
+- Fix X-POT-Import-date formatting
+* 2010-09-29
+- Lots of code cleanup related to message groups
+* 2010-09-27
+- Added parameter 'skip' to make it possible to export * but not 'en' and 'qqq' for example.
+* 2010-09-26
+- Allow translation of titles of translatable pages
+- Avoid Fatal when someone tries to export AggregateMessageGroup
+* 2010-09-25
+- First Api Query Module messagecollection
+* 2010-08-23
+- Fixed multiple bugs in Special:TranslationStats
+* 2010-08-20
+- Documentation updated a lot, added doxygen spec file.
+* 2010-06-12
+- Removed $wgGoogleApiKey, $wgTranslateTM and $wgTranslateApertium
+- You need to migrate to $wgTranslateTranslationServices
+- Page translation has been improved a lot
+- translation memory suggestions can now be loaded asynchronously
+* 2010-03-27
+- Improvements to js edit, like save and open next
+* 2010-02-24
+- Dropped $wgTranslateCssLocation. In MW 1.16 and later you can use $wgExtensionAssetsPath
+* 2010-01-24
+- Support for Apertium machine translation service
+- Fixed issue with jQuery and Vector skin
+* 2010-01-22
+- Support for using Google's translation api as a source for tm suggestions
+- Interface element for copying tm suggestion into text area
+* 2010-01-16 Translation memory suggestions are aggregated intelligently to save space
+ There is link back to the original message in tm suggestions
+ tm-export superceded dump-tm
+
+=== Changes in version 12 ===
+- Page translation
+- New fuzzy system, should be faster now
+- Edit are consistently filled when starting translation
+
+=== Changes in version 11 ===
+* 2009-05-09
+- Support added for OpenLayers
+- Page translation feature updated. Not backwards compatible
+* 2009-04-28 Tweaks for new preferences system
+* 2009-04-27 Updated preferences handling; only show when relevant
+* 2009-04-24 Updated preferences handling to work with MediaWiki 1.15a r49790 and up
+* 2009-04-23 Link added to message group in the source message fieldset description
+* 2009-04-15 Added class 'mw-sp-translate-[id]' to all message groups on Special:Translate
+* 2009-04-04:2 Support added for NOCC
+* 2009-04-04:1 PhpVariables Mantis header inconsistency fixed
+* 2009-03-29 Table header on Special:LanguageStats suppressed when all translations are complete
+
+=== Changes in version 10 ===
+* 2009-03-24 Also 2 decimals for fuzzy in Special:LanguageStats
+* 2009-03-23 Localised number formatting in Special:LanguageStats
+* 2009-02-22
+- Intro suppressed
+- message documentation no longer show warnings
+- improved keeping track of the language that is being translated in
+* 2009-02-14 Export header for MantisBT updated
+* 2009-01-18:1 Bug in autoexport fixed. Provides realiable output again
+* 2009-01-17:2 Content translation was disabled
+* 2009-01-17:1 Headers in gettext files no longer multiply
+* 2009-01-15 Support structure for Commonist added
+* 2009-01-11:4 Export time checks for special page aliases and updated FreeCol problematic checks
+* 2009-01-11:3 Skip optionals and ignored messages in previous/next to avoid accidental translations
+* 2009-01-11:2 wgMaxShellMemory increased for sync-group.php to avoid out of memory issues
+* 2009-01-11:1 New options added to and performance improvements for autoexport.php
+* 2008-12-31 XHTML fixes in Special:LanguageStats
+* 2008-12-18 Special:TranslationStats allow passing a language parameter now and uses fontconfig if available
+* 2008-12-16 Miscellaneous problematic checks updated
+* 2008-12-15:2 Export headers for MantisBT updated
+* 2008-12-15:1 task to display untranslation optional messages was added
+* 2008-12-07 bug in export to file in web interface caused empty translations to be exported
+* 2008-11-30:1 added Special:LanguageStats for real-time translation statistics
+* 2008-11-24:2 fallback messages in the edit screen now have an edit link
+* 2008-11-24:1 added Special:Translations to show all translations of a message
+* 2008-09-09:1 add message checks for Mantis
+* 2008-09-07 add support for Mantis
+* 2008-09-05 add languageeditstats.php to provide a top list of edits per language
+* 2008-09-05 add import for groups containing "." like core-1.13
+* 2008-08-08:1 experimental sync-group to import external changes and keep them in sync
+* 2008-08-04:1 add "Other translations" link to Special:Prefexindex in sidebar toolbox
+* 2008-07-29:2 support for variables and purging and fallbacks in page translation
+* 2008-07-29:1 bug fixes and enhanced magic word support for AdvancedTranslate
+* 2008-07-26:2 proper parents for branched messages
+* 2008-07-26:1
+ - improved speed and memory usage
+ - experimental <translate> tag for wikipage translation
+* 2008-07-17:1
+- simple form to generate graphs
+- scale and count parameters for graphs
+* 2008-07-15:2 new check for unaltered namespaces in pagename messages of MediaWiki
+* 2008-07-15:1
+ - improved message checks
+ - can now work for meta groups too
+ - fixed messages not shown in the list anymore, but script still needed to find
+ the broken ones
+* 2008-07-13:2 group filter for graphs
+* 2008-07-13:1 experimental alias-export for extensions
+* 2008-07-08:1 simple edit stats with phplot
+* 2008-07-05:1 fuzzy.php was ignoring the namespace
+* 2008-07-04:2
+ - use an existing hook for quick links
+ - possible fix for Windows
+* 2008-07-04:1 poimport uses correct namespace
+* 2008-07-01:1
+ - Fix gettext message name snippets: filter / and trim after trailing
+ - Load normal comments from gettext files
+ - Quick links for viewing, using a private hook
+* 2008-06-30:3 Check for unknown vars for mediawiki type
+* 2008-06-30:2 Checks for missing and unknown variables for FreeCol
+* 2008-06-30:1 More contrast in the new default page for Special translate
+* 2008-06-29:2 Branched core messages now load translations from trunk files too
+
+=== Changes in version 9 ===
+* Released 2008-06-29
+* (bug 12955) Numbers should be localized in Translate extension
+* Support branches for core messages
+* fuzzy.php got support for --comment and --skiplanguages
+* untranslated is now the default task
+* truncate very long message names in table view
+* show definition closer to edit area
+* show the group of message when editing
+* branched core messages should now export properly
+* $wgTranslateBlacklist added to prevent edits to certain language/group combinations
+* new exporters
+* changed the way of adding MediaWiki extensions
+* support for having messages in namespaces other than NS_MEDIAWIKi
+* try to load qqq from files if not found from the database
+* added $wgTranslateAuthorBlacklist
+* new options to groupStatistics.php: --fuzzy and --skipzero
+* fuzzy respects now $wgTranslateMessageNamespaces
+* freecol not added by default
+* Messages keys can now start with capital letter, but have to be unique after normalising case and spaces
+* poimport will no longer replace translation in database with empty string
+* Support for exporting very basic Xliff files (no import yet)
+* Task for listing problematic messages
+* CreateCheckIndex.php maintenance script for creating a list of problematic messsages
+* Aliases for special pages
+* improvements to Special:Magic and support for translating special page aliases for extensions
+* more generic check framework
+* new front page for Special:Translate
+* desc and descmsg support for mediawiki extension group definitions
+
+=== Changes in version 8 ===
+* Released 2008-02-06
+* export.php for even faster exporting (only core messages supported currently)
+* Message groups can now have description or notes for translations
+* Enhanced edit view with fieldsets and message documentation
+* New variable $wgTranslateDocumentationLanguageCode
+* Fixed Special:Magic form submission and selector bug
+* --skiplanguages option for groupStatistics.php
+* Force keys to lower case in serialised index to avoid misses due to case
+* Changed fuzzy.php to assume location of commandline.inc, so that it can be run from any location
+* Preserve whitespace also in Special:Translate table
+* Run checks on editing view (current translation or submitted translation in preview) and display them to user
+* Fixed a bug where empty text area would be filled with current translation even after first page load
+* Don't fuzzy Documentation language
+* Export task was erronously outputting as plain
+* New variable $wgTranslateCC for adding custom groups
+* WikiMessageGroup class, which is easy to use class for defining a message group for wiki's custom user interface elements
+* Array keys in $wgTranslateEC and $wgTranslateAC are now used for alphabetical sorting only. MessageGroup::$id and MessageGroup::getId() are used for everything else.
+* Export documentation messages as "extracted comments" in po export
+* Import fuzzy messages as fuzzy in po import
+* Experimental support for some extensions in export.php
+* export.php can now export most supported groups
+* autoexport.php helper
+* Content-Disposition header for po export
+* Link to Special:Translate added in Special:Version
+* Possibility to add comments when editing with Special:magic
+
+=== Changes in version 7 ===
+* Released 2007-12-29
+* New HTMLSelector class
+* Cleaned user interface of Special:Translate
+* Special:TranslationChanges remembers hours option
+* Removed for a while unused setting $wgTranslateTryLoad
+* TranslateTasks::getTask returns null for invalid keys instead of throwing en error
+* New member function TranslateTask::plainOutput - Tasks can use it to request plain text output
+* Add css as a separate file instead of adding it to request output and new setting for it: $wgTranslateCssLocation
+* Filled readme for old changes a bit
+* Experimental po import
+* groupStatistics.php for creating statistics for groups
+
+=== Changes in version 6 ===
+* Released 2007-12-21
+* New classes MessageCollection and TMessage
+* Experimental po export
+* MessageGroup::fillBools has changed to getBools and usage changed a little
+* New member function MessageGroup::isMeta
+* Lots of new extensions supported
+
+=== Changes in version 5 ===
+* Released 2007-11-14
+* CreateMessageIndex.php which creates serialised index of message key -> group
+
+=== Changes in version 4 ===
+* New "task-based" interface
+* Paging of messages
+* Separation of translation and interface language
+* New mediawiki extensions available for translation
+* Support for extensions which use splitted i18n files
diff --git a/MLEB/Translate/api/ApiSearchTranslations.php b/MLEB/Translate/api/ApiSearchTranslations.php
new file mode 100644
index 00000000..d2787a0c
--- /dev/null
+++ b/MLEB/Translate/api/ApiSearchTranslations.php
@@ -0,0 +1,131 @@
+<?php
+/**
+ * API module for search translations
+ * @since 2015.07
+ * @license GPL-2.0-or-later
+ */
+class ApiSearchTranslations extends ApiBase {
+ public function execute() {
+ global $wgTranslateTranslationServices;
+
+ if ( !$this->getAvailableTranslationServices() ) {
+ $this->dieWithError( 'apierror-translate-notranslationservices' );
+ }
+
+ $params = $this->extractRequestParams();
+
+ $config = $wgTranslateTranslationServices[$params['service']];
+ /** @var SearchableTTMServer $server */
+ $server = TTMServer::factory( $config );
+
+ $result = $this->getResult();
+
+ if ( $params['filter'] !== '' ) {
+ $translationSearch = new CrossLanguageTranslationSearchQuery( $params, $server );
+ $documents = $translationSearch->getDocuments();
+ $total = $translationSearch->getTotalHits();
+ } else {
+ $searchResults = $server->search(
+ $params['query'],
+ $params,
+ [ '', '' ]
+ );
+ $documents = $server->getDocuments( $searchResults );
+ $total = $server->getTotalHits( $searchResults );
+ }
+ $result->addValue( [ 'search', 'metadata' ], 'total', $total );
+ $result->addValue( 'search', 'translations', $documents );
+ }
+
+ protected function getAvailableTranslationServices() {
+ global $wgTranslateTranslationServices;
+
+ $good = [];
+ foreach ( $wgTranslateTranslationServices as $id => $config ) {
+ if ( TTMServer::factory( $config ) instanceof SearchableTTMServer ) {
+ $good[] = $id;
+ }
+ }
+
+ return $good;
+ }
+
+ protected function getAllowedFilters() {
+ return [
+ '',
+ 'translated',
+ 'fuzzy',
+ 'untranslated'
+ ];
+ }
+
+ public function getAllowedParams() {
+ global $wgLanguageCode,
+ $wgTranslateTranslationDefaultService;
+ $available = $this->getAvailableTranslationServices();
+
+ $filters = $this->getAllowedFilters();
+
+ $ret = [
+ 'service' => [
+ ApiBase::PARAM_TYPE => $available,
+ ],
+ 'query' => [
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_REQUIRED => true,
+ ],
+ 'sourcelanguage' => [
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_DFLT => $wgLanguageCode,
+ ],
+ 'language' => [
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_DFLT => '',
+ ],
+ 'group' => [
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_DFLT => '',
+ ],
+ 'filter' => [
+ ApiBase::PARAM_TYPE => $filters,
+ ApiBase::PARAM_DFLT => '',
+ ],
+ 'match' => [
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_DFLT => '',
+ ],
+ 'case' => [
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_DFLT => '0',
+ ],
+ 'offset' => [
+ ApiBase::PARAM_TYPE => 'integer',
+ ApiBase::PARAM_DFLT => 0,
+ ],
+ 'limit' => [
+ ApiBase::PARAM_DFLT => 25,
+ ApiBase::PARAM_TYPE => 'limit',
+ ApiBase::PARAM_MIN => 1,
+ ApiBase::PARAM_MAX => ApiBase::LIMIT_SML1,
+ ApiBase::PARAM_MAX2 => ApiBase::LIMIT_SML2
+ ],
+ ];
+
+ if ( $available ) {
+ // Don't add this if no services are available, it makes
+ // ApiStructureTest unhappy
+ $ret['service'][ApiBase::PARAM_DFLT] = $wgTranslateTranslationDefaultService;
+ }
+
+ return $ret;
+ }
+
+ protected function getExamplesMessages() {
+ return [
+ 'action=searchtranslations&language=fr&query=aide'
+ => 'apihelp-searchtranslations-example-1',
+ 'action=searchtranslations&language=fr&query=edit&filter=untranslated'
+ => 'apihelp-searchtranslations-example-2',
+ ];
+ }
+}
diff --git a/MLEB/Translate/api/ApiTranslationCheck.php b/MLEB/Translate/api/ApiTranslationCheck.php
new file mode 100644
index 00000000..b4921b4b
--- /dev/null
+++ b/MLEB/Translate/api/ApiTranslationCheck.php
@@ -0,0 +1,78 @@
+<?php
+/**
+ * @since 2017.10
+ * @license GPL-2.0-or-later
+ */
+class ApiTranslationCheck extends ApiBase {
+ public function execute() {
+ $params = $this->extractRequestParams();
+
+ $title = Title::newFromText( $params[ 'title' ] );
+ if ( !$title ) {
+ $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $params['title'] ) ] );
+ }
+ $handle = new MessageHandle( $title );
+ $translation = $params[ 'translation' ];
+
+ $checkResults = $this->getWarnings( $handle, $translation );
+
+ $warnings = [];
+ foreach ( $checkResults as $item ) {
+ $key = array_shift( $item );
+ $msg = $this->getContext()->msg( $key, $item )->parse();
+ $this->getResult()->addValue( 'warnings', null, $msg );
+ }
+ }
+
+ public function getWarnings( MessageHandle $handle, $translation ) {
+ if ( $translation === '' ) {
+ return [];
+ }
+
+ if ( $handle->isDoc() || !$handle->isValid() ) {
+ return [];
+ }
+
+ $checker = $handle->getGroup()->getChecker();
+ if ( !$checker ) {
+ return [];
+ }
+
+ $definition = $this->getDefinition( $handle );
+ $message = new FatMessage( $handle->getKey(), $definition );
+ $message->setTranslation( $translation );
+
+ $checks = $checker->checkMessage( $message, $handle->getCode() );
+ if ( $checks === [] ) {
+ return [];
+ }
+
+ return $checks;
+ }
+
+ private function getDefinition( MessageHandle $handle ) {
+ $group = $handle->getGroup();
+ if ( method_exists( $group, 'getMessageContent' ) ) {
+ return $group->getMessageContent( $handle );
+ } else {
+ return $group->getMessage( $handle->getKey(), $group->getSourceLanguage() );
+ }
+ }
+
+ public function getAllowedParams() {
+ return [
+ 'title' => [
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_REQUIRED => true,
+ ],
+ 'translation' => [
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_REQUIRED => true,
+ ],
+ ];
+ }
+
+ public function isInternal() {
+ return true;
+ }
+}
diff --git a/MLEB/Translate/i18n/api/bcl.json b/MLEB/Translate/i18n/api/bcl.json
new file mode 100644
index 00000000..dda24d75
--- /dev/null
+++ b/MLEB/Translate/i18n/api/bcl.json
@@ -0,0 +1,12 @@
+{
+ "@metadata": {
+ "authors": [
+ "Geopoet"
+ ]
+ },
+ "apihelp-query+messagecollection-example-2": "Listahan kan bakong opsyonal na mga pakahulugan nin mensahe para sa grupo \"pahina-Ehemplo\"",
+ "apihelp-query+messagecollection-example-3": "Listahan kan opsyonal na mga mensahe sa Finnish na igwang mga marka para sa grupo \"pahina-Ehemplo\"",
+ "apihelp-query+messagecollection-example-4": "Kadagdagan na impormasyon manunungod sa pinakahuring rebisyon sa dinakit-taramon para sa grupo \"pahina-Ehemplo\"",
+ "apihelp-query+messagegroupstats-example-1": "Listahan nin estadistika kan nakumpleto nang dinakit-taramon para sa grupo \"pahina-Ehemplo\"",
+ "apihelp-query+messagetranslations-example-1": "Listahan kan mga dinakit-taramon sa laog kan wiki para sa \"MediaWiki:Enero\""
+}
diff --git a/MLEB/Translate/i18n/api/bg.json b/MLEB/Translate/i18n/api/bg.json
new file mode 100644
index 00000000..59c27fa4
--- /dev/null
+++ b/MLEB/Translate/i18n/api/bg.json
@@ -0,0 +1,27 @@
+{
+ "@metadata": {
+ "authors": [
+ "StanProg"
+ ]
+ },
+ "apihelp-aggregategroups-param-group": "ID на група съобщения.",
+ "apihelp-groupreview-param-group": "Група съобщения.",
+ "apihelp-groupreview-param-language": "Езиков код.",
+ "apihelp-groupreview-example-1": "Отбелязване на състоянието на немския превод за групата съобщения „group-Example“ като готов",
+ "apihelp-query+languagestats-param-language": "Езиков код.",
+ "apihelp-query+messagecollection-param-group": "Група съобщения.",
+ "apihelp-query+messagecollection-param-language": "Езиков код.",
+ "apihelp-query+messagecollection-param-limit": "Колко съобщения да бъдат показани (след филтриране).",
+ "apihelp-query+messagecollection-example-1": "Списък на поддържаните езици",
+ "apihelp-query+messagegroups-example-1": "Показване на групите съобщения",
+ "apihelp-query+messagegroupstats-param-group": "ID на група съобщения.",
+ "apihelp-translationreview-description": "Отбелязване на преводите като проверени.",
+ "apihelp-translationstash-param-subaction": "Действие.",
+ "apihelp-translationstash-param-metadata": "JSON-обект.",
+ "apihelp-searchtranslations-description": "Търсене на преводи.",
+ "apihelp-searchtranslations-summary": "Търсене на преводи.",
+ "apihelp-searchtranslations-param-limit": "Размер на резултата.",
+ "apihelp-searchtranslations-example-1": "Показване на преводи на този език.",
+ "apierror-translate-duplicateaggregategroup": "Групата съобщения вече съществува",
+ "apierror-translate-sandbox-invalidppassword": "Невалидна парола"
+}
diff --git a/MLEB/Translate/i18n/api/bn.json b/MLEB/Translate/i18n/api/bn.json
new file mode 100644
index 00000000..fa5e7850
--- /dev/null
+++ b/MLEB/Translate/i18n/api/bn.json
@@ -0,0 +1,12 @@
+{
+ "@metadata": {
+ "authors": [
+ "Aftabuzzaman",
+ "আফতাবুজ্জামান"
+ ]
+ },
+ "apihelp-searchtranslations-summary": "অনুবাদ অনুসন্ধান করে।",
+ "apierror-translate-invalidlanguage": "অনুরোধকৃত ভাষাটি অবৈধ।",
+ "apierror-translate-sandbox-invalidppassword": "পাসওয়ার্ড সঠিক নয়",
+ "apierror-translate-unknownmessage": "অজানা বার্তা"
+}
diff --git a/MLEB/Translate/i18n/api/bs.json b/MLEB/Translate/i18n/api/bs.json
new file mode 100644
index 00000000..a0d72d14
--- /dev/null
+++ b/MLEB/Translate/i18n/api/bs.json
@@ -0,0 +1,122 @@
+{
+ "@metadata": {
+ "authors": [
+ "Srdjan m"
+ ]
+ },
+ "apihelp-aggregategroups-description": "Upravljanje zbirnim grupama poruka.\n\nMožete dodavati i uklanjati zbirne grupe poruka i pridruživati ili odstranjivati iz zbirnih poruka (jednu po jednu).",
+ "apihelp-aggregategroups-summary": "Upravljaj zbirnim grupama poruka.",
+ "apihelp-aggregategroups-extended-description": "Možete dodavati i uklanjati zbirne grupe poruka i pridruživati ili odstranjivati grupe poruka iz njih (jednu po jednu).",
+ "apihelp-aggregategroups-param-do": "Šta raditi sa zbirnom grupom poruka.",
+ "apihelp-aggregategroups-param-aggregategroup": "ID zbirne grupe poruka.",
+ "apihelp-aggregategroups-param-group": "ID grupe poruka.",
+ "apihelp-aggregategroups-param-groupname": "Naziv zbirne grupe poruka.",
+ "apihelp-aggregategroups-param-groupdescription": "Opis zbirne grupe poruka.",
+ "apihelp-aggregategroups-example-1": "Pridruži grupu",
+ "apihelp-groupreview-description": "Postavi stanja radnog toka za grupe poruka.",
+ "apihelp-groupreview-summary": "Postavi stanja radnog toka za grupe poruka.",
+ "apihelp-groupreview-param-group": "Grupa poruka.",
+ "apihelp-groupreview-param-language": "Jezički kôd.",
+ "apihelp-groupreview-param-state": "Novo stanje grupe.",
+ "apihelp-groupreview-example-1": "Označi stanje njemačkog prijevoda grupa poruka \"group-Example\" spremnim",
+ "apihelp-query+languagestats-description": "Upit jezičkih statistika.",
+ "apihelp-query+languagestats-summary": "Ispitaj jezičke statistike.",
+ "apihelp-query+languagestats-param-timelimit": "Maksimalno vrijeme koje se može potrošiti na izračunavanje statistike koja nedostaje. Ako je nula, prikazat će se samo keširani rezultati od početka.",
+ "apihelp-query+languagestats-param-language": "Jezički kôd.",
+ "apihelp-query+languagestats-example-1": "Spisak statistika dovršenosti prijevoda na finski",
+ "apihelp-query+messagecollection-description": "Izvrši upit Zbirci podataka o prijevodima.",
+ "apihelp-query+messagecollection-summary": "Izvrši upit Zbirci podataka o prijevodima.",
+ "apihelp-query+messagecollection-param-group": "Grupa poruka.",
+ "apihelp-query+messagecollection-param-language": "Jezički kôd.",
+ "apihelp-query+messagecollection-param-limit": "Koliko poruka prikazati (nakon filtriranja).",
+ "apihelp-query+messagecollection-param-offset": "Cijeli broj ili odstupanje s ključem za početak.",
+ "apihelp-query+messagecollection-param-filter": "Filteri za prikupljanje poruka. Koristite <kbd>!</kbd> za negaciju nekog uslova. Naprimjer, <kbd>!fuzzy</kbd> znači da treba ispisati sve poruke koje nisu zastarjele. Filteri se primjenjuju po ukazanom redoslijedu.\n;fuzzy:Zastarjele poruke (s oznakom \"fuzzy\").\n;optional:Poruke koje treba prevoditi samo ako je potrebna promjena.\n;ignored:Poruke koje se nikad ne prevode.\n;hastranslation:Poruke koje imaju prijevod bez obzira je li zastario.\n;translated:Poruke koje imaju prijevod koji nije zastario.\n;changed:Poruke koje su prevedene ili izmijenjene od posljednjeg izvoza.\n;reviewer&#58;N:Poruke koje među pregledavačima imaju korisnika s brojem <kbd>N</kbd>.\n;last-translator&#58;N:Poruke čiji je posljednji prevodilac korisnik s brojem <kbd>N</kbd>.",
+ "apihelp-query+messagecollection-param-prop": "Koja svojstva treba dati:\n;definition:Definicija poruke.\n;translation:Trenutni prijevod (bez niza $1 ako ga ima, koristite oznake da biste provjerili ima li zastarjelih ili pokvarenih prijevoda).\n;tags:Oznake poruka, kao što su optional, ignored i fuzzy.\n;properties:Svojstva poruka, kao što su status, revision, last-translator. Može se razlikovati od poruke do poruke.\n;revision:<span class=\"deprecated\">Zastarjelo!</span> Koristite $2prop=properties.",
+ "apihelp-query+messagecollection-example-1": "Spisak podržanih jezika",
+ "apihelp-query+messagecollection-example-2": "Spisak neobaveznih definicija poruka za grupu \"page-Example\"",
+ "apihelp-query+messagecollection-example-3": "Spisak neobaveznih poruka na finskom s oznakama za grupu \"page-Example\"",
+ "apihelp-query+messagecollection-example-4": "Više informacija o najnovijim izmjenama grupe \"page-Example\"",
+ "apihelp-query+messagegroups-description": "Daj informacije za grupe poruka.\n\nImajte na umu da parametar \"uselang\" utiče na ispis jezički-zavisnih dijelova.",
+ "apihelp-query+messagegroups-summary": "Daj informacije za grupe poruka.",
+ "apihelp-query+messagegroups-extended-description": "Imajte na umu da parametar \"uselang\" utiče za ispis dijelova koji ovise o jeziku.",
+ "apihelp-query+messagegroups-param-depth": "Kad se koristi format \"stablo\", ograniči dubinu na ovoliko nivoa. Vrijednost 0 znači da se neće prikazati nijedna podgrupa. Ako se dostigne granica, na ispisu će se prikazati vrijednost \"groupcount\", koja navodi koliko je neposrednih podgrupa.",
+ "apihelp-query+messagegroups-param-filter": "Daj samo poruke s ID-ovima koji odgovaraju jednom ili više zadanih unosa (ne razlikuju se mala i velika slova, razdvaja se uspravnom crtom, džoker *).",
+ "apihelp-query+messagegroups-param-format": "U formatu \"stablo\", grupe poruka mogu biti na više mjesta u stablu.",
+ "apihelp-query+messagegroups-param-iconsize": "Željena veličina rasterizirane ikone grupe.",
+ "apihelp-query+messagegroups-param-prop": "Koje informacije u vezi s prijevodom treba dati:\n;id:Uključi ID grupe.\n;label:Uključi oznaku grupe.\n;description:Uključi opis grupe.\n;class:Uključi ime klase grupe.\n;namespace:Uključi imenski prostor grupe. Ne pripadaju sve grupe jednom imenskom prostoru.\n;exists:Uključi samokalkulirano svojstvo postojanja grupe.\n;icon:Uključi URL-ove ka ikoni grupe.\n;priority:Uključi prioritetni status, kao što je \"nepreporučeno\".\n;prioritylangs:Uključi prioritetne jezike. Ako nije postavljeno, povratna vrijednost će biti \"false\".\n;priorityforce:Uključi prioritetni status – ovo je forsirana postavka prioritetnih jezika.\n;workflowstates:Uključi stanja radnog toka za grupu poruka.",
+ "apihelp-query+messagegroups-param-root": "Kad se koristi format \"stablo\", umjesto da se započne od najvišeg nivoa, započni od date grupe poruka, koja mora biti zbirna. Kad se koristi ravni format, prikazat će se samo navedena grupa.",
+ "apihelp-query+messagegroups-example-1": "Prikaži grupe poruka",
+ "apihelp-query+messagegroupstats-description": "Ispitaj statistike grupa poruka.",
+ "apihelp-query+messagegroupstats-summary": "Ispitaj statistike grupa poruka.",
+ "apihelp-query+messagegroupstats-param-timelimit": "Maksimalno vrijeme koje se može potrošiti na izračunavanje statistike koja nedostaje. Ako je nula, prikazat će se samo keširani rezultati od početka.",
+ "apihelp-query+messagegroupstats-param-group": "ID grupe poruka.",
+ "apihelp-query+messagegroupstats-example-1": "Spisak statistike dovršenosti prijevoda za grupu \"page-Example\"",
+ "apihelp-query+messagetranslations-description": "Ispitaj sve prijevode jedne poruke.",
+ "apihelp-query+messagetranslations-summary": "Ispitaj sve prijevode jedne poruke.",
+ "apihelp-query+messagetranslations-param-title": "Cijeli naziv poznate poruke.",
+ "apihelp-query+messagetranslations-example-1": "Spisak prijevoda na wikiju za \"MediaWiki:January\"",
+ "apihelp-translatesandbox-description": "Registracija i upravljanje korisnicima u pješčaniku.",
+ "apihelp-translatesandbox-summary": "Registracija i upravljanje korisnicima u pješčaniku.",
+ "apihelp-translatesandbox-param-do": "Šta uraditi.",
+ "apihelp-translatesandbox-param-userid": "Korisnički ID-ovi korisnika kojima se upravlja. Koristite 0 za stvaranja.",
+ "apihelp-translatesandbox-param-username": "Korisničko ime pri stvaranju korisnika.",
+ "apihelp-translatesandbox-param-password": "Lozinka pri stvaranju korisnika.",
+ "apihelp-translatesandbox-param-email": "E-pošta pri stvaranju korisnika.",
+ "apihelp-translationaids-description": "Ispitaj sva prevodilačka pomagala.",
+ "apihelp-translationaids-summary": "Ispitaj sva prevodilačka pomagala.",
+ "apihelp-translationaids-param-title": "Cijeli naziv poznate poruke.",
+ "apihelp-translationaids-param-group": "Kojoj grupi pripada poruka. Ako je prazno, koristit će se glavna grupa.",
+ "apihelp-translationaids-param-prop": "Koja prevodilačka pomagala uključiti.",
+ "apihelp-translationaids-example-1": "Prikaži pomagala za [[MediaWiki:January/fi]]",
+ "apihelp-translationreview-description": "Označi prijevode pregledanim.",
+ "apihelp-translationreview-summary": "Označi prijevode pregledanim.",
+ "apihelp-translationreview-param-revision": "Broj izmjene za pregled.",
+ "apihelp-translationreview-example-1": "Pregledaj izmjenu 1",
+ "apihelp-translationstash-description": "Dodaj prijevode u skladište.",
+ "apihelp-translationstash-summary": "Dodaj prijevode u skladište.",
+ "apihelp-translationstash-param-subaction": "Radnja.",
+ "apihelp-translationstash-param-title": "Naziv stranice prevodilačke jedinice.",
+ "apihelp-translationstash-param-translation": "Korisnikovi prijevodi.",
+ "apihelp-translationstash-param-metadata": "JSON-objekt.",
+ "apihelp-translationstash-param-username": "Ili korisnik čije skladište treba preuzeti. Ovo mogu raditi samo ovlašteni korisnici.",
+ "apihelp-translationstash-example-1": "Dodaj prijevod u skladište za [[MediaWiki:Jan/fi]]",
+ "apihelp-translationstash-example-2": "Ispitaj skladište",
+ "apihelp-ttmserver-description": "Ispitaj prijedloge iz zapamćenih prijevoda.",
+ "apihelp-ttmserver-summary": "Ispitaj prijedloge iz zapamćenih prijevoda.",
+ "apihelp-ttmserver-param-service": "Koju od dostupnih usluga za prijevod koristiti.",
+ "apihelp-ttmserver-param-sourcelanguage": "Jezički kôd izvornog teksta.",
+ "apihelp-ttmserver-param-targetlanguage": "Jezički kôd prijedloga.",
+ "apihelp-ttmserver-param-text": "Za koji tekst naći prijedloge.",
+ "apihelp-ttmserver-example-1": "Daj prijedloge za prevođenje \"Help\" s engleskog na finski",
+ "apihelp-searchtranslations-description": "Pretraži prijevode.",
+ "apihelp-searchtranslations-summary": "Pretraži prijevode.",
+ "apihelp-searchtranslations-param-service": "Koju od dostupnih usluga za prijevod koristiti.",
+ "apihelp-searchtranslations-param-query": "Niz koji treba tražiti.",
+ "apihelp-searchtranslations-param-sourcelanguage": "Jezički kôd izvornog teksta.",
+ "apihelp-searchtranslations-param-language": "Koji jezički kôd tražiti u nizu.",
+ "apihelp-searchtranslations-param-group": "ID grupe koju treba tražiti u nizu.",
+ "apihelp-searchtranslations-param-filter": "Filter za status prijevoda.",
+ "apihelp-searchtranslations-param-match": "Poklapanje bilo kojih/svih riječi.",
+ "apihelp-searchtranslations-param-case": "Pretraga koja (ne) razlikuje velika i mala slova.",
+ "apihelp-searchtranslations-param-offset": "Odstup za prijevode.",
+ "apihelp-searchtranslations-param-limit": "Veličina rezultata.",
+ "apihelp-searchtranslations-example-1": "Prikaži prijevode na ovaj jezik.",
+ "apihelp-searchtranslations-example-2": "Prikaži neprevedene poruke koji odgovaraju upitu na izvornom jeziku.",
+ "apierror-translate-duplicateaggregategroup": "Grupa poruka već postoji",
+ "apierror-translate-fuzzymessage": "Ne možete pregledati zastarjele prijevode",
+ "apierror-translate-groupreviewdisabled": "Pregled grupe poruka nije u upotrebi.",
+ "apierror-translate-invalidaggregategroup": "Neispravna grupa zbirnih poruka",
+ "apierror-translate-invalidaggregategroupname": "Neispravni naziv za grupu zbirnih poruka",
+ "apierror-translate-invalidgroup": "Grupa ne postoji ili nije ispravna",
+ "apierror-translate-invalidstate": "Traženo stanje nije ispravno.",
+ "apierror-translate-invalidupdate": "Neispravno ažuriranje",
+ "apierror-translate-language-disabled": "Prijevod na ovaj jezik je onemogućen",
+ "apierror-translate-language-disabled-source": "Izvorni jezik ove grupe je $1. Izaberite jezik na koji želite prevoditi.",
+ "apierror-translate-nodynamicgroups": "Dinamičke grupe poruka ovdje nisu podržane",
+ "apierror-translate-nomessagefortitle": "Naslov ne odgovara prevodivoj poruci",
+ "apierror-translate-owntranslation": "Ne možete pregledati vlastite prijevode",
+ "apierror-translate-sandboxdisabled": "Funkcija pijeska nije u upotrebi",
+ "apierror-translate-sandbox-invalidppassword": "Neispravna lozinka",
+ "apierror-translate-unknownmessage": "Nepoznata poruka",
+ "apiwarn-translate-alreadyreviewedbyyou": "Već ste je označili pregledanom"
+}
diff --git a/MLEB/Translate/i18n/api/ckb.json b/MLEB/Translate/i18n/api/ckb.json
new file mode 100644
index 00000000..38e10556
--- /dev/null
+++ b/MLEB/Translate/i18n/api/ckb.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Épine"
+ ]
+ },
+ "apierror-translate-language-disabled-source": "سەرچاوەی زمانی ئەم گرووپە $1ە. تکایە زمانێکی تر ھەڵبژێرە بۆ وەرگێڕان بۆی."
+}
diff --git a/MLEB/Translate/i18n/api/cs.json b/MLEB/Translate/i18n/api/cs.json
new file mode 100644
index 00000000..6cb2d230
--- /dev/null
+++ b/MLEB/Translate/i18n/api/cs.json
@@ -0,0 +1,12 @@
+{
+ "@metadata": {
+ "authors": [
+ "Vks"
+ ]
+ },
+ "apihelp-query+languagestats-param-language": "Kód jazyka.",
+ "apihelp-query+messagecollection-param-group": "Skupina zpráv.",
+ "apihelp-query+messagecollection-param-language": "Kód jazyka.",
+ "apihelp-translationstash-param-subaction": "Akce.",
+ "apihelp-translationstash-param-metadata": "JSON objekt."
+}
diff --git a/MLEB/Translate/i18n/api/da.json b/MLEB/Translate/i18n/api/da.json
new file mode 100644
index 00000000..133437bb
--- /dev/null
+++ b/MLEB/Translate/i18n/api/da.json
@@ -0,0 +1,28 @@
+{
+ "@metadata": {
+ "authors": [
+ "Saederup92"
+ ]
+ },
+ "apihelp-groupreview-param-group": "Beskedgruppe.",
+ "apihelp-groupreview-param-language": "Sprogkode.",
+ "apihelp-query+languagestats-param-language": "Sprogkode.",
+ "apihelp-query+messagecollection-param-group": "Beskedgruppe.",
+ "apihelp-query+messagecollection-param-language": "Sprogkode.",
+ "apihelp-query+messagecollection-example-1": "Liste over understøttede sprog",
+ "apihelp-query+messagegroups-example-1": "Vis beskedgrupper",
+ "apihelp-translatesandbox-param-username": "Brugernavn når ny bruger oprettes.",
+ "apihelp-translatesandbox-param-password": "Kodeord når ny bruger oprettes.",
+ "apihelp-translationaids-param-title": "Den fulde titel af en kendt besked.",
+ "apihelp-translationstash-param-subaction": "Handling.",
+ "apihelp-searchtranslations-description": "Søg i oversættelser.",
+ "apihelp-searchtranslations-summary": "Søg i oversættelser.",
+ "apihelp-searchtranslations-param-query": "Strengen der skal søges efter.",
+ "apihelp-searchtranslations-param-limit": "Resultatets størrelse.",
+ "apihelp-searchtranslations-example-1": "Vis oversættelser for sproget.",
+ "apierror-translate-invalidupdate": "Ugyldig opdatering",
+ "apierror-translate-language-disabled": "Oversættelser til $1 er deaktiveret.",
+ "apierror-translate-language-disabled-reason": "Oversættelser til $1 er deaktiveret: $2",
+ "apierror-translate-sandbox-invalidppassword": "Ugyldig adgangskode",
+ "apierror-translate-unknownmessage": "Ukendt besked"
+}
diff --git a/MLEB/Translate/i18n/api/diq.json b/MLEB/Translate/i18n/api/diq.json
new file mode 100644
index 00000000..dc570cb1
--- /dev/null
+++ b/MLEB/Translate/i18n/api/diq.json
@@ -0,0 +1,27 @@
+{
+ "@metadata": {
+ "authors": [
+ "Kumkumuk",
+ "Mirzali"
+ ]
+ },
+ "apihelp-aggregategroups-param-group": "Kamiya Gruba mesacan",
+ "apihelp-aggregategroups-param-groupname": "Namey gruba mesacê aredi.",
+ "apihelp-aggregategroups-param-groupdescription": "Akerdena gruba mesacê aredi",
+ "apihelp-aggregategroups-example-1": "Gruba Verlisansi",
+ "apihelp-groupreview-param-group": "Gruba mesaca.",
+ "apihelp-groupreview-param-language": "Kodê zıwani.",
+ "apihelp-groupreview-param-state": "Qandê gruber dewleta newi.",
+ "apihelp-query+languagestats-description": "Perseya istatistikanê zıwani.",
+ "apihelp-query+languagestats-param-language": "Kodê zıwani.",
+ "apihelp-query+languagestats-example-1": "Lisya temam biyayışê istatistikané açarnayışanê Finki",
+ "apihelp-query+messagecollection-description": "Heqdê MessagesCollection i persi",
+ "apihelp-query+messagecollection-param-group": "Gruba mesaca.",
+ "apihelp-query+messagecollection-param-language": "Kodê zıwani.",
+ "apihelp-translatesandbox-param-do": "Çıçi.",
+ "apihelp-translationstash-param-subaction": "Kerdış.",
+ "apihelp-translationstash-param-metadata": "JSON çi.",
+ "apihelp-searchtranslations-description": "Açarnayışan cı geyre.",
+ "apihelp-searchtranslations-param-limit": "Ebadê neticeyi.",
+ "apihelp-searchtranslations-example-1": "Qandê zıwani açarnayışan bımocne."
+}
diff --git a/MLEB/Translate/i18n/api/fa.json b/MLEB/Translate/i18n/api/fa.json
new file mode 100644
index 00000000..d283d944
--- /dev/null
+++ b/MLEB/Translate/i18n/api/fa.json
@@ -0,0 +1,55 @@
+{
+ "@metadata": {
+ "authors": [
+ "Reza1615",
+ "Huji",
+ "Ladsgroup"
+ ]
+ },
+ "apihelp-aggregategroups-description": "مدیریت گروه‌های پیغام دسته‌جمعی.\n\nشما می توانید گروه‌های پیغام دسته‌جمعی را بیفزایید یا حذف کنید یا گروه‌های پیغام را به آن‌ها متصل یا جدا کنید (در هر زمان یکی).",
+ "apihelp-aggregategroups-param-do": "با گروه پیغام دسته‌جمعی چه کنیم.",
+ "apihelp-aggregategroups-param-aggregategroup": "شناسهٔ گروه پیغام دسته‌جمعی.",
+ "apihelp-aggregategroups-param-group": "شناسهٔ گروه پیغام.",
+ "apihelp-aggregategroups-param-groupname": "نام گروه پیغام دسته‌جمعی.",
+ "apihelp-aggregategroups-param-groupdescription": "توصیف گروه پیغام دسته‌جمعی.",
+ "apihelp-aggregategroups-example-1": "متصل کردن یک گروه",
+ "apihelp-groupreview-description": "مشخص کردن حالت‌های روند کار گروه پیغام.",
+ "apihelp-groupreview-param-group": "گروه پیغام.",
+ "apihelp-groupreview-param-language": "کد زبان.",
+ "apihelp-groupreview-param-state": "حالت تازه برای گروه.",
+ "apihelp-query+languagestats-description": "کوئری آمار زبان",
+ "apihelp-query+languagestats-summary": "کوئری آمار زبان",
+ "apihelp-query+languagestats-param-language": "کد زبان.",
+ "apihelp-query+messagecollection-param-group": "گروه پیغام‌ها.",
+ "apihelp-query+messagecollection-param-language": "کد زبان.",
+ "apihelp-query+messagecollection-example-1": "فهرست زبان‌های پشتیبانی شده",
+ "apihelp-query+messagegroups-summary": "بازگردانی اطلاعات درباره گروه‌های پیام",
+ "apihelp-query+messagegroups-example-1": "نمایش گروه‌های پیام",
+ "apihelp-query+messagegroupstats-param-group": "شناسهٔ گروه پیغام.",
+ "apihelp-query+messagetranslations-param-title": "عنوان کامل یک پیام شناخته شده.",
+ "apihelp-translatesandbox-param-do": "چه کاری برای انجام دادن هست.",
+ "apihelp-translatesandbox-param-email": "رایانامه هنگام ایجاد حساب",
+ "apihelp-translationreview-example-1": "بازبینی نسخه ۱",
+ "apihelp-translationstash-param-subaction": "فعالیت.",
+ "apihelp-translationstash-param-metadata": "شی JSON",
+ "apihelp-ttmserver-param-sourcelanguage": "کد زبان متن مبدا",
+ "apihelp-ttmserver-param-targetlanguage": "کد زبان پیشنهاد",
+ "apihelp-ttmserver-param-text": "متن برای یافتن پیشنهاد",
+ "apihelp-searchtranslations-description": "جستجوی ترجمه‌ها.",
+ "apihelp-searchtranslations-summary": "جستجوی ترجمه‌ها.",
+ "apihelp-searchtranslations-param-query": "متن برای جستجو",
+ "apihelp-searchtranslations-param-limit": "اندازه نتیجه",
+ "apihelp-translationcheck-summary": "اعتبارسنجی ترجمه‌ها.",
+ "apihelp-translationcheck-param-translation": "ترجمه‌های برای اعتبارسنجی",
+ "apierror-translate-duplicateaggregategroup": "گروه پیام پیش از این وجود دارد.",
+ "apierror-translate-invalidgroup": "گروه وجود ندارد یا نامعتبر است.",
+ "apierror-translate-invalidstate": "وضعیت درخواستی نامعتبر است.",
+ "apierror-translate-invalidlanguage": "زبان درخواستی نامعتبر است.",
+ "apierror-translate-invalidupdate": "برورزسانی نامعتبر",
+ "apierror-translate-language-disabled": "ترجمه روی $1 غیرفعال شده‌است.",
+ "apierror-translate-language-disabled-reason": "ترجمه روی $1 غیرفعال شده‌است:$2",
+ "apierror-translate-owntranslation": "نمی‌توانید پیام‌های خودتان را بازبینی کنید",
+ "apierror-translate-sandbox-invalidppassword": "گذرواژه نامعتبر",
+ "apierror-translate-unknownmessage": "پیام ناشناخته",
+ "apiwarn-translate-alreadyreviewedbyyou": "قبلا به عنوان بازبینی‌شده توسط شما علامت خورده است"
+}
diff --git a/MLEB/Translate/i18n/api/fi.json b/MLEB/Translate/i18n/api/fi.json
new file mode 100644
index 00000000..25d9887a
--- /dev/null
+++ b/MLEB/Translate/i18n/api/fi.json
@@ -0,0 +1,54 @@
+{
+ "@metadata": {
+ "authors": [
+ "Nike",
+ "Pyscowicz"
+ ]
+ },
+ "apihelp-aggregategroups-param-group": "Viestiryhmän tunnus.",
+ "apihelp-aggregategroups-param-groupname": "Kokoelmaviestiryhmän nimi.",
+ "apihelp-aggregategroups-param-groupdescription": "Kokoelmaviestiryhmän kuvaus.",
+ "apihelp-aggregategroups-example-1": "Liitä ryhmä",
+ "apihelp-groupreview-description": "Asettaa viestiryhmien työnkulkutilan.",
+ "apihelp-groupreview-param-group": "Viestiryhmä.",
+ "apihelp-groupreview-param-language": "Kielikoodi.",
+ "apihelp-groupreview-param-state": "Viestiryhmän uusi tila.",
+ "apihelp-groupreview-example-1": "Merkitsee saksankielisen käännöksen viestiryhmälle \"group-Example\" valmiiksi",
+ "apihelp-query+languagestats-description": "Hakee kielitilastoja.",
+ "apihelp-query+languagestats-param-language": "Kielikoodi.",
+ "apihelp-query+languagestats-example-1": "Luettelo käännösten kattavuustilastoista suomeksi",
+ "apihelp-query+messagecollection-param-group": "Viestiryhmä.",
+ "apihelp-query+messagecollection-param-language": "Kielikoodi.",
+ "apihelp-query+messagecollection-param-limit": "Kuinka monta viestiä näytetään (suotimien jälkeen).",
+ "apihelp-query+messagecollection-example-1": "Palauttaa luettelon tuetuista kielistä",
+ "apihelp-query+messagegroups-example-1": "Näytä viestiryhmät",
+ "apihelp-query+messagegroupstats-param-group": "Viestiryhmän tunnus.",
+ "apihelp-translatesandbox-param-do": "Mitä tehdä.",
+ "apihelp-translatesandbox-param-username": "Käyttäjimi käyttäjää luodessa",
+ "apihelp-translatesandbox-param-password": "Salasana käyttäjää luodessa",
+ "apihelp-translatesandbox-param-email": "Sähköpostiosoite käyttäjää luodessa",
+ "apihelp-translationreview-description": "Merkitse käännökset tarkastetuiksi.",
+ "apihelp-translationstash-param-metadata": "JSON-objekti.",
+ "apihelp-translationstash-param-username": "Käyttäjä, jonka kätkö ladataan. Vain erioikeutetut käyttäjät voivat lukea toisten kätköjä.",
+ "apihelp-ttmserver-param-sourcelanguage": "Lähdetekstin kielikoodi.",
+ "apihelp-ttmserver-param-targetlanguage": "Ehdotuksen kielikoodi.",
+ "apihelp-searchtranslations-description": "Etsi käännöksiä.",
+ "apihelp-searchtranslations-summary": "Hae käännöksiä.",
+ "apihelp-searchtranslations-param-sourcelanguage": "Lähdetekstin kielikoodi.",
+ "apihelp-searchtranslations-param-limit": "Tuloksen koko.",
+ "apihelp-searchtranslations-example-1": "Näytä käännökset kielelle.",
+ "apihelp-translationcheck-param-title": "Sivun otsikko nimiavaruudella ja kielikoodilla.",
+ "apierror-translate-duplicateaggregategroup": "Viestiryhmä on jo olemassa",
+ "apierror-translate-invalidgroup": "Ryhmä ei ole olemassa tai se on virheellinen",
+ "apierror-translate-invalidlanguage": "Pyydetty kieli ei kelpaa.",
+ "apierror-translate-invalidupdate": "Virheellinen päivitys",
+ "apierror-translate-language-disabled": "Kääntäminen kielelle $1 ei ole mahdollista.",
+ "apierror-translate-language-disabled-reason": "Käänätminen kielelle $1 ei ole mahdollista: $2",
+ "apierror-translate-language-disabled-source": "Tämän ryhmän lähdekieli on $1. Valitse toinen kieli käännöksille.",
+ "apierror-translate-nodynamicgroups": "Dynaamisiä viestiryhmiä ei tueta täällä",
+ "apierror-translate-nomessagefortitle": "Otsikko ei vastaa käännettävää viesti",
+ "apierror-translate-owntranslation": "Ei voida tarkistaa omia käännöksiä",
+ "apierror-translate-sandboxdisabled": "Hiekkalaatikko-ominaisuus ei ole käytössä",
+ "apierror-translate-sandbox-invalidppassword": "Virheellinen salasana",
+ "apierror-translate-unknownmessage": "Tuntematon viesti"
+}
diff --git a/MLEB/Translate/i18n/api/fr.json b/MLEB/Translate/i18n/api/fr.json
new file mode 100644
index 00000000..e5fe1df9
--- /dev/null
+++ b/MLEB/Translate/i18n/api/fr.json
@@ -0,0 +1,139 @@
+{
+ "@metadata": {
+ "authors": [
+ "Djiboun",
+ "Pathe",
+ "TomT0m",
+ "Gomoko",
+ "Wladek92",
+ "Sacamol",
+ "Hpetit",
+ "Verdy p",
+ "Derugon",
+ "Urhixidur",
+ "ALDO CP"
+ ]
+ },
+ "apihelp-aggregategroups-description": "Gérer les groupes d'agrégation de messages.\n\nVous pouvez ajouter et supprimer des agrégats de groupes de messages et les associer ou de les dissocier les uns des autres (un à la fois).",
+ "apihelp-aggregategroups-summary": "Gérer des groupes de messages aggrégés.",
+ "apihelp-aggregategroups-extended-description": "Vous pouvez ajouter et supprimer des groupes de messages agrégés et leur associer ou non des groupes de messages (un par un).",
+ "apihelp-aggregategroups-param-do": "Que faire avec le groupe de message agrégé.",
+ "apihelp-aggregategroups-param-aggregategroup": "ID du groupe de message agrégé.",
+ "apihelp-aggregategroups-param-group": "ID du groupe de messages.",
+ "apihelp-aggregategroups-param-groupname": "Nom du groupe de message agrégé.",
+ "apihelp-aggregategroups-param-groupdescription": "Description du groupe de messages agrégé.",
+ "apihelp-aggregategroups-example-1": "Associer un groupe",
+ "apihelp-groupreview-description": "Définir les états de transition du groupe de messages.",
+ "apihelp-groupreview-summary": "Définir les états de flux de travail du groupe de messages.",
+ "apihelp-groupreview-param-group": "Groupe de messages.",
+ "apihelp-groupreview-param-language": "Code de langue",
+ "apihelp-groupreview-param-state": "Le nouvel état pour le groupe.",
+ "apihelp-groupreview-example-1": "Marquer l’état de la traduction allemande du groupe de messages « group-Example » comme prêt",
+ "apihelp-query+languagestats-description": "Demande les statistiques de langue.",
+ "apihelp-query+languagestats-summary": "Demander des statistiques de langue.",
+ "apihelp-query+languagestats-param-timelimit": "Temps Maximum pour calculer les statistiques manquantes. S'il vaut zéro, seuls les résultats mis en cache depuis le début sont retournés.",
+ "apihelp-query+languagestats-param-language": "Code de langue.",
+ "apihelp-query+languagestats-example-1": "Liste de statistiques des traductions terminées pour le finlandais",
+ "apihelp-query+messagecollection-description": "Requête MessageCollection sur les traductions.",
+ "apihelp-query+messagecollection-summary": "Rechercher des traductions dans MessageCollection.",
+ "apihelp-query+messagecollection-param-group": "Groupe du message.",
+ "apihelp-query+messagecollection-param-language": "Code de langue.",
+ "apihelp-query+messagecollection-param-limit": "Combien de messages à afficher (après filtrage).",
+ "apihelp-query+messagecollection-param-offset": "Entier ou clé de décalage pour le début.",
+ "apihelp-query+messagecollection-param-filter": "Filtres sur les collections de messages. Utilisez <kbd>! </kbd> pour nier la condition. Par exemple <kbd>!floue</kbd> signifie uniquement la liste de tous les messages non-floue . Les filtres sont appliqués dans l'ordre indiqué.\n;floue: messages avec étiquette fuzzy.\n;optionnel: messages qui doivent être traduits que si des changements sont nécessaires.\n;ignoré: messages qui ne sont jamais traduits.\n;possède traduction: messages qui ont une traduction peu importe si elle est floue ou pas.\n;traduit: messages qui ont une traduction qui n'est pas floue.\n;changé: messages qui ont été traduits ou changé depuis la dernière exportation.\n;examinateur&#58;N: messages dont le numéro d'utilisateur <kbd>N</kbd> figure parmi les examinateurs.\n;dernier traducteur&#58;N: messages dont le numéro d'utilisateur <kbd>N</kbd> est celui de la dernière personne qui a traduit.",
+ "apihelp-query+messagecollection-param-prop": "Quelles sont les propriétés à obtenir :\n;definition : la définition du message.\n;translation : traduction actuelle (sans la chaîne $1 si elle est présente, utiliser les étiquettes pour vérifier les traductions désuètes ou cassées).\n;tags : balises de message, comme optionnel, ignoré et flou.\n;properties : les propriétés d’un message, comme son état, la révision, le dernier traducteur. Peut varier entre les messages.\n;revision :<span class=\"deprecated\">Désuet !</span> Utiliser $2prop=propriétés.",
+ "apihelp-query+messagecollection-example-1": "Liste des langues prises en charge",
+ "apihelp-query+messagecollection-example-2": "Liste des définitions de messages non-facultatifs pour le groupe \"Exemple-de-page\"",
+ "apihelp-query+messagecollection-example-3": "Liste des messages facultatifs en finnois, avec les balises pour le groupe de la \"page d'exemple\"",
+ "apihelp-query+messagecollection-example-4": "Plus d'informations sur les dernières révisions de traduction pour le groupe de la \"page d'exemple\"",
+ "apihelp-query+messagegroups-description": "Retourne des informations sur les groupes de messages.\n\nNotez que le paramètre uselang concerne la sortie des éléments dépendants de la langue.",
+ "apihelp-query+messagegroups-summary": "Renvoie des informations concernant les groupes de messges.",
+ "apihelp-query+messagegroups-extended-description": "Soyez conscient que le paramètre « uselang » modifie la sortie de tous les éléments qui sont dépendants de la langue.",
+ "apihelp-query+messagegroups-param-depth": "Lors de l'utilisation sous forme d'arbre, limiter la profondeur à ces différents niveaux. La valeur 0 signifie qu'aucun des sous-groupes n'est présenté. Si la limite est atteinte, la sortie inclut un \"compteur de groupes\", qui indique le nombre d'enfants directs.",
+ "apihelp-query+messagegroups-param-filter": "Seulement les messages retournés avec l'Id correspondant à l'une ou plusieurs des entrées fournies (insensible à la casse, séparées par des canaux (pipes), caractère générique *).",
+ "apihelp-query+messagegroups-param-format": "Dans la présentation sous forme d'arbre, les groupes de messages peuvent exister en plusieurs endroits de l'arborescence.",
+ "apihelp-query+messagegroups-param-iconsize": "La taille préférée de l'icône pixellisée du groupe.",
+ "apihelp-query+messagegroups-param-prop": "Quelle information relative à la traduction obtenir :\n;id: contient l’ID du groupe.\n;label: contient l’étiquette du groupe.\n;description: contient la description du groupe.\n;class: contient le nom de classe du groupe.\n;namespace: contient l’espace de noms du groupe. Tous les groupes n’appartiennent pas forcément à un unique espace de noms.\n;exists: contient une propriété d’existence du groupe auto-calculée.\n;icon: contient des URLs vers l’icône du groupe.\n;priority: contient l’état de priorité (par ex., 'découragé').\n;prioritylangs: contient les langues préférées. S’il n’est pas renseigné, retourne false.\n;priorityforce: inclut l’état de priorité —prioritylangs est-il forcé ?\n;workflowstates: contient les états du processus pour le groupe de messages.",
+ "apihelp-query+messagegroups-param-root": "En utilisant le format arborescent, au lieu de démarrer depuis le plus haut niveau, partir du groupe de message fourni, qui doit être un groupe de message agrégé. En utilisant le format plat, seul le groupe spécifié est renvoyé.",
+ "apihelp-query+messagegroups-example-1": "Affiche les groupes de message",
+ "apihelp-query+messagegroupstats-description": "Demande de statistiques de groupe de message.",
+ "apihelp-query+messagegroupstats-summary": "Demander les statistiques des groupes de messages.",
+ "apihelp-query+messagegroupstats-param-timelimit": "Durée maximale pour calculer les statistiques manquantes. Si c’est zéro, seuls les résultats en cache depuis le début seront renvoyés.",
+ "apihelp-query+messagegroupstats-param-group": "Identifiant du groupe de message.",
+ "apihelp-query+messagegroupstats-example-1": "Liste des statistiques d’achèvement des traductions pour le groupe « page-Example »",
+ "apihelp-query+messagetranslations-description": "Demande l'ensemble des traductions pour un seul message.",
+ "apihelp-query+messagetranslations-summary": "Recherche toutes les traductions d'un unique message.",
+ "apihelp-query+messagetranslations-param-title": "Titre complet d'un message connu.",
+ "apihelp-query+messagetranslations-example-1": "Liste des traductions du wiki pour «MediaWiki:janvier»",
+ "apihelp-translatesandbox-description": "Inscrivez-vous et gérez les utilisateurs du bac à sable.",
+ "apihelp-translatesandbox-summary": "Inscrivez-vous et gérez les utilisateurs du bac à sable.",
+ "apihelp-translatesandbox-param-do": "Que faire.",
+ "apihelp-translatesandbox-param-userid": "IDs des utilisateurs gérés. Utiliser 0 pour les créations.",
+ "apihelp-translatesandbox-param-username": "Nom d'utilisateur lors de la création d'un utilisateur.",
+ "apihelp-translatesandbox-param-password": "Mot de passe lors de la création d'un utilisateur.",
+ "apihelp-translatesandbox-param-email": "Courriel lors de la création d’un utilisateur.",
+ "apihelp-translationaids-description": "Recherche toutes les aides à la traduction.",
+ "apihelp-translationaids-summary": "Rechercher toutes les aides à la traduction.",
+ "apihelp-translationaids-param-title": "Titre complet d'un message connu.",
+ "apihelp-translationaids-param-group": "Le groupe de messages auquel appartient le message. S'il est vide, alors le groupe principal est utilisé.",
+ "apihelp-translationaids-param-prop": "Quelles aides à la traduction inclure.",
+ "apihelp-translationaids-example-1": "Afficher les aides pour [[MediaWiki:January/fi]]",
+ "apihelp-translationreview-description": "Marquer les traductions comme relues.",
+ "apihelp-translationreview-summary": "Marquer les traductions relues.",
+ "apihelp-translationreview-param-revision": "Le numéro de révision à revoir.",
+ "apihelp-translationreview-example-1": "Relire la révision 1",
+ "apihelp-translationstash-description": "Ajouter des traductions à la réserve.",
+ "apihelp-translationstash-summary": "Mettre des traductions en réserve.",
+ "apihelp-translationstash-param-subaction": "Action.",
+ "apihelp-translationstash-param-title": "Titre de la page d’unité de traduction.",
+ "apihelp-translationstash-param-translation": "Traduction faite par l’utilisateur.",
+ "apihelp-translationstash-param-metadata": "Objet JSON.",
+ "apihelp-translationstash-param-username": "Facultativement, l’utilisateur dont il faut récupérer la réserve. Seuls les utilisateurs privilégiés peuvent faire cela.",
+ "apihelp-translationstash-example-1": "Ajouter une traduction à la réserve pour [[MediaWiki:Jan/fi]]",
+ "apihelp-translationstash-example-2": "Interroger la réserve",
+ "apihelp-ttmserver-description": "Rechercher des suggestions à partire de l'historique des traductions.",
+ "apihelp-ttmserver-summary": "Rechercher des suggestions à partir de l'historique des traductions.",
+ "apihelp-ttmserver-param-service": "Quels services de traduction disponibles utiliser.",
+ "apihelp-ttmserver-param-sourcelanguage": "Le code de langue du texte de la source.",
+ "apihelp-ttmserver-param-targetlanguage": "Le code de langue de la suggestion.",
+ "apihelp-ttmserver-param-text": "Le texte pour lequel on cherche des suggestions.",
+ "apihelp-ttmserver-example-1": "Obtenir des suggestions pour la traduire \"Aide\" de l'anglais vers le finnois",
+ "apihelp-searchtranslations-description": "Rechercher des traductions.",
+ "apihelp-searchtranslations-summary": "Rechercher des traductions.",
+ "apihelp-searchtranslations-param-service": "Quels services de traduction disponibles utiliser.",
+ "apihelp-searchtranslations-param-query": "La chaîne à rechercher.",
+ "apihelp-searchtranslations-param-sourcelanguage": "Le code de langue du texte de la source.",
+ "apihelp-searchtranslations-param-language": "Le code de langue pour lequel rechercher la chaîne.",
+ "apihelp-searchtranslations-param-group": "L’id de groupe où rechercher la chaîne.",
+ "apihelp-searchtranslations-param-filter": "Filtre d’état de traduction de message.",
+ "apihelp-searchtranslations-param-match": "Correspondance avec n'importe quel ou tous les mots de la recherche.",
+ "apihelp-searchtranslations-param-case": "Cas de recherche insensible/sensible à la casse.",
+ "apihelp-searchtranslations-param-offset": "Décalage pour les traductions.",
+ "apihelp-searchtranslations-param-limit": "Taille du résultat.",
+ "apihelp-searchtranslations-example-1": "Afficher les traductions pour la langue.",
+ "apihelp-searchtranslations-example-2": "Afficher les messages non traduits correspondant à la demande dans la langue source.",
+ "apihelp-translationcheck-description": "Valider la saisie de la traduction fournie d’après les vérificateurs de message.",
+ "apihelp-translationcheck-summary": "Valider les traductions.",
+ "apihelp-translationcheck-param-title": "Titre de page avec espace de noms et code langue.",
+ "apihelp-translationcheck-param-translation": "La traduction à valider.",
+ "apierror-translate-duplicateaggregategroup": "Le groupe de messages existe déjà",
+ "apierror-translate-fuzzymessage": "Impossible de relire des traductions trop vagues",
+ "apierror-translate-groupreviewdisabled": "La revue du groupe de message n'est pas utilisée.",
+ "apierror-translate-invalidaggregategroup": "Groupe de messages aggrégés invalide",
+ "apierror-translate-invalidaggregategroupname": "Nom du groupe de messages aggrégés invalide",
+ "apierror-translate-invalidgroup": "Le groupe n'existe pas ou n'est pas valide",
+ "apierror-translate-invalidstate": "L'état demandé n'est pas valide.",
+ "apierror-translate-invalidlanguage": "La langue demandée n'est pas valide.",
+ "apierror-translate-invalidupdate": "Correction invalide",
+ "apierror-translate-language-disabled": "La traduction en $1 est désactivée.",
+ "apierror-translate-language-disabled-reason": "La traduction en $1 est désactivée: $2",
+ "apierror-translate-language-disabled-source": "La langue source de ce groupe est $1. Veuillez sélectionner une autre langue vers laquelle traduire.",
+ "apierror-translate-nodynamicgroups": "Les groupes de messages dynamiques ne sont pas pris en charge ici",
+ "apierror-translate-nomessagefortitle": "Le titre ne correspond pas à un message traductible",
+ "apierror-translate-notranslationservices": "Aucun service de traduction public n’a été configuré. L’administrateur du wiki doit positionner <var>$wgTranslateTranslationServices</var> comme il faut avant que cette fonctionnalité puisse être utilisée.",
+ "apierror-translate-owntranslation": "Impossible de relire ses propres traductions",
+ "apierror-translate-sandboxdisabled": "Les fonctions du bac à sable ne sont pas utilisées",
+ "apierror-translate-sandbox-invalidppassword": "Mot de passe invalide",
+ "apierror-translate-unknownmessage": "Message inconnu",
+ "apiwarn-translate-alreadyreviewedbyyou": "Déjà marqué comme ayant été relu par vous"
+}
diff --git a/MLEB/Translate/i18n/api/gu.json b/MLEB/Translate/i18n/api/gu.json
new file mode 100644
index 00000000..553f884a
--- /dev/null
+++ b/MLEB/Translate/i18n/api/gu.json
@@ -0,0 +1,11 @@
+{
+ "@metadata": {
+ "authors": [
+ "NehalDaveND"
+ ]
+ },
+ "apihelp-query+messagecollection-param-group": "સંદેશ જૂથો",
+ "apihelp-query+messagecollection-param-language": "ભાષાસંહિતા:",
+ "apihelp-translatesandbox-param-do": "શું કરવું.",
+ "apihelp-translationstash-param-subaction": "ક્રિયા"
+}
diff --git a/MLEB/Translate/i18n/api/hi.json b/MLEB/Translate/i18n/api/hi.json
new file mode 100644
index 00000000..07c84af3
--- /dev/null
+++ b/MLEB/Translate/i18n/api/hi.json
@@ -0,0 +1,24 @@
+{
+ "@metadata": {
+ "authors": [
+ "NehalDaveND",
+ "Sfic"
+ ]
+ },
+ "apihelp-groupreview-param-group": "संदेश-समूह",
+ "apihelp-groupreview-param-language": "भाषा कोड",
+ "apihelp-query+languagestats-param-language": "भाषा कोड",
+ "apihelp-query+messagecollection-param-group": "संदेश-समूह",
+ "apihelp-query+messagecollection-param-language": "भाषा कोड",
+ "apihelp-translatesandbox-param-do": "क्या करें",
+ "apihelp-translationreview-example-1": "अवलोकन अवतरण 1",
+ "apihelp-translationstash-param-subaction": "क्रिया",
+ "apihelp-ttmserver-param-sourcelanguage": "स्रोत पाठ का भाषा कोड",
+ "apihelp-ttmserver-param-targetlanguage": "सुझाव का भाषा कोड",
+ "apihelp-ttmserver-param-text": "जिसके लिए सुझाव ढूँढना है, वह पाठ",
+ "apihelp-searchtranslations-description": "अनुवाद खोजें।",
+ "apihelp-searchtranslations-param-limit": "परिणाम का आकार",
+ "apihelp-searchtranslations-example-1": "भाषा का अनुवाद दिखाएँ",
+ "apierror-translate-invalidupdate": "अमान्य अद्यतन",
+ "apierror-translate-unknownmessage": "अज्ञात संदेश"
+}
diff --git a/MLEB/Translate/i18n/api/hu.json b/MLEB/Translate/i18n/api/hu.json
new file mode 100644
index 00000000..3553d655
--- /dev/null
+++ b/MLEB/Translate/i18n/api/hu.json
@@ -0,0 +1,107 @@
+{
+ "@metadata": {
+ "authors": [
+ "Tacsipacsi"
+ ]
+ },
+ "apihelp-aggregategroups-description": "Összesített üzenetcsoportok kezelése.\n\nHozzá tudsz adni és el tudsz távolítani összesített üzenetcsoportokat, és hozzájuk tudsz adni vagy el tudsz távolítani belőlük üzenetcsoportokat (egyszerre egyet).",
+ "apihelp-aggregategroups-summary": "Összesített üzenetcsoportok kezelése.",
+ "apihelp-aggregategroups-extended-description": "Összesített üzenetcsoportok hozzáadása és eltávolítása, illetve üzenetcsoportok hozzáadása egy összesített csoporthoz vagy eltávolítása onnan (egyszerre egy).",
+ "apihelp-aggregategroups-param-do": "Mit csináljon az összesített üzenetcsoporttal.",
+ "apihelp-aggregategroups-param-aggregategroup": "Összesített üzenetcsoport azonosítója.",
+ "apihelp-aggregategroups-param-group": "Üzenetcsoport azonosítója.",
+ "apihelp-aggregategroups-param-groupname": "Összesített üzenetcsoport neve.",
+ "apihelp-aggregategroups-param-groupdescription": "Összesített üzenetcsoport leírása.",
+ "apihelp-aggregategroups-example-1": "Üzenetcsoport hozzáadása",
+ "apihelp-groupreview-param-group": "Üzenetcsoport.",
+ "apihelp-groupreview-param-language": "Nyelvkód.",
+ "apihelp-groupreview-param-state": "A csoport új állapota.",
+ "apihelp-groupreview-example-1": "A „group-Example” csoport német fordításának állapota késznek jelölése",
+ "apihelp-query+languagestats-description": "Nyelvi statisztika lekérdezése.",
+ "apihelp-query+languagestats-summary": "Nyelvi statisztika lekérdezése.",
+ "apihelp-query+languagestats-param-timelimit": "Maximális idő, ami a hiányzó statisztikák kiszámításával tölthető. Ha nulla, csak a gyorsítótárazott eredményeket adja vissza az elejétől.",
+ "apihelp-query+languagestats-param-language": "Nyelvkód.",
+ "apihelp-query+languagestats-example-1": "A finn fordítások készültségi fokainak listája",
+ "apihelp-query+messagecollection-description": "MessageCollection lekérése a fordításokról.",
+ "apihelp-query+messagecollection-summary": "MessageCollection lekérése a fordításokról.",
+ "apihelp-query+messagecollection-param-group": "Üzenetcsoport.",
+ "apihelp-query+messagecollection-param-language": "Nyelvkód.",
+ "apihelp-query+messagecollection-param-limit": "Mennyi üzenetet mutasson (szűrés után).",
+ "apihelp-query+messagecollection-example-1": "Támogatott nyelvek listája",
+ "apihelp-query+messagegroups-summary": "Információk lekérése üzenetcsoportokról.",
+ "apihelp-query+messagegroups-extended-description": "A <var>uselang</var> paraméterrel befolyásolható a nyelvfüggő részek kimenete.",
+ "apihelp-query+messagegroups-param-depth": "A fa formátum használatakor a fa mélységének korlátozása ennyi szintre. A 0 jelenti csak a legfelső szint megjelenítését. A limit elérésekor a kimenet tartalmaz egy <code>groupcount</code> értéket, a közvetlen gyerekek számát.",
+ "apihelp-query+messagegroups-param-filter": "Csak a megadott azonosítójú üzenetek lekérése (a kis- és nagybetűk nem különbözőek, a * helyettesítő karakter).",
+ "apihelp-query+messagegroups-param-format": "A fa formátumban egyes üzenetcsoportok többször is szerepelhetnek a fában.",
+ "apihelp-query+messagegroups-param-iconsize": "A raszterizált ikon preferált mérete.",
+ "apihelp-query+messagegroups-param-root": "A fa formátum használatakor a legfelső szint helyett kezdés ettől az üzenetcsoporttól, aminek egy összesített csoportnak kell lennie. A lapos formátum használatakor csak az adott csoport visszaadása.",
+ "apihelp-query+messagegroups-example-1": "Üzenetcsoportok megjelenítése",
+ "apihelp-query+messagegroupstats-description": "Üzenetcsoport-statisztika lekérdezése.",
+ "apihelp-query+messagegroupstats-summary": "Üzenetcsoport-statisztika lekérdezése.",
+ "apihelp-query+messagegroupstats-param-timelimit": "Maximális idő, ami a hiányzó statisztikák kiszámításával tölthető. Ha nulla, csak a gyorsítótárazott eredményeket adja vissza az elejétől.",
+ "apihelp-query+messagegroupstats-param-group": "Üzenetcsoport azonosítója.",
+ "apihelp-query+messagetranslations-description": "Egy üzenet összes fordításának lekérdezése.",
+ "apihelp-query+messagetranslations-summary": "Egy üzenet összes fordításának lekérdezése.",
+ "apihelp-query+messagetranslations-param-title": "Egy ismert üzenet teljes címe.",
+ "apihelp-query+messagetranslations-example-1": "A „MediaWiki:January” fordításainak listázása a wikin.",
+ "apihelp-translatesandbox-param-do": "A végrehajtandó művelet.",
+ "apihelp-translatesandbox-param-userid": "A kezelendő felhasználók azonosítói, 0 a létrehozáshoz.",
+ "apihelp-translatesandbox-param-username": "Felhasználónév felhasználó létrehozásakor.",
+ "apihelp-translatesandbox-param-password": "Jelszó felhasználó létrehozásakor.",
+ "apihelp-translatesandbox-param-email": "E-mail-cím felhasználó létrehozásakor.",
+ "apihelp-translationaids-description": "Az összes fordítási segítség lekérdezése.",
+ "apihelp-translationaids-summary": "Az összes fordítási segítség lekérdezése.",
+ "apihelp-translationaids-param-title": "Egy ismert üzenet teljes címe.",
+ "apihelp-translationaids-param-group": "Üzenetcsoport, amihez az üzenet tartozik. Ha üres, az alapértelmezett üzenetcsoport lesz használva.",
+ "apihelp-translationaids-param-prop": "A befoglalandó fordítási segítségek.",
+ "apihelp-translationaids-example-1": "Segítségek megjelenítése a [[MediaWiki:January/fi]] üzenethez",
+ "apihelp-translationreview-description": "Fordítások átnézettnek jelölése.",
+ "apihelp-translationreview-summary": "Fordítások átnézettnek jelölése.",
+ "apihelp-translationreview-param-revision": "Az átnézettnek jelölendő változat azonosítója.",
+ "apihelp-translationreview-example-1": "Az 1. lapváltozat átnézettnek jelölése",
+ "apihelp-translationstash-param-subaction": "Művelet.",
+ "apihelp-translationstash-param-title": "A fordítási egység lapcíme.",
+ "apihelp-translationstash-param-translation": "A felhasználó fordítása.",
+ "apihelp-translationstash-param-metadata": "JSON-objektum.",
+ "apihelp-ttmserver-description": "Javaslatok lekérdezése a fordítási memóriából.",
+ "apihelp-ttmserver-summary": "Javaslatok lekérdezése a fordítási memóriából.",
+ "apihelp-ttmserver-param-sourcelanguage": "A forrásszöveg nyelvkódja.",
+ "apihelp-ttmserver-param-targetlanguage": "A javaslat nyelve.",
+ "apihelp-ttmserver-param-text": "A fordítandó szöveg.",
+ "apihelp-ttmserver-example-1": "Javaslatok lekérése a „Help” angolról finnre fordításához.",
+ "apihelp-searchtranslations-description": "Fordítások keresése.",
+ "apihelp-searchtranslations-summary": "Fordítások keresése.",
+ "apihelp-searchtranslations-param-query": "A keresendő szöveg.",
+ "apihelp-searchtranslations-param-sourcelanguage": "A forrásszöveg nyelvkódja.",
+ "apihelp-searchtranslations-param-language": "A keresendő nyelv kódja.",
+ "apihelp-searchtranslations-param-group": "Keresés ebben a csoportazonosítójú csoportban.",
+ "apihelp-searchtranslations-param-filter": "Az üzenet fordítási állapotának szűrése.",
+ "apihelp-searchtranslations-param-match": "Az összes/bármely szó keresése.",
+ "apihelp-searchtranslations-param-case": "Kis- és nagybetűk megkülönböztetése.",
+ "apihelp-searchtranslations-param-limit": "Az eredmény mérete.",
+ "apihelp-searchtranslations-example-1": "Fordítások megjelenítése a nyelvre.",
+ "apihelp-searchtranslations-example-2": "Lefordítatlan üzenetek megjelenítése, amik illeszkednek a lekérdezésre a forrásnyelven.",
+ "apihelp-translationcheck-description": "A megadott fordítás érvényesítése üzenet-ellenőrzőkkel.",
+ "apihelp-translationcheck-summary": "Fordítások ellenőrzése.",
+ "apihelp-translationcheck-param-title": "Lapcím névtérrel és nyelvkóddal.",
+ "apihelp-translationcheck-param-translation": "Az érvényesítendő fordítás.",
+ "apierror-translate-duplicateaggregategroup": "Az üzenetcsoport már létezik",
+ "apierror-translate-groupreviewdisabled": "Az üzenetcsoport-ellenőrzés nincs használatban.",
+ "apierror-translate-invalidaggregategroup": "Érvénytelen összesített üzenetcsoport",
+ "apierror-translate-invalidaggregategroupname": "Érvénytelen név összesített üzenetcsoportnak",
+ "apierror-translate-invalidgroup": "A csoport nem létezik vagy érvénytelen",
+ "apierror-translate-invalidstate": "A kért állapot érvénytelen.",
+ "apierror-translate-invalidlanguage": "A kért nyelv érvénytelen.",
+ "apierror-translate-invalidupdate": "Érvénytelen frissítés",
+ "apierror-translate-language-disabled": "$1 nyelvre a fordítás le van tiltva.",
+ "apierror-translate-language-disabled-reason": "$1 nyelvre a fordítás le van tiltva: $2",
+ "apierror-translate-language-disabled-source": "Ennek az üzenetcsoportnak a forrásnyelve $1. Válassz egy másik nyelvet a fordításhoz.",
+ "apierror-translate-nodynamicgroups": "A dinamikus üzenetcsoportok itt nem támogatottak",
+ "apierror-translate-nomessagefortitle": "A cím nem tartozik egy lefordítható üzenethez",
+ "apierror-translate-notranslationservices": "Nincs beállítva nyilvános fordítási szolgáltatás. A wiki rendszergazdájának be kell állítania megfelelően a <var>$wgTranslateTranslationServices</var> változót, mielőtt ez a funkció használható lenne.",
+ "apierror-translate-owntranslation": "Nem lehet ellenőrizni saját fordításokat",
+ "apierror-translate-sandboxdisabled": "A homokozófunkció nincs használatban",
+ "apierror-translate-sandbox-invalidppassword": "Érvénytelen jelszó",
+ "apierror-translate-unknownmessage": "Ismeretlen üzenet",
+ "apiwarn-translate-alreadyreviewedbyyou": "Már megjelölted átnézettként"
+}
diff --git a/MLEB/Translate/i18n/api/id.json b/MLEB/Translate/i18n/api/id.json
new file mode 100644
index 00000000..a5188f30
--- /dev/null
+++ b/MLEB/Translate/i18n/api/id.json
@@ -0,0 +1,13 @@
+{
+ "@metadata": {
+ "authors": [
+ "Gombang"
+ ]
+ },
+ "apihelp-aggregategroups-param-group": "ID kelompok pesan.",
+ "apihelp-groupreview-param-group": "Kelompok pesan.",
+ "apihelp-groupreview-param-language": "Kode bahasa.",
+ "apihelp-query+languagestats-param-language": "Kode bahasa.",
+ "apihelp-query+messagecollection-param-group": "Kelompok pesan.",
+ "apihelp-query+messagecollection-param-language": "Kode bahasa."
+}
diff --git a/MLEB/Translate/i18n/api/is.json b/MLEB/Translate/i18n/api/is.json
new file mode 100644
index 00000000..73a83492
--- /dev/null
+++ b/MLEB/Translate/i18n/api/is.json
@@ -0,0 +1,25 @@
+{
+ "@metadata": {
+ "authors": [
+ "Sveinn í Felli",
+ "MyraMidnight"
+ ]
+ },
+ "apihelp-groupreview-param-group": "Þýðingahópur.",
+ "apihelp-groupreview-param-language": "Tungumálakóði.",
+ "apihelp-query+languagestats-param-language": "Tungumálakóði.",
+ "apihelp-query+messagecollection-param-group": "Þýðingahópur.",
+ "apihelp-query+messagecollection-param-language": "Tungumálakóði.",
+ "apihelp-query+messagecollection-param-limit": "Hversu mörg skilaboð viltu sjá (eftir síun).",
+ "apihelp-query+messagetranslations-param-title": "Heildartitill á þekktum skilaboðum.",
+ "apihelp-translatesandbox-param-do": "Hvað skal gera.",
+ "apihelp-translatesandbox-param-username": "Notendanafn við sköpun notanda.",
+ "apihelp-translatesandbox-param-password": "Lykilorð við sköpun notanda.",
+ "apihelp-translatesandbox-param-email": "Netfang við sköpun notanda.",
+ "apihelp-translationaids-param-title": "Heildartitill á þekktum skilaboðum.",
+ "apihelp-translationstash-param-subaction": "Aðgerð.",
+ "apihelp-searchtranslations-description": "Leita í þýðingum",
+ "apihelp-searchtranslations-summary": "Leita í þýðingum",
+ "apierror-translate-sandbox-invalidppassword": "Ógilt lykilorð",
+ "apierror-translate-unknownmessage": "Ókunn skilaboð"
+}
diff --git a/MLEB/Translate/i18n/api/ja.json b/MLEB/Translate/i18n/api/ja.json
new file mode 100644
index 00000000..ff4bc40c
--- /dev/null
+++ b/MLEB/Translate/i18n/api/ja.json
@@ -0,0 +1,10 @@
+{
+ "@metadata": {
+ "authors": [
+ "Sujiniku"
+ ]
+ },
+ "apihelp-aggregategroups-param-group": "メッセージグループID",
+ "apihelp-query+messagegroupstats-param-group": "メッセージグループID。",
+ "apihelp-searchtranslations-param-match": "任意/すべて の検索ワードのマッチ"
+}
diff --git a/MLEB/Translate/i18n/api/kab.json b/MLEB/Translate/i18n/api/kab.json
new file mode 100644
index 00000000..9a0e09a4
--- /dev/null
+++ b/MLEB/Translate/i18n/api/kab.json
@@ -0,0 +1,37 @@
+{
+ "@metadata": {
+ "authors": [
+ "Belkacem77"
+ ]
+ },
+ "apihelp-groupreview-param-group": "Agraw n yiznan.",
+ "apihelp-groupreview-param-language": "Tangalt n tutlayt.",
+ "apihelp-query+languagestats-description": "Asuter n tidaddanin n tutlayt.",
+ "apihelp-query+languagestats-summary": "Asuter n tidaddanin n tutlayt.",
+ "apihelp-query+languagestats-param-language": "Tangalt n tutlayt.",
+ "apihelp-query+messagecollection-param-group": "Agraw n yiznan.",
+ "apihelp-query+messagecollection-param-language": "Tangalt n tutlayt.",
+ "apihelp-query+messagecollection-example-1": "Tabdart n tutlayin yettwasefraken",
+ "apihelp-query+messagegroups-example-1": "Sken igrawen n yiznan",
+ "apihelp-query+messagetranslations-param-title": "Azwel ummid n yizen yettwassnen.",
+ "apihelp-query+messagetranslations-example-1": "Abdart n tsuqilin deg uwiki i \"MediaWiki:Yennayer\"",
+ "apihelp-translatesandbox-param-do": "Acu ara txedmeḍ.",
+ "apihelp-translatesandbox-param-userid": "Isulay n iseqdacen yettwasefraken. Seqdec 0 i tmerna.",
+ "apihelp-translatesandbox-param-username": "Ismawen n iseqdacen di tmerna n useqdac.",
+ "apihelp-translatesandbox-param-password": "Awal uffir di tmerna n useqdac.",
+ "apihelp-translatesandbox-param-email": "Imayl di tmerna n useqdac.",
+ "apihelp-translationaids-description": "Anadai n tallelt n tsuqilt",
+ "apihelp-translationaids-summary": "Anadi n tallelt n tsuqilt.",
+ "apihelp-translationaids-param-title": "Azwel ummid n yizen yettwassnen.",
+ "apihelp-translationaids-param-group": "Agraw n yiznan aniɣer ittekka yizen. Ma d ilem, ihi agraw agejdan yettwaseqdec.",
+ "apihelp-translationaids-param-prop": "Anta tallelt n tsuqilt ara yeddun.",
+ "apihelp-ttmserver-param-sourcelanguage": "Tangalt n tutlayt n uḍris n uɣbalu.",
+ "apihelp-searchtranslations-description": "Nadi tisuqilin.",
+ "apihelp-searchtranslations-param-service": "Anwa imeẓla n tsuqilt yellan ara tesqedceḍ.",
+ "apihelp-searchtranslations-param-sourcelanguage": "Tangalt n tutlayt n uḍris n uɣbalu.",
+ "apihelp-searchtranslations-param-filter": "Imzizdeg n waddad n tsuqilt n yizen.",
+ "apihelp-searchtranslations-example-1": "Sken tisuqilin i tutlayt.",
+ "apierror-translate-fuzzymessage": "Ur tezmireḍ ara ad alseḍ taɣuri n tsuqilt ur yemmiden ara",
+ "apierror-translate-language-disabled": "Tasuqilt ar tutlayt-agi tensa",
+ "apierror-translate-owntranslation": "Ur tezmireḍ ara ad alseḍ taɣuri n tsuqilt i teggid"
+}
diff --git a/MLEB/Translate/i18n/api/kk-cyrl.json b/MLEB/Translate/i18n/api/kk-cyrl.json
new file mode 100644
index 00000000..ac33f3dd
--- /dev/null
+++ b/MLEB/Translate/i18n/api/kk-cyrl.json
@@ -0,0 +1,20 @@
+{
+ "@metadata": {
+ "authors": [
+ "Arystanbek"
+ ]
+ },
+ "apihelp-query+languagestats-param-language": "Тіл коды",
+ "apihelp-query+messagecollection-param-group": "Хабарлама тобы.",
+ "apihelp-query+messagecollection-param-language": "Тіл коды.",
+ "apihelp-query+messagecollection-param-limit": "Қанша хабарлама көрсету (сүзгілеуден кейін).",
+ "apihelp-query+messagegroups-example-1": "Хабарлама топтарфн көрсету",
+ "apihelp-query+messagegroupstats-param-group": "Хабарлама топ сәйкестендіогіші.",
+ "apihelp-translatesandbox-param-username": "Қатысушы есімі қатысушы бастау кезінде.",
+ "apihelp-translatesandbox-param-password": "Құптя сөз қатысушы пайдалану кезінде.",
+ "apihelp-translatesandbox-param-email": "Е-почта қатысушы бастау кезінде.",
+ "apihelp-translationstash-param-subaction": "Әрекет",
+ "apihelp-translationstash-param-title": "Аударма бірлік бетінің тақырыбы.",
+ "apihelp-translationstash-param-translation": "Қатысушы арқылы жасадған аудармалар.",
+ "apihelp-translationstash-param-metadata": "JSON обьект."
+}
diff --git a/MLEB/Translate/i18n/api/km.json b/MLEB/Translate/i18n/api/km.json
new file mode 100644
index 00000000..0d184366
--- /dev/null
+++ b/MLEB/Translate/i18n/api/km.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "គីមស៊្រុន"
+ ]
+ },
+ "apihelp-query+languagestats-param-language": "កូដ​ភាសា។"
+}
diff --git a/MLEB/Translate/i18n/api/ko.json b/MLEB/Translate/i18n/api/ko.json
new file mode 100644
index 00000000..72d8e34c
--- /dev/null
+++ b/MLEB/Translate/i18n/api/ko.json
@@ -0,0 +1,37 @@
+{
+ "@metadata": {
+ "authors": [
+ "LiteHell",
+ "아라",
+ "Ykhwong"
+ ]
+ },
+ "apihelp-aggregategroups-param-group": "메시지 그룹 ID.",
+ "apihelp-groupreview-param-group": "메시지 그룹.",
+ "apihelp-groupreview-param-language": "언어 코드.",
+ "apihelp-query+languagestats-param-language": "언어 코드입니다.",
+ "apihelp-query+messagecollection-param-group": "메시지 그룹.",
+ "apihelp-query+messagecollection-param-language": "언어 코드.",
+ "apihelp-query+messagecollection-example-1": "지원하는 언어 목록",
+ "apihelp-query+messagegroups-summary": "메시지 그룹에 관한 정보를 반환합니다.",
+ "apihelp-query+messagegroupstats-param-group": "메시지 그룹 ID입니다.",
+ "apihelp-translationreview-summary": "번역을 검토한 것으로 표시합니다.",
+ "apihelp-ttmserver-param-targetlanguage": "제안의 언어 코드입니다.",
+ "apihelp-searchtranslations-param-query": "검색할 문자열입니다.",
+ "apihelp-searchtranslations-param-limit": "결과의 크기입니다.",
+ "apihelp-translationcheck-summary": "번역을 확인합니다.",
+ "apihelp-translationcheck-param-title": "이름공간과 언어 코드가 있는 문서 제목입니다.",
+ "apihelp-translationcheck-param-translation": "확인할 번역입니다.",
+ "apierror-translate-duplicateaggregategroup": "메시지 그룹이 이미 존재합니다",
+ "apierror-translate-invalidgroup": "그룹이 존재하지 않거나 잘못되었습니다",
+ "apierror-translate-invalidlanguage": "요청한 언어는 유효하지 않습니다.",
+ "apierror-translate-invalidupdate": "유효하지 않은 업데이트",
+ "apierror-translate-language-disabled": "$1로의 번역이 비활성화되어 있습니다.",
+ "apierror-translate-language-disabled-reason": "$1로의 번역이 비활성화되어 있습니다: $2",
+ "apierror-translate-language-disabled-source": "이 그룹의 원본 언어는 $1입니다. 번역 결과물에 쓰일 다른 언어를 선택해 주십시오.",
+ "apierror-translate-nomessagefortitle": "제목이 번역 가능한 메시지와 일치하지 않습니다",
+ "apierror-translate-notranslationservices": "공용 번역 서비스가 구성되지 않았습니다. 위키 관리자는 <var>$wgTranslateTranslationServices</var>를 적절하게 설정해야 이 기능을 사용할 수 있습니다.",
+ "apierror-translate-sandboxdisabled": "연습장 기능을 사용하고 있지 않습니다",
+ "apierror-translate-sandbox-invalidppassword": "유효하지 않은 비밀번호",
+ "apierror-translate-unknownmessage": "알 수 없는 메시지"
+}
diff --git a/MLEB/Translate/i18n/api/ku-latn.json b/MLEB/Translate/i18n/api/ku-latn.json
new file mode 100644
index 00000000..5be63017
--- /dev/null
+++ b/MLEB/Translate/i18n/api/ku-latn.json
@@ -0,0 +1,14 @@
+{
+ "@metadata": {
+ "authors": [
+ "George Animal",
+ "Bikarhêner"
+ ]
+ },
+ "apihelp-groupreview-param-language": "Koda ziman.",
+ "apihelp-query+languagestats-param-language": "Koda ziman.",
+ "apihelp-query+messagecollection-param-group": "Koma peyamê.",
+ "apihelp-query+messagecollection-param-language": "Koda zimanê.",
+ "apihelp-query+messagegroups-example-1": "Komên peyaman nîşan bide",
+ "apihelp-searchtranslations-description": "Li wergeran bigere."
+}
diff --git a/MLEB/Translate/i18n/api/li.json b/MLEB/Translate/i18n/api/li.json
new file mode 100644
index 00000000..cdfe5e16
--- /dev/null
+++ b/MLEB/Translate/i18n/api/li.json
@@ -0,0 +1,61 @@
+{
+ "@metadata": {
+ "authors": [
+ "Ooswesthoesbes"
+ ]
+ },
+ "apihelp-aggregategroups-param-group": "Berichgroep-id.",
+ "apihelp-groupreview-param-group": "Berichgroep.",
+ "apihelp-groupreview-param-language": "Spraokcode.",
+ "apihelp-query+languagestats-description": "Queryspraokstattestieke.",
+ "apihelp-query+languagestats-summary": "Queryspraokstattestieke.",
+ "apihelp-query+languagestats-param-language": "Spraokcode.",
+ "apihelp-query+messagecollection-param-group": "Berichgroep.",
+ "apihelp-query+messagecollection-param-language": "Spraokcode.",
+ "apihelp-query+messagecollection-param-limit": "Wieväöl te tuine berichte (nao filter).",
+ "apihelp-query+messagecollection-example-1": "Lies van óngerstäönde spraoke",
+ "apihelp-query+messagegroups-example-1": "Tuin berichtegruup",
+ "apihelp-query+messagegroupstats-param-group": "Berichgroep-id.",
+ "apihelp-query+messagegroupstats-example-1": "Liest mit euverzèttingsstattestieke veur de groep \"page-Example\"",
+ "apihelp-query+messagetranslations-description": "Vraog alle euverzèttinge veur ei berich op.",
+ "apihelp-query+messagetranslations-summary": "Vraog alle euverzèttinge veur ei berich op.",
+ "apihelp-query+messagetranslations-param-title": "Gansen titel van e bekand berich.",
+ "apihelp-query+messagetranslations-example-1": "Lies mit euverzèttinge in de wiki veur \"MediaWiki:January\"",
+ "apihelp-translatesandbox-description": "Sjrief dich in en behieër zandjbakgebroekers.",
+ "apihelp-translatesandbox-summary": "Sjrief dich in en behieër zandjbakgebroekers.",
+ "apihelp-translatesandbox-param-do": "Waat te doon.",
+ "apihelp-translatesandbox-param-userid": "Gebroeker-id's van gebroekers die waere behieërd. Gebroek 0 veur aanmake.",
+ "apihelp-translatesandbox-param-username": "Gebroekersnaam bie 't aanmake.",
+ "apihelp-translatesandbox-param-password": "Wachwaord bie 't aanmake van de gebroeker.",
+ "apihelp-translatesandbox-param-email": "E-mailadres bie 't aanmake van de gebroeker.",
+ "apihelp-translationaids-description": "Vraog alle euverzèttingshölpmiddele op.",
+ "apihelp-translationaids-summary": "Vraog alle euverzèttingshölpmiddele op.",
+ "apihelp-translationaids-param-title": "Gansen titel van e bekand berich.",
+ "apihelp-translationaids-example-1": "Tuin hölpmiddele veur [[MediaWiki:January/fi]]",
+ "apihelp-translationreview-description": "Markeer euverzèttinge es proofgelaeze.",
+ "apihelp-translationreview-summary": "Markeer euverzèttinge es proofgelaeze.",
+ "apihelp-translationreview-param-revision": "Proof te laeze bewirkingsnómmer.",
+ "apihelp-translationreview-example-1": "Laes bewirking 1 proof",
+ "apihelp-translationstash-description": "Veug euverzètte tou aan stash.",
+ "apihelp-translationstash-summary": "Veug euverzètte tou aan stash.",
+ "apihelp-translationstash-param-subaction": "Hanjeling.",
+ "apihelp-translationstash-param-title": "Naam van de euverzèttingseinheidspagina.",
+ "apihelp-translationstash-param-translation": "Euverzètting gemaak door de gebroeker.",
+ "apihelp-translationstash-param-metadata": "JSON-veurwerp.",
+ "apihelp-searchtranslations-description": "Doorzeuk euverzèttinge.",
+ "apihelp-searchtranslations-summary": "Doorzeuk euverzèttinge.",
+ "apihelp-searchtranslations-param-query": "De te zeuke string.",
+ "apihelp-searchtranslations-param-sourcelanguage": "De spraokcode veur de brónteks.",
+ "apihelp-searchtranslations-param-language": "De spraokcode veur de string van te zeuke.",
+ "apihelp-searchtranslations-param-group": "De groep-id veur de string van te zeuke.",
+ "apihelp-searchtranslations-param-limit": "Gruuedje van 't rizzeltaot.",
+ "apihelp-searchtranslations-example-1": "Tuin euverzèttinge veur de spraok.",
+ "apihelp-searchtranslations-example-2": "Tuin óneuvergezatje berichte die euvereinkómme mit de query in de brónspraok.",
+ "apierror-translate-invalidupdate": "Óngeljigen update",
+ "apierror-translate-language-disabled": "Euverzètte nao dees spraok is oetgezatj",
+ "apierror-translate-owntranslation": "Kan gein eige euverzèttinge prooflaeze",
+ "apierror-translate-sandboxdisabled": "Zandjbak-óngerdeil weurt neet gebroek",
+ "apierror-translate-sandbox-invalidppassword": "Óngeljig wachwaord",
+ "apierror-translate-unknownmessage": "Ónbekand berich",
+ "apiwarn-translate-alreadyreviewedbyyou": "Al gemarkeerd es proofgelaeze door dich"
+}
diff --git a/MLEB/Translate/i18n/api/lki.json b/MLEB/Translate/i18n/api/lki.json
new file mode 100644
index 00000000..5233acd0
--- /dev/null
+++ b/MLEB/Translate/i18n/api/lki.json
@@ -0,0 +1,10 @@
+{
+ "@metadata": {
+ "authors": [
+ "Hosseinblue"
+ ]
+ },
+ "apihelp-groupreview-param-language": "کد زوون.",
+ "apihelp-query+languagestats-param-language": "کد زوون.",
+ "apihelp-query+messagecollection-example-1": "لیست زوونةل پشتیبانی بی"
+}
diff --git a/MLEB/Translate/i18n/api/lt.json b/MLEB/Translate/i18n/api/lt.json
new file mode 100644
index 00000000..2debe428
--- /dev/null
+++ b/MLEB/Translate/i18n/api/lt.json
@@ -0,0 +1,36 @@
+{
+ "@metadata": {
+ "authors": [
+ "Eitvys200"
+ ]
+ },
+ "apihelp-translatesandbox-param-do": "Ką daryti.",
+ "apihelp-translatesandbox-param-username": "Vartotojo vardas, kai kuriamas vartotojas.",
+ "apihelp-translatesandbox-param-password": "Slaptažodis, kai kuriamas vartotojas.",
+ "apihelp-translatesandbox-param-email": "El. paštas, kai kuriamas vartotojas.",
+ "apihelp-translationaids-param-title": "Pilnas žinomos žinutės pavadinimas.",
+ "apihelp-translationaids-param-prop": "Kuriuos vertimo pagalbininkus įtraukti.",
+ "apihelp-translationreview-description": "Pažymėti vertimus peržiūrėtais.",
+ "apihelp-translationstash-param-subaction": "Veiksmas.",
+ "apihelp-translationstash-param-translation": "Vertimas atliktas vartotojo.",
+ "apihelp-translationstash-param-metadata": "JSON objektas.",
+ "apihelp-ttmserver-param-service": "Kurią iš galimų vertimų paslaugų naudoti.",
+ "apihelp-ttmserver-param-sourcelanguage": "Šaltinio teksto kalbos kodas.",
+ "apihelp-ttmserver-param-targetlanguage": "Siūlymo kalbos kodas.",
+ "apihelp-ttmserver-param-text": "Tekstas, kuriam rasti pasiūlymus.",
+ "apihelp-ttmserver-example-1": "Gauti pasiūlymus verčiant „Help“ iš anglų k. į suomių k.",
+ "apihelp-searchtranslations-description": "Ieškoti vertimų.",
+ "apihelp-searchtranslations-param-service": "Kurią iš galimų vertimų paslaugų naudoti.",
+ "apihelp-searchtranslations-param-sourcelanguage": "Šaltinio teksto kalbos kodas.",
+ "apihelp-searchtranslations-param-filter": "Pranešimo vertimo būsenos filtras.",
+ "apihelp-searchtranslations-param-limit": "Rezultato dydis.",
+ "apihelp-searchtranslations-example-1": "Rodyti kalbos vertimus.",
+ "apierror-translate-duplicateaggregategroup": "Pranešimo grupė jau egzistuoja",
+ "apierror-translate-invalidlanguage": "Prašoma kalba negalima.",
+ "apierror-translate-invalidupdate": "Negalimas atnaujinimas",
+ "apierror-translate-language-disabled": "Vertimai į šią kalbą yra išjungtas",
+ "apierror-translate-owntranslation": "Negalima peržiūrėti savo paties vertimų",
+ "apierror-translate-sandboxdisabled": "Smėlio dėžės funkcija nenaudojama",
+ "apierror-translate-unknownmessage": "Nežinomas pranešimas",
+ "apiwarn-translate-alreadyreviewedbyyou": "Jau pažymėtas kaip peržiūrėtas jūsų"
+}
diff --git a/MLEB/Translate/i18n/api/my.json b/MLEB/Translate/i18n/api/my.json
new file mode 100644
index 00000000..7133ad72
--- /dev/null
+++ b/MLEB/Translate/i18n/api/my.json
@@ -0,0 +1,27 @@
+{
+ "@metadata": {
+ "authors": [
+ "Dr Lotus Black"
+ ]
+ },
+ "apihelp-groupreview-param-group": "မက်ဆေ့အုပ်စု",
+ "apihelp-groupreview-param-language": "ဘာသာစကားကုဒ်",
+ "apihelp-query+languagestats-param-language": "ဘာသာစကားကုဒ်",
+ "apihelp-query+messagecollection-param-group": "မက်ဆေ့အုပ်စု",
+ "apihelp-query+messagecollection-param-language": "ဘာသာစကားကုဒ်",
+ "apihelp-query+messagecollection-example-1": "ထောက်ပံ့ထားသော ဘာသားစကားများ၏ စာရင်း",
+ "apihelp-query+messagegroups-example-1": "မက်ဆေ့အုပ်စုများကို ပြရန်",
+ "apihelp-query+messagegroupstats-param-group": "မက်ဆေ့အုပ်စု အိုင်ဒီ",
+ "apihelp-translationstash-param-translation": "အသုံးပြုသူ လုပ်ဆောင်သော ဘာသာပြန်ဆိုမှု။",
+ "apihelp-searchtranslations-description": "ဘာသာပြန်များ ရှာဖွေရန်။",
+ "apihelp-searchtranslations-summary": "ဘာသာပြန်များ ရှာဖွေရန်။",
+ "apihelp-searchtranslations-example-1": "ဘာသာစကားအတွက် ဘာသာပြန်များကို ပြရန်။",
+ "apierror-translate-duplicateaggregategroup": "မက်ဆေ့အုပ်စု ရှိနှင့်ပြီးဖြစ်သည်",
+ "apierror-translate-language-disabled": "$1 သို့ ဘာသာပြန်ခြင်းကို ပိတ်ထားသည်။",
+ "apierror-translate-language-disabled-reason": "$1 သို့ ဘာသာပြန်ခြင်းကို ပိတ်ထားသည်: $2",
+ "apierror-translate-language-disabled-source": "ဤအုပ်စု၏ ရင်းမြစ်ဘာသာစကားမှာ $1 ဖြစ်သည်။ ကျေးဇူးပြု၍ ဘာသာပြန်ရန် အခြားဘာသာစကားကို ရွေးချယ်ပါ။",
+ "apierror-translate-owntranslation": "ကိုယ်ပိုင်ဘာသာပြန်များကို ပြန်လည်မဆန်းစစ်နိုင်ပါ",
+ "apierror-translate-sandbox-invalidppassword": "မဆီလျော်သော စကားဝှက်",
+ "apierror-translate-unknownmessage": "အမည်မသိ မက်ဆေ့",
+ "apiwarn-translate-alreadyreviewedbyyou": "သင်မှ ပြန်လည်ဆန်းစစ်ပြီးဖြစ်ကြောင်း လုပ်ဆောင်နှင့်ပြီးသားဖြစ်သည်"
+}
diff --git a/MLEB/Translate/i18n/api/nb.json b/MLEB/Translate/i18n/api/nb.json
new file mode 100644
index 00000000..0e3fef32
--- /dev/null
+++ b/MLEB/Translate/i18n/api/nb.json
@@ -0,0 +1,106 @@
+{
+ "@metadata": {
+ "authors": [
+ "Kingu",
+ "Jon Harald Søby"
+ ]
+ },
+ "apihelp-aggregategroups-description": "Behandle aggregatmeldingsgrupper.\n\nDu kan legge til og fjerne aggregatmeldingsgrupper og assosiere eller disassosiere meldingsgrupper fra dem (én av gangen).",
+ "apihelp-aggregategroups-summary": "Behandle aggregatmeldingsgrupper.",
+ "apihelp-aggregategroups-extended-description": "Du kan legge til og fjerne aggregatmeldingsgrupper og assosiere eller disassosiere meldingsgrupeer fra dem (én av gangen).",
+ "apihelp-aggregategroups-param-do": "Hva som skal gjøres med aggregatmeldingsgruppa.",
+ "apihelp-aggregategroups-param-aggregategroup": "ID for aggregatmeldingsgruppe.",
+ "apihelp-aggregategroups-param-group": "Meldingsgruppe-ID.",
+ "apihelp-aggregategroups-param-groupname": "Navn på aggregatmeldingsgruppe.",
+ "apihelp-aggregategroups-param-groupdescription": "Beskrivelse for aggregatmeldingsgruppe.",
+ "apihelp-aggregategroups-example-1": "Assosier ei gruppe",
+ "apihelp-groupreview-description": "Sett arbeidsflytstilstander for meldingsgruppe.",
+ "apihelp-groupreview-summary": "Sett arbeidsflytstilstander for meldingsgruppe.",
+ "apihelp-groupreview-param-group": "Beskjedgruppe.",
+ "apihelp-groupreview-param-language": "Språkkode.",
+ "apihelp-groupreview-param-state": "Gruppas nye tilstand.",
+ "apihelp-groupreview-example-1": "Merk tilstanden for den tyske oversettelsen av meldingsgruppa «group-Example» som klar",
+ "apihelp-query+languagestats-description": "Spør om språkstatistikk.",
+ "apihelp-query+languagestats-summary": "Spør om språkstatistikk.",
+ "apihelp-query+languagestats-param-timelimit": "Maksimal tid som skal brukes til å regne ut manglende statistikk. Om den er satt til null vil bare de mellomlagrede resultatene fra begynnelsen returneres.",
+ "apihelp-query+languagestats-param-language": "Språkkode.",
+ "apihelp-query+languagestats-example-1": "Liste over statistikk for fullførte oversettelser til finsk",
+ "apihelp-query+messagecollection-description": "Spør MessageCollection om oversettelser.",
+ "apihelp-query+messagecollection-summary": "Spør MessageCollection om oversettelser.",
+ "apihelp-query+messagecollection-param-group": "Beskjedgruppe.",
+ "apihelp-query+messagecollection-param-language": "Språkkode.",
+ "apihelp-query+messagecollection-param-limit": "Hvor mange beskjeder som skal vises (etter filtrering).",
+ "apihelp-query+messagecollection-param-offset": "Heltall eller nøkkeloffset til å begynne med.",
+ "apihelp-query+messagecollection-param-filter": "Filtre for beskjedsamlinger. Bruk <kbd>!</kbd> for å få motsatt av den gitte effekten. For eksempel vil betyr <kbd>!fuzzy</kbd> alle beskjeder som ikke er fuzzy. Filtre brukes i den ordren som blir gitt.\n;fuzzy:Beskjeder med fuzzy-tagg.\n;optional:Beskjeder som kun bør oversettes om det er nødvendig å endre dem.\n;ignored:Beskjeder som aldre oversettes.\n;hastranslation:Beskjeder som har en oversettelse uavhengig av om den er fuzzy eller ikke.\n;translated:Beskjeder som har en oversettelse som ikke er fuzzy.\n;changed:Beskjeder som har blitt oversatt eller endret siden forrige eksport.\n;reviewer&#58;N: Beskjeder der bruker nummer <kbd>N</kbd> er blant godkjennerne.\n;last-translator&#58;N:Beskjeder der bruker nummer <kbd>N</kbd> er siste oversetter.",
+ "apihelp-query+messagecollection-param-prop": "Hvilke egenskaper som skal hentes:\n;definition:Beskjeddefinisjonen.\n;translation:Den gjeldende oversettelsen (uten $1-streng om det er noen, bruk taggene for å sjekke utdaterte eller ugyldige oversettelser).\n;tags:Beskjedtagger, som optional, ignored og fuzzy.\n;properties:Beskjedegenskaper, som status, revision, last-translator. Kan variere mellom beskjeder.\n;revision:<span class=\"deprecated\">Foreldet!</span> Bruk $2prop=properties.",
+ "apihelp-query+messagecollection-example-1": "Liste over støttede språk",
+ "apihelp-query+messagecollection-example-2": "Liste over ikke-valgfrie beskjeddefinisjoner for gruppa «page-Example»",
+ "apihelp-query+messagecollection-example-3": "Liste over valgfrie beskjeder på finsk med tagger for gruppa «page-Example»",
+ "apihelp-query+messagecollection-example-4": "Mer informasjon om de siste oversettelsesrevisjonene for gurppa «page-Example»",
+ "apihelp-query+messagegroups-description": "Returnerer informasjon om beskjedgrupper.\n\nMerk at parameteren uselang påvirker resultatet i språkavhengige deler.",
+ "apihelp-query+messagegroups-summary": "Returnerer informasjon om beskjedgrupper.",
+ "apihelp-query+messagegroups-extended-description": "Merk at parameteren uselang påvirker resultatet i språkavhengige deler.",
+ "apihelp-query+messagegroups-param-depth": "Når du bruker treformatet, begrens dybden til så mange nivåer. Verdien 0 betyr at ingen undergrupper vises. Hvis grensa nås vil resultatet inkludere en «groupcount»-verdi, som viser antall direkte barn.",
+ "apihelp-query+messagegroups-param-filter": "Bare returner beskjeder med ID-er som matcher én eller flere av de gitte innputtene (uavhengig av små og store bokstaver, atskilt med vertikalstreker, * som jokertegn).",
+ "apihelp-query+messagegroups-param-format": "I treformatet kan beskjedgrupper eksistere på flere steder i treet.",
+ "apihelp-query+messagegroups-param-iconsize": "Foretrukket størrelse for rasterisert gruppeikon.",
+ "apihelp-query+messagegroups-param-prop": "Hvilken oversettelsesrelatert informasjon som skal hentes:\n;id:Inkluder gruppas ID.\n;label:Inkluder gruppas etikett.\n;description:Inkluder gruppas beskrivelse.\n;class:Inkluder gruppas klasse.\n;namespace:Inkluder gruppas navnerom. Ikke alle grupper hører til ett enkelt navnerom.\n;exists:Inkluder selv-utregnet eksistensegenskap for gruppa.\n;icon:Inkluder URL-er til gruppas ikon.\n;priority:Inkluder prioritetsstatus, som f.eks. frarådet.\n;prioritylangs:Inkluder foretrukne språk. Om det ikke er satt returnerer denne false.\n;priorityforce:Inkluder prioritetsstatus – er innstillingen for prioriterte språk påtvunget?\n;workflowstates:Inkluder arbeidsflyttilstanden for beskjedgruppa.",
+ "apihelp-query+messagegroups-param-root": "Under bruk av treformatet: I stedet for å starte fra toppnivået, start fra den gitte meldingsgruppa, som må være en aggregatgruppe. Når du bruker flatt format vil bare den gitte gruppa returneres.",
+ "apihelp-query+messagegroups-example-1": "Vis beskjedgrupper",
+ "apihelp-query+messagegroupstats-description": "Kjør spørring på meldingsgruppestatistikk.",
+ "apihelp-query+messagegroupstats-summary": "Kjør spørring på meldingsgruppestatistikk.",
+ "apihelp-query+messagegroupstats-param-timelimit": "Maksimal tid som skal brukes på å regne ut manglende statistikk. Om denne er satt til null vil bare de mellomlagrede resultatene fra begynnelsen returneres.",
+ "apihelp-query+messagegroupstats-param-group": "Meldingsgruppe-ID.",
+ "apihelp-query+messagegroupstats-example-1": "List opp oversettelsesfullføringsstatistikk for gruppa «page-Example»",
+ "apihelp-query+messagetranslations-description": "Spør om alle oversettelser av en enkelt beskjed.",
+ "apihelp-query+messagetranslations-summary": "Spør om alle oversettelser av en enkelt beskjed.",
+ "apihelp-query+messagetranslations-param-title": "Den hele tittelen til en kjent beskjed.",
+ "apihelp-query+messagetranslations-example-1": "List opp oversettelser i wikien for «MediaWiki:January»",
+ "apihelp-translatesandbox-description": "Registrering og behandling av sandkassebrukere.",
+ "apihelp-translatesandbox-summary": "Registrering og behandling av sandkassebrukere.",
+ "apihelp-translatesandbox-param-do": "Hva som skal gjøres.",
+ "apihelp-translatesandbox-param-userid": "Bruker-ID-ene til brukerne som behandles. Bruk 0 for opprettelser.",
+ "apihelp-translatesandbox-param-username": "Brukernavn når ny bruker opprettes.",
+ "apihelp-translatesandbox-param-password": "Passord når ny bruker opprettes.",
+ "apihelp-translatesandbox-param-email": "Epost når ny bruker opprettes.",
+ "apihelp-translationaids-description": "Spør mot alle oversettelseshjelpemilder.",
+ "apihelp-translationaids-summary": "Spør mot alle oversettelseshjelpemidler.",
+ "apihelp-translationaids-param-title": "Den hele tittelen til en kjent beskjed.",
+ "apihelp-translationaids-param-group": "Meldingsgruppe meldingsgruppa hører til. Om den er tom vil primærgruppa brukes.",
+ "apihelp-translationaids-param-prop": "Hvilke oversettelseshjelpemidler som skal inkluderes.",
+ "apihelp-translationaids-example-1": "Vis hjelp for [[MediaWiki:January/fi]]",
+ "apihelp-translationreview-description": "Mark oversettelser som godkjent.",
+ "apihelp-translationreview-summary": "Merk oversettelser som godkjent.",
+ "apihelp-translationreview-param-revision": "Revisjonsnummeret som skal godkjennes.",
+ "apihelp-translationreview-example-1": "Godkjenn revisjon 1",
+ "apihelp-translationstash-description": "Legg til oversettelser til stashen.",
+ "apihelp-translationstash-summary": "Legg til oversettelser til stashen.",
+ "apihelp-translationstash-param-subaction": "Handling.",
+ "apihelp-translationstash-param-title": "Tittelen på sida med oversettelsesenheter.",
+ "apihelp-translationstash-param-translation": "Oversettelse gjort av brukeren.",
+ "apihelp-translationstash-param-metadata": "JSON-objekt.",
+ "apihelp-translationstash-example-1": "Legg til en oversettelse for stashen for [[MediaWiki:Jan/fi]]",
+ "apihelp-searchtranslations-description": "Søk i oversettelser.",
+ "apihelp-searchtranslations-summary": "Søk i oversettelser.",
+ "apihelp-searchtranslations-param-service": "Hvilken av de tilgjengelige oversettelsestjenestene som skal brukes.",
+ "apihelp-searchtranslations-param-query": "Strengen det skal søkes etter.",
+ "apihelp-searchtranslations-param-sourcelanguage": "Språkkoden til kildeteksten.",
+ "apihelp-searchtranslations-param-language": "Språkkoden strengen skal søkes etter i.",
+ "apihelp-searchtranslations-param-group": "Gruppe-ID-en strengen skal søkes etter i.",
+ "apihelp-searchtranslations-param-filter": "Filter for meldingsoversettelsesstatus.",
+ "apihelp-searchtranslations-param-limit": "Resultatets størrelse.",
+ "apihelp-searchtranslations-example-1": "Vis oversettelser for språket.",
+ "apihelp-searchtranslations-example-2": "Vis uoversatte beskjeder som matcher spørringen på målspråket.",
+ "apierror-translate-duplicateaggregategroup": "Meldingsgruppa finnes allerede",
+ "apierror-translate-invalidaggregategroup": "Ugyldig aggregatmeldingsgruppe",
+ "apierror-translate-invalidaggregategroupname": "Ugyldig navn på aggregatmeldingsgruppe",
+ "apierror-translate-invalidgroup": "Gruppa finnes ikke eller er ugyldig",
+ "apierror-translate-invalidstate": "Den forespurte tilstanden er ugyldig.",
+ "apierror-translate-invalidupdate": "Ugyldig oppdatering",
+ "apierror-translate-language-disabled": "Oversettelse til dette språket er slått av",
+ "apierror-translate-nodynamicgroups": "Dynamiske beskjedgrupper støttes ikke her",
+ "apierror-translate-nomessagefortitle": "Tittelen samsvarer ikke med en oversettbar melding",
+ "apierror-translate-sandboxdisabled": "Sandkassefunksjonen er ikke i bruk",
+ "apierror-translate-sandbox-invalidppassword": "Ugyldig passord",
+ "apierror-translate-unknownmessage": "Ukjent melding"
+}
diff --git a/MLEB/Translate/i18n/api/ne.json b/MLEB/Translate/i18n/api/ne.json
new file mode 100644
index 00000000..be43d772
--- /dev/null
+++ b/MLEB/Translate/i18n/api/ne.json
@@ -0,0 +1,16 @@
+{
+ "@metadata": {
+ "authors": [
+ "Nirjal stha",
+ "NehalDaveND"
+ ]
+ },
+ "apihelp-groupreview-param-group": "सन्देश समूह",
+ "apihelp-groupreview-param-language": "भाषाकोड",
+ "apihelp-query+languagestats-param-language": "भाषाकोड",
+ "apihelp-query+messagecollection-param-group": "सन्देश समूह",
+ "apihelp-query+messagecollection-param-language": "भाषाकोड",
+ "apihelp-translatesandbox-param-do": "के गर्न",
+ "apihelp-translationstash-param-subaction": "क्रिया",
+ "apihelp-ttmserver-param-targetlanguage": "सुझावको भाषा कोड ।"
+}
diff --git a/MLEB/Translate/i18n/api/nn.json b/MLEB/Translate/i18n/api/nn.json
new file mode 100644
index 00000000..33fef6cf
--- /dev/null
+++ b/MLEB/Translate/i18n/api/nn.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Njardarlogar"
+ ]
+ },
+ "apierror-translate-language-disabled-source": "Kjeldespråket til denne gruppa er $1. Vel eit anna språk å setja om til."
+}
diff --git a/MLEB/Translate/i18n/api/oc.json b/MLEB/Translate/i18n/api/oc.json
new file mode 100644
index 00000000..b31bee23
--- /dev/null
+++ b/MLEB/Translate/i18n/api/oc.json
@@ -0,0 +1,20 @@
+{
+ "@metadata": {
+ "authors": [
+ "Cedric31"
+ ]
+ },
+ "apihelp-groupreview-param-language": "Còdi de lenga.",
+ "apihelp-query+languagestats-param-language": "Còdi de lenga.",
+ "apihelp-query+messagecollection-description": "Requèsta MessageCollection sus las traduccions.",
+ "apihelp-query+messagecollection-param-language": "Còdi de lenga.",
+ "apihelp-query+messagetranslations-description": "Demanda l'ensemble de las traduccions per un sol messatge.",
+ "apihelp-query+messagetranslations-example-1": "Lista de las traduccions del wiki per «MediaWiki:genièr»",
+ "apihelp-translationaids-description": "Recèrca totas las ajudas a la traduccion.",
+ "apihelp-translationreview-description": "Marcar las traduccions coma repassadas.",
+ "apihelp-translationstash-description": "Apondre de traduccions a la resèrva.",
+ "apihelp-ttmserver-example-1": "Obténer de suggestions per traduire \"Ajuda\" de l'anglés cap al finés",
+ "apihelp-searchtranslations-description": "Recercar de traduccions.",
+ "apihelp-searchtranslations-param-offset": "Decalatge per las traduccions.",
+ "apihelp-searchtranslations-example-1": "Afichar las traduccions per la lenga."
+}
diff --git a/MLEB/Translate/i18n/api/pam.json b/MLEB/Translate/i18n/api/pam.json
new file mode 100644
index 00000000..de5b647a
--- /dev/null
+++ b/MLEB/Translate/i18n/api/pam.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Leeheonjin"
+ ]
+ },
+ "apierror-translate-sandbox-invalidppassword": "E ustung udyat"
+}
diff --git a/MLEB/Translate/i18n/api/qu.json b/MLEB/Translate/i18n/api/qu.json
new file mode 100644
index 00000000..8afa62c5
--- /dev/null
+++ b/MLEB/Translate/i18n/api/qu.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "AlimanRuna"
+ ]
+ },
+ "apihelp-searchtranslations-summary": "T'ikrasqakunapi maskay."
+}
diff --git a/MLEB/Translate/i18n/api/roa-tara.json b/MLEB/Translate/i18n/api/roa-tara.json
new file mode 100644
index 00000000..1a1bd07c
--- /dev/null
+++ b/MLEB/Translate/i18n/api/roa-tara.json
@@ -0,0 +1,56 @@
+{
+ "@metadata": {
+ "authors": [
+ "Joetaras"
+ ]
+ },
+ "apihelp-aggregategroups-description": "Gestisce gruppe de messàgge aggregate.\n\nPuè aggiungere e luà gruppe de messàgge aggregate e associà o luà le gruppe de messàgge l'une da l'otre (une a vote).",
+ "apihelp-aggregategroups-summary": "Gestisce gruppe de messàgge aggregate.",
+ "apihelp-aggregategroups-extended-description": "Puè aggiungere e luà gruppe de messàgge aggregate e associà o luà le gruppe de messàgge l'une da l'otre (une a vote).",
+ "apihelp-aggregategroups-param-do": "Ce puè ffà cu 'u gruppe de messàgge aggregate.",
+ "apihelp-aggregategroups-param-aggregategroup": "ID d'u gruppe de messàgge aggregate.",
+ "apihelp-aggregategroups-param-group": "ID d'u gruppe de messàgge.",
+ "apihelp-aggregategroups-param-groupname": "Nome d'u gruppe de messàgge aggregate.",
+ "apihelp-aggregategroups-param-groupdescription": "Descrizione d'u gruppe de messàgge aggregate.",
+ "apihelp-aggregategroups-example-1": "Associe 'nu gruppe",
+ "apihelp-groupreview-description": "'Mboste 'state d'u flusse d'u gruppe de messàgge.",
+ "apihelp-groupreview-summary": "'Mboste 'state d'u flusse d'u gruppe de messàgge.",
+ "apihelp-groupreview-param-group": "Gruppe de messàgge.",
+ "apihelp-groupreview-param-language": "Codece d'a Lènghe.",
+ "apihelp-groupreview-param-state": "'U state nuève pu gruppe.",
+ "apihelp-groupreview-example-1": "'Mboste 'u state d'a traduziune jndr'à 'u tedesche (d'a Germanie) pu gruppe de messàgge \"group-Example\" cumme pronde",
+ "apihelp-query+languagestats-description": "Statisteche de le 'nderrogaziune d'a lènghe.",
+ "apihelp-query+languagestats-summary": "Statisteche de le 'nderrogaziune d'a lènghe.",
+ "apihelp-query+languagestats-param-language": "Codece d'a Lènghe.",
+ "apihelp-query+languagestats-example-1": "Elenghe de le statisteche de combletamende d'a traduzione pu finlandese",
+ "apihelp-query+messagecollection-description": "'Nderroghe MessageCollection sus a le traduziune.",
+ "apihelp-query+messagecollection-summary": "'Nderroghe MessageCollection sus a le traduziune.",
+ "apihelp-query+messagecollection-param-group": "Gruppe de messàgge.",
+ "apihelp-query+messagecollection-param-language": "Codece d'a Lènghe.",
+ "apihelp-query+messagecollection-param-limit": "Quanda messàgge da 'ndrucà (apprisse 'u filtre).",
+ "apihelp-query+messagecollection-param-offset": "Indere o distanze d'a chiave pe accumenzà.",
+ "apihelp-query+messagegroups-summary": "Torne le 'mbormaziune sus a le gruppe de messàgge.",
+ "apihelp-query+messagegroupstats-param-group": "ID d'u gruppe de messàgge.",
+ "apihelp-query+messagegroupstats-example-1": "Elenghe de le statisteche de combletamende de le traduziune pu gruppe \"page-Example\"",
+ "apihelp-query+messagetranslations-description": "'Nderroghe tutte le traduziune pe 'nu messàgge singole.",
+ "apihelp-translatesandbox-param-email": "Email quanne stoche a ccreje 'n'utende.",
+ "apihelp-translationaids-description": "'Nderroghe tutte le aijute de traduziune.",
+ "apihelp-translationaids-summary": "'Nderroghe tutte le aijute de traduziune.",
+ "apihelp-translationaids-param-title": "Titole comblete de 'nu messàgge canusciute.",
+ "apihelp-translationstash-description": "Aggiunge le traduziune a 'a scorte.",
+ "apihelp-translationstash-summary": "Aggiunge le traduziune a 'a scorte.",
+ "apihelp-translationstash-param-subaction": "Azione.",
+ "apihelp-translationstash-param-title": "Titole d'a pàgene de aunità d'a traduzione.",
+ "apihelp-translationstash-param-metadata": "Oggette JSON.",
+ "apihelp-searchtranslations-param-limit": "Dimenzione d'u resultate.",
+ "apihelp-searchtranslations-example-1": "Fà 'ndrucà le traduziune pa lènghe.",
+ "apierror-translate-invalidupdate": "Aggiornamente invalide",
+ "apierror-translate-language-disabled": "'A traduzione jndr'à $1 jè disabbilitate.",
+ "apierror-translate-nodynamicgroups": "Le gruppe de messàgge dinamece non ge sò supportate aqquà",
+ "apierror-translate-nomessagefortitle": "Quiste non ge corresponne a 'nu messàgge traducibbile",
+ "apierror-translate-owntranslation": "Non ge puè revisionà le traduziune tune",
+ "apierror-translate-sandboxdisabled": "'A funzione Sandbox non ge s'ause",
+ "apierror-translate-sandbox-invalidppassword": "Password invalide",
+ "apierror-translate-unknownmessage": "Messàgge scanusciute",
+ "apiwarn-translate-alreadyreviewedbyyou": "Già signate cumme revisionate da te"
+}
diff --git a/MLEB/Translate/i18n/api/sa.json b/MLEB/Translate/i18n/api/sa.json
new file mode 100644
index 00000000..67807d56
--- /dev/null
+++ b/MLEB/Translate/i18n/api/sa.json
@@ -0,0 +1,14 @@
+{
+ "@metadata": {
+ "authors": [
+ "NehalDaveND"
+ ]
+ },
+ "apihelp-groupreview-param-group": "सन्देशसमूहः",
+ "apihelp-groupreview-param-language": "भाषाकूटः",
+ "apihelp-query+languagestats-param-language": "भाषाकूटः",
+ "apihelp-query+messagecollection-param-group": "सन्देशसमूहः",
+ "apihelp-query+messagecollection-param-language": "भाषाकूटः",
+ "apihelp-translatesandbox-param-do": "किं करणीयम् ।",
+ "apihelp-translationstash-param-subaction": "क्रिया ।"
+}
diff --git a/MLEB/Translate/i18n/api/sah.json b/MLEB/Translate/i18n/api/sah.json
new file mode 100644
index 00000000..b144e33b
--- /dev/null
+++ b/MLEB/Translate/i18n/api/sah.json
@@ -0,0 +1,11 @@
+{
+ "@metadata": {
+ "authors": [
+ "HalanTul",
+ "Туллук"
+ ]
+ },
+ "apihelp-aggregategroups-description": "Биллэриилэр бөлөхтөрүн оҥоруоххун сөп, кинилэри бөлөхтүөххүн эбэтэр тус туспа араарыаххын сөп (биири биир кэмҥэ)",
+ "apihelp-groupreview-param-group": "Этиилэр бөлөхтөрө.",
+ "apihelp-query+messagecollection-param-group": "Этиилэр бөлөхтөрө."
+}
diff --git a/MLEB/Translate/i18n/api/skr-arab.json b/MLEB/Translate/i18n/api/skr-arab.json
new file mode 100644
index 00000000..7cf0fd8f
--- /dev/null
+++ b/MLEB/Translate/i18n/api/skr-arab.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Saraiki"
+ ]
+ },
+ "apihelp-query+languagestats-param-language": "زبان دا کوڈ۔",
+ "apihelp-query+messagecollection-param-language": "زبان دا کوڈ۔"
+}
diff --git a/MLEB/Translate/i18n/api/sq.json b/MLEB/Translate/i18n/api/sq.json
new file mode 100644
index 00000000..01f88edc
--- /dev/null
+++ b/MLEB/Translate/i18n/api/sq.json
@@ -0,0 +1,10 @@
+{
+ "@metadata": {
+ "authors": [
+ "Bjakupi"
+ ]
+ },
+ "apierror-translate-language-disabled": "Perkthimi ne $1 eshte pa-mundesuar.",
+ "apierror-translate-language-disabled-reason": "Perkthimi ne $1 eshte i pamundesuar:$2",
+ "apierror-translate-language-disabled-source": "Gjuha baze e ketij grupi eshte $1. Ju lutemi perzgjidhni gjuhen per te perkthyer ne ate gjuhe."
+}
diff --git a/MLEB/Translate/i18n/api/sr-ec.json b/MLEB/Translate/i18n/api/sr-ec.json
new file mode 100644
index 00000000..6170314c
--- /dev/null
+++ b/MLEB/Translate/i18n/api/sr-ec.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Obsuser",
+ "Acamicamacaraca"
+ ]
+ },
+ "apierror-translate-sandbox-invalidppassword": "Невалидна лозинка"
+}
diff --git a/MLEB/Translate/i18n/api/ta.json b/MLEB/Translate/i18n/api/ta.json
new file mode 100644
index 00000000..1ead96ed
--- /dev/null
+++ b/MLEB/Translate/i18n/api/ta.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Rakeshonwiki"
+ ]
+ },
+ "apihelp-aggregategroups-description": "மொத்த தகவல்களின் தொகுதியை நிர்வகி.\n\nநீங்கள் மொத்த தகவல்களின் தொகுதியை சேர்க்க மற்றும் நீக்க முடியம், மேலும் தகவல்களின் தொகுதியை இணைக்க அல்லது துண்டிக்க முடியும்(ஒவ்வொன்றாக)"
+}
diff --git a/MLEB/Translate/i18n/api/th.json b/MLEB/Translate/i18n/api/th.json
new file mode 100644
index 00000000..c99b278a
--- /dev/null
+++ b/MLEB/Translate/i18n/api/th.json
@@ -0,0 +1,17 @@
+{
+ "@metadata": {
+ "authors": [
+ "Octahedron80"
+ ]
+ },
+ "apihelp-aggregategroups-param-group": "ไอดีกลุ่มข้อความ",
+ "apihelp-groupreview-param-group": "กลุ่มข้อความ",
+ "apihelp-groupreview-param-language": "รหัสภาษา",
+ "apihelp-query+languagestats-param-language": "รหัสภาษา",
+ "apihelp-query+messagecollection-param-group": "กลุ่มข้อความ",
+ "apihelp-query+messagecollection-param-language": "รหัสภาษา",
+ "apihelp-query+messagecollection-example-1": "รายชื่อภาษาที่รองรับ",
+ "apihelp-query+messagegroups-example-1": "แสดงกลุ่มข้อความ",
+ "apihelp-query+messagegroupstats-param-group": "ไอดีกลุ่มข้อความ",
+ "apihelp-searchtranslations-description": "ค้นหาการแปล"
+}
diff --git a/MLEB/Translate/i18n/api/tl.json b/MLEB/Translate/i18n/api/tl.json
new file mode 100644
index 00000000..068ee37c
--- /dev/null
+++ b/MLEB/Translate/i18n/api/tl.json
@@ -0,0 +1,11 @@
+{
+ "@metadata": {
+ "authors": [
+ "Leeheonjin",
+ "Emem.calist",
+ "Jojit fb"
+ ]
+ },
+ "apierror-translate-language-disabled": "Ang pagsasaling wika sa $1 ay naka-'disabled'",
+ "apierror-translate-sandbox-invalidppassword": "Di-wastong password"
+}
diff --git a/MLEB/Translate/i18n/api/tr.json b/MLEB/Translate/i18n/api/tr.json
new file mode 100644
index 00000000..dd7e0997
--- /dev/null
+++ b/MLEB/Translate/i18n/api/tr.json
@@ -0,0 +1,64 @@
+{
+ "@metadata": {
+ "authors": [
+ "Arystanbek",
+ "Sayginer",
+ "Vito Genovese"
+ ]
+ },
+ "apihelp-aggregategroups-param-group": "Mesaj grup KİMLİĞİ.",
+ "apihelp-aggregategroups-param-groupname": "Toplu mesaj grubu adı.",
+ "apihelp-aggregategroups-param-groupdescription": "Toplu mesaj grubu açıklama.",
+ "apihelp-aggregategroups-example-1": "Önlisans grubu",
+ "apihelp-groupreview-description": "Set message grup iş akışı Birleşik Devletleri.",
+ "apihelp-groupreview-param-group": "İleti grubu",
+ "apihelp-groupreview-param-language": "Dil kodu.",
+ "apihelp-groupreview-param-state": "Grup için yeni bir devlet.",
+ "apihelp-groupreview-example-1": "Mark grup \"grup\" Örnek olarak hazır Alman için",
+ "apihelp-query+languagestats-description": "Dil istatistikler sorgu.",
+ "apihelp-query+languagestats-param-timelimit": "Maksimum zaman eksik istatistikleri hesaplamak geçirmek. Sıfır, başından itibaren yalnızca önbelleğe alınan sonuçları da döndürülür.",
+ "apihelp-query+languagestats-param-language": "Dil kodu.",
+ "apihelp-query+languagestats-example-1": "Fince tercüme tamamlanması istatistikleri listesi",
+ "apihelp-query+messagecollection-description": "Çeviriler hakkında MessageCollection sorgu.",
+ "apihelp-query+messagecollection-param-group": "İleti grubu",
+ "apihelp-query+messagecollection-param-language": "Dil kodu.",
+ "apihelp-query+messagecollection-param-limit": "Kaç tane mesaj göstermek için (süzdükten sonra).",
+ "apihelp-query+messagecollection-param-offset": "Tamsayı veya anahtar başlatmak için ofset.",
+ "apihelp-query+messagegroups-example-1": "Haritayı mesaj grupları",
+ "apihelp-query+messagegroupstats-description": "Sorgu mesajı grup istatistikleri.",
+ "apihelp-query+messagegroupstats-param-timelimit": "Maksimum zaman eksik istatistikleri hesaplamak geçirmek. Sıfır, başından itibaren yalnızca önbelleğe alınan sonuçları da döndürülür.",
+ "apihelp-query+messagegroupstats-param-group": "Mesaj grup KİMLİĞİ.",
+ "apihelp-query+messagegroupstats-example-1": "Grup için çeviri tamamlama istatistikleri listesi \"sayfa-Örnek\"",
+ "apihelp-query+messagetranslations-description": "Tek bir mesaj için tüm çeviriler sorgu.",
+ "apihelp-query+messagetranslations-param-title": "Bilinen bir mesajın tam başlık.",
+ "apihelp-query+messagetranslations-example-1": "\"Medyaviki:Ocak\"için wiki çevirilerin listesi",
+ "apihelp-translatesandbox-description": "Ve korumalı kullanıcıların kayıt yönetmek.",
+ "apihelp-translatesandbox-param-do": "Ne.",
+ "apihelp-translatesandbox-param-userid": "Kullanıcılar yönetilen kullanıcı Kimlikleri. Yarattıkları için 0 kullanın.",
+ "apihelp-translatesandbox-param-username": "Kullanıcı oluştururken kullanıcı adı.",
+ "apihelp-translatesandbox-param-password": "Kullanıcı oluştururken parola.",
+ "apihelp-translatesandbox-param-email": "Kullanıcı oluşturma e-posta.",
+ "apihelp-translationaids-description": "Tüm çeviriler aıds sorgu.",
+ "apihelp-translationaids-param-title": "Bilinen bir mesajın tam başlık.",
+ "apihelp-translationaids-param-group": "İleti grup mesajı aittir. Boş ise o zaman birinci grup kullanılır.",
+ "apihelp-translationaids-param-prop": "Çeviri eklemek için yardımcılar.",
+ "apihelp-translationaids-example-1": "Haritayı [[: Ocak/fi MediaWiki]]aıds",
+ "apihelp-translationreview-description": "Mark çeviriler yapılmıştır.",
+ "apihelp-translationreview-param-revision": "Gözden Geçirme numarası.",
+ "apihelp-translationreview-example-1": "Gözden Geçirme 1",
+ "apihelp-translationstash-description": "Çeviriler saklamak için ekleyin.",
+ "apihelp-translationstash-param-subaction": "Eylem.",
+ "apihelp-translationstash-param-title": "Çeviri birim sayfanın Başlığı.",
+ "apihelp-translationstash-param-translation": "Çeviri kullanıcı tarafından yapılmış.",
+ "apihelp-translationstash-param-metadata": "JSON nesne.",
+ "apihelp-translationstash-param-username": "Almak saklamak kimin isteğe bağlı olarak kullanıcı. Sadece ayrıcalıklı kullanıcılar bunu yapabilirsiniz.",
+ "apihelp-translationstash-example-1": "İçin saklamak için bir çeviri [[: Jan/fi MediaWiki]]ekleyin",
+ "apihelp-translationstash-example-2": "Sorgu zula",
+ "apihelp-ttmserver-description": "Çeviri anılar sorgu önerileri.",
+ "apihelp-ttmserver-param-service": "Kullanmak için hangi.",
+ "apihelp-ttmserver-param-sourcelanguage": "Kaynak metnin dil kodu.",
+ "apihelp-ttmserver-param-targetlanguage": "Öneri dil kodu.",
+ "apihelp-ttmserver-param-text": "Metin için öneriler bulmak için.",
+ "apihelp-ttmserver-example-1": "Çeviri \"\" ingilizce Yardım için öneriler",
+ "apierror-translate-invalidlanguage": "İstenilen dil geçersiz."
+}
diff --git a/MLEB/Translate/i18n/api/tt-cyrl.json b/MLEB/Translate/i18n/api/tt-cyrl.json
new file mode 100644
index 00000000..0fc81d68
--- /dev/null
+++ b/MLEB/Translate/i18n/api/tt-cyrl.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Ильнар"
+ ]
+ },
+ "apihelp-groupreview-param-group": "Язмалар төркеме."
+}
diff --git a/MLEB/Translate/i18n/api/vi.json b/MLEB/Translate/i18n/api/vi.json
new file mode 100644
index 00000000..857aa529
--- /dev/null
+++ b/MLEB/Translate/i18n/api/vi.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "KhangND"
+ ]
+ },
+ "apihelp-query+messagegroups-param-depth": "Khi sử dụng định dạng cây, giới hạn chiều cao đến các cấp độ. Giá trị 0 nghĩa là không nhóm con nào được hiển thị. Nếu đạt tới giới hạn, kết quả xuất sẽ bao gồm giá trị \"số nhóm\", tức số lượng con trực tiếp.",
+ "apihelp-ttmserver-param-sourcelanguage": "Mã ngôn ngữ của văn bản nguồn."
+}
diff --git a/MLEB/Translate/i18n/api/wa.json b/MLEB/Translate/i18n/api/wa.json
new file mode 100644
index 00000000..fdbd140e
--- /dev/null
+++ b/MLEB/Translate/i18n/api/wa.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Srtxg"
+ ]
+ },
+ "apihelp-query+messagecollection-example-1": "Djivêye des lingaedjes sopoirtés"
+}
diff --git a/MLEB/Translate/i18n/core/abs.json b/MLEB/Translate/i18n/core/abs.json
new file mode 100644
index 00000000..53e3d5db
--- /dev/null
+++ b/MLEB/Translate/i18n/core/abs.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Anok kutai jang"
+ ]
+ },
+ "translate-documentation-language": "Dokumentasi pasang"
+}
diff --git a/MLEB/Translate/i18n/core/ady-cyrl.json b/MLEB/Translate/i18n/core/ady-cyrl.json
new file mode 100644
index 00000000..38f7eef0
--- /dev/null
+++ b/MLEB/Translate/i18n/core/ady-cyrl.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Bedynokue.nart"
+ ]
+ },
+ "translate-documentation-language": "Тхыгъэм и къэбар"
+}
diff --git a/MLEB/Translate/i18n/core/aeb-arab.json b/MLEB/Translate/i18n/core/aeb-arab.json
new file mode 100644
index 00000000..2569f5e1
--- /dev/null
+++ b/MLEB/Translate/i18n/core/aeb-arab.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Csisc"
+ ]
+ },
+ "tux-proofread-edit-label": "بدّل"
+}
diff --git a/MLEB/Translate/i18n/core/ais.json b/MLEB/Translate/i18n/core/ais.json
new file mode 100644
index 00000000..6771822f
--- /dev/null
+++ b/MLEB/Translate/i18n/core/ais.json
@@ -0,0 +1,24 @@
+{
+ "@metadata": {
+ "authors": [
+ "Bunukwiki",
+ "Benel",
+ "Tokoabibi"
+ ]
+ },
+ "translate-edit-translation": "mibelih",
+ "translate-export-form-format": "kese",
+ "translate-statsf-count-registrations": "baluhay misaungayay",
+ "translations": "sacahamin a belih nu kamu",
+ "translate-langstats-collapse": "piked",
+ "translate-documentation-language": "palatuh buhci tu kamu a sulit",
+ "translate-searchprofile": "mibelih",
+ "tux-editor-discard-changes-button-label": "miales tu masumaday",
+ "tux-warnings-hide": "midimut",
+ "tux-editor-message-desc-less": "mikabu",
+ "tux-editor-translate-mode": "piazihan tu sulit",
+ "tux-editor-close-tooltip": "edeben",
+ "tux-editor-collapse-tooltip": "piked",
+ "tux-editor-message-tools-translations": "sacahamin a belih nu kamu",
+ "tux-editor-loading": "miasip henay ayza..."
+}
diff --git a/MLEB/Translate/i18n/core/ami.json b/MLEB/Translate/i18n/core/ami.json
new file mode 100644
index 00000000..7d3f271a
--- /dev/null
+++ b/MLEB/Translate/i18n/core/ami.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Vickylin77s"
+ ]
+ },
+ "translate-documentation-language": " pitahapinangan to lihaf a codad"
+}
diff --git a/MLEB/Translate/i18n/core/anp.json b/MLEB/Translate/i18n/core/anp.json
new file mode 100644
index 00000000..efb62c3f
--- /dev/null
+++ b/MLEB/Translate/i18n/core/anp.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Angpradesh"
+ ]
+ },
+ "translate-documentation-language": "सन्देश दस्तावेज़ीकरण"
+}
diff --git a/MLEB/Translate/i18n/core/atj.json b/MLEB/Translate/i18n/core/atj.json
new file mode 100644
index 00000000..5020153a
--- /dev/null
+++ b/MLEB/Translate/i18n/core/atj.json
@@ -0,0 +1,16 @@
+{
+ "@metadata": {
+ "authors": [
+ "Benoit Rochon",
+ "Jeannette Coocoo"
+ ]
+ },
+ "translate-page-settings-legend": "Ke actain",
+ "translate-magic-cm-current": "Mekwatc",
+ "translate-langstats-expand": "otamirota",
+ "translate-js-summary": "Nosem:",
+ "translate-documentation-language": "E itatcitcikatek",
+ "tux-editor-page-mode": "Masinhikan",
+ "tux-message-filter-advanced-button": "Awocamec nantona",
+ "tux-editor-message-tools-delete": "Wepina"
+}
diff --git a/MLEB/Translate/i18n/core/bqi.json b/MLEB/Translate/i18n/core/bqi.json
new file mode 100644
index 00000000..31e7493d
--- /dev/null
+++ b/MLEB/Translate/i18n/core/bqi.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Mogoeilor"
+ ]
+ },
+ "translate-documentation-language": "سندسازی سی پیغوم"
+}
diff --git a/MLEB/Translate/i18n/core/btm.json b/MLEB/Translate/i18n/core/btm.json
new file mode 100644
index 00000000..1a1a6bde
--- /dev/null
+++ b/MLEB/Translate/i18n/core/btm.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Simartampua"
+ ]
+ },
+ "translate-documentation-language": "Dokumentasi tona"
+}
diff --git a/MLEB/Translate/i18n/core/cak.json b/MLEB/Translate/i18n/core/cak.json
new file mode 100644
index 00000000..b78954d4
--- /dev/null
+++ b/MLEB/Translate/i18n/core/cak.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Chocoj"
+ ]
+ },
+ "translate-documentation-language": "Ruwujil ri taqom"
+}
diff --git a/MLEB/Translate/i18n/core/ceb.json b/MLEB/Translate/i18n/core/ceb.json
new file mode 100644
index 00000000..89577066
--- /dev/null
+++ b/MLEB/Translate/i18n/core/ceb.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Bentong Isles"
+ ]
+ },
+ "translate-documentation-language": "Tabang sa paghubad"
+}
diff --git a/MLEB/Translate/i18n/core/din.json b/MLEB/Translate/i18n/core/din.json
new file mode 100644
index 00000000..e41aec86
--- /dev/null
+++ b/MLEB/Translate/i18n/core/din.json
@@ -0,0 +1,10 @@
+{
+ "@metadata": {
+ "authors": [
+ "Dinkawiki",
+ "Kumkumuk"
+ ]
+ },
+ "translate-page-edit": "cokic",
+ "translate-documentation-language": "Wɛ̈tcïgɔ̈t ë weltuɔc"
+}
diff --git a/MLEB/Translate/i18n/core/dty.json b/MLEB/Translate/i18n/core/dty.json
new file mode 100644
index 00000000..f2e1c7b0
--- /dev/null
+++ b/MLEB/Translate/i18n/core/dty.json
@@ -0,0 +1,18 @@
+{
+ "@metadata": {
+ "authors": [
+ "रमेश सिंह बोहरा",
+ "राम प्रसाद जोशी",
+ "Nirajan pant"
+ ]
+ },
+ "translate-edit-no-information": "<em>यै रैबारको दस्तावेजीकरण नाइथिन।\nयदि तम यो सन्देश काँ और कस्याँ प्रयोग अरीन्छ भण्णेइ कुरडी जाणन्छौ भँण्या तम येइ सन्देश को दस्तावेजीकरण थपीबर और अनुवादकअन लाई मद्दत अरी सकन्छौ।</em>",
+ "translate-edit-information": "($1) रैबारका बारेमी जानकारी",
+ "translate-edit-in-other-languages": "रैबार अन्य भाषाहरूमी",
+ "translate-stats-edits": "सम्पादनहरू",
+ "translate-stats-users": "अनुवादकहरू",
+ "translate-statsf-count-users": "सक्रिय अनुवादकहरू",
+ "translate-statsf-count-registrations": "नौला प्रयोगकर्ताहरू",
+ "translate-sidebar-alltrans": "अन्य भाषामी",
+ "translate-documentation-language": "खबर दस्तावेज"
+}
diff --git a/MLEB/Translate/i18n/core/gaa.json b/MLEB/Translate/i18n/core/gaa.json
new file mode 100644
index 00000000..6f3da63b
--- /dev/null
+++ b/MLEB/Translate/i18n/core/gaa.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Chivano"
+ ]
+ },
+ "translate-documentation-language": "Shiɛmɔ he saneŋmaa"
+}
diff --git a/MLEB/Translate/i18n/core/gcr.json b/MLEB/Translate/i18n/core/gcr.json
new file mode 100644
index 00000000..47d6770a
--- /dev/null
+++ b/MLEB/Translate/i18n/core/gcr.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "LeGuyanaisPure"
+ ]
+ },
+ "translate-documentation-language": "Dokimantasyon di mésaj"
+}
diff --git a/MLEB/Translate/i18n/core/gd.json b/MLEB/Translate/i18n/core/gd.json
new file mode 100644
index 00000000..96f920aa
--- /dev/null
+++ b/MLEB/Translate/i18n/core/gd.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "GunChleoc"
+ ]
+ },
+ "translate-documentation-language": "Treòir eadar-theangachaidh"
+}
diff --git a/MLEB/Translate/i18n/core/glk.json b/MLEB/Translate/i18n/core/glk.json
new file mode 100644
index 00000000..5a7d0fec
--- /dev/null
+++ b/MLEB/Translate/i18n/core/glk.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "شیخ",
+ "V6rg"
+ ]
+ },
+ "translate-documentation-language": "پىغؤمˇ سندؤن"
+}
diff --git a/MLEB/Translate/i18n/core/gor.json b/MLEB/Translate/i18n/core/gor.json
new file mode 100644
index 00000000..3fb8b6fa
--- /dev/null
+++ b/MLEB/Translate/i18n/core/gor.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Marwan Mohamad"
+ ]
+ },
+ "translate-documentation-language": "Dokumentasi tahuli"
+}
diff --git a/MLEB/Translate/i18n/core/got.json b/MLEB/Translate/i18n/core/got.json
new file mode 100644
index 00000000..b674d51f
--- /dev/null
+++ b/MLEB/Translate/i18n/core/got.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Gothicspeaker"
+ ]
+ },
+ "translate-documentation-language": "𐍅𐌰𐌿𐍂𐌳𐌹𐍃 𐌳𐌰𐌿𐌺𐌿𐌼𐌰𐌹𐌽𐍄𐌰𐍄𐍃𐌾𐍉"
+}
diff --git a/MLEB/Translate/i18n/core/hif-latn.json b/MLEB/Translate/i18n/core/hif-latn.json
new file mode 100644
index 00000000..57016680
--- /dev/null
+++ b/MLEB/Translate/i18n/core/hif-latn.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Abdul Kadir"
+ ]
+ },
+ "translate-documentation-language": "Message ke baare me jaankari"
+}
diff --git a/MLEB/Translate/i18n/core/hyw.json b/MLEB/Translate/i18n/core/hyw.json
new file mode 100644
index 00000000..c5037155
--- /dev/null
+++ b/MLEB/Translate/i18n/core/hyw.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "ArmenBakkalian"
+ ]
+ },
+ "translate-documentation-language": "Հաղորդագրութեան փաստագրութիւն"
+}
diff --git a/MLEB/Translate/i18n/core/inh.json b/MLEB/Translate/i18n/core/inh.json
new file mode 100644
index 00000000..f014f0ad
--- /dev/null
+++ b/MLEB/Translate/i18n/core/inh.json
@@ -0,0 +1,23 @@
+{
+ "@metadata": {
+ "authors": [
+ "Умар",
+ "Adam-Yourist",
+ "ElizaMag"
+ ]
+ },
+ "translate-page-group": "Тоаба",
+ "translate-page-language": "Мотт",
+ "translate-edit-translation": "Таржам",
+ "translate-rcfilters-translations": "Таржамаш",
+ "translate-rcfilters-translations-only-label": "Таржамаш",
+ "translate-rcfilters-translations-filter-label": "Таржамаш дац",
+ "translations": "Деррига таржамаш",
+ "translate-language": "Мотт",
+ "translate-mgs-column-language": "Мотт",
+ "translate-documentation-language": "Хоам бара документаци",
+ "translate-searchprofile": "Таржамаш",
+ "translate-msggroupselector-search-all": "Деррига",
+ "tux-tab-all": "Деррига",
+ "tux-editor-message-tools-translations": "Деррига таржамаш"
+}
diff --git a/MLEB/Translate/i18n/core/jut.json b/MLEB/Translate/i18n/core/jut.json
new file mode 100644
index 00000000..566b263e
--- /dev/null
+++ b/MLEB/Translate/i18n/core/jut.json
@@ -0,0 +1,10 @@
+{
+ "@metadata": {
+ "authors": [
+ "Jyllanj"
+ ]
+ },
+ "translate-magic-notsaved": "Din redigiireng ä ett djiemen!",
+ "translations": "Åll öwesättelse",
+ "translate-documentation-language": "Beskeddokumentasjon"
+}
diff --git a/MLEB/Translate/i18n/core/kbp.json b/MLEB/Translate/i18n/core/kbp.json
new file mode 100644
index 00000000..7be5438e
--- /dev/null
+++ b/MLEB/Translate/i18n/core/kbp.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Gnangbade"
+ ]
+ },
+ "translate-documentation-language": "Tɔm kɩmamatʋ yɔɔ aseɣɖe"
+}
diff --git a/MLEB/Translate/i18n/core/kjp.json b/MLEB/Translate/i18n/core/kjp.json
new file mode 100644
index 00000000..ffb630ab
--- /dev/null
+++ b/MLEB/Translate/i18n/core/kjp.json
@@ -0,0 +1,23 @@
+{
+ "@metadata": {
+ "authors": [
+ "Rul1902"
+ ]
+ },
+ "translate-taction-lstats": "ဆ်ုခၠါင်ဘာႋသာ့ စ်ုရင့်ကါင်ကါ",
+ "translate-taction-mstats": "လိက်သုံ့ၜိင်းကုံရွာဲ စ်ုရင့်ကါင်ကါ",
+ "translate-rcfilters-translations": "ဆ်ုခၠယ်ထသယ်လ်ုဖး",
+ "translationstats": "ခၠယ်ထသယ် စ်ုရင့်ကါင်ကါ",
+ "translate-sidebar-alltrans": "အ်ုၰာႋၰံင် ဆ်ုခၠါင်ဘာႋသာ့လ်ုဖးသိုဝ်",
+ "translate-translations-fieldset-title": "လိက်သုံ့ၜိင်း",
+ "translate-translations-messagename": "အ်ုမၠိင်:",
+ "translate-translations-project": "ပ်ုရောဲဂျက်:",
+ "languagestats": "ဆ်ုခၠါင်ဘာႋသာ့ စ်ုရင့်ကါင်ကါ",
+ "languagestats-stats-for": "$1 ($2) အ်ုၯင်း ခၠယ်ထသယ် စ်ုရင့်ကါင်ကါ",
+ "languagestats-recenttranslations": "လ်ုယိက်လ်ုမဝ်ႋဆ်ုအင်းတာင်",
+ "translate-mgs-pagename": "လိက်သုံ့ၜိင်းကုံရွာဲ စ်ုရင့်ကါင်ကါ",
+ "supportedlanguages-recenttranslations": "လ်ုယိက်လ်ုမဝ်ႋဆ်ုအင်းတာင်",
+ "translate-documentation-language": "လိက်ၜၠာ် လိက်မါၮါင်း",
+ "translate-dynagroup-recent-label": "လ်ုယိက်လ်ုမဝ်ႋဆ်ုအင်းတာင်",
+ "tux-editor-in-other-languages": "အ်ုၰာႋၰံင် ဆ်ုခၠါင်ဘာႋသာ့လ်ုဖးသိုဝ်"
+}
diff --git a/MLEB/Translate/i18n/core/kum.json b/MLEB/Translate/i18n/core/kum.json
new file mode 100644
index 00000000..b975a1c9
--- /dev/null
+++ b/MLEB/Translate/i18n/core/kum.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Arsenekoumyk"
+ ]
+ },
+ "translate-documentation-language": "Мактупну маълюматы"
+}
diff --git a/MLEB/Translate/i18n/core/kw.json b/MLEB/Translate/i18n/core/kw.json
new file mode 100644
index 00000000..816e4926
--- /dev/null
+++ b/MLEB/Translate/i18n/core/kw.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Nrowe"
+ ]
+ },
+ "translate-langstats-collapse": "diskara",
+ "translate-documentation-language": "Gweres ow treylya"
+}
diff --git a/MLEB/Translate/i18n/core/lag.json b/MLEB/Translate/i18n/core/lag.json
new file mode 100644
index 00000000..07cc4886
--- /dev/null
+++ b/MLEB/Translate/i18n/core/lag.json
@@ -0,0 +1,11 @@
+{
+ "@metadata": {
+ "authors": [
+ "Baba Tabita"
+ ]
+ },
+ "translate-statsf-scale-months": "Myeeri",
+ "tux-editor-close-tooltip": "Chuunga",
+ "tux-editor-message-tools-history": "Hɨstoríya",
+ "tux-editor-message-tools-delete": "Honola"
+}
diff --git a/MLEB/Translate/i18n/core/lfn.json b/MLEB/Translate/i18n/core/lfn.json
new file mode 100644
index 00000000..88f6ce2c
--- /dev/null
+++ b/MLEB/Translate/i18n/core/lfn.json
@@ -0,0 +1,20 @@
+{
+ "@metadata": {
+ "authors": [
+ "Cgboeree",
+ "Katxis",
+ "Robin van der Vliet",
+ "Mafcadio"
+ ]
+ },
+ "translate-page-language": "Lingua",
+ "translate-stats-users": "Traduores",
+ "translate-sidebar-alltrans": "En otra linguas",
+ "translate-translations-project": "Projeta:",
+ "translate-language": "Lingua",
+ "translate-mgs-column-language": "Lingua",
+ "supportedlanguages-translators": "{{PLURAL:$2|{{GENDER:$3|Traduor}}|Traduores}}: $1",
+ "translate-documentation-language": "Documentos de mesajes",
+ "tux-editor-in-other-languages": "En otra linguas",
+ "tux-message-filter-advanced-button": "Xerca avansada"
+}
diff --git a/MLEB/Translate/i18n/core/lij.json b/MLEB/Translate/i18n/core/lij.json
new file mode 100644
index 00000000..945a12b2
--- /dev/null
+++ b/MLEB/Translate/i18n/core/lij.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Giromin Cangiaxo"
+ ]
+ },
+ "translate-js-support": "Fanni 'na domanda",
+ "translate-documentation-language": "Documentassion do messaggio"
+}
diff --git a/MLEB/Translate/i18n/core/lld.json b/MLEB/Translate/i18n/core/lld.json
new file mode 100644
index 00000000..5f1e5bff
--- /dev/null
+++ b/MLEB/Translate/i18n/core/lld.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Asenoner"
+ ]
+ },
+ "translate-documentation-language": "Documentazion di messajes"
+}
diff --git a/MLEB/Translate/i18n/core/luz.json b/MLEB/Translate/i18n/core/luz.json
new file mode 100644
index 00000000..74a67d48
--- /dev/null
+++ b/MLEB/Translate/i18n/core/luz.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "علی ساکی لرستانی"
+ ]
+ },
+ "translate-documentation-language": "سأنأدل ڤ مدرأکل پیوٙم"
+}
diff --git a/MLEB/Translate/i18n/core/mni.json b/MLEB/Translate/i18n/core/mni.json
new file mode 100644
index 00000000..eaf43345
--- /dev/null
+++ b/MLEB/Translate/i18n/core/mni.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Awangba Mangang"
+ ]
+ },
+ "translate-documentation-language": "Documentationda paojel thao"
+}
diff --git a/MLEB/Translate/i18n/core/mnw.json b/MLEB/Translate/i18n/core/mnw.json
new file mode 100644
index 00000000..b31a268e
--- /dev/null
+++ b/MLEB/Translate/i18n/core/mnw.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Htawmonzel"
+ ]
+ },
+ "translate-documentation-language": "Message documentation"
+}
diff --git a/MLEB/Translate/i18n/core/mui.json b/MLEB/Translate/i18n/core/mui.json
new file mode 100644
index 00000000..39164bf7
--- /dev/null
+++ b/MLEB/Translate/i18n/core/mui.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Jawadywn"
+ ]
+ },
+ "translate-documentation-language": "Pencatetan pesen"
+}
diff --git a/MLEB/Translate/i18n/core/mwl.json b/MLEB/Translate/i18n/core/mwl.json
new file mode 100644
index 00000000..aac2ecf3
--- /dev/null
+++ b/MLEB/Translate/i18n/core/mwl.json
@@ -0,0 +1,20 @@
+{
+ "@metadata": {
+ "authors": [
+ "MokaAkashiyaPT",
+ "Athena in Wonderland"
+ ]
+ },
+ "translate-taction-export": "Sportar",
+ "translate-edit-translation": "Traduçon",
+ "translate-magic-cm-export": "Sportar",
+ "languagestats": "Statísticas de la lhéngua",
+ "supportedlanguages": "Lhénguas sustentadas",
+ "translate-import-load": "Cargar fexeiro",
+ "translate-documentation-language": "Decumentaçon de la mensaige",
+ "translate-searchprofile": "Traduçones",
+ "tux-editor-edit-desc": "Eiditar la decumentaçon",
+ "tux-editor-add-desc": "Poner decumentaçon",
+ "tux-editor-doc-editor-placeholder": "Decumentaçon de la mensaige",
+ "tux-editor-doc-editor-save": "Grabar decumentaçon"
+}
diff --git a/MLEB/Translate/i18n/core/mzn.json b/MLEB/Translate/i18n/core/mzn.json
new file mode 100644
index 00000000..37d4d7b3
--- /dev/null
+++ b/MLEB/Translate/i18n/core/mzn.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "محک"
+ ]
+ },
+ "translate-documentation-language": "پیغوم ِمستندات"
+}
diff --git a/MLEB/Translate/i18n/core/nan.json b/MLEB/Translate/i18n/core/nan.json
new file mode 100644
index 00000000..c6c40a5a
--- /dev/null
+++ b/MLEB/Translate/i18n/core/nan.json
@@ -0,0 +1,11 @@
+{
+ "@metadata": {
+ "authors": [
+ "Ianbu",
+ "Luuva"
+ ]
+ },
+ "translate-magic-module": "Module:",
+ "translate-rcfilters-translations": "Hoan-e̍k",
+ "translate-documentation-language": "訊息說明書"
+}
diff --git a/MLEB/Translate/i18n/core/nys.json b/MLEB/Translate/i18n/core/nys.json
new file mode 100644
index 00000000..1123c6d4
--- /dev/null
+++ b/MLEB/Translate/i18n/core/nys.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Gnangarra"
+ ]
+ },
+ "translate-documentation-language": "Boorna wangkiny"
+}
diff --git a/MLEB/Translate/i18n/core/olo.json b/MLEB/Translate/i18n/core/olo.json
new file mode 100644
index 00000000..fcf107ef
--- /dev/null
+++ b/MLEB/Translate/i18n/core/olo.json
@@ -0,0 +1,13 @@
+{
+ "@metadata": {
+ "authors": [
+ "Ilja.mos",
+ "Mashoi7"
+ ]
+ },
+ "translate-page-navigation-legend": "Navigacii",
+ "translate-edit-title": "Kohendele sivuu \"$1\"",
+ "translate-suppress-complete": "Peitä viestijoukot, kuduat on kiännetty kogonah",
+ "translate-ls-noempty": "Peitä viestijoukot, kudamii ei ole kiännetty kogonah",
+ "translate-documentation-language": "Viestien dokumentatsii"
+}
diff --git a/MLEB/Translate/i18n/core/sat.json b/MLEB/Translate/i18n/core/sat.json
new file mode 100644
index 00000000..1a4d735d
--- /dev/null
+++ b/MLEB/Translate/i18n/core/sat.json
@@ -0,0 +1,10 @@
+{
+ "@metadata": {
+ "authors": [
+ "Albinus",
+ "Manik Soren",
+ "Amire80"
+ ]
+ },
+ "translate-documentation-language": "Message documentation"
+}
diff --git a/MLEB/Translate/i18n/core/sd.json b/MLEB/Translate/i18n/core/sd.json
new file mode 100644
index 00000000..966dab88
--- /dev/null
+++ b/MLEB/Translate/i18n/core/sd.json
@@ -0,0 +1,54 @@
+{
+ "@metadata": {
+ "authors": [
+ "Aursani",
+ "Mehtab ahmed",
+ "Indus Asia",
+ "Tweety"
+ ]
+ },
+ "translate": "ترجمايو",
+ "translate-extensionname": "ترجامايو",
+ "translate-taction-translate": "ترجمايو",
+ "translate-taction-proofread": "جائزو وٺو",
+ "translate-taction-lstats": "ٻوليءَ جا انگ اکر",
+ "translate-taction-export": "برآمديو",
+ "translate-language-disabled": "هن ٻوليءَ ۾ ترجمو ڪرڻ غيرفعال ڪيو ويو آهي.",
+ "translate-next": "اڳيون صفحو",
+ "translate-prev": "پويون صفحو",
+ "translate-edit-translation": "ترجمو",
+ "translate-edit-in-other-languages": "ٻين ٻولين ۾ نياپو",
+ "translate-magic-cm-save": "سانڍيو",
+ "translate-rcfilters-translations-only-desc": "ترجمايل صفحن ۾ تبديليون.",
+ "translate-stats-edits": "ترميمون",
+ "translate-statsf-options": "گراف",
+ "translate-statsf-scale-days": "ڏينھن",
+ "translate-statsf-scale-hours": "ڪلاڪَ",
+ "languagestats": "ٻوليءَ جا انگ اکر",
+ "translate-untranslated": "اڻ-ترجمايل",
+ "translate-percentage-fuzzy": "مدي-خارج",
+ "translate-smg-right": "اندر ايندڙ تبديليون",
+ "translate-js-save": "سانڍيو",
+ "translate-js-support": "سوال پڇو",
+ "translate-documentation-language": "نياپاتي دستاويزسازي",
+ "translate-msggroupselector-search-all": "سڀ",
+ "tux-tab-all": "سڀ",
+ "tux-tab-untranslated": "اڻ-ترجمايل",
+ "tux-tab-outdated": "مدي-خارج",
+ "tux-tab-translated": "ترجمايل",
+ "tux-tab-unproofread": "اڻ-جائزو-ورتل",
+ "tux-status-fuzzy": "مدي-خارج",
+ "tux-status-translated": "ترجمايل",
+ "tux-editor-discard-changes-button-label": "تبديليون ڦٽو ڪريو",
+ "tux-editor-save-button-label": "ترجمو سانڍيو",
+ "tux-editor-cancel-button-label": "رد",
+ "tux-editor-confirm-button-label": "ترجمي جي پڪ ڪريو",
+ "tux-editor-in-other-languages": "ٻيون ٻوليون",
+ "tux-editor-clear-translated": "ترجمايل لڪايو",
+ "tux-editor-doc-editor-cancel": "رد",
+ "tux-proofread-translated-by-self": "مون پاران ترجمايل",
+ "tux-empty-list-translated": "پيغام ترجمايل ناھن",
+ "tux-editor-close-tooltip": "بند ڪريو",
+ "tux-editor-message-tools-delete": "ڊاھيو",
+ "translate-statsbar-tooltip": "$1% تجمايل، $2% جائزو-ورتل"
+}
diff --git a/MLEB/Translate/i18n/core/sdh.json b/MLEB/Translate/i18n/core/sdh.json
new file mode 100644
index 00000000..02ea8972
--- /dev/null
+++ b/MLEB/Translate/i18n/core/sdh.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Paraw2"
+ ]
+ },
+ "translate-documentation-language": "پەیام بەڵگەناوە"
+}
diff --git a/MLEB/Translate/i18n/core/sgs.json b/MLEB/Translate/i18n/core/sgs.json
new file mode 100644
index 00000000..f0626dd2
--- /dev/null
+++ b/MLEB/Translate/i18n/core/sgs.json
@@ -0,0 +1,20 @@
+{
+ "@metadata": {
+ "authors": [
+ "Hugo.arg"
+ ]
+ },
+ "translate-sidebar-alltrans": "Kėtuom kalbuom",
+ "translate-untranslated": "Napargoldītė",
+ "translate-percentage-fuzzy": "Vetošė",
+ "translate-js-support": "Paklaustė",
+ "translate-documentation-language": "Pranešėma aprašā",
+ "tux-tab-all": "Vėsė",
+ "tux-tab-untranslated": "Napargoldītė",
+ "tux-tab-outdated": "Vetošė",
+ "tux-tab-translated": "Pargoldītė",
+ "tux-status-fuzzy": "Vetošė",
+ "tux-editor-save-button-label": "Ėšsauguotė pargoldīma",
+ "tux-editor-skip-button-label": "Ētė ont kėta",
+ "tux-editor-clear-translated": "Kavuotė tous, katrėi pargoldītė īr"
+}
diff --git a/MLEB/Translate/i18n/core/shy-latn.json b/MLEB/Translate/i18n/core/shy-latn.json
new file mode 100644
index 00000000..a349e2d1
--- /dev/null
+++ b/MLEB/Translate/i18n/core/shy-latn.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Vikoula5"
+ ]
+ },
+ "translate-statsf-scale-months": "Iyaren",
+ "translate-statsf-scale-days": "Ussan"
+}
diff --git a/MLEB/Translate/i18n/core/skr-arab.json b/MLEB/Translate/i18n/core/skr-arab.json
new file mode 100644
index 00000000..13409684
--- /dev/null
+++ b/MLEB/Translate/i18n/core/skr-arab.json
@@ -0,0 +1,76 @@
+{
+ "@metadata": {
+ "authors": [
+ "Saraiki"
+ ]
+ },
+ "translate-export-form-format": "فارمیٹ",
+ "translate-magic-cm-current": "موجودہ",
+ "translate-magic-cm-original": "اصل",
+ "translate-magic-cm-save": "بچاؤ",
+ "translate-magic-words": "جادوئی الفاظ",
+ "translate-rcfilters-translations": "ترجمے",
+ "translate-rcfilters-translations-only-label": "ترجمے",
+ "translate-rcfilters-translations-filter-label": "ترجمے کائنی",
+ "translate-stats-users": "مترجمین",
+ "translate-statsf-scale-months": "مہینے",
+ "translate-statsf-scale-weeks": "ہفتے",
+ "translate-statsf-scale-days": "ݙیہاڑے",
+ "translate-statsf-scale-hours": "گھنٹے",
+ "translate-statsf-count": "پیمائش:",
+ "translate-statsf-count-edits": "تبدیلیاں دی تعداد",
+ "translate-statsf-count-users": "فعال مترجمین",
+ "translate-statsf-count-registrations": "نویں ورتݨ آلے",
+ "translate-statsf-submit": "نمائش",
+ "translate-translations-fieldset-title": "سنیہا",
+ "translate-translations-messagename": "ناں:",
+ "translate-translations-project": "منصوبہ:",
+ "translate-langstats-expand": "ودھاؤ",
+ "translate-langstats-collapse": "لکاؤ",
+ "translate-language-code": "زبان دا کوڈ",
+ "translate-language-code-field-name": "زبان دا کوڈ:",
+ "translate-language": "زبان",
+ "translate-total": "سنیہے",
+ "translate-untranslated": "ترجمے ٻاجھ",
+ "translate-percentage-complete": "مکمل",
+ "translate-percentage-fuzzy": "پُراݨے",
+ "translate-languagestats-overall": "سنیہیاں دے سارے گروہ اکٹھے",
+ "translate-ls-submit": "شماریات ݙکھاؤ",
+ "translate-mgs-submit": "شماریات ݙکھاؤ",
+ "translate-mgs-column-language": "زبان",
+ "translate-js-summary": "خلاصہ:",
+ "translate-js-save": "بچاؤ",
+ "translate-gs-code": "کوڈ",
+ "translate-gs-continent": "براعظم",
+ "translate-gs-speakers": "الاوݨ آلے",
+ "translate-gs-score": "سکور",
+ "translate-gs-multiple": "ہک کنوں ودھ",
+ "translate-gs-count": "ڳیݨ",
+ "translate-gs-total": "مجموعہ",
+ "translate-documentation-language": "سنیہہ دستاویز",
+ "translate-msggroupselector-search-all": "یکے",
+ "translate-msggroupselector-search-recent": "حالیہ",
+ "tux-tab-all": "یکے",
+ "tux-tab-untranslated": "ترجمے ٻاجھ",
+ "tux-tab-outdated": "پُراݨے",
+ "tux-tab-translated": "ترجمہ تھی ڳیا",
+ "tux-status-fuzzy": "پُراݨے",
+ "tux-status-proofread": "ولدا ݙٹھے ہوئے",
+ "tux-status-translated": "ترجمہ تھی ڳیا",
+ "tux-status-saving": "بچیندا پئے۔۔۔",
+ "tux-status-unsaved": "غیر محفوظ",
+ "tux-editor-cancel-button-label": "منسوخ",
+ "tux-editor-confirm-button-label": "ترجمہ دی تصدیق کرو",
+ "tux-warnings-hide": "لُکاؤ",
+ "tux-editor-translate-mode": "فہرست",
+ "tux-proofread-edit-label": "لکھو",
+ "tux-editor-page-mode": "ورقہ",
+ "tux-editor-doc-editor-cancel": "منسوخ",
+ "tux-empty-list-translated-action": "ترجمہ کرو",
+ "tux-editor-close-tooltip": "بند کرو",
+ "tux-editor-expand-tooltip": "ودھاؤ",
+ "tux-editor-collapse-tooltip": "لکاؤ",
+ "tux-editor-message-tools-delete": "مٹاؤ",
+ "tux-editor-message-tools-translations": "سارے ترجمے",
+ "tux-editor-loading": "لوڈ تھیندا پئے۔۔۔"
+}
diff --git a/MLEB/Translate/i18n/core/sty.json b/MLEB/Translate/i18n/core/sty.json
new file mode 100644
index 00000000..eb69471a
--- /dev/null
+++ b/MLEB/Translate/i18n/core/sty.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Khanmarat"
+ ]
+ },
+ "translate-documentation-language": "Ҡәбәрнеңке документациясы"
+}
diff --git a/MLEB/Translate/i18n/core/tay.json b/MLEB/Translate/i18n/core/tay.json
new file mode 100644
index 00000000..4d7c54c6
--- /dev/null
+++ b/MLEB/Translate/i18n/core/tay.json
@@ -0,0 +1,13 @@
+{
+ "@metadata": {
+ "authors": [
+ "Akamycoco",
+ "Hitaypayan"
+ ]
+ },
+ "translate-page-edit": "smr’zyut miru’",
+ "translate-edit-title": "Smr’zyut miru’ \"$1\"",
+ "translate-documentation-language": "Minblaq kmal biru’ na pintkaykay’",
+ "tux-edit": "Smr’zyut miru’",
+ "tux-proofread-edit-label": "Smr’zyut miru’"
+}
diff --git a/MLEB/Translate/i18n/core/tokipona.json b/MLEB/Translate/i18n/core/tokipona.json
new file mode 100644
index 00000000..c608bb38
--- /dev/null
+++ b/MLEB/Translate/i18n/core/tokipona.json
@@ -0,0 +1,10 @@
+{
+ "@metadata": {
+ "authors": [
+ "Robin0van0der0vliet"
+ ]
+ },
+ "translate-page-language": "toki",
+ "translate-language": "toki",
+ "translate-mgs-column-language": "toki"
+}
diff --git a/MLEB/Translate/i18n/core/tt-latn.json b/MLEB/Translate/i18n/core/tt-latn.json
new file mode 100644
index 00000000..69fb8af7
--- /dev/null
+++ b/MLEB/Translate/i18n/core/tt-latn.json
@@ -0,0 +1,11 @@
+{
+ "@metadata": {
+ "authors": [
+ "Frhdkazan"
+ ]
+ },
+ "translate-sidebar-alltrans": "Başqa tellärdä",
+ "translations": "Bar tärcemälär",
+ "supportedlanguages": "Qullanıluçı tellär",
+ "translate-documentation-language": "Xäbär dokumentatsiäse"
+}
diff --git a/MLEB/Translate/i18n/core/tyv.json b/MLEB/Translate/i18n/core/tyv.json
new file mode 100644
index 00000000..876dba94
--- /dev/null
+++ b/MLEB/Translate/i18n/core/tyv.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Монгуш Салим",
+ "Agilight"
+ ]
+ },
+ "translate-documentation-language": "Дыңнадыгның документилели"
+}
diff --git a/MLEB/Translate/i18n/core/udm.json b/MLEB/Translate/i18n/core/udm.json
new file mode 100644
index 00000000..2a3454ec
--- /dev/null
+++ b/MLEB/Translate/i18n/core/udm.json
@@ -0,0 +1,16 @@
+{
+ "@metadata": {
+ "authors": [
+ "Kaganer",
+ "Irus",
+ "Wadorgurt"
+ ]
+ },
+ "translate-statsf-submit": "Эскерон",
+ "translate-sidebar-alltrans": "Мукет кылъёсын",
+ "languagestats": "Кыл статистика",
+ "translate-documentation-language": "Ивортонлэн документациез",
+ "translate-msggroupselector-search-all": "Ваньзэ",
+ "tux-tab-all": "Ваньзэ",
+ "tux-editor-in-other-languages": "Мукет кылъёсын"
+}
diff --git a/MLEB/Translate/i18n/core/vro.json b/MLEB/Translate/i18n/core/vro.json
new file mode 100644
index 00000000..fe50852b
--- /dev/null
+++ b/MLEB/Translate/i18n/core/vro.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Võrok"
+ ]
+ },
+ "translate-documentation-language": "Sõnomi dokumentatsiuun"
+}
diff --git a/MLEB/Translate/i18n/core/war.json b/MLEB/Translate/i18n/core/war.json
new file mode 100644
index 00000000..af84bf0d
--- /dev/null
+++ b/MLEB/Translate/i18n/core/war.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "JinJian"
+ ]
+ },
+ "translate-documentation-language": "Dokyumentasyon han mensahe"
+}
diff --git a/MLEB/Translate/i18n/core/wo.json b/MLEB/Translate/i18n/core/wo.json
new file mode 100644
index 00000000..21e0ff7c
--- /dev/null
+++ b/MLEB/Translate/i18n/core/wo.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Ibou"
+ ]
+ },
+ "translate-documentation-language": "Faramfàcceg xibaar bi"
+}
diff --git a/MLEB/Translate/i18n/core/wuu.json b/MLEB/Translate/i18n/core/wuu.json
new file mode 100644
index 00000000..15e25e72
--- /dev/null
+++ b/MLEB/Translate/i18n/core/wuu.json
@@ -0,0 +1,10 @@
+{
+ "@metadata": {
+ "authors": [
+ "Poiuyt",
+ "飞舞回堂前"
+ ]
+ },
+ "translate-documentation-language": "信息说明文件",
+ "tux-warnings-hide": "囥脱"
+}
diff --git a/MLEB/Translate/i18n/core/yo.json b/MLEB/Translate/i18n/core/yo.json
new file mode 100644
index 00000000..4ada6f2f
--- /dev/null
+++ b/MLEB/Translate/i18n/core/yo.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Wikicology"
+ ]
+ },
+ "translate-documentation-language": "Àkọsílẹ̀ ìfiránṣẹ́"
+}
diff --git a/MLEB/Translate/i18n/core/zgh.json b/MLEB/Translate/i18n/core/zgh.json
new file mode 100644
index 00000000..c36f91e4
--- /dev/null
+++ b/MLEB/Translate/i18n/core/zgh.json
@@ -0,0 +1,31 @@
+{
+ "@metadata": {
+ "authors": [
+ "Amara-Amaziɣ",
+ "Mdb897"
+ ]
+ },
+ "translate": "ⵙⵙⵓⵖⵍ",
+ "translate-desc": "[[Special:Translate|ⵜⴰⵙⵏⴰ ⵉⵥⵍⵉⵏ]] ⵃⵎⴰ ⴰⵙⵓⵖⵍ ⵏ ⵎⵉⴷⵢⴰⵡⵉⴽⵉ ⴷ ⵓⴳⴳⴰⵔ",
+ "translate-fuzzybot-desc": "ⵡⴰⴷ ⵉⴳⴰ ⴰⵏⴳⵔⴰⵡ ⵉⵣⵍⵉⵏ ⵉⵜⵜⵡⴰⵙⵙⵎⵔⵙ ⴳ ⵎⵉⴷⵢⴰⵡⵉⴽⵉ [https://www.mediawiki.org/wiki/Extension:Translate Translate extension] ⵉⵜⵜⵡⴰⵙⵎⵔⵙ ⵃⵎⴰ ⴰⵏⵀⵍⵍⴰ ⵏ ⵜⵙⵓⵖⴰⵍ.\nⴰⵎⵉⴹⴰⵏ ⴰⴷ ⵉⴳⴰ ⵙⴳ ⵓⵖⴰⵡⴰⵙ ⵏ ⵎⵉⴷⵢⴰⵡⵉⴽⵉ ⴷ ⵓⵔ ⵉⴳⵉ ⵡⵉⵏ ⵃⵜⵜⴰ ⴽⵔⴰ ⵏ ⵓⵏⵙⵙⵎⵔⵙ.",
+ "translate-taskui-export-to-file": "ⵙⵙⵓⴼⵖ ⴳ ⵜⵍⵖⴰ ⵜⴰⵥⵖⵓⵕⴰⵏⵜ",
+ "translate-taskui-export-as-po": "ⵙⵙⵓⴼⵖ ⵃⵎⴰ ⴰⵙⵓⵖⵍ ⴱⵔⵔⴰ ⵏ ⵓⵣⴷⴰⵢ",
+ "translate-taction-translate": "ⵙⵙⵓⵖⵍ",
+ "translate-taction-proofread": "ⵣⵣⵔⵉ",
+ "translate-taction-lstats": "ⵉⵙⵏⵎⴽⵜⴰⵏ ⵏ ⵜⵓⵜⵍⴰⵢⵜ",
+ "translate-taction-mstats": "ⵉⵙⵏⵎⴽⵜⴰⵏ ⵏ ⵉⴳⵔⴰⵡⵏ ⵏ ⵜⵓⵣⵉⵏⵉⵏ",
+ "translate-taction-export": "ⵙⵙⵓⴼⵖ",
+ "translate-page-language": "ⵜⵓⵜⵍⴰⵢⵜ",
+ "translate-page-limit": "ⴰⵡⵜⵜⵓ",
+ "translate-pref-nonewsletter": "ⴰⴷ ⵢⵢⵉ ⵏ ⵓⵔ ⵜⵚⵚⵉⴼⵉⴹⴷ ⵜⵉⵎⵢⴰⵣⴰⵏⵉⵏ ⵏ ⵉⵏⵖⵎⵉⵙⵏ",
+ "right-translate": "ⵙⵏⴼⵍ ⵙ ⵓⵏⴳⵔⵓⴷⵎ ⵏ ⵓⵙⵓⵖⵍ",
+ "translate-statsf-count-registrations": "ⵉⵏⵙⵙⵎⵔⵙⵏ ⵉⵎⴰⵢⵏⵓⵜⵏ",
+ "translate-manage-import-new": "ⵜⵓⵣⵉⵏⵜ ⵜⴰⵎⴰⵢⵏⵓⵜ $1",
+ "translate-manage-import-summary": "ⴰⵙⴽⵛⵎ ⵏ ⵜⵓⵏⵖⵉⵍⵜ ⵜⴰⵎⴰⵢⵏⵓⵜ ⵙⴳ ⵓⵙⴰⴳⵎ ⴰⴱⵔⵔⴰⵏⵉ",
+ "translate-js-support-title": "ⵜⵜⵔ ⵜⵉⵡⵉⵙⵉ ⵉⵖ ⵓⵔ ⵜⵓⴼⵉⵜ ⵉⵏⵖⵎⵉⵙⵏ ⵢⵓⴷⴰⵏ ⵃⵎⴰ ⴰⵙⵓⵖⵍ ⵉⵎⵉⴷⵉ ⵏ ⵜⵓⵣⵉⵏⵜ ⴰⴷ.",
+ "translate-documentation-language": "ⴰⵏⵜⴰⵎ ⵏ ⵜⵓⵣⵉⵏⵜ",
+ "translate-dynagroup-additions-desc": "ⵜⴰⴳⵔⴰⵡⵜ ⴰⴷ ⴰⵔ ⵜⵙⵎⴰⵍ ⵜⵉⵎⵢⴰⵣⴰⵏⵉⵏ ⵜⵉⵎⴰⵢⵏⵓⵜⵉⵏ ⴷ ⵜⵉⴷ ⵉⵜⵜⵡⴰⵙⵏⴼⵍⵏ",
+ "tux-languageselector": "ⵙⵙⵓⵖⵍ ⵙ",
+ "tux-empty-list-translated-action": "ⵙⵙⵓⵖⵍ",
+ "tux-empty-you-can-help-providing": "ⵜⵣⵎⵔⴷ ⴰⴷ ⵜⴰⵡⵙⴷ ⴳ ⵓⵎⵔⵏⵉⵡ ⵏ ⵜⵙⵓⵖⴰⵍ ⵜⵉⵎⴰⵢⵏⵓⵜⵉⵏ"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/abs.json b/MLEB/Translate/i18n/pagetranslation/abs.json
new file mode 100644
index 00000000..ef8541a3
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/abs.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Anok kutai jang"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2 terjemahan)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/ace.json b/MLEB/Translate/i18n/pagetranslation/ace.json
new file mode 100644
index 00000000..9b9169e4
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/ace.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Si Gam Acèh"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2% geuteujeumah)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/ady-cyrl.json b/MLEB/Translate/i18n/pagetranslation/ady-cyrl.json
new file mode 100644
index 00000000..07288592
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/ady-cyrl.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Highlander45temp",
+ "Amire80"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2% зэредзэкӀыгъэ)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/aeb-latn.json b/MLEB/Translate/i18n/pagetranslation/aeb-latn.json
new file mode 100644
index 00000000..c0009d90
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/aeb-latn.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Csisc"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2% ittarjim)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/ais.json b/MLEB/Translate/i18n/pagetranslation/ais.json
new file mode 100644
index 00000000..aa8e31fa
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/ais.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Benel"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2% mabelih tuway)",
+ "tpt-aggregategroup-add": "cunus"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/ami.json b/MLEB/Translate/i18n/pagetranslation/ami.json
new file mode 100644
index 00000000..d2df9d87
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/ami.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Vickylin77s"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2% macoyakay to)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/ang.json b/MLEB/Translate/i18n/pagetranslation/ang.json
new file mode 100644
index 00000000..2468fe3a
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/ang.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Gott wisst"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ({{PLURAL:$2|$2% is awended|$2% sindon awended}})"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/anp.json b/MLEB/Translate/i18n/pagetranslation/anp.json
new file mode 100644
index 00000000..95414099
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/anp.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Angpradesh"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2% अनूदित)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/atj.json b/MLEB/Translate/i18n/pagetranslation/atj.json
new file mode 100644
index 00000000..2b37068d
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/atj.json
@@ -0,0 +1,12 @@
+{
+ "@metadata": {
+ "authors": [
+ "Benoit Rochon",
+ "Nehirowisiw"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2% nisisatamowisanihikatew)",
+ "tpt-aggregategroup-new-description": "Kotak matci avant e witcikemakak",
+ "tpt-aggregategroup-edit-description": "E witcikemakak:",
+ "pt-deletepage-current": "Icinikatamowin Ickwemakinikan:"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/bho.json b/MLEB/Translate/i18n/pagetranslation/bho.json
new file mode 100644
index 00000000..2370d2af
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/bho.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "SatyamMishra"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2% अनुवाद पूरा)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/bqi.json b/MLEB/Translate/i18n/pagetranslation/bqi.json
new file mode 100644
index 00000000..8a0c47ed
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/bqi.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Mogoeilor"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2% ڤورگأنيڌإ ڤابيڌإ)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/btm.json b/MLEB/Translate/i18n/pagetranslation/btm.json
new file mode 100644
index 00000000..54579d06
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/btm.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Simartampua"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2% iartion)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/cdo.json b/MLEB/Translate/i18n/pagetranslation/cdo.json
new file mode 100644
index 00000000..851efd45
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/cdo.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Davidzdh"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2% gó muôi huăng-ĭk)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/csb.json b/MLEB/Translate/i18n/pagetranslation/csb.json
new file mode 100644
index 00000000..fb0e25cb
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/csb.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Kaszeba"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 (zdolmaczono $2%)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/din.json b/MLEB/Translate/i18n/pagetranslation/din.json
new file mode 100644
index 00000000..6b828adb
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/din.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Dinkawiki"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2% cï waaric)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/dty.json b/MLEB/Translate/i18n/pagetranslation/dty.json
new file mode 100644
index 00000000..47f357e4
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/dty.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "जनक राज भट्ट"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2% आनुबादित)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/gaa.json b/MLEB/Translate/i18n/pagetranslation/gaa.json
new file mode 100644
index 00000000..ddcaf875
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/gaa.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Mybluberet"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 (atsake $2%)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/gcr.json b/MLEB/Translate/i18n/pagetranslation/gcr.json
new file mode 100644
index 00000000..975805f7
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/gcr.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "LeGuyanaisPure"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2% tradjwi)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/gd.json b/MLEB/Translate/i18n/pagetranslation/gd.json
new file mode 100644
index 00000000..d4b0c4cf
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/gd.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Akerbeltz"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2% air eadar-theangachadh)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/glk.json b/MLEB/Translate/i18n/pagetranslation/glk.json
new file mode 100644
index 00000000..59628a86
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/glk.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "شیخ"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2٪ واگردان بۊبؤ)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/gom-latn.json b/MLEB/Translate/i18n/pagetranslation/gom-latn.json
new file mode 100644
index 00000000..ec35a5f0
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/gom-latn.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "The Discoverer"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2% onkarla)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/gor.json b/MLEB/Translate/i18n/pagetranslation/gor.json
new file mode 100644
index 00000000..8c2699c4
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/gor.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Marwan Mohamad"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2% terjemahan)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/got.json b/MLEB/Translate/i18n/pagetranslation/got.json
new file mode 100644
index 00000000..980cd482
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/got.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Gothicspeaker"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2% 𐌲𐌰𐍃𐌺𐌴𐌹𐍂𐌹𐌸)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/grc.json b/MLEB/Translate/i18n/pagetranslation/grc.json
new file mode 100644
index 00000000..4cc55cd5
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/grc.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Gts-tg"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2% μεταφράσθηκε)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/hak.json b/MLEB/Translate/i18n/pagetranslation/hak.json
new file mode 100644
index 00000000..34c56945
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/hak.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Hakka"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2% yí-kîn fân-yi̍t)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/hif-latn.json b/MLEB/Translate/i18n/pagetranslation/hif-latn.json
new file mode 100644
index 00000000..ea48f1fd
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/hif-latn.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Abdul Kadir"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2% translate kar dewa gais)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/hy.json b/MLEB/Translate/i18n/pagetranslation/hy.json
new file mode 100644
index 00000000..2d024180
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/hy.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Դավիթ Սարոյան"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2% թարգմանված)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/hyw.json b/MLEB/Translate/i18n/pagetranslation/hyw.json
new file mode 100644
index 00000000..01adf3a9
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/hyw.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Rajemian"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2% թարգմանուած)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/ie.json b/MLEB/Translate/i18n/pagetranslation/ie.json
new file mode 100644
index 00000000..5dbee0fb
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/ie.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Stavanger7"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2% traductet)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/ilo.json b/MLEB/Translate/i18n/pagetranslation/ilo.json
new file mode 100644
index 00000000..ee478e6e
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/ilo.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Lam-ang"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2% a naipatarus)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/inh.json b/MLEB/Translate/i18n/pagetranslation/inh.json
new file mode 100644
index 00000000..e3fb8ad1
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/inh.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Adam-Yourist"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2% таржам даьд)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/jbo.json b/MLEB/Translate/i18n/pagetranslation/jbo.json
new file mode 100644
index 00000000..790d27d4
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/jbo.json
@@ -0,0 +1,15 @@
+{
+ "@metadata": {
+ "authors": [
+ "Xbony2"
+ ]
+ },
+ "tpt-rev-discourage": "toldarsygau",
+ "tpt-tab-translate": "fanva",
+ "tpt-aggregategroup-add": "jmina",
+ "tpt-aggregategroup-save": "gau se vreji",
+ "tpt-aggregategroup-update": "gau se vreji",
+ "tpt-aggregategroup-update-cancel": "kansele",
+ "pm-savepages-button-label": "gau se vreji",
+ "pm-cancel-button-label": "kansele"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/jut.json b/MLEB/Translate/i18n/pagetranslation/jut.json
new file mode 100644
index 00000000..acf9f33e
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/jut.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Jyllanj"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2% öwesat)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/kab.json b/MLEB/Translate/i18n/pagetranslation/kab.json
new file mode 100644
index 00000000..9ea051e3
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/kab.json
@@ -0,0 +1,64 @@
+{
+ "@metadata": {
+ "authors": [
+ "Belkacem77"
+ ]
+ },
+ "pagetranslation": "Asebter n tsuqilt",
+ "right-pagetranslation": "Creḍ ileqman n isebtar ara ttwasuqlen",
+ "action-pagetranslation": "Sefrek isebtar izmren ad ttwasuqlen",
+ "tpt-section": "Tayunt n tsuqilt: $1",
+ "tpt-section-new": "Tayunt tamaynut n tsuqilt. \nIsem : $1",
+ "tpt-section-deleted": "Taynut n tsuqilt $1",
+ "tpt-diff-old": "Aḍris yezrin",
+ "tpt-diff-new": "Aḍris d-iteddun",
+ "tpt-submit": "Creḍ lqem-agi ad yettwasuqel",
+ "tpt-sections-oldnew": "Tayunin n tsuqilt timaynutin neɣ tid yellan",
+ "tpt-sections-deleted": "Tayunin n tsuqilt yettwakksen",
+ "tpt-sections-template": "Taneɣruft n usebter n tsuqilt",
+ "tpt-badtitle": "Isem n usebter yettunefken ($1) mačči d azwel ameɣtu",
+ "tpt-nosuchpage": "Asebter $1 ulac-it",
+ "tpt-mark-summary": "Lqem-agi yettwacreḍ i tsuqilt",
+ "tpt-edit-failed": "Ur izmir ara ad ileqqem asebter $1",
+ "tpt-duplicate": "Isem n tayunt n tsuqilt $1 tettwaseqdec ugar n tikelt.",
+ "tpt-already-marked": "Lqem aneggaru n usebter-agi yettwacreḍ i tsuqilt.",
+ "tpt-unmarked": "Asebter $1 ur yezgi yettwacreḍ i tsuqilt",
+ "tpt-list-nopages": "Ulac isebtar yettwacerḍen i tsuqilt neɣ yettwaheggan ad ttwacerḍen i tsuqilt.",
+ "tpt-new-pages-title": "Isebtar yettwasumren i tsuqilt",
+ "tpt-old-pages-title": "Isebtar yettwasqqalen tura",
+ "tpt-other-pages-title": "Isebtar s tuccḍiwin",
+ "tpt-discouraged-pages-title": "Isebtar s war afud",
+ "tpt-select-prioritylangs-reason": "Taɣẓint :",
+ "tpt-rev-mark": "creḍ i tsuqilt",
+ "tpt-rev-unmark": "kkes si tsuqilt",
+ "tpt-rev-encourage": "err-d",
+ "tpt-rev-mark-tooltip": "Creḍ lqem aneggaru n usebter-agi i tsuqilt.",
+ "tpt-rev-unmark-tooltip": "Kkes asebter-agi si tsuqilt.",
+ "tpt-rev-encourage-tooltip": "Err-d asebter-agi ar tsuqilt tamagnut.",
+ "translate-tag-translate-link-desc": "Suqel asebter-agi",
+ "translate-tag-markthis": "Creḍ asebter-agi i tsuqilt",
+ "tpt-translation-intro-fuzzy": "Tisuqilin ifaten ttwacerḍent akka.",
+ "tpt-languages-legend": "Tutlayin-nniḍen:",
+ "tpt-languages-zero": "Senker tasuqilt i tutlayt-a",
+ "tpt-languages-nonzero": "$1 ($2% yettwasuqel)",
+ "tpt-tab-translate": "Suqel",
+ "tpt-discouraged-language-force-header": "Asebter-agi ur yezmir ara ad yettwasuqle ar $1.",
+ "tpt-discouraged-language-force-content": "Anebdal n tsuqilt igzem-itt d iṛṛay d akken asebter-agi ur yezmir ara ad yettwsuqel ar $1.",
+ "tpt-discouraged-language-header": "Tasuqilt ar $1 mačči d ayen yezwaren deg usebter-agi.",
+ "tpt-discouraged-language-reason": "Taɣzint: $1",
+ "tpt-aggregategroup-add": "Rnu",
+ "tpt-aggregategroup-save": "Sekles",
+ "tpt-aggregategroup-new-name": "Isem:",
+ "tpt-aggregategroup-new-description": "Aglam (afrayan):",
+ "tpt-aggregategroup-invalid-group": "Ulac agraw",
+ "tpt-aggregategroup-edit-name": "Isem:",
+ "tpt-aggregategroup-edit-description": "Aglam:",
+ "tpt-aggregategroup-update": "Sekles",
+ "tpt-aggregategroup-update-cancel": "Sefsex",
+ "tpt-invalid-group": "Agraw arameɣtu",
+ "pt-deletepage-subpages": "Kkes akk isebtar isnawanen",
+ "pt-deletepage-list-other": "Isebtar-nniḍen isnawanen",
+ "pm-import-button-label": "Kter",
+ "pm-summary-import": "Tasuqlit tettwakter s useqdec n [[Special:PageMigration|n usgaǧi n usebter]]",
+ "pp-save-message": "Asebter yettwasekles. Tzemreḍ ad tt-[$1 ẓergeḍ]."
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/kbp.json b/MLEB/Translate/i18n/pagetranslation/kbp.json
new file mode 100644
index 00000000..7d202ada
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/kbp.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Gnangbade"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2% kɩɖɛzʋ|kɩɖɛzaa|kɩɖɛzʋʋ|kɩɖɛzɩŋ|kɩɖɛzaɣ|kɩɖɛzasɩ|kɩɖɛzɩyɛ|kɩɖɛza||kɩɖɛzɩtʋ|kɩɖɛzɩm)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/kjp.json b/MLEB/Translate/i18n/pagetranslation/kjp.json
new file mode 100644
index 00000000..3f6cbcd5
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/kjp.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Rul1902"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2% ၮှ်ခၠယ့်ၯင်ႋ)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/krl.json b/MLEB/Translate/i18n/pagetranslation/krl.json
new file mode 100644
index 00000000..f2b85e98
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/krl.json
@@ -0,0 +1,10 @@
+{
+ "@metadata": {
+ "authors": [
+ "Likopiän tyttö",
+ "Mashoi7"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2% kiännetty)",
+ "tpt-tab-translate": "Kiännä"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/kum.json b/MLEB/Translate/i18n/pagetranslation/kum.json
new file mode 100644
index 00000000..30b39284
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/kum.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "ArslanX"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2% гёчюрюлген)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/kw.json b/MLEB/Translate/i18n/pagetranslation/kw.json
new file mode 100644
index 00000000..8460b4b7
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/kw.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Nrowe"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2% treylyes)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/lag.json b/MLEB/Translate/i18n/pagetranslation/lag.json
new file mode 100644
index 00000000..4accf629
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/lag.json
@@ -0,0 +1,10 @@
+{
+ "@metadata": {
+ "authors": [
+ "Baba Tabita"
+ ]
+ },
+ "tpt-aggregategroup-add": "Ongerya",
+ "tpt-aggregategroup-new-name": "Irina:",
+ "tpt-aggregategroup-edit-name": "Irina:"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/lfn.json b/MLEB/Translate/i18n/pagetranslation/lfn.json
new file mode 100644
index 00000000..da414469
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/lfn.json
@@ -0,0 +1,11 @@
+{
+ "@metadata": {
+ "authors": [
+ "Cgboeree",
+ "Robin van der Vliet"
+ ]
+ },
+ "tpt-languages-legend": "Otra linguas:",
+ "tpt-languages-nonzero": "$1 ($2% traduida)",
+ "tpt-aggregategroup-edit-description": "Descrive:"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/li.json b/MLEB/Translate/i18n/pagetranslation/li.json
new file mode 100644
index 00000000..a1e8ef48
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/li.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Ooswesthoesbes"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2% euvergezatte)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/lij.json b/MLEB/Translate/i18n/pagetranslation/lij.json
new file mode 100644
index 00000000..2280b603
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/lij.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Giromin Cangiaxo"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2% traduta)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/lki.json b/MLEB/Translate/i18n/pagetranslation/lki.json
new file mode 100644
index 00000000..d2c5a47e
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/lki.json
@@ -0,0 +1,66 @@
+{
+ "@metadata": {
+ "authors": [
+ "Hosseinblue",
+ "Lakzon"
+ ]
+ },
+ "pagetranslation": "چاوواشەکردن زوون وەڵگە",
+ "right-pagetranslation": "علامت‌گذاری نسخه‌های صفحه برای ترجمه",
+ "action-pagetranslation": "مدیریت صفحه‌های ترجمه‌پذیر",
+ "tpt-section": "واحد ترجمهٔ $1",
+ "tpt-section-new": "واحد جدید ترجمه.\nنام: $1",
+ "tpt-section-deleted": "واحد ترجمهٔ $1",
+ "tpt-diff-old": "متن قبلی",
+ "tpt-diff-new": "متن تازه",
+ "tpt-submit": "علامت‌گذاری این نسخه برای ترجمه",
+ "tpt-sections-oldnew": "واحدهای تازه و موجود ترجمه",
+ "tpt-sections-deleted": "واحدهای حذف‌شدهٔ ترجمه",
+ "tpt-sections-template": "الگوی ترجمهٔ صفحه",
+ "tpt-action-nofuzzy": "عدم ابطال ترجمه‌ها",
+ "tpt-badtitle": "نام صفحهٔ داده‌شده ($1) عنوان معتبری نیست",
+ "tpt-nosuchpage": "اێ وەڵگە $1 هەنی(هالی)وجود نِئرێ",
+ "tpt-oldrevision": "$2 آخرین نسخهٔ صفحهٔ [[:$1]] نیست.\nفقط آخرین نسخه‌ها می‌توانند برای ترجمه علامت‌گذاری شوند.",
+ "tpt-notsuitable": "وةڵگة $1 برای ترجمه مناسب نیست.\nمطمئن شوید برچسب <nowiki><translate></nowiki> و نحو مناسبی دارد.",
+ "tpt-saveok": "صفحه [[:$1]] برای ترجمه با $2 {{PLURAL:$2|translation unit|translation units}} مشخص شده‌است.\nاکنون صفحه می‌تواند <span class=\"plainlinks\">[$3 ترجمه شده]</span> باشد.",
+ "tpt-offer-notify": "شما می‌توانید <span class=\"plainlinks\">[$1 notify translators]</span> درباره این صفحه.",
+ "tpt-old-pages-title": "وةڵگةدر ترجمه",
+ "tpt-other-pages-title": "وةڵگةل نادرست",
+ "tpt-discouraged-pages-title": "صفحات دلسرد",
+ "tpt-new-pages": "{{PLURAL:$1|این صفحه شامل|این صفحات شامل}} متن با برچسب‌های ترجمه،\nاما هیچ نسخه‌ {{PLURAL:$1|این صفحه|این صفحات}} در حال حاضر برای ترجمه علامت‌گذاری نشده‌است.",
+ "tpt-select-prioritylangs-reason": ":دةلیل",
+ "tpt-sections-prioritylangs": "زبان‌های با اولویت بالا",
+ "tpt-rev-mark": "علامت‌گذاری برای ترجمه",
+ "tpt-rev-unmark": "حذف از ترجمه",
+ "tpt-rev-discourage": "دلسرد",
+ "tpt-rev-encourage": "احیا",
+ "tpt-rev-mark-tooltip": "علامت‌گذاری آخرین نسخهٔ این صفحه برای ترجمه.",
+ "tpt-rev-unmark-tooltip": "حذف این صفحه از ترجمه.",
+ "tpt-rev-discourage-tooltip": "دلسرد شدن از ترجمه‌های بیشتر در این صفحه",
+ "tpt-rev-encourage-tooltip": "بازگرداندن این صفحه به ترجمه عادی.",
+ "translate-tag-translate-link-desc": "اێ وەڵگە چاوواشآکە زوونێ تر",
+ "translate-tag-markthis": "علامت‌گذاری این صفحه برای ترجمه",
+ "tpt-translation-intro": "این صفحه <span class=\"plainlinks\">[$1 نسخهٔ ترجمه‌شدهٔ]</span> صفحهٔ [[$2]] است و ترجمهٔ آن $3٪ کامل شده‌است.",
+ "tpt-languages-legend": "وۀ زوونۀلئ تر:",
+ "tpt-languages-zero": "شروع ترجمه برای این زبان",
+ "tpt-languages-nonzero": "$1 ($2% ترجمه‌شده)",
+ "tpt-tab-translate": "چاوواشەکِردن زوون",
+ "tpt-aggregategroup-add": "افزودن",
+ "tpt-aggregategroup-save": "هیشتن(ذخیره)",
+ "tpt-aggregategroup-add-new": "افزودن گروه تازه جمع‌شده",
+ "tpt-aggregategroup-new-name": ":نام",
+ "tpt-aggregategroup-new-description": "توضیحات (اختیاری):",
+ "tpt-aggregategroup-edit-name": ":نام",
+ "tpt-aggregategroup-edit-description": "شرح:",
+ "tpt-aggregategroup-update": "هیشتن(ذخیره)",
+ "tpt-aggregategroup-update-cancel": "ئآهووسانن/لغو",
+ "pt-movepage-current": "نام فعلی(ایسە):",
+ "pt-movepage-new": "نام تازه:",
+ "pt-movepage-reason": ":دةلیل",
+ "pt-deletepage-list-translation": "وةڵگةل تةرجؤمة",
+ "pm-import-button-label": "درون‌ریزی شود",
+ "pm-savepages-button-label": "هیشتن(ذخیره)",
+ "pm-cancel-button-label": "ئآهووسانن/لغو",
+ "pp-save-button-label": "هیشتن(ذخیره)",
+ "pp-cancel-button-label": "ئآهووسانن/لغو"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/lo.json b/MLEB/Translate/i18n/pagetranslation/lo.json
new file mode 100644
index 00000000..7111edbf
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/lo.json
@@ -0,0 +1,10 @@
+{
+ "@metadata": {
+ "authors": [
+ "Aefgh39622"
+ ]
+ },
+ "pt-movepage-logreason": "ສ່ວນຂອງໜ້າ \"$1\" ທີ່ສາມາດແປໄດ້",
+ "pt-deletepage-full-logreason": "ສ່ວນຂອງໜ້າ \"$1\" ທີ່ສາມາດແປໄດ້",
+ "pt-deletepage-lang-logreason": "ສ່ວນຂອງໜ້າແປຂອງ \"$1\""
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/luz.json b/MLEB/Translate/i18n/pagetranslation/luz.json
new file mode 100644
index 00000000..6b5bcbf5
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/luz.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "علی ساکی لرستانی"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2% ترجمه ۉابیدھ)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/mni.json b/MLEB/Translate/i18n/pagetranslation/mni.json
new file mode 100644
index 00000000..4c5e41a3
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/mni.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Awangba Mangang"
+ ]
+ },
+ "tpt-languages-nonzero": "$1($2% ꯍꯟꯗꯣꯛꯂꯦ)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/mnw.json b/MLEB/Translate/i18n/pagetranslation/mnw.json
new file mode 100644
index 00000000..1003ba62
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/mnw.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Htawmonzel"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2% ကၠာဲစၠောအ်လဝ်)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/mo.json b/MLEB/Translate/i18n/pagetranslation/mo.json
new file mode 100644
index 00000000..a3a0406f
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/mo.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Cybernenea11"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2% традусэ)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/mwl.json b/MLEB/Translate/i18n/pagetranslation/mwl.json
new file mode 100644
index 00000000..358ad84f
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/mwl.json
@@ -0,0 +1,10 @@
+{
+ "@metadata": {
+ "authors": [
+ "MokaAkashiyaPT",
+ "Athena in Wonderland"
+ ]
+ },
+ "translate-tag-translate-link-desc": "Traduzir esta páigina",
+ "tpt-languages-nonzero": "$1 ($2% traduzida)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/my.json b/MLEB/Translate/i18n/pagetranslation/my.json
new file mode 100644
index 00000000..89c2828a
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/my.json
@@ -0,0 +1,106 @@
+{
+ "@metadata": {
+ "authors": [
+ "Ninjastrikers",
+ "Dr Lotus Black"
+ ]
+ },
+ "pagetranslation": "စာမျက်နှာ ဘာသာပြန်ခြင်း",
+ "right-pagetranslation": "စာမျက်နှာများ၏ဗားရှင်းကို ဘာသာပြန်အတွက် မှတ်သားရန်",
+ "action-pagetranslation": "ဘာသာပြန်နိုင်သော စာမျက်နှာများကို စီမံရန်",
+ "tpt-diff-old": "ယခင်စာသား",
+ "tpt-diff-new": "ရှေ့စာသား",
+ "tpt-submit": "ဤဗားရှင်းကို ဘာသာပြန်အတွက် မှတ်သားရန်",
+ "tpt-nosuchpage": "စာမျက်နှာ $1 မတည်ရှိပါ",
+ "tpt-mark-summary": "ဤဗားရှင်းကို ဘာသာပြန်အတွက် မှတ်သားခဲ့သည်",
+ "tpt-edit-failed": "ဤစာမျက်နှာကို မမွမ်းမံနိုင်ပါ: $1",
+ "tpt-already-marked": "ဤစာမျက်နှာ၏ နောက်ဆုံးဗားရှင်းကို ဘာသာပြန်ရန်အတွက် မှတ်သားပြီးဖြစ်သည်။",
+ "tpt-new-pages-title": "ဘာသာပြန်အတွက် အဆိုပြုထားသော စာမျက်နှာများ",
+ "tpt-old-pages-title": "ဘာသာပြန်ရှိ စာမျက်နှာများ",
+ "tpt-other-pages-title": "ကျိုးပျက်နေသော စာမျက်နှာများ",
+ "tpt-select-prioritylangs-reason": "အ​ကြောင်း​ပြ​ချက်:",
+ "tpt-sections-prioritylangs": "ဦးစားပေး ဘာသာစကားများ",
+ "tpt-rev-mark": "ဘာသာပြန်အတွက် မှတ်သားရန်",
+ "tpt-rev-unmark": "ဘာသာပြန်မှ ဖယ်ရှားရန်",
+ "tpt-rev-mark-tooltip": "ဤစာမျက်နှာ၏ နောက်ဆုံးဗားရှင်း ဘာသာပြန်အတွက် မှတ်သားရန်",
+ "tpt-rev-unmark-tooltip": "ဘာသာပြန်မှ ဤစာမျက်နှာအား ဖယ်ရှားရန်",
+ "tpt-rev-encourage-tooltip": "ဤစာမျက်နှာကို ပုံမှန်ဘာသာပြန်ခြင်းသို့ ပြန်ထားရန်။",
+ "translate-tag-translate-link-desc": "ဤစာမျက်နှာကို ဘာသာပြန်ပါ",
+ "translate-tag-markthis": "ဘာသာပြန်ခြင်းအတွက် ဤစာမျက်နှာကို မှတ်သားရန်",
+ "tpt-translation-intro-fuzzy": "ခေတ်နောက်ကျသော ဘာသာပြန်များကို ဤကဲ့သို့ မှတ်သားထားသည်။",
+ "tpt-languages-legend": "အခြားဘာသာစကားများ:",
+ "tpt-languages-zero": "ဤဘာသာစကားအတွက် ဘာသာပြန်ခြင်း စတင်ရန်",
+ "tpt-languages-nonzero": "$1 ($2% ဘာသာပြန်ပြီး)",
+ "tpt-tab-translate": "ဘာသာပြန်ပါ",
+ "tpt-translation-restricted": "ဤစာမျက်ကို ဤဘာသာစကားသို့ ဘာသာပြန်ခြင်းကို ဘာသာပြန် စီမံခန့်ခွဲသူတစ်ဦးမှ တားဆီးထားသည်။\n\nအကြောင်းပြချက်: $1",
+ "tpt-discouraged-language-force-header": "ဤစာမျက်နှာကို $1 သို့ ဘာသာမပြန်နိုင်ပါ",
+ "tpt-discouraged-language-force-content": "ဤစာမျက်နှာကို $1 တစ်ခုတည်းသို့သာ ဘာသာပြန်နိုင်သည်ဟု ဘာသာပြန် စီမံခန့်ခွဲသူတစ်ဦးက ဆုံးဖြတ်ခဲ့သည်။",
+ "tpt-discouraged-language-header": "$1 သို့ ဘာသာပြန်ခြင်းသည် ဤစာမျက်နှာအတွက် ဦးစားပေးမဟုတ်ပါ။",
+ "tpt-discouraged-language-content": "ဘာသာပြန် စီမံခန့်ခွဲသူတစ်ဦးမှ ဘာသာပြန်ခြင်းကို $1 တွင် အာရုံစိုက်ကြရန် ဆုံးဖြတ်ခဲ့သည်။",
+ "tpt-discouraged-language-force": "<strong>ဤစာမျက်နှာကို $2 သို့ ဘာသာမပြန်နိုင်ပါ။</strong>\n\nဘာသာပြန် စီမံခန့်ခွဲသူတစ်ဦးက ဤစာမျက်နှာကို $3 တစ်ခုတည်းသို့သာ ဘာသာပြန်နိုင်သည်ဟု ဆုံးဖြတ်ခဲ့သည်။",
+ "tpt-discouraged-language": "<strong>ဤစာမျက်နှာအတွက် $2 သို့ ဘာသာပြန်ခြင်းသည် ဦးစားပေးမဟုတ်ပါ။</strong>\n\nဘာသာပြန် စီမံခန့်ခွဲသူတစ်ဦးက $3 သို့ ဘာသာပြန်ဆိုခြင်းအတွက် အာရုံစူးစိုက်ကြရန် ဆုံးဖြတ်ခဲ့သည်။",
+ "tpt-discouraged-language-reason": "အကြောင်းပြချက်: $1",
+ "tpt-priority-languages": "ဘာသာပြန် စီမံခန့်ခွဲသူတစ်ဦးက ဤအုပ်စုအတွက် ဦးစားပေးဘာသာစကားများကို $1 သို့ သတ်မှတ်ထားသည်။",
+ "aggregategroups": "စုပေါင်း အုပ်စုများ",
+ "tpt-aggregategroup-add": "ပေါင်းထည့်ရန်",
+ "tpt-aggregategroup-save": "သိမ်းရန်",
+ "tpt-aggregategroup-add-new": "စုပေါင်းအုပ်စု အသစ်တစ်ခု ပေါင်းထည့်ရန်",
+ "tpt-aggregategroup-new-name": "အမည်:",
+ "tpt-aggregategroup-new-description": "ဖော်ပြချက် (မထည့်လည်းရသည်)",
+ "tpt-aggregategroup-remove-confirm": "ဤစုပေါင်းအုပ်စုကို ဖျက်လိုသည်မှာ သေချာပါသလား?",
+ "tpt-aggregategroup-invalid-group": "အုပ်စု မတည်ရှိပါ",
+ "tpt-aggregategroup-edit-name": "အမည်:",
+ "tpt-aggregategroup-edit-description": "ဖော်ပြချက်:",
+ "tpt-aggregategroup-update": "သိမ်းရန်",
+ "tpt-aggregategroup-update-cancel": "မလုပ်တော့ပါ",
+ "log-description-pagetranslation": "စာမျက်နှာ ဘာသာပြန်စနစ်နှင့်ဆက်နွယ်သော လုပ်ဆောင်ချက်များ၏ မှတ်တမ်း",
+ "log-name-pagetranslation": "စာမျက်နှာဘာသာပြန်မှု မှတ်တမ်း",
+ "logentry-pagetranslation-mark": "$1 က $3 ကို ဘာသာပြန်အတွက် {{GENDER:$2|မှတ်သားခဲ့သည်}}",
+ "logentry-pagetranslation-unmark": "$1 က $3 ကို ဘာသာပြန်မှ {{GENDER:$2|ဖယ်ရှားခဲ့သည်}}",
+ "log-action-filter-pagetranslation": "လုပ်ဆောင်ချက် အမျိုးအစား:",
+ "log-action-filter-pagetranslation-mark": "ဘာသာပြန်အတွက် မှတ်သားရန်",
+ "log-action-filter-pagetranslation-unmark": "ဘာသာပြန်မှ ဖယ်ရှားမှု",
+ "log-action-filter-pagetranslation-move": "စာမျက်နှာ ရွေ့ပြောင်းမှုများ",
+ "log-action-filter-pagetranslation-delete": "စာမျက်နှာ ဖျက်ပစ်မှုများ",
+ "pt-movepage-list-pages": "ရွေ့ပြောင်းရန် စာမျက်နှာများစာရင်း",
+ "pt-movepage-list-translation": "ဘာသာပြန် {{PLURAL:$1|စာမျက်နှာ|စာမျက်နှာများ}}",
+ "pt-movepage-list-other": "အခြား စာမျက်နှာ{{PLURAL:$1|ခွဲ|ခွဲများ}}",
+ "pt-movepage-legend": "ဘာသာပြန်နိုင်သော စာမျက်နှာကို ရွေ့ပြောင်းရန်",
+ "pt-movepage-current": "လက်ရှိအမည်:",
+ "pt-movepage-new": "နာမည်သစ်:",
+ "pt-movepage-reason": "အ​ကြောင်း​ပြ​ချက်:",
+ "pt-movepage-subpages": "စာမျက်နှာခွဲများအားလုံးကို ရွေ့ပြောင်းရန်",
+ "pt-movepage-logreason": "ဘာသာပြန်နိုင်သော စာမျက်နှာ \"$1\" ၏ အစိတ်အပိုင်း",
+ "pt-deletepage-lang-legend": "ဘာသာပြန်စာမျက်နှာကို ဖျက်ပစ်ရန်",
+ "pt-deletepage-full-legend": "ဘာသာပြန်နိုင်သော စာမျက်နှာကို ဖျက်ပစ်ရန်",
+ "pt-deletepage-any-legend": "ဘာသာပြန်နိုင်သောစာမျက်နှာ သို့မဟုတ် ဘာသာပြန်စာမျက်နှာကို ဖျက်ပစ်ရန်",
+ "pt-deletepage-current": "စာမျက်နှာ အမည်:",
+ "pt-deletepage-reason": "အ​ကြောင်း​ပြ​ချက်:",
+ "pt-deletepage-subpages": "စာမျက်နှာခွဲများအားလုံးကို ဖျက်ရန်",
+ "pt-deletepage-list-pages": "ဖျက်ရန် စာမျက်နှာများ စာရင်း",
+ "pt-deletepage-list-translation": "ဘာသာပြန် စာမျက်နှာများ",
+ "pt-deletepage-list-other": "အခြား စာမျက်နှာခွဲများ",
+ "pt-deletepage-full-logreason": "ဘာသာပြန်နိုင်သော စာမျက်နှာ \"$1\" ၏ အစိတ်အပိုင်း",
+ "pt-deletepage-lang-logreason": "ဘာသာပြန်နိုင်သော စာမျက်နှာ \"$1\" ၏ အစိတ်အပိုင်း",
+ "pm-import-button-label": "တင်သွင်းရန်",
+ "pm-savepages-button-label": "သိမ်းရန်",
+ "pm-cancel-button-label": "မလုပ်တော့ပါ",
+ "pm-page-does-not-exist": "$1 မတည်ရှိပါ။",
+ "pm-old-translations-missing": "$1 တွင် ဘာသာပြန်အဟောင်းများ မပါရှိပါ။",
+ "pm-pagename-missing": "ကျေးဇူးပြု၍ စာမျက်နှာနာမည် ရိုက်ထည့်ပါ။",
+ "pm-pagetitle-placeholder": "စာမျက်နှာခေါင်းစဉ် ရိုက်ထည့်ပါ",
+ "pm-pagetitle-missing": "ကျေးဇူးပြု၍ စာမျက်နှာခေါင်းစဉ် ရိုက်ထည့်ပါ",
+ "tpt-translate-title": "စာမျက်နှာခေါင်းစဉ် ဘာသာပြန်မှုကို ခွင့်ပြုရန်",
+ "pp-save-summary": "ဘာသာပြန်အတွက် စာမျက်နှာ ကြိုတင်ပြင်ဆင်ရန်",
+ "pagepreparation": "ဘာသာပြန်အတွက် စာမျက်နှာ ကြိုတင်ပြင်ဆင်ရန်",
+ "pp-pagename-placeholder": "စာမျက်နှာအမည် ရိုက်ထည့်ပါ",
+ "pp-prepare-button-label": "ပြင်ဆင်",
+ "pp-save-button-label": "သိမ်းရန်",
+ "pp-cancel-button-label": "မလုပ်တော့ပါ",
+ "pp-save-message": "စာမျက်နှာကို သိမ်းဆည်းခဲ့သည်။ ယင်းအား [$1 တည်းဖြတ်]နိုင်သည်။",
+ "pp-pagename-missing": "ကျေးဇူးပြု၍ စာမျက်နှာနာမည် ရိုက်ထည့်ပါ။",
+ "tpt-unlink-button": "ဘာသာပြန်မှ ဖယ်ရှားရန်",
+ "tpt-unlink-summary": "ဘာသာပြန်မှ စာမျက်နှာအား ဖယ်ရှားခဲ့သည်",
+ "tpt-generic-confirm": "လုပ်ဆောင်ချက်ကို အတည်ပြုပါ။",
+ "tpt-generic-button": "အတည်ပြု"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/mzn.json b/MLEB/Translate/i18n/pagetranslation/mzn.json
new file mode 100644
index 00000000..9854560a
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/mzn.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "محک"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2% وَردِگاردِسته بیّه)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/nys.json b/MLEB/Translate/i18n/pagetranslation/nys.json
new file mode 100644
index 00000000..d15a369e
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/nys.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Gnangarra"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2% translated)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/olo.json b/MLEB/Translate/i18n/pagetranslation/olo.json
new file mode 100644
index 00000000..743bad57
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/olo.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Mashoi7"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2% kiännetty)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/pnb.json b/MLEB/Translate/i18n/pagetranslation/pnb.json
new file mode 100644
index 00000000..dc1ede45
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/pnb.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Khalid Mahmood"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2% بولی پلٹی گئی)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/qu.json b/MLEB/Translate/i18n/pagetranslation/qu.json
new file mode 100644
index 00000000..f6247f0f
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/qu.json
@@ -0,0 +1,11 @@
+{
+ "@metadata": {
+ "authors": [
+ "AlimanRuna"
+ ]
+ },
+ "pagetranslation": "P'anqakunata t'ikray",
+ "tpt-languages-nonzero": "$1 ($2% t'ikrasqa)",
+ "pt-movepage-list-translation": "T'ikrasqa {{PLURAL:$1|p'anqa|p'anqakuna}}",
+ "pt-deletepage-list-translation": "T'ikray p'anqakuna"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/sat.json b/MLEB/Translate/i18n/pagetranslation/sat.json
new file mode 100644
index 00000000..dc3315b5
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/sat.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Albinus",
+ "Manik Soren"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2% ᱛᱚᱨᱡᱚᱢᱟᱣᱠᱟᱱᱟ)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/sd.json b/MLEB/Translate/i18n/pagetranslation/sd.json
new file mode 100644
index 00000000..fecd5bf8
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/sd.json
@@ -0,0 +1,20 @@
+{
+ "@metadata": {
+ "authors": [
+ "Sindhu",
+ "Aursani",
+ "Mehtab ahmed"
+ ]
+ },
+ "tpt-diff-old": "پويون متن",
+ "tpt-mark-nochanges": "تبديلين جي ڪا به نظرثاني نه. ترجمي لاءِ هي صفحو نشاني بڻائڻ صفحي کي ترميم نه ڪندو، نه ئي اڳ موجود ترجمي جي يونٽ کي ترميم ڪندو",
+ "tpt-languages-nonzero": "$1 ($2% ترجمو ٿيل)",
+ "tpt-aggregategroup-save": "سانڍيو",
+ "tpt-aggregategroup-update": "سانڍيو",
+ "tpt-aggregategroup-update-cancel": "رد",
+ "pm-savepages-button-label": "سانڍيو",
+ "pm-cancel-button-label": "رد",
+ "pp-save-button-label": "سانڍيو",
+ "pp-cancel-button-label": "رد",
+ "tpt-generic-button": "پڪ ڪريو"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/sgs.json b/MLEB/Translate/i18n/pagetranslation/sgs.json
new file mode 100644
index 00000000..367d0411
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/sgs.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Hugo.arg"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2% pargoldīta)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/sh.json b/MLEB/Translate/i18n/pagetranslation/sh.json
new file mode 100644
index 00000000..f03a1c9f
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/sh.json
@@ -0,0 +1,13 @@
+{
+ "@metadata": {
+ "authors": [
+ "OC Ripper",
+ "Conquistador"
+ ]
+ },
+ "tpt-languages-legend": "Drugi jezici:",
+ "tpt-languages-nonzero": "$1 ($2% prevedeno)",
+ "tpt-aggregategroup-update-cancel": "Otkaži",
+ "pm-cancel-button-label": "Otkaži",
+ "pp-cancel-button-label": "Otkaži"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/shi.json b/MLEB/Translate/i18n/pagetranslation/shi.json
new file mode 100644
index 00000000..2cd3a7f4
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/shi.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Amara-Amaziɣ"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2% ⴰⴷ ⵉⵜⵜⵢⵓⵙⵓⵖⵍⵏ)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/skr-arab.json b/MLEB/Translate/i18n/pagetranslation/skr-arab.json
new file mode 100644
index 00000000..6bf82cfa
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/skr-arab.json
@@ -0,0 +1,25 @@
+{
+ "@metadata": {
+ "authors": [
+ "Saraiki"
+ ]
+ },
+ "tpt-select-prioritylangs-reason": "سبب:",
+ "tpt-languages-legend": "ٻیاں زباناں:",
+ "tpt-languages-nonzero": "$1 ($2% ترجمہ تھی ڳیا)",
+ "tpt-aggregategroup-save": "بچاؤ",
+ "tpt-aggregategroup-new-name": "ناں:",
+ "tpt-aggregategroup-edit-name": "ناں:",
+ "tpt-aggregategroup-edit-description": "تفصیل:",
+ "tpt-aggregategroup-update-cancel": "منسوخ",
+ "pt-movepage-new": "نواں ناں:",
+ "pt-movepage-reason": "سبب:",
+ "pt-deletepage-reason": "سبب:",
+ "pm-import-button-label": "اندر گھن آؤ",
+ "pm-savepages-button-label": "بچاؤ",
+ "pm-cancel-button-label": "منسوخ",
+ "pp-prepare-button-label": "تیار کرو",
+ "pp-save-button-label": "بچاؤ",
+ "pp-cancel-button-label": "منسوخ",
+ "tpt-generic-button": "تصدیق"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/sq.json b/MLEB/Translate/i18n/pagetranslation/sq.json
new file mode 100644
index 00000000..9d0442af
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/sq.json
@@ -0,0 +1,10 @@
+{
+ "@metadata": {
+ "authors": [
+ "Liridon",
+ "Ammartivari"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2% i përkëthyer)",
+ "pt-deletepage-reason": "Arsyeja:"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/sty.json b/MLEB/Translate/i18n/pagetranslation/sty.json
new file mode 100644
index 00000000..3b24bf9e
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/sty.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Khanmarat"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2% телгә күцерелгән)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/szl.json b/MLEB/Translate/i18n/pagetranslation/szl.json
new file mode 100644
index 00000000..78d29070
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/szl.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Krol111"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 (przetuplikowano $2%)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/tay.json b/MLEB/Translate/i18n/pagetranslation/tay.json
new file mode 100644
index 00000000..b0bd9a82
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/tay.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Hitaypayan"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2% wal gmbang miru’)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/tg-cyrl.json b/MLEB/Translate/i18n/pagetranslation/tg-cyrl.json
new file mode 100644
index 00000000..ba2ac3b1
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/tg-cyrl.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Ibrahim"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2% тарҷумашуда)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/udm.json b/MLEB/Translate/i18n/pagetranslation/udm.json
new file mode 100644
index 00000000..d690afe1
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/udm.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Wadorgurt"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2% берыктэмын)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/vro.json b/MLEB/Translate/i18n/pagetranslation/vro.json
new file mode 100644
index 00000000..3f3a4863
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/vro.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Võrok"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2% ümbre pant)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/war.json b/MLEB/Translate/i18n/pagetranslation/war.json
new file mode 100644
index 00000000..abd6bcec
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/war.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "JinJian"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2% an nahubad na)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/wo.json b/MLEB/Translate/i18n/pagetranslation/wo.json
new file mode 100644
index 00000000..3a35b6fd
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/wo.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Ibou"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2% lañu tekki)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/yo.json b/MLEB/Translate/i18n/pagetranslation/yo.json
new file mode 100644
index 00000000..e908711f
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/yo.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Wikicology",
+ "Demmy"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2% ti jẹ́ yíyípadàlédè)"
+}
diff --git a/MLEB/Translate/i18n/pagetranslation/zgh.json b/MLEB/Translate/i18n/pagetranslation/zgh.json
new file mode 100644
index 00000000..9403534a
--- /dev/null
+++ b/MLEB/Translate/i18n/pagetranslation/zgh.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Amara-Amaziɣ"
+ ]
+ },
+ "tpt-languages-nonzero": "$1 ($2% ⵉⵜⵜⵓⵙⵓⵖⵍⵏ)"
+}
diff --git a/MLEB/Translate/i18n/sandbox/bs.json b/MLEB/Translate/i18n/sandbox/bs.json
new file mode 100644
index 00000000..aaf87811
--- /dev/null
+++ b/MLEB/Translate/i18n/sandbox/bs.json
@@ -0,0 +1,52 @@
+{
+ "@metadata": {
+ "authors": [
+ "Srdjan m"
+ ]
+ },
+ "managetranslatorsandbox": "Upravljanje prevodilačkim pješčanikom",
+ "tsb-filter-pending": "Zahtjevi na čekanju",
+ "tsb-reminder-title-generic": "Dovršite uvodni rad da biste postali prevodilac",
+ "tsb-reminder-content-generic": "Zdravo, $1,\n\nHvala Vam što ste se registrirali na {{GRAMMAR:dativ|{{SITENAME}}}}.\n\nAko dovršite probne prijevode, administratori će Vam ubrzo nakon tog dodijeliti potpuni pristup prevođenju.\n\nPosjetite naredni sajt i prevedite još poruka:\n$2\n\n$3,\nosoblje {{GRAMMAR:genitiv|{{SITENAME}}}}",
+ "tsb-reminder-sending": "Šaljem podsjetnik...",
+ "tsb-reminder-sent": "{{PLURAL:$1|Poslan $1 podsjetnik. Vrijeme: $2.|Poslana $1 podsjetnika. Posljednji: $2.}}",
+ "tsb-reminder-sent-new": "Pošalji podsjetnik",
+ "tsb-reminder-failed": "Nisam uspio poslati podsjetnik",
+ "tsb-email-promoted-subject": "Sad ste prevodilac na {{GRAMMAR:dativ|{{SITENAME}}}}",
+ "tsb-email-promoted-body": "Zdravo {{GENDER:$1|$1}},\n\nČestitke! {{GENDER:$3|Pregledao|Pregledala}} sam probne prijevode koje ste napravili na {{GRAMMAR:dativ|{{SITENAME}}}} i {{GENDER:$3|dao|dala}} sam Vam puna prevodilačka prava.\n\nDođite na {{GRAMMAR:akuzativ|{{SITENAME}}}} da nastavite prevoditi sada i svaki naredni dan:\n$2\n\nDobro došli i hvala Vam na doprinosima!\n{{GENDER:$3|$3}},\nosoblje {{GRAMMAR:genitiv|{{SITENAME}}}}",
+ "tsb-email-rejected-subject": "Vaša zahtjev da postanete prevodilac na {{GRAMMAR:dativ|{{SITENAME}}}} je odbijen",
+ "tsb-email-rejected-body": "Zdravo, {{GENDER:$1|$1}},\n\nHvala Vam što ste se prijavili da budete prevodilac na {{GRAMMAR:dativ|{{SITENAME}}}}. Nažalost, moram Vas obavijestiti da sam {{GENDER:$3|odbio|odbila}} Vaš zahtjev jer kvaliteta Vaših prijevoda ne odgovara našim potrebama.\n\nAko smatrate da ste greškom odbijeni, ponovo podnesite zahtjev na {{GRAMMAR:dativ|{{SITENAME}}}}. To možete uraditi ovdje:\n$2\n\n{{GENDER:$3|$3}},\nosoblje {{GRAMMAR:genitiv|{{SITENAME}}}}",
+ "tsb-request-count": "$1 {{PLURAL:$1|zahtjev|zahtjeva}}",
+ "tsb-all-languages-button-label": "Svi jezici",
+ "tsb-search-requests": "Pretraži zahtjeve",
+ "tsb-accept-button-label": "Prihvati",
+ "tsb-reject-button-label": "Odbij",
+ "tsb-selected-count": "{{PLURAL:$1|Izabran $1 korisnik|Izabrana $1 korisnika|Izabrano $1 korisnika}}",
+ "tsb-older-requests": "$1 {{PLURAL:$1|stariji zahtjev|starija zahtjeva|starijih zahtjeva}}",
+ "tsb-accept-all-button-label": "Prihvati sve",
+ "tsb-reject-all-button-label": "Odbij sve",
+ "tsb-user-posted-a-comment": "Nije prevodilac",
+ "tsb-reminder-link-text": "Pošalji podsjetnik e-poštom",
+ "tsb-didnt-make-any-translations": "Ovaj korisnik nije napravio nijedan prijevod.",
+ "tsb-translations-source": "Izvor",
+ "tsb-translations-user": "{{GENDER:$1|Korisnički}} prijevodi",
+ "tsb-translations-current": "Postojeći prijevodi",
+ "tsb-delete-userpage-summary": "Brisanje korisničke stranice korisnika u pješčaniku",
+ "tsb-reject-confirmation": "{{PLURAL:$1|Korisnik odbijen|Korisnici odbijeni}}",
+ "tsb-accept-confirmation": "{{PLURAL:$1|Korisnik prihvaćen|Korisnici prihvaćeni}}",
+ "translationstash": "Dobro došli",
+ "translate-translationstash-welcome": "Dobro došli {{GENDER:$1|$1}}, Vi ste novi prevodilac",
+ "translate-translationstash-welcome-note": "Upoznajte se s prevodilačkim alatima. Prevedite nekoliko poruka i steknite puna prevodilačka prava da biste učestvovali u svojim omiljenim projektima.",
+ "translate-translationstash-initialtranslation": "Vaš prvobitan prijevod",
+ "translate-translationstash-translations": "$1 {{PLURAL:$1|završen prijevod|završena prijevoda|završenih prijevoda}}",
+ "translate-translationstash-skip-button-label": "Daj mi drugi",
+ "tsb-limit-reached-title": "Hvala Vam na prijevodima",
+ "tsb-limit-reached-body": "Dostigli ste maksimalan broj prijevoda za nove prevodioce.\nNaša ekipa uskoro će ih provjeriti i nadograditi Vaš račun.\nPotom ćete moći prevoditi neograničeno.",
+ "tsb-no-requests-from-new-users": "Nema zahtjeva novih korisnika",
+ "tsb-create-user-page": "Stvaranje osnovne korisničke stranice",
+ "log-name-translatorsandbox": "Zapisnik prijevoda u pješčaniku",
+ "log-description-translatorsandbox": "Zapisnik radnji poduzetih nad korisnicima u prevodilačkom pješčaniku",
+ "logentry-translatorsandbox-promoted": "$1 {{GENDER:$2|promovirao|promovirala}} je $3 u {{GENDER:$4|prevodioca}}",
+ "logentry-translatorsandbox-rejected": "$1 {{GENDER:$2|odbio|odbila}} je zahtjev korisnika/korisnice \"$3\" da postane prevodilac",
+ "logentry-newusers-tsbpromoted": "Korisnički račun $3 {{GENDER:$2|napravljen}} je promocijom iz pješčanika"
+}
diff --git a/MLEB/Translate/i18n/sandbox/da.json b/MLEB/Translate/i18n/sandbox/da.json
new file mode 100644
index 00000000..105f2847
--- /dev/null
+++ b/MLEB/Translate/i18n/sandbox/da.json
@@ -0,0 +1,19 @@
+{
+ "@metadata": {
+ "authors": [
+ "Sarrus",
+ "Saederup92"
+ ]
+ },
+ "tsb-all-languages-button-label": "Alle sprog",
+ "tsb-accept-button-label": "Accepter",
+ "tsb-reject-button-label": "Afvis",
+ "tsb-accept-all-button-label": "Accepter alle",
+ "tsb-reject-all-button-label": "Afvis alle",
+ "tsb-user-posted-a-comment": "Ikke en oversætter",
+ "tsb-translations-source": "Kilde",
+ "translationstash": "Velkommen",
+ "translate-translationstash-welcome": "Velkommen {{GENDER:$1|$1}}, du er nu en ny oversætter",
+ "translate-translationstash-skip-button-label": "Prøv en anden",
+ "tsb-limit-reached-title": "Tak for dine oversættelser"
+}
diff --git a/MLEB/Translate/i18n/sandbox/de-ch.json b/MLEB/Translate/i18n/sandbox/de-ch.json
new file mode 100644
index 00000000..9d17accd
--- /dev/null
+++ b/MLEB/Translate/i18n/sandbox/de-ch.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "J. 'mach' wust"
+ ]
+ },
+ "tsb-email-rejected-body": "Hallo $1\n\nVielen Dank für deinen Antrag auf Beförderung {{GENDER:$1|zum Übersetzer|zur Übersetzerin}} auf {{SITENAME}}. Ich bedauere, dich informieren zu müssen, dass ich deinen Antrag abgelehnt habe, da die Qualität deiner Übersetzungen nicht den Anforderungen entspricht.\n\nFalls du denkst, dass dein Antrag durch einen Fehler abgelehnt wurde, versuche, deinen Übersetzerantrag auf {{SITENAME}} erneut einzureichen. Du kannst dich hier registrieren:\n$2\n\n$3,\n{{GENDER:$3|Mitarbeiter|Mitarbeiterin}} von {{SITENAME}}"
+}
diff --git a/MLEB/Translate/i18n/sandbox/el.json b/MLEB/Translate/i18n/sandbox/el.json
new file mode 100644
index 00000000..09cda0d9
--- /dev/null
+++ b/MLEB/Translate/i18n/sandbox/el.json
@@ -0,0 +1,18 @@
+{
+ "@metadata": {
+ "authors": [
+ "Stam.nikos"
+ ]
+ },
+ "managetranslatorsandbox": "Διαχείριση αμμοδοχείου μεταφραστή",
+ "tsb-filter-pending": "Εκκρεμή αιτήματα",
+ "tsb-reminder-title-generic": "Ολοκληρώστε την εισαγωγή σας για να γίνετε μεταφραστής",
+ "tsb-reminder-content-generic": "Γεια σας, $1,\n\nΕυχαριστούμε για την εγγραφή σας στο {{SITENAME}}.\n\nΑν ολοκληρώσετε τις δοκιμαστικές μεταφράσεις σας, οι διαχειριστές θα σας χορηγήσουν πλήρη μεταφραστική πρόσβαση αμέσως μετά.\n\nΠαρακαλούμε ελάτε να κάνετε μερικές ακόμα μεταφράσεις εδώ:\n$2\n\n$3,\nΤο προσωπικό του {{SITENAME}}",
+ "tsb-reminder-sending": "Η υπενθύμιση αποστέλλεται...",
+ "tsb-reminder-sent": "{{PLURAL:$1|Εστάλη $1 υπενθύμιση. Ώρα: $2.|Στάλθηκαν $1 υπενθυμίσεις. Ώρα αποστολής της τελευταίας: $2.}}",
+ "tsb-reminder-sent-new": "Στάλθηκε μία υπενθύμιση",
+ "tsb-reminder-failed": "Απέτυχε η αποστολή της υπενθύμισης",
+ "tsb-email-promoted-subject": "Τώρα είστε μεταφραστής στο {{SITENAME}}",
+ "log-name-translatorsandbox": "Αμμοδοχείο μετάφρασης",
+ "log-description-translatorsandbox": "Ένα αρχείο καταγραφής ενεργειών από χρήστες του αμμοδοχείου μετάφρασης"
+}
diff --git a/MLEB/Translate/i18n/sandbox/en-gb.json b/MLEB/Translate/i18n/sandbox/en-gb.json
new file mode 100644
index 00000000..7b34ba8b
--- /dev/null
+++ b/MLEB/Translate/i18n/sandbox/en-gb.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Shirayuki"
+ ]
+ },
+ "translate-translationstash-welcome-note": "Become familiar with the translation tools. Translate some messages and get full-translator rights to participate in your favourite projects."
+}
diff --git a/MLEB/Translate/i18n/sandbox/fy.json b/MLEB/Translate/i18n/sandbox/fy.json
new file mode 100644
index 00000000..02c19f80
--- /dev/null
+++ b/MLEB/Translate/i18n/sandbox/fy.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Robin0van0der0vliet",
+ "Robin van der Vliet"
+ ]
+ },
+ "translationstash": "Wolkom"
+}
diff --git a/MLEB/Translate/i18n/sandbox/hi.json b/MLEB/Translate/i18n/sandbox/hi.json
new file mode 100644
index 00000000..b5b1c3ec
--- /dev/null
+++ b/MLEB/Translate/i18n/sandbox/hi.json
@@ -0,0 +1,17 @@
+{
+ "@metadata": {
+ "authors": [
+ "NehalDaveND",
+ "Sfic"
+ ]
+ },
+ "tsb-all-languages-button-label": "सभी भाषाएँ",
+ "tsb-accept-button-label": "स्वीकृत",
+ "tsb-reject-button-label": "अस्वीकृत",
+ "tsb-accept-all-button-label": "सभी स्वीकृत",
+ "tsb-reject-all-button-label": "सभी अस्वीकृत",
+ "tsb-user-posted-a-comment": "अनुवादक नहीं है",
+ "tsb-translations-source": "मूल",
+ "translationstash": "स्वागत",
+ "translate-translationstash-welcome": "स्वागत है {{GENDER:$1|$1}} जी, आप नए अनुवादक हैं"
+}
diff --git a/MLEB/Translate/i18n/sandbox/hr.json b/MLEB/Translate/i18n/sandbox/hr.json
new file mode 100644
index 00000000..ebaa643b
--- /dev/null
+++ b/MLEB/Translate/i18n/sandbox/hr.json
@@ -0,0 +1,14 @@
+{
+ "@metadata": {
+ "authors": [
+ "Bugoslav"
+ ]
+ },
+ "managetranslatorsandbox": "Upravljanje prevoditeljskim pješčanikom",
+ "tsb-filter-pending": "Zahtjevi na čekanju",
+ "tsb-reminder-title-generic": "Dovršite Vaše predstavljanje da bi ste postali prevoditelj/ica",
+ "translationstash": "Dobro došli",
+ "log-name-translatorsandbox": "Evidencija prijevoda u pješčaniku",
+ "logentry-translatorsandbox-promoted": "$1 {{GENDER:$2|promovirao|promovirala}} je $3 u {{GENDER:$4|prevoditelja|prevoditeljicu}}",
+ "logentry-translatorsandbox-rejected": "$1 {{GENDER:$2|odbacio|odbacila}} je zahtjev suradnika/suradnice »$3« da postane prevoditelj/ica"
+}
diff --git a/MLEB/Translate/i18n/sandbox/hu.json b/MLEB/Translate/i18n/sandbox/hu.json
new file mode 100644
index 00000000..b51f75ea
--- /dev/null
+++ b/MLEB/Translate/i18n/sandbox/hu.json
@@ -0,0 +1,45 @@
+{
+ "@metadata": {
+ "authors": [
+ "Tacsipacsi"
+ ]
+ },
+ "managetranslatorsandbox": "Fordítói homokozó kezelése",
+ "tsb-filter-pending": "Függőben lévő kérések",
+ "tsb-reminder-title-generic": "Fejezd be a bemutatkozásodat a fordítóvá váláshoz",
+ "tsb-reminder-sending": "Emlékeztető küldése…",
+ "tsb-reminder-sent": "{{PLURAL:$1|Emlékeztető elküldve ekkor: $2.|$1 emlékeztető elküldve. Utolsó kézbesítve: $2.}}",
+ "tsb-reminder-sent-new": "Emlékeztető elküldve",
+ "tsb-reminder-failed": "Az emlékeztető küldése sikertelen",
+ "tsb-email-promoted-subject": "Mostantól fordító vagy a(z) {{SITENAME}} wikin",
+ "tsb-email-rejected-subject": "A fordítói jelentkezésedet a(z) {{SITENAME}} wikin elutasították",
+ "tsb-request-count": "$1 kérés",
+ "tsb-all-languages-button-label": "Minden nyelv",
+ "tsb-search-requests": "Keresés a kérések között",
+ "tsb-accept-button-label": "Elfogadás",
+ "tsb-reject-button-label": "Elutasítás",
+ "tsb-selected-count": "$1 felhasználó kiválasztva",
+ "tsb-older-requests": "$1 régebbi kérés",
+ "tsb-accept-all-button-label": "Összes elfogadása",
+ "tsb-reject-all-button-label": "Összes elutasítása",
+ "tsb-user-posted-a-comment": "Nem fordító",
+ "tsb-reminder-link-text": "Emlékeztető e-mail küldése",
+ "tsb-didnt-make-any-translations": "Ez a felhasználó nem készített fordításokat.",
+ "tsb-translations-source": "Forrás",
+ "tsb-translations-user": "{{GENDER:$1|Felhasználó}} fordításai",
+ "tsb-translations-current": "Létező fordítások",
+ "tsb-reject-confirmation": "{{PLURAL:$1|Felhasználó|Felhasználók}} elutasítva",
+ "tsb-accept-confirmation": "{{PLURAL:$1|Felhasználó|Felhasználók}} elfogadva",
+ "translationstash": "Üdvözlünk",
+ "translate-translationstash-welcome": "Helló $1, új fordító vagy",
+ "translate-translationstash-welcome-note": "Ismerkedj meg a fordítói eszközökkel. Fordíts le néhány üzenetet a teljes jogú fordítóvá váláshoz, hogy részt vehess a kedvenc projektjeidben.",
+ "translate-translationstash-initialtranslation": "Kezdeti fordításod",
+ "translate-translationstash-translations": "$1 befejezett fordítás",
+ "translate-translationstash-skip-button-label": "Próbálkozás másikkal",
+ "tsb-limit-reached-title": "Köszönjük a fordításaidat",
+ "tsb-limit-reached-body": "Elérted az új fordítók fordítási limitjét.\nA csapatunk hamarosan megerősíti és frissíti a fiókodat.\nUtána korlátlanul fordíthatsz.",
+ "tsb-no-requests-from-new-users": "Nincs kérés új felhasználóktól",
+ "tsb-create-user-page": "Alapvető felhasználói lap készítése",
+ "log-name-translatorsandbox": "Fordítói homokozó naplója",
+ "log-description-translatorsandbox": "A fordítói homokozó felhasználóinak műveleteinek naplója"
+}
diff --git a/MLEB/Translate/i18n/sandbox/is.json b/MLEB/Translate/i18n/sandbox/is.json
new file mode 100644
index 00000000..24282b75
--- /dev/null
+++ b/MLEB/Translate/i18n/sandbox/is.json
@@ -0,0 +1,14 @@
+{
+ "@metadata": {
+ "authors": [
+ "Sveinn í Felli"
+ ]
+ },
+ "tsb-all-languages-button-label": "Öll tungumál",
+ "tsb-accept-button-label": "Samþykkja",
+ "tsb-reject-button-label": "Hafna",
+ "tsb-accept-all-button-label": "Samþykkja allt",
+ "tsb-reject-all-button-label": "Hafna öllu",
+ "tsb-translations-source": "Uppruni",
+ "translationstash": "Velkomin"
+}
diff --git a/MLEB/Translate/i18n/sandbox/ka.json b/MLEB/Translate/i18n/sandbox/ka.json
new file mode 100644
index 00000000..660f7377
--- /dev/null
+++ b/MLEB/Translate/i18n/sandbox/ka.json
@@ -0,0 +1,38 @@
+{
+ "@metadata": {
+ "authors": [
+ "David1010"
+ ]
+ },
+ "tsb-filter-pending": "მომლოდინე მოთხოვნები",
+ "tsb-reminder-title-generic": "დაასრულეთ შესავალი რათა გახდეთ მთარგმნელი",
+ "tsb-reminder-sending": "შეხსენება იგზავნება...",
+ "tsb-reminder-sent": "{{PLURAL:$1|გაიგზავნა $1 შეხსენება $2-ზე|გაიგზავნა $1 შეხსენება, ბოლო $2-ზე}}",
+ "tsb-reminder-sent-new": "შეხსენება გაგზავნილია",
+ "tsb-reminder-failed": "შეხსენების გაგზავნა ვერ მოხერხდა",
+ "tsb-email-promoted-subject": "თქვენ ახლა მთარგმნელი ხართ პროექტში „{{SITENAME}}“",
+ "tsb-email-rejected-subject": "თქვენი მთარგმნელის უფლებების მოთხოვნა პროექტისათვის „{{SITENAME}}“ უარყოფილია",
+ "tsb-request-count": "$1 {{PLURAL:$1|მოთხოვნა|მოთხოვნა}}",
+ "tsb-all-languages-button-label": "ყველა ენა",
+ "tsb-search-requests": "მოთხოვნების ძიება",
+ "tsb-accept-button-label": "მიღება",
+ "tsb-reject-button-label": "უარყოფა",
+ "tsb-selected-count": "{{PLURAL:$1|არჩეულია $1 მომხმარებელი|არჩეულია $1 მომხმარებელი}}",
+ "tsb-older-requests": "$1 ძველი {{PLURAL:$1|მოთხოვნა|მოთხოვნა}}",
+ "tsb-accept-all-button-label": "ყველას მიღება",
+ "tsb-reject-all-button-label": "ყველას უარყოფა",
+ "tsb-user-posted-a-comment": "არ არის მთარგმნელი",
+ "tsb-reminder-link-text": "შეხსენების ელ.ფოსტით გაგზავნა",
+ "tsb-didnt-make-any-translations": "ამ მომხმარებელს არცერთი თარგმანი არ შეუსრულებია.",
+ "tsb-translations-source": "წყარო",
+ "tsb-translations-user": "{{GENDER:$1|მომხმარებლის}} თარგმანები",
+ "tsb-translations-current": "არსებული თარგმანები",
+ "translationstash": "მოგესალმებით",
+ "translate-translationstash-welcome": "მოგესალმებით {{GENDER:$1|$1}}, თქვენ ახალი მთარგმნელი ხართ",
+ "translate-translationstash-initialtranslation": "თქვენი თავდაპირველი თარგმანი",
+ "translate-translationstash-translations": "$1 დასრულებული {{PLURAL:$1|თარგმანი|თარგმანი}}",
+ "translate-translationstash-skip-button-label": "სხვა სცადეთ",
+ "tsb-limit-reached-title": "მადლობა თქვენი თარგმანებისთვის",
+ "tsb-no-requests-from-new-users": "ახალი მოხმარებლებისგან მოთხოვნები არ არის",
+ "tsb-create-user-page": "მომხმარებლის საბაზო გვერდის შექმნა"
+}
diff --git a/MLEB/Translate/i18n/sandbox/kab.json b/MLEB/Translate/i18n/sandbox/kab.json
new file mode 100644
index 00000000..cdf40346
--- /dev/null
+++ b/MLEB/Translate/i18n/sandbox/kab.json
@@ -0,0 +1,28 @@
+{
+ "@metadata": {
+ "authors": [
+ "Belkacem77"
+ ]
+ },
+ "tsb-all-languages-button-label": "Akk tutlayin",
+ "tsb-accept-all-button-label": "Qbel meṛṛa",
+ "tsb-reject-all-button-label": "Agwi meṛṛa",
+ "tsb-user-posted-a-comment": "Mačči d amsuqel",
+ "tsb-reminder-link-text": "Azen imayl n usmekti",
+ "tsb-didnt-make-any-translations": "Aseqdac-agi ur isuqel ara yakan.",
+ "tsb-translations-source": "Aɣbalu",
+ "tsb-translations-user": "Tisiqilin n{{GENDER:$1|useqdac|tseqdact}}",
+ "tsb-translations-current": "Tisuqilin yellan",
+ "tsb-reject-confirmation": "{{PLURAL:$1|n usqdac yettwagwin|n iseqdacen yettwagwin}}",
+ "tsb-accept-confirmation": "{{PLURAL:$1|n useqdac yettwaqeblen|n iseqdacen yettwaqeblen}}",
+ "translationstash": "Anṣuf",
+ "translate-translationstash-welcome": "Anṣuf {{GENDER:$1|$1}}, aqla-k d amsuqel amaynut",
+ "translate-translationstash-initialtranslation": "Tasuqilt-ik tamezwarut",
+ "translate-translationstash-translations": "Tfukeḍ $1 {{PLURAL:$1|n tsuqilt|n tsuqilin}}.",
+ "translate-translationstash-skip-button-label": "Ɛreḍ tayeḍ",
+ "tsb-limit-reached-title": "Tanemmirt ɣef tsuqilin-ik",
+ "tsb-no-requests-from-new-users": "Ulac tuttriwin n iseqdacen imaynuten",
+ "tsb-create-user-page": "Rnu asebter aseqdac azadur",
+ "logentry-translatorsandbox-promoted": "$1 {{GENDER:$2|yessuli}} $3 ar {{GENDER:$4|umsuqel|tamsuqelt}}.",
+ "logentry-translatorsandbox-rejected": "$1 {{GENDER:$2|yugwi|tugwi}} asuter n $3 akken ad yuɣal d amsuqel"
+}
diff --git a/MLEB/Translate/i18n/sandbox/kjp.json b/MLEB/Translate/i18n/sandbox/kjp.json
new file mode 100644
index 00000000..98ee5077
--- /dev/null
+++ b/MLEB/Translate/i18n/sandbox/kjp.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Rul1902"
+ ]
+ },
+ "tsb-translations-source": "အ်ုထိုဝ်"
+}
diff --git a/MLEB/Translate/i18n/sandbox/kk-cyrl.json b/MLEB/Translate/i18n/sandbox/kk-cyrl.json
new file mode 100644
index 00000000..f89bddea
--- /dev/null
+++ b/MLEB/Translate/i18n/sandbox/kk-cyrl.json
@@ -0,0 +1,37 @@
+{
+ "@metadata": {
+ "authors": [
+ "Arystanbek"
+ ]
+ },
+ "tsb-filter-pending": "Күтудегі сұраулар",
+ "tsb-reminder-title-generic": "Аудармашы болу үшін өзіңіз туралу таныстурды енгізуді аяқтаңыз",
+ "tsb-reminder-sent-new": "Еске салуды жіберу",
+ "tsb-reminder-failed": "Еске салуды жіберу сәтсіздікке ұшырады",
+ "tsb-email-promoted-subject": "Сіз қазір {{SITENAME}} сайтында аудармашысыз",
+ "tsb-request-count": "$1 {{PLURAL:$1|сұраным|сұраным}}",
+ "tsb-all-languages-button-label": "Барлық тілдер",
+ "tsb-search-requests": "Іздеусұраныстары",
+ "tsb-accept-button-label": "Қабылдау",
+ "tsb-reject-button-label": "Қайтару",
+ "tsb-selected-count": "{{PLURAL:$1|$1 қатысушы таңдалды|$1 қатысушылар таңдалды}}",
+ "tsb-older-requests": "$1 бұрынғы {{PLURAL:$1|сұраным|сұраным}}",
+ "tsb-accept-all-button-label": "Барлығын қабылдау",
+ "tsb-reject-all-button-label": "Барлығын қайтару",
+ "tsb-user-posted-a-comment": "Аудармашы емес",
+ "tsb-reminder-link-text": "Еске салу электронды хатын жіберу",
+ "tsb-didnt-make-any-translations": "Бұл қатысушы қандайда бір аударма жасаған жоқ.",
+ "tsb-translations-source": "Қайнары",
+ "tsb-translations-user": "{{GENDER:$1|Қатысушы}} аудармалары",
+ "tsb-translations-current": "Бар аудармалар",
+ "translationstash": "Қош келдіңіз!",
+ "translate-translationstash-welcome": "Қош келдіңіз {{GENDER:$1|$1}}, сіз жаңа аудармашысыз",
+ "translate-translationstash-initialtranslation": "Сіздің алғашқы аудармаңыз",
+ "translate-translationstash-translations": "$1 {{PLURAL:$1|аударма|аудармалар}} жасады",
+ "translate-translationstash-skip-button-label": "Басқасын көріңіз",
+ "tsb-limit-reached-title": "Аудармаларыңыз үшін рақмет",
+ "tsb-no-requests-from-new-users": "Жаңа қатысушылардан өтініш жоқ",
+ "tsb-create-user-page": "Негізгі қатысушы бетін бастау",
+ "log-name-translatorsandbox": "Аударма зертханасы",
+ "logentry-translatorsandbox-promoted": "$1 $3 есімді қатысушыны {{GENDER:$4|аудармашы}} дегенге {{GENDER:$2|дәрежесін көтерді}}"
+}
diff --git a/MLEB/Translate/i18n/sandbox/km.json b/MLEB/Translate/i18n/sandbox/km.json
new file mode 100644
index 00000000..03ae8d66
--- /dev/null
+++ b/MLEB/Translate/i18n/sandbox/km.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "គីមស៊្រុន"
+ ]
+ },
+ "tsb-all-languages-button-label": "ភាសាទាំងអស់",
+ "tsb-limit-reached-title": "សូមអរគុណចំពោះការបកប្រែរបស់អ្នក"
+}
diff --git a/MLEB/Translate/i18n/sandbox/ku-latn.json b/MLEB/Translate/i18n/sandbox/ku-latn.json
new file mode 100644
index 00000000..9d9a05b8
--- /dev/null
+++ b/MLEB/Translate/i18n/sandbox/ku-latn.json
@@ -0,0 +1,12 @@
+{
+ "@metadata": {
+ "authors": [
+ "Bikarhêner",
+ "George Animal"
+ ]
+ },
+ "tsb-all-languages-button-label": "Hemû ziman",
+ "tsb-search-requests": "Lê daxwaziyan bigere",
+ "tsb-accept-button-label": "Bipejirîne",
+ "tsb-translations-source": "Çavkanî"
+}
diff --git a/MLEB/Translate/i18n/sandbox/lag.json b/MLEB/Translate/i18n/sandbox/lag.json
new file mode 100644
index 00000000..734c29b0
--- /dev/null
+++ b/MLEB/Translate/i18n/sandbox/lag.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Baba Tabita"
+ ]
+ },
+ "tsb-translations-source": "Nchoongo",
+ "translationstash": "Teenga"
+}
diff --git a/MLEB/Translate/i18n/sandbox/lki.json b/MLEB/Translate/i18n/sandbox/lki.json
new file mode 100644
index 00000000..4db8b79a
--- /dev/null
+++ b/MLEB/Translate/i18n/sandbox/lki.json
@@ -0,0 +1,39 @@
+{
+ "@metadata": {
+ "authors": [
+ "Lakzon",
+ "Hosseinblue"
+ ]
+ },
+ "managetranslatorsandbox": "مدیریت کاربران مترجم",
+ "tsb-filter-pending": "در حال انتظار درخواست‌ها",
+ "tsb-reminder-title-generic": "معرفی‌نامهٔ خود را برای تبدیل شدن به یک مترجم کامل کنید",
+ "tsb-reminder-content-generic": "سلام $1،\n تشکر برای ثبت‌نام با {{SITENAME}}.\nاگر شما ترجمه‌های امتحانی خود را کامل کنید، سرپرستان به شما ترجمه کامل در دسترس را زود پس از آن به شما تعطا خواهند کرد.\nلطفاً بیایید و چند ترجمه بیشتر اینجا ایجاد کنیدː\n$2\n$3،\nکارکنان {{SITENAME}}",
+ "tsb-reminder-sending": "در حال فرستادن یادآوری...",
+ "tsb-reminder-sent-new": "فرستادن یک یادآوری",
+ "tsb-reminder-failed": "عدم موفقیت در فرستادن یادآوری",
+ "tsb-email-promoted-subject": "شما در حال حاضر یک مترجم در {{SITENAME}} هستید",
+ "tsb-email-promoted-body": "سلام {{GENDER:$1|$1}}،\n\nتبریکǃ من ترجمه‌های امتحانی که شما در {{SITENAME}} ایجاد کردید را بررسی کردم و به شما تمام حقوق مترجم را دادم.\n\nاکنون به {{SITENAME}} برای ادامه ترجمه بیایید، و هر روزː \n$2\n\nخوش‌‌آمدید، و از شما برای کمک تشکر می کنمǃ\n{{GENDER:$3|$3}}،\n{{SITENAME}} کارکنان",
+ "tsb-email-rejected-subject": "درخواست شما برای مترجم شدن در {{SITENAME}} رد شد",
+ "tsb-email-rejected-body": "سلام {{GENDER:$1|$1}}،\n\nتشکر از شما برای درخواست به عنوان مترجم در {{SITENAME}}. متأسفم که به شما اطلاع دادم که درخواست شما را رد کرده‌ام، زیرا کیفیت ترجمه‌های شما، نیازها را برآورد نکرد.\n\nاگر فکر می‌کنید که درخواست شما اشتباهاً رد شده‌است، لطفاً برای درخواست مجدد به عنوان مترجم در {{SITENAME}} درخواست دهید. شما می‌توانید اینجا ثبت‌نام کنیدː\n$2\n\n{{GENDER:$3|$3}}،\n{{SITENAME}} کارکنان",
+ "tsb-request-count": "$1 {{PLURAL:$1|درخواست|درخواست‌ها}}",
+ "tsb-all-languages-button-label": "کؤل(گشت)زوونەل",
+ "tsb-search-requests": "نتایج مِنِی کردن",
+ "tsb-accept-button-label": "پذیرفتن",
+ "tsb-reject-button-label": "ردکردن",
+ "tsb-accept-all-button-label": "پذیرش همه",
+ "tsb-reject-all-button-label": "رد همه",
+ "tsb-user-posted-a-comment": "یک مترجم نیست",
+ "tsb-reminder-link-text": "ارسال ایمیل یادآور",
+ "tsb-didnt-make-any-translations": "این کاربر هیچ ترجمه‌ای انجام نداده.",
+ "tsb-translations-source": "بنچەک(منبع)",
+ "translationstash": "خؤةش هةتین/هاتین",
+ "translate-translationstash-welcome": "خؤةش هةتین {{GENDER:$1|$1}}، شما یک مترجم تازه هستید",
+ "translate-translationstash-welcome-note": "آشنا شدن با ابزارهای ترجمه. چند پیام را ترجمه کنید و تمام حقوق مترجم را برای شرکت در پروژه‌های مورد علاقه خود بدست آورید.",
+ "translate-translationstash-initialtranslation": "ترجمه‌های اولیهٔ شما",
+ "translate-translationstash-skip-button-label": "امتحان دیگری",
+ "tsb-limit-reached-title": "سپاسگزاری برای ترجمه‌های شما",
+ "log-name-translatorsandbox": "جعبه شنی ترجمه",
+ "log-description-translatorsandbox": "سیاهه‌ای از عملکرد بر روی کاربران جعبه شنی ترجمه",
+ "logentry-translatorsandbox-promoted": "$1، $3 را به {{GENDER:$4|مترجم}} {{GENDER:$2|ترفیع}} داد"
+}
diff --git a/MLEB/Translate/i18n/sandbox/lt.json b/MLEB/Translate/i18n/sandbox/lt.json
new file mode 100644
index 00000000..4f3943f8
--- /dev/null
+++ b/MLEB/Translate/i18n/sandbox/lt.json
@@ -0,0 +1,46 @@
+{
+ "@metadata": {
+ "authors": [
+ "Eitvys200",
+ "Manvydasz"
+ ]
+ },
+ "managetranslatorsandbox": "Tvarkyti vertėjo smėlio dėžę",
+ "tsb-filter-pending": "Neišnagrinėti prašymai",
+ "tsb-reminder-title-generic": "Užbaigti įvadą ir tapti vertėju",
+ "tsb-reminder-sending": "Siunčiamas priminimas...",
+ "tsb-reminder-sent": "{{PLURAL:$1|Išsiųstas $1 priminimas. Laikas: $2.|Išsiųsti $1 priminimai. Paskutinio laikas: $2.}}",
+ "tsb-reminder-sent-new": "Išsiųstas priminimas",
+ "tsb-reminder-failed": "Priminimo išsiųsti nepavyko",
+ "tsb-email-promoted-subject": "Dabar esate {{SITENAME}} vertėjas",
+ "tsb-email-promoted-body": "Sveiki, {{GENDER:$1|$1}},\n\nSveikinu! Aš patikrinau jūsų bandomuosius vertimus, kuriuos jūs atlikote {{SITENAME}} ir suteikiau jums visas vertėjo teises.\n\nEikite į {{SITENAME}}, kad galėtumėte versti dabar ir kiekvieną dieną:\n$2\n\nSveiki atvykę ir ačiū už jūsų įnašą!\n\n{{GENDER:$3|$3}},\n{{SITENAME}} darbuotojas",
+ "tsb-email-rejected-subject": "Jūsų prašymas tapti {{SITENAME}} vertėju buvo atmestas",
+ "tsb-request-count": "$1 {{PLURAL:$1|prašymas|prašymai}}",
+ "tsb-all-languages-button-label": "Visos kalbos",
+ "tsb-search-requests": "Ieškoti prašymų",
+ "tsb-accept-button-label": "Priimti",
+ "tsb-reject-button-label": "Atmesti",
+ "tsb-selected-count": "{{PLURAL:$1|$1 pasirinktas vartotojas|$1 pasirinkti vartotojai}}",
+ "tsb-older-requests": "$1 {{PLURAL:$1|senesnis prašymas|senesni prašymai}}",
+ "tsb-accept-all-button-label": "Priimti visus",
+ "tsb-reject-all-button-label": "Atmesti visus",
+ "tsb-user-posted-a-comment": "Ne vertėjas",
+ "tsb-reminder-link-text": "Siųsti priminimą el. paštu",
+ "tsb-didnt-make-any-translations": "Šis vartotojas neatliko jokių vertimų.",
+ "tsb-translations-source": "Šaltinis",
+ "tsb-translations-user": "{{GENDER:$1|Vartotojo|Vartotojos}} vertimai",
+ "tsb-translations-current": "Egzistuojantys vertimai",
+ "tsb-reject-confirmation": "{{PLURAL:$1|Vartotojas atmestas|Vartotojai atmesti}}",
+ "tsb-accept-confirmation": "{{PLURAL:$1|Vartotojas priimtas|Vartotojai priimti}}",
+ "translationstash": "Sveiki atvykę",
+ "translate-translationstash-welcome": "Sveiki atvykę, {{GENDER:$1|$1}}, esate {{GENDER:$1|naujas vertėjas|nauja vertėja}}",
+ "translate-translationstash-welcome-note": "Susipažinkite su vertimo įrankiais. Išverskite kelis pranešimus ir gaukite pilnas vertėjo teises, kad galėtumėte dalyvauti savo mėgstamiausiuose projektuose.",
+ "translate-translationstash-initialtranslation": "Jūsų pirmininis vertimas",
+ "translate-translationstash-translations": "$1 atliko {{PLURAL:$1|vertimą|vertimus}}",
+ "translate-translationstash-skip-button-label": "Pabandyti kitą",
+ "tsb-limit-reached-title": "Ačiū už jūsų vertimus",
+ "tsb-limit-reached-body": "Jūs pasiekėte naujų vertėjų vertimų limitą.\nMūsų komanda netrukus patikrins ir atnaujins jūsų paskyra.\nTada galėsite versti be limitų.",
+ "tsb-no-requests-from-new-users": "Nėra prašymų iš naujų vartotojų",
+ "tsb-create-user-page": "Sukurti pagrindinį vartotojo puslapį",
+ "log-name-translatorsandbox": "Vertimo smėlio dėžės žurnalas"
+}
diff --git a/MLEB/Translate/i18n/sandbox/my.json b/MLEB/Translate/i18n/sandbox/my.json
new file mode 100644
index 00000000..fb3b1a65
--- /dev/null
+++ b/MLEB/Translate/i18n/sandbox/my.json
@@ -0,0 +1,46 @@
+{
+ "@metadata": {
+ "authors": [
+ "Dr Lotus Black"
+ ]
+ },
+ "managetranslatorsandbox": "ဘာသာပြန်သူ သဲပုံးကို စီမံရန်",
+ "tsb-filter-pending": "ဆိုင်းငံ့ထားသော တောင်းဆိုချက်များ",
+ "tsb-reminder-title-generic": "ဘာသာပြန်သူတစ်ဦးဖြစ်ရန် သင်၏ မိတ်ဆက်ကို ပြီးဆုံအောင်ဆောင်ရွက်ပါ",
+ "tsb-reminder-content-generic": "မင်္ဂလာပါ $1၊\n\n{{SITENAME}} တွင် မှတ်ပုံတင်သည့်အတွက် ကျေးဇူးတင်ပါသည်။\n\nသင်၏ အစမ်းဘာသာပြန်များကို ပြီးမြောက်ပါက အက်ဒမင်များသည် မကြာခင် ဘာသာပြန်အခွင့်အရေး အပြည့်အဝအပ်နှင်းပေးပါလိမ့်မည်။\n\nဤနေရာတွင် လာရောက်၍ ဘာသာပြန်ဆိုခြင်းများကို ဆောင်ရွက်ပေးပါ: \n$2\n\n$3၊\n{{SITENAME}} ဝန်ထမ်း",
+ "tsb-email-promoted-subject": "သင်သည် ယခု {{SITENAME}} တွင် ဘာသာပြန်သူတစ်ဦး ဖြစ်ပါပြီ",
+ "tsb-email-promoted-body": "မင်္ဂလာပါ {{GENDER:$1|$1}}၊\n\nဂုဏ်ယူပါသည်။ {{SITENAME}} တွင် သင် လုပ်ဆောင်ခဲ့သည့် ဘာသာပြန်အစမ်းများကို ကျွန်ုပ် စစ်ဆေးခဲ့ပြီး သင်အား ဘာသာပြန်အခွင့်အရေး အပြည့်အဝ ပေးအပ်လိုက်ပါသည်။\n\nနေ့စဉ် ဆက်လက် ဘာသာပြန်ဆိုရန် {{SITENAME}} တွင်လာရောက်ပေးပါ:\n$2\n\nကြိုဆိုပါသည်၊ သင်၏ပံ့ပိုးမှုများအတွက် ကျေးဇူးတင်ပါသည်။\n\n{{GENDER:$3|$3}}၊\n{{SITENAME}} ဝန်ထမ်း",
+ "tsb-email-rejected-subject": "{{SITENAME}} ရှိ ဘာသာပြန်သူတစ်ဦးဖြစ်ရန် သင်၏လျှောက်လွှာကို ငြင်းပယ်လိုက်သည်",
+ "tsb-email-rejected-body": "မင်္ဂလပါ {{GENDER:$1|$1}}၊\n\n{{SITENAME}} တွင် ဘာသာပြန်တစ်ဦးအဖြစ် လျှောက်လွှာတင်သည့်အတွက် ကျေးဇူးတင်ပါသည်။ သင်၏လျှောက်လွှာကို ငြင်းပယ်လိုက်သည်ဟု အသိပေးရမှာ ကျွန်ုပ် နောင်တရမိပါသည်၊ အကြောင်းမှာ သင်၏ ဘာသာပြန်အရည်အသွေးများမှာ လိုအပ်ချက်များနှင့် မကိုက်ညီသောကြောင့်ဖြစ်ပါသည်။\n\nသင်၏လျှောက်လွှာ ငြင်းပယ်ခံရသည်မှာ အမှားဟုထင်မိပါက ကျေးဇူးပြု၍ {{SITENAME}} တွင် ဘာသာပြန်တစ်ဦးအဖြစ် ထပ်မံလျှောက်လွှာတင်ပေးပါ။ ဤနေရာတွင် သင် မှတ်ပုံတင်နိုင်ပါသည်။:\n$2\n\n{{GENDER:$3|$3}}၊\n{{SITENAME}} ဝန်ထမ်း",
+ "tsb-request-count": "{{PLURAL:$1|တောင်းဆိုမှု|တောင်းဆိုမှုများ}} $1",
+ "tsb-all-languages-button-label": "ဘာသာစကားများ အားလုံး",
+ "tsb-search-requests": "တောင်းဆိုချက်များ ရှာဖွေရန်",
+ "tsb-accept-button-label": "လက်ခံသည်",
+ "tsb-reject-button-label": "လက်မခံပါ",
+ "tsb-selected-count": "{{PLURAL:$1|$1 အသုံးပြုသူ ရွေးချယ်ထား|$1 အသုံးပြုသူများ ရွေးချယ်ထား}}",
+ "tsb-older-requests": "ပိုဟောင်းသော {{PLURAL:$1|တောင်းဆိုမှု|တောင်းဆိုမှုများ}} $1",
+ "tsb-accept-all-button-label": "အကုန် လက်ခံသည်",
+ "tsb-reject-all-button-label": "အကုန် လက်မခံပါ",
+ "tsb-user-posted-a-comment": "ဘာသာပြန်သူတစ်ဦး မဟုတ်",
+ "tsb-didnt-make-any-translations": "ဤအသုံးပြုသူသည် မည်သည့်ဘာသာပြန်မှုမျှ မလုပ်ဆောင်ခဲ့ပါ။",
+ "tsb-translations-source": "ရင်းမြစ်",
+ "tsb-translations-user": "{{GENDER:$1|အသုံးပြုသူ}} ဘာသာပြန်များ",
+ "tsb-translations-current": "ရှိနှင့်ပြီးသား ဘာသာပြန်များ",
+ "tsb-reject-confirmation": "{{PLURAL:$1|အသုံးပြုသူ|အသုံးပြုသူများ}}ကို ငြင်းပယ်လိုက်သည်",
+ "tsb-accept-confirmation": "{{PLURAL:$1|အသုံးပြုသူ|အသုံးပြုသူများ}}ကို လက်ခံလိုက်သည်",
+ "translationstash": "ကြိုဆိုပါသည်",
+ "translate-translationstash-welcome": "ကြိုဆိုပါသည် {{GENDER:$1|$1}}၊ သင်သည် လူသစ် ဘာသာပြန်သူတစ်ဦးဖြစ်သည်",
+ "translate-translationstash-welcome-note": "ဘာသာပြန် ကိရိယာများနှင့် ရင်းနှီးအောင် လုပ်ဆောင်ပါ။ မက်ဆေ့အချို့ကို ဘာသာပြန်ပြီး သင် နှစ်သက်သော ပရောဂျက်များတွင် ပါဝင်ရန် ဘာသာပြန်အခွင့်အရေး အပြည့်အဝ ရယူလိုက်ပါ။",
+ "translate-translationstash-initialtranslation": "သင်၏ ကနဦး ဘာသာပြန်ဆိုမှု",
+ "translate-translationstash-translations": "{{PLURAL:$1|ဘာသာပြန်|ဘာသာပြန်များ}} $1 ခုပြီးမြောက်သည်",
+ "translate-translationstash-skip-button-label": "နောက်တစ်ခု စမ်းကြည့်ပါ",
+ "tsb-limit-reached-title": "သင်၏ဘာသာပြန်ဆိုမှုများအတွက် ကျေးဇူးတင်ပါသည်",
+ "tsb-limit-reached-body": "လူသစ် ဘာသာပြန်သူများအတွက် ကန့်သတ်ချက်ကို သင် ရောက်ရှိသွားပါပြီ။ ကျွန်ုပ်တို့အဖွဲ့က မကြာခင် သင်၏အကောင့်ကို အတည်ပြုစစ်ဆေးပြီး အဆင့်မြှင့်တင်ပါလိမ့်မည်။ သင်သည် ကန့်သတ်ချက်မရှိဘဲ ဘာသာပြန်နိုင်တော့မည်ဖြစ်ပါသည်။",
+ "tsb-no-requests-from-new-users": "အသုံးပြုသူအသစ်များမှ တောင်းဆိုချက်များ မရှိပါ",
+ "tsb-create-user-page": "အခြေခံ အသုံးပြုသူစာမျက်နှာ ဖန်တီးရန်",
+ "log-name-translatorsandbox": "ဘာသာပြန်မှုသဲပုံး မှတ်တမ်း",
+ "log-description-translatorsandbox": "ဘာသာပြန်သဲပုံး အသုံးပြုသူများရှိ လုပ်ဆောင်ချက်များ မှတ်တမ်း",
+ "logentry-translatorsandbox-promoted": "$1 က $3 ကို {{GENDER:$4|ဘာသာပြန်သူ}}အဖြစ် {{GENDER:$2|ရာထူးတိုးပေးခဲ့သည်}}",
+ "logentry-translatorsandbox-rejected": "$1 က \"$3\" ၏ ဘာသာပြန်သူတစ်ဦးဖြစ်ရန် တောင်းဆိုမှုကို {{GENDER:$2|ငြင်းပယ်လိုက်သည်}}",
+ "logentry-newusers-tsbpromoted": "အသုံးပြုသူအကောင့် $3 သည် သဲပုံးမှရာထူးတိုးကာ {{GENDER:$2|ဖန်တီးခဲ့သည်}}"
+}
diff --git a/MLEB/Translate/i18n/sandbox/nb.json b/MLEB/Translate/i18n/sandbox/nb.json
new file mode 100644
index 00000000..86431a2d
--- /dev/null
+++ b/MLEB/Translate/i18n/sandbox/nb.json
@@ -0,0 +1,53 @@
+{
+ "@metadata": {
+ "authors": [
+ "Jon Harald Søby",
+ "Matěj Suchánek"
+ ]
+ },
+ "managetranslatorsandbox": "Behandle oversettelsessandkasse",
+ "tsb-filter-pending": "Ventende forespørsler",
+ "tsb-reminder-title-generic": "Fullfør introduksjonen din for å bli en oversetter",
+ "tsb-reminder-content-generic": "Hei, $1\n\nTakk for at du registrerte deg på {{SITENAME}}.\n\nOm du fullfører testoversettelsene dine vil administratorene gi deg full oversettelsestilgang snart.\n\nKom gjerne tilbake og oversett mer her:\n$2\n\n$3,\nPersonalet på {{SITENAME}}",
+ "tsb-reminder-sending": "Sender påminnelsen ...",
+ "tsb-reminder-sent": "{{PLURAL:$1|Sendte $1 påminnelse. Tid: $2|Sendte $1 påminnelser. Tiden for den siste: $2.}}",
+ "tsb-reminder-sent-new": "Sendte en påminnelse",
+ "tsb-reminder-failed": "Sending av påminnelsen mislyktes",
+ "tsb-email-promoted-subject": "Du er nå en oversetter på {{SITENAME}}",
+ "tsb-email-promoted-body": "Hei, {{GENDER:$1|$1}}\n\nGratulerer! Jeg sjekket testoversettelsene du gjorde på {{SITENAME}} og ga deg full oversettelsestilgang.\n\nKom til {{SITENAME}} for å fortsette å oversette nå:\n$2\n\nVelkommen, og takk for at du bidrar!\n\n{{GENDER:$3|$3}},\nPersonalet på {{SITENAME}}",
+ "tsb-email-rejected-subject": "Søknaden din om å bli en oversetter på {{SITENAME}} ble avvist",
+ "tsb-email-rejected-body": "Hei {{GENDER:$1|$1}}\n\nTakk for at du søkte om å bli oversetter på {{SITENAME}}. Jeg må dessverre meddele at jeg har avvist søknaden din, fordi kvaliteten på oversettelsene dine ikke oppfylte kravene.\n\nOm du mener at dette er en feil, søk om å bli oversetter på {{SITENAME}} igjen. Du kan melde deg på her:\n$2\n\n{{GENDER:$3|$3}},\nPersonalet på {{SITENAME}}",
+ "tsb-request-count": "$1 {{PLURAL:$1|forespørsel|forespørsler}}",
+ "tsb-all-languages-button-label": "Alle språk",
+ "tsb-search-requests": "Søk i forespørsler",
+ "tsb-accept-button-label": "Godkjenn",
+ "tsb-reject-button-label": "Avslå",
+ "tsb-selected-count": "{{PLURAL:$1|$1 bruker|$1 brukere}} valgt",
+ "tsb-older-requests": "$1 eldre {{PLURAL:$1|forespørsel|forespørsler}}",
+ "tsb-accept-all-button-label": "Godkjenn alle",
+ "tsb-reject-all-button-label": "Avslå alle",
+ "tsb-user-posted-a-comment": "Ikke en oversetter",
+ "tsb-reminder-link-text": "Send påminnelse på epost",
+ "tsb-didnt-make-any-translations": "Brukeren har ikke gjort noen oversettelser.",
+ "tsb-translations-source": "Kilde",
+ "tsb-translations-user": "{{GENDER:$1|Brukeroversettelser}}",
+ "tsb-translations-current": "Eksisterende oversettelser",
+ "tsb-delete-userpage-summary": "Sletter brukersiden til en sandkassebruker",
+ "tsb-reject-confirmation": "{{PLURAL:$1|Avvist bruker|Avviste brukere}}",
+ "tsb-accept-confirmation": "{{PLURAL:$1|Godtatt bruker|Godtatte brukere}}",
+ "translationstash": "Velkommen",
+ "translate-translationstash-welcome": "Velkommen {{GENDER:$1|$1}}, du er en ny oversetter",
+ "translate-translationstash-welcome-note": "Bli kjent med oversettelsesverktøyene. Oversett noen beskjeder og få full oversettelsestillatelse for å delta i dine favorittprosjekter.",
+ "translate-translationstash-initialtranslation": "Din første oversettelse",
+ "translate-translationstash-translations": "$1 {{PLURAL:$1|fullført oversettelse|fullførte oversettelser}}",
+ "translate-translationstash-skip-button-label": "Prøv en annen",
+ "tsb-limit-reached-title": "Takk for oversettelsene dine",
+ "tsb-limit-reached-body": "Du har nådd grensa for oversettelser av nye oversettere.\nTeamet vårt vil sjekke og oppgradere kontoen din snart.\nDa vil du kunne oversette uten begrensninger.",
+ "tsb-no-requests-from-new-users": "Ingen forespørsler fra nye brukere",
+ "tsb-create-user-page": "Opprett enkel brukerside",
+ "log-name-translatorsandbox": "Logg for oversettelsessandkasse",
+ "log-description-translatorsandbox": "En logg over handlinger på brukere av oversettelsessandkassa",
+ "logentry-translatorsandbox-promoted": "$1 {{GENDER:$2|forfremmet}} $3 til {{GENDER:$4|oversetter}}",
+ "logentry-translatorsandbox-rejected": "$1 {{GENDER:$2|avslo}} forespørselen fra «$3» om å bli oversetter",
+ "logentry-newusers-tsbpromoted": "Brukerkontoen $3 ble {{GENDER:$2|opprettet}} ved forfremmelse fra sandkassa"
+}
diff --git a/MLEB/Translate/i18n/sandbox/qu.json b/MLEB/Translate/i18n/sandbox/qu.json
new file mode 100644
index 00000000..089969d1
--- /dev/null
+++ b/MLEB/Translate/i18n/sandbox/qu.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "AlimanRuna"
+ ]
+ },
+ "translate-translationstash-initialtranslation": "Qampa ñawpaq t'ikrayniyki"
+}
diff --git a/MLEB/Translate/i18n/sandbox/sa.json b/MLEB/Translate/i18n/sandbox/sa.json
new file mode 100644
index 00000000..bd343f3e
--- /dev/null
+++ b/MLEB/Translate/i18n/sandbox/sa.json
@@ -0,0 +1,13 @@
+{
+ "@metadata": {
+ "authors": [
+ "NehalDaveND"
+ ]
+ },
+ "tsb-accept-button-label": "स्वीक्रियताम्",
+ "tsb-reject-button-label": "तिरस्क्रियताम्",
+ "tsb-accept-all-button-label": "सर्वं स्वीक्रियताम्",
+ "tsb-reject-all-button-label": "सर्वं तिरस्क्रियताम्",
+ "tsb-translations-source": "स्रोतः",
+ "translationstash": "स्वागतम्"
+}
diff --git a/MLEB/Translate/i18n/sandbox/sah.json b/MLEB/Translate/i18n/sandbox/sah.json
new file mode 100644
index 00000000..22c0ed51
--- /dev/null
+++ b/MLEB/Translate/i18n/sandbox/sah.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Туллук"
+ ]
+ },
+ "tsb-email-promoted-body": "Үтүө күнүнэн, {{GENDER:$1|$1}}\nЭҕэрдэлиибин! Мин эн {{SITENAME}}ка оҥорбут тылбаастаргын бэрэбиэркэлээтим, мантан ыла эйиэхэ тылбаасчыт толору бырааба бэриллэр.\n\nмантан ыла күн аайы тылбаастыырга, {{SITENAME}} сайтка көс\n$2\n Нөрүн нөргүй, эн кылаатын иһин махталбын тириэрдэбин\n{{GENDER:$3|$3}},\n{{SITENAME}} үлэһит"
+}
diff --git a/MLEB/Translate/i18n/sandbox/scn.json b/MLEB/Translate/i18n/sandbox/scn.json
new file mode 100644
index 00000000..624c4040
--- /dev/null
+++ b/MLEB/Translate/i18n/sandbox/scn.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Sarvaturi"
+ ]
+ },
+ "translationstash": "Bimminutu/a"
+}
diff --git a/MLEB/Translate/i18n/sandbox/sd.json b/MLEB/Translate/i18n/sandbox/sd.json
new file mode 100644
index 00000000..88b07423
--- /dev/null
+++ b/MLEB/Translate/i18n/sandbox/sd.json
@@ -0,0 +1,35 @@
+{
+ "@metadata": {
+ "authors": [
+ "Mehtab ahmed"
+ ]
+ },
+ "tsb-filter-pending": "رڪيل درخواستون",
+ "tsb-reminder-title-generic": "ترجميڪار ٿيڻ لاءِ پنھنجو تعارف مڪمل ڪريو",
+ "tsb-email-promoted-subject": "توھان ھاڻي {{SITENAME}} تي ترجميڪار آھيو",
+ "tsb-request-count": "$1 {{PLURAL:$1|درخواست|درخواستون}}",
+ "tsb-all-languages-button-label": "سڀ ٻوليون",
+ "tsb-search-requests": "درخواستون ڳوليو",
+ "tsb-accept-button-label": "قبوليو",
+ "tsb-reject-button-label": "رد ڪريو",
+ "tsb-selected-count": "{{PLURAL:$1|$1 رڪن چونڊيل}}",
+ "tsb-older-requests": "$1 {{PLURAL:$1|پراڻي درخواست|پراڻيون درخواستون}}",
+ "tsb-accept-all-button-label": "سڀ قبوليو",
+ "tsb-reject-all-button-label": "سڀ رد ڪريو",
+ "tsb-user-posted-a-comment": "ترجميڪار نہ آھيو",
+ "tsb-didnt-make-any-translations": "ھن رڪن ڪي بہ ترجما نہ ڪيا.",
+ "tsb-translations-source": "ذريعو",
+ "tsb-translations-user": "{{GENDER:$1|رڪن}} جا ترجما",
+ "tsb-translations-current": "موجود ترجما",
+ "translationstash": "ڀليڪار",
+ "translate-translationstash-welcome": "ڀليڪار {{GENDER:$1|$1}} توھان نوان ترجميڪار آھيو",
+ "translate-translationstash-initialtranslation": "توھان جاشروعاتي ترجما",
+ "translate-translationstash-translations": "$1 مڪمل ڪيل {{PLURAL:$1|ترجمو|ترجما}}",
+ "translate-translationstash-skip-button-label": "ھڪ ٻيو آزمايو",
+ "tsb-limit-reached-title": "توھان جي ترجمن لاءِ مھرباني",
+ "tsb-no-requests-from-new-users": "نون رڪنن مان ڪي بہ درخواستون نہ آھن",
+ "tsb-create-user-page": "بنيادي واپرائيندڙ صفحو سرجيو",
+ "log-name-translatorsandbox": "ترجمي جي مشق پٽيءَ جو لاگ",
+ "logentry-translatorsandbox-promoted": "$1 {{GENDER:$2|ترقي ڏن}} $3 ڏانھن {{GENDER:$4|ترجميڪار}}",
+ "logentry-newusers-tsbpromoted": "رڪن کاتو $3 {{GENDER:$2|تخليق ڪيو}} ويو مشقي تختي جي ترقيءَ سان"
+}
diff --git a/MLEB/Translate/i18n/sandbox/sk.json b/MLEB/Translate/i18n/sandbox/sk.json
new file mode 100644
index 00000000..257037bd
--- /dev/null
+++ b/MLEB/Translate/i18n/sandbox/sk.json
@@ -0,0 +1,12 @@
+{
+ "@metadata": {
+ "authors": [
+ "LacoR",
+ "Lexected"
+ ]
+ },
+ "tsb-reject-confirmation": "{{PLURAL:$1|User|Users}} úspešne zamietnutý",
+ "tsb-accept-confirmation": "{{PLURAL:$1|User|Users}} úspešne prijatý",
+ "tsb-limit-reached-title": "Ďakujeme za vaše preklady",
+ "log-name-translatorsandbox": "Záznam z pieskoviska prekladov"
+}
diff --git a/MLEB/Translate/i18n/sandbox/skr-arab.json b/MLEB/Translate/i18n/sandbox/skr-arab.json
new file mode 100644
index 00000000..f66aa7b2
--- /dev/null
+++ b/MLEB/Translate/i18n/sandbox/skr-arab.json
@@ -0,0 +1,10 @@
+{
+ "@metadata": {
+ "authors": [
+ "Saraiki"
+ ]
+ },
+ "tsb-all-languages-button-label": "ساریاں زباناں",
+ "tsb-accept-button-label": "منظور",
+ "translationstash": "ست بسم اللہ"
+}
diff --git a/MLEB/Translate/i18n/sandbox/sq.json b/MLEB/Translate/i18n/sandbox/sq.json
new file mode 100644
index 00000000..497be515
--- /dev/null
+++ b/MLEB/Translate/i18n/sandbox/sq.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Kosovastar"
+ ]
+ },
+ "tsb-reminder-content-generic": "Përshëndetje $1,\n\nFaleminderit për regjistrimin tek {{SITENAME}}.\n\nNëse e plotëson testin tënd të përkthimeve, administratorët më pas do të japin qasje të plotë përkthimi.\n\nTë lutem eja dhe bëjë disa përkthime më shumë këtu:\n$2\n\n$3,\nstafi i {{SITENAME}}"
+}
diff --git a/MLEB/Translate/i18n/sandbox/sr-el.json b/MLEB/Translate/i18n/sandbox/sr-el.json
new file mode 100644
index 00000000..25a03eda
--- /dev/null
+++ b/MLEB/Translate/i18n/sandbox/sr-el.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Zoranzoki21"
+ ]
+ },
+ "log-name-translatorsandbox": "Dnevnik prevodilačkog peska"
+}
diff --git a/MLEB/Translate/i18n/sandbox/ta.json b/MLEB/Translate/i18n/sandbox/ta.json
new file mode 100644
index 00000000..c035e286
--- /dev/null
+++ b/MLEB/Translate/i18n/sandbox/ta.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Rakeshonwiki"
+ ]
+ },
+ "tsb-email-promoted-body": "வணக்கம் {{GENDER:$1|$1}},\n\nவாழ்த்துக்கள் ! நான் உங்களின் மொழிபெயப்புகளை {{SITENAME}} இல் ஆய்வு செய்தேன். இதன் மூலம் உங்களுக்கான மொழிபெயர்ப்பு உரிமத்தை வழங்குகிறேன்.\n\n{{SITENAME}} க்கு வந்து உங்களின் மொழிபெயர்ப்பை இன்றும் , என்றும் தொடருங்கள்:\n$2\n\nநல்வரவு, உங்களின் பங்களிப்ப்பிற்கு மிக்க நன்றி!\n\n{{GENDER:$3|$3}},\n{{SITENAME}} பணியாள்"
+}
diff --git a/MLEB/Translate/i18n/sandbox/tcy.json b/MLEB/Translate/i18n/sandbox/tcy.json
new file mode 100644
index 00000000..ead03ab3
--- /dev/null
+++ b/MLEB/Translate/i18n/sandbox/tcy.json
@@ -0,0 +1,17 @@
+{
+ "@metadata": {
+ "authors": [
+ "Vishwanatha Badikana",
+ "BHARATHESHA ALASANDEMAJALU"
+ ]
+ },
+ "tsb-email-promoted-body": "ಓಯ್{{GENDER:$1|$1}},\n\nಅಭಿನಂದನೆಲು! ಈರ್ ಅನುಮಾದೊ ಮಲ್ತಿನೆನ್ ಯಾನ್ ತೂಯೆ {{SITENAME}} ಬುಕ್ಕೊ ಈರೆಗ್ ಪೂರ್ತಿ ಅನುವಾದೊ ಮಲ್ಪುನ ಹಕ್ಕ್‌ ಕೊರ್ಪೆ.\n\nಈಡೆಗ್ ಬಲೆ {{SITENAME}} ಮುಲ್ತುಡ್ದ್ ಇತ್ತೆ ಅನುವಾದೊ ದುಂಬರಿಪುಲೆ, ಬುಕ್ಕೊ ದಿನೋಲ:\n$2\n\nಸ್ವಾಗತೊ, ಬುಕ್ಕೊ ಈರೆಗ್ ಈರೆನ ಕಾನಿಕೆಗ್ ಸೊಲ್ಮೆಲು!\n\n{{GENDER:$3|$3}},\n{{SITENAME}} ಸಿಬಂದಿ",
+ "tsb-all-languages-button-label": "ಮಾಂತಾ ಬಾಸೆಲೆಡ್",
+ "tsb-search-requests": "ನಾಡ್‍ಪತ್ತ್‌ನೆದ ಪಲಿತಾಂಸೊಲು",
+ "tsb-accept-button-label": "ಒತ್ತೋನಿ",
+ "tsb-reject-button-label": "ತಿರಸ್ಕಾರ ಮಲ್ಪುಲೆ",
+ "tsb-accept-confirmation": "{{PLURAL:$1|User|ಸದಸ್ಯೆರ್}} ಒಪ್ಪಿಯೇರ್",
+ "translationstash": "ಸೊಲ್ಮೆಲೋ",
+ "translate-translationstash-skip-button-label": "ನನೊಂಜಿ ಪ್ರಯತ್ನ ಮಾಂಪಿ",
+ "tsb-limit-reached-title": "ಈರೆನ ಅನುವಾದಾಗ್ ಉಡಲ್ ದಿಂಜಿ ಸೊಲ್ಮೆಲೋ"
+}
diff --git a/MLEB/Translate/i18n/sandbox/tg-cyrl.json b/MLEB/Translate/i18n/sandbox/tg-cyrl.json
new file mode 100644
index 00000000..771e6314
--- /dev/null
+++ b/MLEB/Translate/i18n/sandbox/tg-cyrl.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "ToJack"
+ ]
+ },
+ "tsb-create-user-page": "Саҳифаи оддии корбар сохта шуд"
+}
diff --git a/MLEB/Translate/i18n/sandbox/tt-cyrl.json b/MLEB/Translate/i18n/sandbox/tt-cyrl.json
new file mode 100644
index 00000000..4b6655d9
--- /dev/null
+++ b/MLEB/Translate/i18n/sandbox/tt-cyrl.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Ильнар"
+ ]
+ },
+ "tsb-all-languages-button-label": "Барлык телләр",
+ "translationstash": "Рәхим итегез"
+}
diff --git a/MLEB/Translate/i18n/sandbox/wa.json b/MLEB/Translate/i18n/sandbox/wa.json
new file mode 100644
index 00000000..66ccbe7d
--- /dev/null
+++ b/MLEB/Translate/i18n/sandbox/wa.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Srtxg"
+ ]
+ },
+ "tsb-all-languages-button-label": "Tos les lingaedjes"
+}
diff --git a/MLEB/Translate/i18n/search/af.json b/MLEB/Translate/i18n/search/af.json
new file mode 100644
index 00000000..53cdd60e
--- /dev/null
+++ b/MLEB/Translate/i18n/search/af.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Fwolff"
+ ]
+ },
+ "tux-sst-facet-language": "Tale"
+}
diff --git a/MLEB/Translate/i18n/search/ais.json b/MLEB/Translate/i18n/search/ais.json
new file mode 100644
index 00000000..b41c8bdc
--- /dev/null
+++ b/MLEB/Translate/i18n/search/ais.json
@@ -0,0 +1,11 @@
+{
+ "@metadata": {
+ "authors": [
+ "Bunukwiki",
+ "Tokoabibi"
+ ]
+ },
+ "tux-sst-search": "kilim",
+ "tux-sst-next": "zikuzan a cacay ›",
+ "tux-sst-default": "mibelih"
+}
diff --git a/MLEB/Translate/i18n/search/as.json b/MLEB/Translate/i18n/search/as.json
new file mode 100644
index 00000000..5d943e15
--- /dev/null
+++ b/MLEB/Translate/i18n/search/as.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Dibya Dutta"
+ ]
+ },
+ "tux-sst-facet-group": "বাৰ্তা গোট:"
+}
diff --git a/MLEB/Translate/i18n/search/atj.json b/MLEB/Translate/i18n/search/atj.json
new file mode 100644
index 00000000..fd45c786
--- /dev/null
+++ b/MLEB/Translate/i18n/search/atj.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Benoit Rochon"
+ ]
+ },
+ "tux-sst-search": "Nantona"
+}
diff --git a/MLEB/Translate/i18n/search/be.json b/MLEB/Translate/i18n/search/be.json
new file mode 100644
index 00000000..334cc008
--- /dev/null
+++ b/MLEB/Translate/i18n/search/be.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Artsiom91"
+ ]
+ },
+ "tux-sst-default": "Пераклады"
+}
diff --git a/MLEB/Translate/i18n/search/bs.json b/MLEB/Translate/i18n/search/bs.json
new file mode 100644
index 00000000..d76749d4
--- /dev/null
+++ b/MLEB/Translate/i18n/search/bs.json
@@ -0,0 +1,31 @@
+{
+ "@metadata": {
+ "authors": [
+ "Srdjan m",
+ "Obsuser"
+ ]
+ },
+ "searchtranslations": "Pretraga prijevoda",
+ "tux-sst-edit": "Uredi prijevod",
+ "tux-sst-view-foreign": "Pogledaj na $1",
+ "tux-sst-search": "Pretraži",
+ "tux-sst-search-ph": "Pretraži prijevode",
+ "tux-sst-count": "{{PLURAL:$1|Pronađen jedan rezultat|Pronađena $1 rezultata|Pronađeno $1 rezultata}}",
+ "tux-sst-facet-language": "Jezici",
+ "tux-sst-facet-group": "Grupe poruka",
+ "tux-sst-nosolr-title": "Pretraga nije dostupna",
+ "tux-sst-nosolr-body": "Ovaj wiki nema uslugu za pretragu prijevoda.",
+ "tux-sst-solr-offline-title": "Pretraga nije dostupna",
+ "tux-sst-solr-offline-body": "Usluga za pretragu privremeno nije dostupna.",
+ "tux-sst-next": "Sljedeće ›",
+ "tux-sst-prev": "‹ Prethodno",
+ "tux-sst-default": "Prijevodi",
+ "tux-sst-translated": "Prijevodi s $1",
+ "tux-sst-untranslated": "Nema prijevoda s $1",
+ "tux-sst-outdated": "Zastarjeli prijevodi s $1",
+ "tux-sst-ellipsis-untranslated": "Nema prijevoda",
+ "tux-sst-ellipsis-outdated": "Zastarjeli prijevodi",
+ "tux-sst-link-all-match": "Zahtijevaj sve riječi.",
+ "tux-sst-match-message": "Prikazani su prijevodi koji odgovaraju bilo kojoj riječi iz pretrage. $1",
+ "tux-sst-case-sensitive": "Razlikuj velika i mala slova"
+}
diff --git a/MLEB/Translate/i18n/search/ckb.json b/MLEB/Translate/i18n/search/ckb.json
new file mode 100644
index 00000000..61e9450f
--- /dev/null
+++ b/MLEB/Translate/i18n/search/ckb.json
@@ -0,0 +1,12 @@
+{
+ "@metadata": {
+ "authors": [
+ "Sarchia",
+ "Lost Whispers",
+ "Épine"
+ ]
+ },
+ "tux-sst-search": "گەڕان",
+ "tux-sst-translated": "وەرگێڕانەکان لە $1ەوە",
+ "tux-sst-match-message": "ھەموو ئەو وەرگێڕانانە پیشان دەدرێت کە لەگەڵ وشەکانی بەکارھێندراون بۆ گەڕان دەگونجێت. $1"
+}
diff --git a/MLEB/Translate/i18n/search/eo.json b/MLEB/Translate/i18n/search/eo.json
new file mode 100644
index 00000000..6200e9f5
--- /dev/null
+++ b/MLEB/Translate/i18n/search/eo.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Fitoschido"
+ ]
+ },
+ "tux-sst-next": "Sekvaj →",
+ "tux-sst-prev": "← Malsekvaj"
+}
diff --git a/MLEB/Translate/i18n/search/gom-deva.json b/MLEB/Translate/i18n/search/gom-deva.json
new file mode 100644
index 00000000..6b56e3d3
--- /dev/null
+++ b/MLEB/Translate/i18n/search/gom-deva.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Vaishali Parab"
+ ]
+ },
+ "tux-sst-view-foreign": " $1 चेर पळयात"
+}
diff --git a/MLEB/Translate/i18n/search/gom-latn.json b/MLEB/Translate/i18n/search/gom-latn.json
new file mode 100644
index 00000000..a74076a3
--- /dev/null
+++ b/MLEB/Translate/i18n/search/gom-latn.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "The Discoverer"
+ ]
+ },
+ "tux-sst-view-foreign": "$1 cher polloi"
+}
diff --git a/MLEB/Translate/i18n/search/hi.json b/MLEB/Translate/i18n/search/hi.json
new file mode 100644
index 00000000..f86ecdc8
--- /dev/null
+++ b/MLEB/Translate/i18n/search/hi.json
@@ -0,0 +1,17 @@
+{
+ "@metadata": {
+ "authors": [
+ "NehalDaveND",
+ "Sfic"
+ ]
+ },
+ "tux-sst-edit": "अनुवाद सम्पादित करें",
+ "tux-sst-view-foreign": "$1 में देखें",
+ "tux-sst-search": "खोजें",
+ "tux-sst-search-ph": "अनुवाद ढूँढे",
+ "tux-sst-facet-language": "भाषाएँ",
+ "tux-sst-facet-group": "संदेश-समूह",
+ "tux-sst-next": "अग्रिम ›",
+ "tux-sst-prev": "‹ पिछला",
+ "tux-sst-ellipsis-outdated": "पुराने अनुवाद"
+}
diff --git a/MLEB/Translate/i18n/search/hr.json b/MLEB/Translate/i18n/search/hr.json
new file mode 100644
index 00000000..33428d05
--- /dev/null
+++ b/MLEB/Translate/i18n/search/hr.json
@@ -0,0 +1,30 @@
+{
+ "@metadata": {
+ "authors": [
+ "MaGa"
+ ]
+ },
+ "searchtranslations": "Pretraga prijevoda",
+ "tux-sst-edit": "Uredi prijevod",
+ "tux-sst-view-foreign": "Vidi na $1",
+ "tux-sst-search": "Pretraži",
+ "tux-sst-search-ph": "Pretraga prijevoda",
+ "tux-sst-count": "{{PLURAL:$1|Pronađen jedan rezultat|Pronađena $1 rezultata|Pronađeno $1 rezultata}}",
+ "tux-sst-facet-language": "Jezici",
+ "tux-sst-facet-group": "Grupe poruka",
+ "tux-sst-nosolr-title": "Pretraživanje nije dostupno",
+ "tux-sst-nosolr-body": "Ovaj wiki nema uslugu pretrage prijevoda.",
+ "tux-sst-solr-offline-title": "Pretraživanje nije dostupno",
+ "tux-sst-solr-offline-body": "Tražilica je privremeno nedostupna.",
+ "tux-sst-next": "Sljedeće ›",
+ "tux-sst-prev": "‹ Prethodno",
+ "tux-sst-default": "Prijevodi",
+ "tux-sst-translated": "Prijevodi s $1",
+ "tux-sst-untranslated": "Nema prijevoda s $1",
+ "tux-sst-outdated": "Zastarjeli prijevodi s $1",
+ "tux-sst-ellipsis-untranslated": "Nema prijevoda",
+ "tux-sst-ellipsis-outdated": "Zastarjeli prijevodi",
+ "tux-sst-link-all-match": "Pretraga prema cijelom izrazu.",
+ "tux-sst-match-message": "Prikazuju se prijevodi koji odgovaraju svim riječima iz pretrage. $1",
+ "tux-sst-case-sensitive": "Razlikuj velika i mala slova"
+}
diff --git a/MLEB/Translate/i18n/search/hu.json b/MLEB/Translate/i18n/search/hu.json
new file mode 100644
index 00000000..3a6cd83e
--- /dev/null
+++ b/MLEB/Translate/i18n/search/hu.json
@@ -0,0 +1,30 @@
+{
+ "@metadata": {
+ "authors": [
+ "Tacsipacsi"
+ ]
+ },
+ "searchtranslations": "Fordítások keresése",
+ "tux-sst-edit": "Fordítás szerkesztése",
+ "tux-sst-view-foreign": "Megtekintés itt: $1",
+ "tux-sst-search": "Keresés",
+ "tux-sst-search-ph": "Fordítások keresése",
+ "tux-sst-count": "$1 találat",
+ "tux-sst-facet-language": "Nyelvek",
+ "tux-sst-facet-group": "Üzenetcsoportok",
+ "tux-sst-nosolr-title": "A kereső nem érhető el",
+ "tux-sst-nosolr-body": "Ez a wiki nem rendelkezik fordításkereső szolgáltatással.",
+ "tux-sst-solr-offline-title": "A kereső nem érhető el",
+ "tux-sst-solr-offline-body": "A keresőszolgáltatás ideiglenesen nem elérhető.",
+ "tux-sst-next": "Következő ›",
+ "tux-sst-prev": "‹ Előző",
+ "tux-sst-default": "Fordítások",
+ "tux-sst-translated": "Fordítások $1 nyelvről",
+ "tux-sst-untranslated": "Nincs fordítás $1 nyelvről",
+ "tux-sst-outdated": "Elavult fordítások $1 nyelvről",
+ "tux-sst-ellipsis-untranslated": "Nincs fordítás",
+ "tux-sst-ellipsis-outdated": "Elavult fordítások",
+ "tux-sst-link-all-match": "Az összes keresőszó megkövetelése.",
+ "tux-sst-match-message": "Minden fordítás látható, ami legalább egy keresett szót tartalmaz. $1",
+ "tux-sst-case-sensitive": "Kisbetű/nagybetű-érzékeny"
+}
diff --git a/MLEB/Translate/i18n/search/ig.json b/MLEB/Translate/i18n/search/ig.json
new file mode 100644
index 00000000..05317e20
--- /dev/null
+++ b/MLEB/Translate/i18n/search/ig.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Ukabia"
+ ]
+ },
+ "tux-sst-search": "Tùwe"
+}
diff --git a/MLEB/Translate/i18n/search/inh.json b/MLEB/Translate/i18n/search/inh.json
new file mode 100644
index 00000000..495c3a30
--- /dev/null
+++ b/MLEB/Translate/i18n/search/inh.json
@@ -0,0 +1,11 @@
+{
+ "@metadata": {
+ "authors": [
+ "Adam-Yourist",
+ "Tusholi"
+ ]
+ },
+ "tux-sst-search": "Хьалаха",
+ "tux-sst-facet-language": "Метташ",
+ "tux-sst-default": "Таржамаш"
+}
diff --git a/MLEB/Translate/i18n/search/is.json b/MLEB/Translate/i18n/search/is.json
new file mode 100644
index 00000000..1d10c24f
--- /dev/null
+++ b/MLEB/Translate/i18n/search/is.json
@@ -0,0 +1,30 @@
+{
+ "@metadata": {
+ "authors": [
+ "Sveinn í Felli"
+ ]
+ },
+ "searchtranslations": "Leita í þýðingum",
+ "tux-sst-edit": "Breyta þýðingu",
+ "tux-sst-view-foreign": "Skoða á $1",
+ "tux-sst-search": "Leita",
+ "tux-sst-search-ph": "Leita í þýðingum",
+ "tux-sst-count": "{{PLURAL:$1|En niðurstaða fannst|$1 niðurstöður fundust}}",
+ "tux-sst-facet-language": "Tungumál",
+ "tux-sst-facet-group": "Þýðingahópar",
+ "tux-sst-nosolr-title": "Leit er ekki tiltæk",
+ "tux-sst-nosolr-body": "Þetta wiki er ekki með þýðingaleitarþjónustu.",
+ "tux-sst-solr-offline-title": "Leit er ekki tiltæk",
+ "tux-sst-solr-offline-body": "Í augnablikinu er leitarþjónustan ekki tiltæk.",
+ "tux-sst-next": "Næsta ›",
+ "tux-sst-prev": "‹ Fyrra",
+ "tux-sst-default": "Þýðingar",
+ "tux-sst-translated": "Þýðingar úr $1",
+ "tux-sst-untranslated": "Engin þýðing úr $1",
+ "tux-sst-outdated": "Úreltar þýðingar frá $1",
+ "tux-sst-ellipsis-untranslated": "Engin þýðing",
+ "tux-sst-ellipsis-outdated": "Úreltar þýðingar",
+ "tux-sst-link-all-match": "Nota öll leitarorðin.",
+ "tux-sst-match-message": "Birtir þýðingar sem samsvara einhverju leitarorðanna. $1",
+ "tux-sst-case-sensitive": "Háð há/lágstöfum"
+}
diff --git a/MLEB/Translate/i18n/search/kab.json b/MLEB/Translate/i18n/search/kab.json
new file mode 100644
index 00000000..adccbe24
--- /dev/null
+++ b/MLEB/Translate/i18n/search/kab.json
@@ -0,0 +1,29 @@
+{
+ "@metadata": {
+ "authors": [
+ "Belkacem77"
+ ]
+ },
+ "searchtranslations": "Nadi tisuqilin",
+ "tux-sst-edit": "Ẓreg tasuqilt",
+ "tux-sst-view-foreign": "Sken di $1",
+ "tux-sst-search": "Nadi",
+ "tux-sst-search-ph": "Nadi tisuqilin",
+ "tux-sst-count": "{{PLURAL:$1|0=Ulac agmuḍ|1=yiwen n ugmuḍ|$1 n igmaḍ}} yettwafen",
+ "tux-sst-facet-language": "Tutlayin",
+ "tux-sst-facet-group": "Igrawen n yiznan",
+ "tux-sst-nosolr-title": "Ulac anadi",
+ "tux-sst-nosolr-body": "Awiki-agi ur ɣur-s ara amezlu n unadi n tsuqilt.",
+ "tux-sst-solr-offline-title": "Ulac anadi",
+ "tux-sst-solr-offline-body": "Ameẓlu n unadi ulac-it akka tura.",
+ "tux-sst-next": "Ar zdat ›",
+ "tux-sst-prev": "‹ ar deffir",
+ "tux-sst-default": "Tisuqilin",
+ "tux-sst-translated": "Tisuqilin si $1",
+ "tux-sst-untranslated": "Ur yettwasuqel ara si $1",
+ "tux-sst-outdated": "Tisuqilin ifaten si $1",
+ "tux-sst-ellipsis-untranslated": "Ur yettwasuqel ara",
+ "tux-sst-ellipsis-outdated": "Tisuqilin ifaten",
+ "tux-sst-link-all-match": "Yesra akk awalen n unadi.",
+ "tux-sst-case-sensitive": "Yettqadaṛ taruẓi n usekkil"
+}
diff --git a/MLEB/Translate/i18n/search/kiu.json b/MLEB/Translate/i18n/search/kiu.json
new file mode 100644
index 00000000..4a31203f
--- /dev/null
+++ b/MLEB/Translate/i18n/search/kiu.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Kumkumuk"
+ ]
+ },
+ "tux-sst-ellipsis-untranslated": "Açarnayış çıniyo"
+}
diff --git a/MLEB/Translate/i18n/search/kjp.json b/MLEB/Translate/i18n/search/kjp.json
new file mode 100644
index 00000000..53d77670
--- /dev/null
+++ b/MLEB/Translate/i18n/search/kjp.json
@@ -0,0 +1,19 @@
+{
+ "@metadata": {
+ "authors": [
+ "Rul1902"
+ ]
+ },
+ "searchtranslations": "မ်ုအင်းၰူ့ ဆ်ုခၠယ်",
+ "tux-sst-edit": "မ်ုအင်းတာင် ဆ်ုခၠယ်",
+ "tux-sst-view-foreign": "မ်ုယောဝ်ႋ $1 ဝယ်",
+ "tux-sst-search": "အင်းၰူ့",
+ "tux-sst-search-ph": "မ်ုအင်းၰူ့ ခၠယ်ၯင်း",
+ "tux-sst-count": "အ်ုတင်ၮေဝ်ႋ {{PLURAL:$1|၁ ၮါင်း|$1 ၮါင်း}} ဍးဝေ့ဆေဝ်ႋလှ်",
+ "tux-sst-facet-language": "ဆ်ုခၠါင်ဘာႋသာ့",
+ "tux-sst-facet-group": "လိက်ၜၠာ်ကုံရွာဲလ်ုဖး",
+ "tux-sst-nosolr-title": "ဆ်ုအင်းၰူ့ လ်ုၮေဝ်ႋသှ်ေၜး",
+ "tux-sst-next": "မေံယာႋ »",
+ "tux-sst-prev": "« မ်ုထါင်လင်ႋခါင့်",
+ "tux-sst-default": "ခၠယ့်ဖှ်ေက်ုဆာႋလ်ုဖး"
+}
diff --git a/MLEB/Translate/i18n/search/kk-cyrl.json b/MLEB/Translate/i18n/search/kk-cyrl.json
new file mode 100644
index 00000000..82891bdf
--- /dev/null
+++ b/MLEB/Translate/i18n/search/kk-cyrl.json
@@ -0,0 +1,30 @@
+{
+ "@metadata": {
+ "authors": [
+ "Arystanbek"
+ ]
+ },
+ "searchtranslations": "Аудармаларды іздеу",
+ "tux-sst-edit": "Аударманы өңдеу",
+ "tux-sst-view-foreign": "$1 дегеннен қарау",
+ "tux-sst-search": "Іздеу",
+ "tux-sst-search-ph": "Аудармаларды іздеу",
+ "tux-sst-count": "{{PLURAL:$1|Бір нәтиже табылды|$1 нәтиже табылды}}",
+ "tux-sst-facet-language": "Тілдер",
+ "tux-sst-facet-group": "Хабарлама топтары",
+ "tux-sst-nosolr-title": "Іздеу қол жетімді емес",
+ "tux-sst-nosolr-body": "Уиикиде аударманы іздеу сервисі жоқ.",
+ "tux-sst-solr-offline-title": "Іздеу қол жетімді емес",
+ "tux-sst-solr-offline-body": "Іздеу сервисі уақытша қол жетімді емес",
+ "tux-sst-next": "Келесі →",
+ "tux-sst-prev": "‹Алдыңғы",
+ "tux-sst-default": "Аудармалар",
+ "tux-sst-translated": "$1 тілінен аудармалар",
+ "tux-sst-untranslated": "$1 тілінен аудармалар жоқ",
+ "tux-sst-outdated": "$1 тілінен жаңартылмаған аудармалар",
+ "tux-sst-ellipsis-untranslated": "Аударма жоқ",
+ "tux-sst-ellipsis-outdated": "Жаңартуды қажет ететін аудармалар",
+ "tux-sst-link-all-match": "Барлық ізделген сөздерді қажет ету.",
+ "tux-sst-match-message": "Кез келген іздеу сөздеріне сәйкес аудармалар көрсетілген. $1",
+ "tux-sst-case-sensitive": "Үлкен-кішілігін ескеру"
+}
diff --git a/MLEB/Translate/i18n/search/km.json b/MLEB/Translate/i18n/search/km.json
new file mode 100644
index 00000000..fc9f402d
--- /dev/null
+++ b/MLEB/Translate/i18n/search/km.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "គីមស៊្រុន"
+ ]
+ },
+ "tux-sst-facet-language": "ភាសា"
+}
diff --git a/MLEB/Translate/i18n/search/krl.json b/MLEB/Translate/i18n/search/krl.json
new file mode 100644
index 00000000..cbdceaac
--- /dev/null
+++ b/MLEB/Translate/i18n/search/krl.json
@@ -0,0 +1,10 @@
+{
+ "@metadata": {
+ "authors": [
+ "Mashoi7",
+ "Likopiän tyttö"
+ ]
+ },
+ "tux-sst-default": "Kiännökšet",
+ "tux-sst-untranslated": "Kiändämättäh kielespäi $1"
+}
diff --git a/MLEB/Translate/i18n/search/lag.json b/MLEB/Translate/i18n/search/lag.json
new file mode 100644
index 00000000..561b498f
--- /dev/null
+++ b/MLEB/Translate/i18n/search/lag.json
@@ -0,0 +1,10 @@
+{
+ "@metadata": {
+ "authors": [
+ "Baba Tabita"
+ ]
+ },
+ "tux-sst-view-foreign": "Laangɨra kwa $1",
+ "tux-sst-search": "Saakɨra",
+ "tux-sst-facet-language": "Ndʉʉsɨka"
+}
diff --git a/MLEB/Translate/i18n/search/lfn.json b/MLEB/Translate/i18n/search/lfn.json
new file mode 100644
index 00000000..beb61b57
--- /dev/null
+++ b/MLEB/Translate/i18n/search/lfn.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Robin van der Vliet"
+ ]
+ },
+ "tux-sst-facet-language": "Linguas"
+}
diff --git a/MLEB/Translate/i18n/search/lki.json b/MLEB/Translate/i18n/search/lki.json
new file mode 100644
index 00000000..a7f33cc3
--- /dev/null
+++ b/MLEB/Translate/i18n/search/lki.json
@@ -0,0 +1,24 @@
+{
+ "@metadata": {
+ "authors": [
+ "Hosseinblue",
+ "Lakzon"
+ ]
+ },
+ "searchtranslations": "مِنِی کردن ترجمةل",
+ "tux-sst-edit": "دةسکاری ترجمه",
+ "tux-sst-view-foreign": "مشاهده در $1",
+ "tux-sst-search": "مِنِی کِردِن(گێردین)",
+ "tux-sst-search-ph": "مِنِی کردن ترجمةل",
+ "tux-sst-count": "{{PLURAL:$1|0=هیچ نتیجه‌ای یافت نشد|یک نتیجه یافت شد|$1 نتیجه یافت شد}}",
+ "tux-sst-facet-language": "زوونەل",
+ "tux-sst-facet-group": "گروه‌های پیام",
+ "tux-sst-nosolr-title": "جستجوی ناموجود است",
+ "tux-sst-nosolr-body": "این ویکی خدمات جستجوی ترجمه ندارد.",
+ "tux-sst-solr-offline-title": "جستجوی ناموجود است",
+ "tux-sst-solr-offline-body": "خدمت جستجو به‌صورت موقت ناموجود است.",
+ "tux-sst-next": "بچۆ نووآ←",
+ "tux-sst-prev": "‹ بووه دؤما/پیشین",
+ "tux-sst-default": "چاوواشەبیەل",
+ "tux-sst-translated": "ترجمه إژ $1"
+}
diff --git a/MLEB/Translate/i18n/search/lkt.json b/MLEB/Translate/i18n/search/lkt.json
new file mode 100644
index 00000000..24bb3d4f
--- /dev/null
+++ b/MLEB/Translate/i18n/search/lkt.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "R12ntech"
+ ]
+ },
+ "tux-sst-search": "Wóle"
+}
diff --git a/MLEB/Translate/i18n/search/lt.json b/MLEB/Translate/i18n/search/lt.json
new file mode 100644
index 00000000..cc43d93f
--- /dev/null
+++ b/MLEB/Translate/i18n/search/lt.json
@@ -0,0 +1,28 @@
+{
+ "@metadata": {
+ "authors": [
+ "Eitvys200"
+ ]
+ },
+ "searchtranslations": "Ieškoti vertimų",
+ "tux-sst-edit": "Redaguoti vertimą",
+ "tux-sst-view-foreign": "Žiūrėti $1",
+ "tux-sst-search": "Ieškoti",
+ "tux-sst-search-ph": "Ieškoti vertimų",
+ "tux-sst-count": "{{PLURAL:$1|Rastas vienas rezultatas|Rasta $1 rezultatų}}",
+ "tux-sst-facet-language": "Kalbos",
+ "tux-sst-facet-group": "Pranešimo grupės",
+ "tux-sst-nosolr-title": "Paieška nepasiekiama",
+ "tux-sst-nosolr-body": "Ši viki neturi vertimų paieškos paslaugos.",
+ "tux-sst-solr-offline-title": "Paieška nepasiekiama",
+ "tux-sst-solr-offline-body": "Paieškos paslauga laikinai neprieinama.",
+ "tux-sst-next": "Kitas ›",
+ "tux-sst-prev": "‹ Ankstesnis",
+ "tux-sst-default": "Vertimai",
+ "tux-sst-translated": "Vertimai iš $1",
+ "tux-sst-untranslated": "nėra vertimų iš $1",
+ "tux-sst-outdated": "Pasenę vertimai iš $1",
+ "tux-sst-ellipsis-untranslated": "Nėra vertimų",
+ "tux-sst-ellipsis-outdated": "Pasenę vertimai",
+ "tux-sst-link-all-match": "Reikalauti visų paieškos žodžių."
+}
diff --git a/MLEB/Translate/i18n/search/mwl.json b/MLEB/Translate/i18n/search/mwl.json
new file mode 100644
index 00000000..4f8cf7d0
--- /dev/null
+++ b/MLEB/Translate/i18n/search/mwl.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "MokaAkashiyaPT",
+ "Athena in Wonderland"
+ ]
+ },
+ "tux-sst-default": "Traduçones"
+}
diff --git a/MLEB/Translate/i18n/search/my.json b/MLEB/Translate/i18n/search/my.json
new file mode 100644
index 00000000..ea9ef4d4
--- /dev/null
+++ b/MLEB/Translate/i18n/search/my.json
@@ -0,0 +1,30 @@
+{
+ "@metadata": {
+ "authors": [
+ "Dr Lotus Black"
+ ]
+ },
+ "searchtranslations": "ဘာသာပြန်များ ရှာဖွေရန်",
+ "tux-sst-edit": "ဘာသာပြန်ကို ပြင်ဆင်ရန်",
+ "tux-sst-view-foreign": "$1 တွင် ကြည့်ရန်",
+ "tux-sst-search": "ရှာဖွေရန်",
+ "tux-sst-search-ph": "ဘာသာပြန်များ ရှာဖွေရန်",
+ "tux-sst-count": "ရလဒ် {{PLURAL:$1|၁ ခု|$1 ခု}} တွေ့ရှိခဲ့သည်",
+ "tux-sst-facet-language": "ဘာသာစကားများ",
+ "tux-sst-facet-group": "မက်ဆေ့အုပ်စုများ",
+ "tux-sst-nosolr-title": "ရှာဖွေခြင်း မရရှိနိုင်ပါ",
+ "tux-sst-nosolr-body": "ဤဝီကီတွင် ဘာသာပြန် ရှာဖွေရေးဝန်ဆောင်မှု မရှိပါ။",
+ "tux-sst-solr-offline-title": "ရှာဖွေခြင်း မရရှိနိုင်ပါ",
+ "tux-sst-solr-offline-body": "ရှာဖွေရေး ဝန်ဆောင်မှု ယာယီ မရရှိနိုင်ပါ။",
+ "tux-sst-next": "ရှေ့ »",
+ "tux-sst-prev": "« ယခင်က",
+ "tux-sst-default": "ဘာသာပြန်များ",
+ "tux-sst-translated": "$1 မှ ဘာသာပြန်များ",
+ "tux-sst-untranslated": "$1 မှ ဘာသာပြန်များ မရှိပါ",
+ "tux-sst-outdated": "$1 မှ ခေတ်နောက်ကျသော ဘာသာပြန်များ",
+ "tux-sst-ellipsis-untranslated": "ဘာသာပြန်များ မရှိပါ",
+ "tux-sst-ellipsis-outdated": "ခေတ်နောက်ကျသော ဘာသာပြန်များ",
+ "tux-sst-link-all-match": "ရှာဖွေစကားလုံးများအားလုံး လိုအပ်သည်။",
+ "tux-sst-match-message": "ရှာဖွေစကားလုံးများ၏ ကိုက်ညီသော မည်သည့်ဘာသာပြန်များကိုမဆို ပြသနေသည်။ $1",
+ "tux-sst-case-sensitive": "စားလုံးအကြီးအသေး အတိမ်းစောင်းမခံ"
+}
diff --git a/MLEB/Translate/i18n/search/ne.json b/MLEB/Translate/i18n/search/ne.json
new file mode 100644
index 00000000..9adc7c14
--- /dev/null
+++ b/MLEB/Translate/i18n/search/ne.json
@@ -0,0 +1,12 @@
+{
+ "@metadata": {
+ "authors": [
+ "NehalDaveND"
+ ]
+ },
+ "tux-sst-search": "खोज",
+ "tux-sst-facet-language": "भाषाहरू",
+ "tux-sst-facet-group": "संदेश समूह",
+ "tux-sst-next": "अर्को ›",
+ "tux-sst-prev": "‹ पछिल्लो"
+}
diff --git a/MLEB/Translate/i18n/search/olo.json b/MLEB/Translate/i18n/search/olo.json
new file mode 100644
index 00000000..7d555a09
--- /dev/null
+++ b/MLEB/Translate/i18n/search/olo.json
@@ -0,0 +1,10 @@
+{
+ "@metadata": {
+ "authors": [
+ "Mashoi7"
+ ]
+ },
+ "tux-sst-default": "Kiännökset",
+ "tux-sst-translated": "Kiännökset kielespäi $1",
+ "tux-sst-untranslated": "Ei kiännösty kielespäi $1"
+}
diff --git a/MLEB/Translate/i18n/search/sa.json b/MLEB/Translate/i18n/search/sa.json
new file mode 100644
index 00000000..3b331415
--- /dev/null
+++ b/MLEB/Translate/i18n/search/sa.json
@@ -0,0 +1,11 @@
+{
+ "@metadata": {
+ "authors": [
+ "NehalDaveND"
+ ]
+ },
+ "tux-sst-search": "अन्विष्यताम्",
+ "tux-sst-facet-language": "भाषाः",
+ "tux-sst-next": "अग्रिमम् ›",
+ "tux-sst-prev": "‹ पूर्वतनम्"
+}
diff --git a/MLEB/Translate/i18n/search/scn.json b/MLEB/Translate/i18n/search/scn.json
new file mode 100644
index 00000000..d593da04
--- /dev/null
+++ b/MLEB/Translate/i18n/search/scn.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Sarvaturi"
+ ]
+ },
+ "searchtranslations": "Arricerca traduzzioni"
+}
diff --git a/MLEB/Translate/i18n/search/sd.json b/MLEB/Translate/i18n/search/sd.json
new file mode 100644
index 00000000..57e8deba
--- /dev/null
+++ b/MLEB/Translate/i18n/search/sd.json
@@ -0,0 +1,10 @@
+{
+ "@metadata": {
+ "authors": [
+ "Mehtab ahmed"
+ ]
+ },
+ "tux-sst-next": "اڳيون",
+ "tux-sst-prev": "‹ پويون",
+ "tux-sst-link-all-match": "ڳولا جا سڀ لفظ گھرجن ٿا."
+}
diff --git a/MLEB/Translate/i18n/search/shn.json b/MLEB/Translate/i18n/search/shn.json
new file mode 100644
index 00000000..902eceea
--- /dev/null
+++ b/MLEB/Translate/i18n/search/shn.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Saosukham"
+ ]
+ },
+ "tux-sst-view-foreign": "ၼႄတီႈ $1"
+}
diff --git a/MLEB/Translate/i18n/search/shy-latn.json b/MLEB/Translate/i18n/search/shy-latn.json
new file mode 100644
index 00000000..def22ae9
--- /dev/null
+++ b/MLEB/Translate/i18n/search/shy-latn.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Vikoula5"
+ ]
+ },
+ "tux-sst-search": "Iruzzi"
+}
diff --git a/MLEB/Translate/i18n/search/sl.json b/MLEB/Translate/i18n/search/sl.json
new file mode 100644
index 00000000..cc37e744
--- /dev/null
+++ b/MLEB/Translate/i18n/search/sl.json
@@ -0,0 +1,30 @@
+{
+ "@metadata": {
+ "authors": [
+ "Janezdrilc"
+ ]
+ },
+ "searchtranslations": "Išči prevode",
+ "tux-sst-edit": "Uredi prevod",
+ "tux-sst-view-foreign": "Ogled na: $1",
+ "tux-sst-search": "Išči",
+ "tux-sst-search-ph": "Išči prevode",
+ "tux-sst-count": "{{PLURAL:$1|En najden zadetek|$1 najdena zadetka|$1 najdeni zadetki|$1 najdenih zadetkov}}",
+ "tux-sst-facet-language": "Jeziki",
+ "tux-sst-facet-group": "Skupine sporočil",
+ "tux-sst-nosolr-title": "Iskanje ni na voljo",
+ "tux-sst-nosolr-body": "Ta wiki ne omogoča iskanje prevodov.",
+ "tux-sst-solr-offline-title": "Iskanje ni na voljo",
+ "tux-sst-solr-offline-body": "Iskanje trenutno ni na voljo.",
+ "tux-sst-next": "Naslednji ›",
+ "tux-sst-prev": "‹ Prejšnji",
+ "tux-sst-default": "Prevodi",
+ "tux-sst-translated": "Prevodi iz jezika: $1",
+ "tux-sst-untranslated": "Ni prevodov iz jezika: $1",
+ "tux-sst-outdated": "Zastareli prevodi iz jezika: $1",
+ "tux-sst-ellipsis-untranslated": "Brez prevoda",
+ "tux-sst-ellipsis-outdated": "Zastareli prevodi",
+ "tux-sst-link-all-match": "Zahteva vse besede iskanja.",
+ "tux-sst-match-message": "Prikazuje prevode, ki se ujemajo s katerokoli od vpisanih besed. $1",
+ "tux-sst-case-sensitive": "Razlikuj velike in male črke"
+}
diff --git a/MLEB/Translate/i18n/search/sq.json b/MLEB/Translate/i18n/search/sq.json
new file mode 100644
index 00000000..5714a8de
--- /dev/null
+++ b/MLEB/Translate/i18n/search/sq.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Kosovastar"
+ ]
+ },
+ "tux-sst-edit": "Redakto përkthimin"
+}
diff --git a/MLEB/Translate/i18n/search/sr-el.json b/MLEB/Translate/i18n/search/sr-el.json
new file mode 100644
index 00000000..d4e63961
--- /dev/null
+++ b/MLEB/Translate/i18n/search/sr-el.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Obsuser"
+ ]
+ },
+ "tux-sst-count": "{{PLURAL:$1|Jedan rezultat pronađen|$1 rezultata pronađena|$1 rezultata pronađeno}}",
+ "tux-sst-case-sensitive": "Uvažavanje verzala"
+}
diff --git a/MLEB/Translate/i18n/search/tay.json b/MLEB/Translate/i18n/search/tay.json
new file mode 100644
index 00000000..e73456df
--- /dev/null
+++ b/MLEB/Translate/i18n/search/tay.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Akamycoco"
+ ]
+ },
+ "tux-sst-search": "Hhkangi’"
+}
diff --git a/MLEB/Translate/i18n/search/tcy.json b/MLEB/Translate/i18n/search/tcy.json
new file mode 100644
index 00000000..ae42c063
--- /dev/null
+++ b/MLEB/Translate/i18n/search/tcy.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Kiranpoojary"
+ ]
+ },
+ "searchtranslations": "ತರ್ಜುಮೆಲೆನ್ ನಾಡ್‌ಲೆ"
+}
diff --git a/MLEB/Translate/i18n/search/te.json b/MLEB/Translate/i18n/search/te.json
new file mode 100644
index 00000000..fdc2708f
--- /dev/null
+++ b/MLEB/Translate/i18n/search/te.json
@@ -0,0 +1,12 @@
+{
+ "@metadata": {
+ "authors": [
+ "Veeven"
+ ]
+ },
+ "tux-sst-facet-language": "భాషలు",
+ "tux-sst-facet-group": "సందేశాల సమూహాలు",
+ "tux-sst-default": "అనువాదాలు",
+ "tux-sst-ellipsis-untranslated": "అనువాదం లేదు",
+ "tux-sst-ellipsis-outdated": "పాతబడిన అనువాదాలు"
+}
diff --git a/MLEB/Translate/i18n/search/tg-cyrl.json b/MLEB/Translate/i18n/search/tg-cyrl.json
new file mode 100644
index 00000000..2ab4a6b9
--- /dev/null
+++ b/MLEB/Translate/i18n/search/tg-cyrl.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "ToJack"
+ ]
+ },
+ "tux-sst-search": "Ҷустуҷӯ",
+ "tux-sst-facet-language": "Забонҳо"
+}
diff --git a/MLEB/Translate/i18n/search/th.json b/MLEB/Translate/i18n/search/th.json
new file mode 100644
index 00000000..d87f878d
--- /dev/null
+++ b/MLEB/Translate/i18n/search/th.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Octahedron80"
+ ]
+ },
+ "tux-sst-count": "ค้นพบ $1 {{PLURAL:$1|ผลลัพธ์|ผลลัพธ์}}"
+}
diff --git a/MLEB/Translate/i18n/search/tt-cyrl.json b/MLEB/Translate/i18n/search/tt-cyrl.json
new file mode 100644
index 00000000..2e42be38
--- /dev/null
+++ b/MLEB/Translate/i18n/search/tt-cyrl.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Ильнар"
+ ]
+ },
+ "tux-sst-facet-language": "Телләр",
+ "tux-sst-default": "Тәрҗемәләр"
+}
diff --git a/MLEB/Translate/i18n/search/udm.json b/MLEB/Translate/i18n/search/udm.json
new file mode 100644
index 00000000..ed721a07
--- /dev/null
+++ b/MLEB/Translate/i18n/search/udm.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Kaganer"
+ ]
+ },
+ "tux-sst-facet-language": "Кылъёс"
+}
diff --git a/MLEB/Translate/i18n/search/wa.json b/MLEB/Translate/i18n/search/wa.json
new file mode 100644
index 00000000..dad938e7
--- /dev/null
+++ b/MLEB/Translate/i18n/search/wa.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Srtxg"
+ ]
+ },
+ "tux-sst-facet-language": "Lingaedjes"
+}
diff --git a/MLEB/Translate/insertables/CombinedInsertablesSuggester.php b/MLEB/Translate/insertables/CombinedInsertablesSuggester.php
new file mode 100644
index 00000000..7a99dda4
--- /dev/null
+++ b/MLEB/Translate/insertables/CombinedInsertablesSuggester.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * A class to combine multiple insertables suggesters.
+ */
+class CombinedInsertablesSuggester implements InsertablesSuggester {
+ protected $suggesters = [];
+
+ /**
+ * CombinedInsertablesSuggester constructor.
+ * @param array $suggesters Array of InsertablesSuggester objects to combine.
+ */
+ public function __construct( $suggesters = [] ) {
+ $this->suggesters = $suggesters;
+ }
+
+ public function getInsertables( $text ) {
+ $insertables = [];
+ foreach ( $this->suggesters as $suggester ) {
+ $new = $suggester->getInsertables( $text );
+ $insertables = array_merge( $insertables, $new );
+ }
+
+ return array_unique( $insertables, SORT_REGULAR );
+ }
+}
diff --git a/MLEB/Translate/insertables/NumericalParameterInsertablesSuggester.php b/MLEB/Translate/insertables/NumericalParameterInsertablesSuggester.php
new file mode 100644
index 00000000..13ffd4af
--- /dev/null
+++ b/MLEB/Translate/insertables/NumericalParameterInsertablesSuggester.php
@@ -0,0 +1,29 @@
+<?php
+/**
+ * Insertables suggester for numerical parameters such as $1, $2, $3
+ *
+ * @file
+ * @author Geoffrey Mon
+ * @license GPL-2.0-or-later
+ */
+
+class NumericalParameterInsertablesSuggester implements InsertablesSuggester {
+ public function getInsertables( $text ) {
+ $insertables = [];
+
+ // $1, $2, $3 etc.
+ $matches = [];
+ preg_match_all(
+ '/\$\d+/',
+ $text,
+ $matches,
+ PREG_SET_ORDER
+ );
+ $new = array_map( function ( $match ) {
+ return new Insertable( $match[0], $match[0] );
+ }, $matches );
+ $insertables = array_merge( $insertables, $new );
+
+ return $insertables;
+ }
+}
diff --git a/MLEB/Translate/package.json b/MLEB/Translate/package.json
new file mode 100644
index 00000000..bb287beb
--- /dev/null
+++ b/MLEB/Translate/package.json
@@ -0,0 +1,16 @@
+{
+ "private": true,
+ "scripts": {
+ "test": "grunt test"
+ },
+ "devDependencies": {
+ "eslint-config-wikimedia": "0.7.2",
+ "grunt": "1.0.3",
+ "grunt-banana-checker": "0.6.0",
+ "grunt-eslint": "21.0.0",
+ "grunt-jsonlint": "1.1.0",
+ "grunt-stylelint": "0.10.0",
+ "stylelint": "9.5.0",
+ "stylelint-config-wikimedia": "0.4.3"
+ }
+}
diff --git a/MLEB/Translate/resources/css/ext.translate.groupselector.less b/MLEB/Translate/resources/css/ext.translate.groupselector.less
new file mode 100644
index 00000000..f1d6764e
--- /dev/null
+++ b/MLEB/Translate/resources/css/ext.translate.groupselector.less
@@ -0,0 +1,142 @@
+@import 'mediawiki.mixins';
+
+/**
+ * Group selector
+ */
+.tux-groupselector {
+ position: absolute;
+ top: 14px;
+ right: 0;
+ z-index: 1000;
+ display: none;
+ margin-top: 13px;
+ width: 600px;
+ padding: 0;
+ border: 1px solid #a2a9b1;
+ background-color: #f0f0f0;
+ border-radius: 5px;
+ box-shadow: 0 5px 10px rgba( 0, 0, 0, 0.2 );
+}
+
+/* The triangle shaped callout */
+.tux-groupselector:before {
+ border-bottom: 7px solid #a2a9b1;
+ border-left: 7px solid transparent;
+ border-right: 7px solid transparent;
+ content: '';
+ display: inline-block;
+ left: 99px;
+ position: absolute;
+ top: -7px;
+}
+
+.tux-groupselector:after {
+ border-bottom: 6px solid #f0f0f0;
+ border-left: 6px solid transparent;
+ border-right: 6px solid transparent;
+ content: '';
+ display: inline-block;
+ left: 100px;
+ position: absolute;
+ top: -6px;
+}
+
+/* Remove the triangle shaped callout */
+.tux-groupselector.removecallout:before,
+.tux-groupselector.removecallout:after {
+ content: none;
+}
+
+.tux-groupselector__filter {
+ padding-top: 10px;
+}
+
+.tux-groupselector__filter__search__input {
+ font-size: 14px;
+ width: 100%;
+ height: 28px;
+ border: 1px solid #c9c9c9;
+ padding: 2px;
+}
+
+.tux-groupselector__filter__search__icon {
+ .background-image( '../images/search.svg' );
+ background-repeat: no-repeat;
+ background-position: right center;
+ background-size: 25px;
+ height: 28px;
+}
+
+/*
+ * Group tab
+ */
+.tux-grouptab {
+ color: #222;
+ line-height: 30px;
+ height: 30px;
+ cursor: pointer;
+ padding: 2px 5px;
+ margin: 0 4px;
+ display: inline-block;
+}
+
+.tux-grouptab--selected {
+ border-bottom: 2px solid #0645ad;
+}
+
+/*
+ * Group list
+ */
+.tux-grouplist {
+ max-height: 400px;
+ overflow-y: auto;
+ overflow-x: hidden;
+ background-color: #fff;
+ min-height: 200px;
+ border-radius: 0 0 5px 5px;
+}
+
+.tux-grouplist__item {
+ position: relative;
+ border-bottom: 1px solid #eee;
+ height: 50px;
+ cursor: pointer;
+
+ &:hover {
+ background-color: #f8f8f8;
+ }
+}
+
+.grid .tux-grouplist__item__label {
+ padding-bottom: 0; /* grid override */
+ padding-left: 15px; /* grid override */
+ line-height: 32px;
+ cursor: pointer;
+ white-space: nowrap;
+}
+
+.tux-grouplist__item__label .tux-statsbar {
+ position: absolute;
+ bottom: 0;
+ width: 150px;
+}
+
+.tux-grouplist__item__icon {
+ .background-image( '../images/project.svg' );
+ background-repeat: no-repeat;
+ background-position: right center;
+ /* Keep this in sync with js! */
+ background-size: 32px;
+ height: 50px;
+}
+
+.grid .row .tux-grouplist__item__subgroups {
+ color: #72777d;
+ position: absolute; /* grid override */
+ padding: 0 15px 2px 0; /* grid override */
+ line-height: 1.25em;
+ bottom: 0;
+ right: 0;
+ text-align: right;
+ font-size: 10pt;
+}
diff --git a/MLEB/Translate/resources/css/ext.translate.legacy.css b/MLEB/Translate/resources/css/ext.translate.legacy.css
new file mode 100644
index 00000000..62353931
--- /dev/null
+++ b/MLEB/Translate/resources/css/ext.translate.legacy.css
@@ -0,0 +1,65 @@
+/* Form at Special:Translate */
+.mw-sp-translate-error {
+ font-style: italic;
+ background-color: #ff0;
+}
+
+/* This gets pretty far on wide screens... */
+.mw-sp-translate-settings input[ type='submit' ] {
+ float: right;
+}
+
+/* For some reason a non-breaking space is not enough to keep the label
+ * with the dropdown. */
+.mw-sp-translate-settings label {
+ white-space: nowrap;
+}
+
+.mw-sp-translate-table {
+ width: 100%;
+ border-width: 1px;
+ border-collapse: collapse;
+}
+
+.mw-sp-translate-table th {
+ background-color: #b2b2ff;
+ border: 1px solid;
+}
+
+.mw-sp-translate-table tr.orig {
+ background-color: #ffe2e2;
+}
+
+.mw-sp-translate-table tr.new {
+ background-color: #e2ffe2;
+}
+
+.mw-sp-translate-table tr.def {
+ background-color: #f0f0ff;
+}
+
+.mw-sp-translate-table tr.ign {
+ background-color: #202020;
+}
+
+.mw-sp-translate-table tr.opt {
+ background-color: #f2f200;
+}
+
+.mw-sp-translate-table .untranslated {
+ background-color: #a2f290;
+}
+
+.mw-sp-translate-table > tbody > tr > * {
+ vertical-align: top;
+ border: 1px solid #909090;
+}
+
+.mw-translate-messagereviewbutton {
+ float: right;
+}
+
+.mw-translate-messagereviewstatus {
+ clear: right;
+ text-align: right;
+}
diff --git a/MLEB/Translate/resources/css/ext.translate.messagetable.less b/MLEB/Translate/resources/css/ext.translate.messagetable.less
new file mode 100644
index 00000000..2a2e10a7
--- /dev/null
+++ b/MLEB/Translate/resources/css/ext.translate.messagetable.less
@@ -0,0 +1,283 @@
+@import 'mediawiki.mixins';
+
+/* Default colors */
+.tux-messagelist {
+ background-color: #f8f8f8;
+ color: #222;
+ max-width: 800px;
+}
+
+.grid.ext-translate-container .row {
+ min-width: 300px !important;
+}
+
+@media screen and ( max-width: 600px ) {
+ .grid.ext-translate-container .tux-messagelist .tux-list-message {
+ width: 100%;
+ }
+
+ .tux-list-status,
+ .tux-list-edit {
+ display: none;
+ }
+}
+
+.tux-message {
+ height: auto;
+ cursor: pointer;
+}
+
+/* The "block" views of page mode and proofreading mode have 0 margin on
+ * .tux-message. To make the actual editor be of same width, set 0 margin on
+ * the open editor (overriding the -5px set by the grid) */
+.grid .tux-message.open {
+ margin: 0 auto;
+}
+
+.tux-message-item {
+ line-height: 50px;
+ height: 50px;
+ overflow: hidden;
+ margin-right: 5px !important;
+ margin-left: 5px !important;
+ vertical-align: middle;
+ border-bottom: 1px solid #c9c9c9;
+ background: #fff;
+}
+
+.tux-message-item.translated,
+.tux-message-item.translated:hover,
+.tux-message-item.proofread,
+.tux-message-item.proofread:hover {
+ background-color: #f0f0f0;
+}
+
+.tux-message-item:hover {
+ background-color: #f8f8f8;
+}
+
+.tux-list-status span,
+.tux-list-edit {
+ padding: 5px;
+ /* 15px space for icon */
+ padding-left: 20px;
+ /* Do not combine these two, unless you also fix the
+ * tux-status-* styles below. That includes you, Siebrand ;)
+ */
+ background-position: left;
+ background-repeat: no-repeat;
+}
+
+.tux-info {
+ background-color: #f0f0f0;
+}
+
+.tux-list-source {
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ unicode-bidi: -webkit-isolate;
+ unicode-bidi: -moz-isolate;
+ unicode-bidi: isolate;
+}
+
+.tux-list-translation {
+ color: #54595d;
+ white-space: nowrap;
+ padding-left: 5px;
+ text-overflow: ellipsis;
+ unicode-bidi: -webkit-isolate;
+ unicode-bidi: -moz-isolate;
+ unicode-bidi: isolate;
+}
+
+.tux-list-message {
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.tux-status-unsaved {
+ .background-image( '../images/label-pen.svg' );
+}
+
+.tux-status-translated,
+.tux-status-proofread {
+ .background-image( '../images/label-tick.svg' );
+}
+
+.tux-status-fuzzy {
+ .background-image( '../images/label-clock.svg' );
+}
+
+.tux-list-edit a {
+ .background-image( '../images/action-edit.svg' );
+ background-position: left center;
+ background-repeat: no-repeat;
+ background-size: 18px 18px;
+ padding-left: 19px;
+}
+
+.tux-messagetable-loader {
+ color: #54595d;
+ padding: 15px;
+ top: 0;
+ background: #f0f0f0 16px 50%;
+ box-shadow: 0 1px 4px rgba( 0, 0, 0, 0.3 ), 0 0 20px rgba( 0, 0, 0, 0.1 ) inset;
+}
+
+.tux-messagetable-loader-info {
+ padding-left: 46px;
+ font-size: 25px;
+}
+
+.tux-action-bar {
+ background-color: #f0f0f0;
+ color: #222;
+ box-shadow: 0 2px 6px rgba( 0, 0, 0, 0.3 );
+ transition: width 250ms;
+}
+
+@media screen and ( min-height: 500px ) {
+ .tux-action-bar.floating {
+ border-top: 1px solid #ddd;
+ position: fixed;
+ bottom: 0;
+ z-index: 200;
+ }
+}
+
+.tux-action-bar .tux-statsbar {
+ position: relative;
+ top: 30px;
+}
+
+.tux-action-bar .tux-view-switcher {
+ padding: 0 5px;
+}
+
+.tux-action-bar button {
+ min-height: 40px;
+ font-size: 14px;
+ margin: 5px 0;
+ cursor: pointer;
+ background-color: #e6e6e6;
+ font-weight: bold;
+ line-height: 1;
+ background-image: linear-gradient( #f0f0f0, #e6e6e6 );
+ border: 1px #c9c9c9 solid;
+}
+
+.tux-action-bar button:hover {
+ background-color: #f0f0f0;
+ background-image: linear-gradient( #f8f8f8, #f0f0f0 );
+}
+
+.tux-action-bar button:active,
+.tux-action-bar button.down {
+ background: #222;
+ color: #fff;
+}
+
+.tux-action-bar button.disabled,
+.tux-action-bar button.disabled:hover {
+ color: #c9c9c9;
+ cursor: default;
+ background-color: #f0f0f0;
+ border-color: #e3e3e3;
+}
+
+.tux-view-switcher button {
+ padding: 0 2px 0 0;
+}
+
+.tux-view-switcher button:first-child {
+ border-radius: 3px 0 0 3px;
+ border-right: 0;
+}
+
+.tux-view-switcher button:last-child {
+ border-radius: 0 3px 3px 0;
+ border-left: 0;
+}
+
+.tux-view-switcher button:before {
+ content: '';
+ height: 15px;
+ width: 25px;
+ display: inline-block;
+ vertical-align: bottom;
+}
+
+.tux-action-bar .translate-mode-button {
+ width: 30%;
+}
+
+.tux-action-bar .translate-mode-button:before {
+ .background-image( '../images/view-list.svg' );
+}
+
+.tux-action-bar .translate-mode-button.down:before {
+ .background-image( '../images/view-list-hi.svg' );
+}
+
+.tux-action-bar .page-mode-button {
+ width: 30%;
+}
+
+.tux-action-bar .page-mode-button:before {
+ .background-image( '../images/view-page.svg' );
+}
+
+.tux-action-bar .page-mode-button.down:before {
+ .background-image( '../images/view-page-hi.svg' );
+}
+
+.tux-action-bar .proofread-mode-button {
+ width: 36%;
+}
+
+.tux-action-bar .proofread-mode-button:before {
+ .background-image( '../images/view-proofread.svg' );
+}
+
+.tux-action-bar .proofread-mode-button.down:before {
+ .background-image( '../images/view-proofread-hi.svg' );
+}
+
+.tux-message-filter-result {
+ color: #222;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ border-bottom: 1px solid #c9c9c9;
+ background: #fff5aa;
+ padding: 5px;
+
+ & button {
+ background: inherit;
+ }
+}
+
+.tux-empty-list {
+ padding: 20px;
+}
+
+.tux-empty-list-header {
+ font-size: 25px;
+ padding: 5px 0;
+}
+
+.tux-empty-list-guide {
+ color: #54595d;
+ font-size: 15px;
+ padding: 5px 0;
+}
+
+.tux-empty-list-actions {
+ font-size: 15px;
+ padding: 8px 0;
+}
+
+.tux-empty-list-actions a {
+ cursor: pointer;
+ margin: 0 10px;
+}
diff --git a/MLEB/Translate/resources/css/ext.translate.special.pagepreparation.css b/MLEB/Translate/resources/css/ext.translate.special.pagepreparation.css
new file mode 100644
index 00000000..d68d6072
--- /dev/null
+++ b/MLEB/Translate/resources/css/ext.translate.special.pagepreparation.css
@@ -0,0 +1,11 @@
+#page {
+ width: 35%;
+}
+
+.client-nojs .grid {
+ display: none;
+}
+
+.client-js .tux-nojs {
+ display: none;
+}
diff --git a/MLEB/Translate/resources/css/ext.translate.tag.languages.css b/MLEB/Translate/resources/css/ext.translate.tag.languages.css
new file mode 100644
index 00000000..36139aae
--- /dev/null
+++ b/MLEB/Translate/resources/css/ext.translate.tag.languages.css
@@ -0,0 +1,73 @@
+.mw-pt-languages {
+ display: table;
+ border: 1px solid #a2a9b1;
+ box-sizing: border-box;
+ background: #f8f9fa none repeat scroll 0 0;
+ border-collapse: collapse;
+ line-height: 1.2;
+ width: 100%;
+ clear: both;
+ overflow: auto;
+}
+
+.mw-pt-languages-label {
+ display: table-cell;
+ border-right: 1px solid #a2a9b1;
+ padding: 0.5em;
+ background: #eaecf0 none repeat scroll 0 0;
+ font-weight: bold;
+ width: 15%;
+}
+
+.mw-pt-languages-list {
+ padding-left: 0.5em;
+ padding-bottom: 0.7em;
+ display: table-cell;
+ width: 80%;
+}
+
+.mw-pt-languages-list a {
+ white-space: nowrap;
+}
+
+.mw-pt-languages-selected,
+.mw-pt-languages-ui {
+ font-weight: bold;
+}
+
+.mw-pt-progress {
+ padding-right: 11px;
+ background: transparent right center no-repeat;
+ background-size: 9px 9px;
+}
+
+/* Need very high specificity to override skin styles in the sidebar */
+#mw-panel .portal .body .mw-pt-progress--none a,
+.interwiki-x-pagetranslation.mw-pt-progress--none a {
+ color: #ba0000;
+}
+
+#mw-panel .portal .body .mw-pt-progress--none a:visited,
+.interwiki-x-pagetranslation.mw-pt-progress--none a:visited {
+ color: #a55858;
+}
+
+.mw-pt-progress--stub {
+ background-image: url( ../images/prog-1.png );
+}
+
+.mw-pt-progress--low {
+ background-image: url( ../images/prog-2.png );
+}
+
+.mw-pt-progress--med {
+ background-image: url( ../images/prog-3.png );
+}
+
+.mw-pt-progress--high {
+ background-image: url( ../images/prog-4.png );
+}
+
+.mw-pt-progress--complete {
+ background-image: url( ../images/prog-5.png );
+}
diff --git a/MLEB/Translate/resources/js/ext.translate.recentgroups.js b/MLEB/Translate/resources/js/ext.translate.recentgroups.js
new file mode 100644
index 00000000..678916a2
--- /dev/null
+++ b/MLEB/Translate/resources/js/ext.translate.recentgroups.js
@@ -0,0 +1,31 @@
+( function ( $, mw ) {
+ 'use strict';
+
+ mw.translate = mw.translate || {};
+
+ /**
+ * Simple wrapper for storing recent groups for an user.
+ *
+ * @class mw.translate.recentGroups
+ * @singleton
+ * @since 2016.03
+ */
+
+ mw.translate.recentGroups = {
+ get: function () {
+ return JSON.parse( mw.storage.get( 'translate-recentgroups' ) ) || [];
+ },
+
+ append: function ( value ) {
+ var items = this.get();
+
+ items.unshift( value );
+ items = items.filter( function ( item, index, array ) {
+ return array.indexOf( item ) === index;
+ } );
+ items = items.slice( 0, 5 );
+
+ mw.storage.set( 'translate-recentgroups', JSON.stringify( items ) );
+ }
+ };
+}( jQuery, mediaWiki ) );
diff --git a/MLEB/Translate/resources/js/ext.translate.special.operatorsuggest.js b/MLEB/Translate/resources/js/ext.translate.special.operatorsuggest.js
new file mode 100644
index 00000000..362a259e
--- /dev/null
+++ b/MLEB/Translate/resources/js/ext.translate.special.operatorsuggest.js
@@ -0,0 +1,39 @@
+/*
+ * Autocomplete search operators.
+ */
+( function ( mw, $ ) {
+ 'use strict';
+
+ function autocompleteOperators( request, response ) {
+ var operators = [ 'language:', 'group:', 'filter:' ],
+ result = [],
+ lastterm = request.term.split( ' ' ).pop();
+
+ $.each( operators, function ( index, value ) {
+ var pos = value.indexOf( lastterm );
+ if ( pos === 0 ) {
+ result.push( value );
+ }
+ } );
+ response( result );
+ }
+
+ $( '.tux-searchpage .searchinputbox' )
+ .autocomplete( {
+ source: autocompleteOperators,
+ select: function ( event, ui ) {
+ var $value = $( this ).val(),
+ operators = $value.split( ' ' );
+
+ operators.pop();
+ operators.push( ui.item.value );
+
+ $( this ).val( operators.join( ' ' ) );
+ return false;
+ },
+
+ focus: function ( event ) {
+ event.preventDefault();
+ }
+ } );
+}( mediaWiki, jQuery ) );
diff --git a/MLEB/Translate/scripts/TranslateCliLogger.php b/MLEB/Translate/scripts/TranslateCliLogger.php
new file mode 100644
index 00000000..6fb8e85f
--- /dev/null
+++ b/MLEB/Translate/scripts/TranslateCliLogger.php
@@ -0,0 +1,20 @@
+<?php
+/**
+ * Simple helper to log things to the console.
+ *
+ * @author Niklas Laxström
+ * @license GPL-2.0-or-later
+ * @file
+ */
+
+use Psr\Log\AbstractLogger;
+
+class TranslateCliLogger extends AbstractLogger {
+ public function __construct( callable $logger ) {
+ $this->logger = $logger;
+ }
+
+ public function log( $level, $msg, array $context = [] ) {
+ ( $this->logger )( "LOG $level: $msg" );
+ }
+}
diff --git a/MLEB/Translate/scripts/expand-groupspec.php b/MLEB/Translate/scripts/expand-groupspec.php
new file mode 100644
index 00000000..57f0b365
--- /dev/null
+++ b/MLEB/Translate/scripts/expand-groupspec.php
@@ -0,0 +1,58 @@
+<?php
+/**
+ * Script that expands a message group specification (such as page-News*,page-Help*).
+ *
+ * @license GPL-2.0-or-later
+ * @file
+ */
+
+// Standard boilerplate to define $IP
+if ( getenv( 'MW_INSTALL_PATH' ) !== false ) {
+ $IP = getenv( 'MW_INSTALL_PATH' );
+} else {
+ $dir = __DIR__;
+ $IP = "$dir/../../..";
+}
+require_once "$IP/maintenance/Maintenance.php";
+
+class TranslateExpandGroupSpec extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = 'Expands a message group specification.';
+ $this->addOption(
+ 'exportable',
+ 'List only groups that can be exported',
+ false, /*required*/
+ false /*has arg*/
+ );
+
+ $this->addArg(
+ 'specification',
+ 'For example page-*,main',
+ true, /*required*/
+ false /*has arg*/
+ );
+ }
+
+ public function execute() {
+ $spec = $this->getArg( 0 );
+ $patterns = explode( ',', trim( $spec ) );
+ $ids = MessageGroups::expandWildcards( $patterns );
+
+ if ( $this->getOption( 'exportable' ) ) {
+ foreach ( $ids as $index => $id ) {
+ if ( !MessageGroups::getGroup( $id ) instanceof FileBasedMessageGroup ) {
+ unset( $ids[ $index ] );
+ }
+ }
+ }
+
+ if ( $ids !== [] ) {
+ // This should not be affected by --quiet
+ echo implode( "\n", $ids ) . "\n";
+ }
+ }
+}
+
+$maintClass = 'TranslateExpandGroupSpec';
+require_once RUN_MAINTENANCE_IF_MAIN;
diff --git a/MLEB/Translate/scripts/test-mt.php b/MLEB/Translate/scripts/test-mt.php
new file mode 100644
index 00000000..429cab6b
--- /dev/null
+++ b/MLEB/Translate/scripts/test-mt.php
@@ -0,0 +1,92 @@
+<?php
+/**
+ * Script to test web services from the command line
+ *
+ * @author Niklas Laxström
+ * @license GPL-2.0-or-later
+ * @file
+ */
+
+// Standard boilerplate to define $IP
+if ( getenv( 'MW_INSTALL_PATH' ) !== false ) {
+ $IP = getenv( 'MW_INSTALL_PATH' );
+} else {
+ $dir = __DIR__;
+ $IP = "$dir/../../..";
+}
+require_once "$IP/maintenance/Maintenance.php";
+
+class TestMT extends Maintenance {
+ public function __construct() {
+ parent::__construct();
+ $this->mDescription = 'Test webservices.';
+
+ $this->addOption(
+ 'service',
+ 'Which service to use',
+ true, /*required*/
+ true /*has arg*/
+ );
+
+ $this->addOption(
+ 'from',
+ 'Source language tag',
+ true, /*required*/
+ true /*has arg*/
+ );
+
+ $this->addOption(
+ 'to',
+ 'Target language tag',
+ true, /*required*/
+ true /*has arg*/
+ );
+
+ $this->addArg(
+ 'text',
+ 'Text to translate',
+ true /*required*/
+ );
+ }
+
+ public function execute() {
+ global $wgTranslateTranslationServices;
+
+ $name = $this->getOption( 'service' );
+
+ if ( !isset( $wgTranslateTranslationServices[ $name ] ) ) {
+ $this->error( "Unknown service.\n", 1 );
+ }
+
+ $service = TranslationWebService::factory( $name, $wgTranslateTranslationServices[ $name ] );
+ $service->setLogger( new TranslateCliLogger( function ( $msg ) {
+ $this->output( "$msg\n" );
+ } ) );
+
+ $from = $this->getOption( 'from' );
+ $to = $this->getOption( 'to' );
+ $text = $this->getArg( 0 );
+
+ if ( !$service->isSupportedLanguagePair( $from, $to ) ) {
+ $this->error( "Unsupported language pair.\n", 1 );
+ }
+
+ $query = $service->getQueries( $text, $from, $to );
+ if ( $query === [] ) {
+ $this->error( "Service query error.\n", 1 );
+ }
+
+ $agg = new QueryAggregator();
+ $id = $agg->addQuery( $query[ 0 ] );
+ $agg->run();
+ $res = $agg->getResponse( $id );
+ if ( $res === null ) {
+ $this->error( "Service response error.\n", 1 );
+ }
+
+ $this->output( $service->getResultData( $res ), 1 );
+ }
+}
+
+$maintClass = 'TestMT';
+require_once RUN_MAINTENANCE_IF_MAIN;
diff --git a/MLEB/Translate/specials/SpecialExportTranslations.php b/MLEB/Translate/specials/SpecialExportTranslations.php
new file mode 100644
index 00000000..8181eee0
--- /dev/null
+++ b/MLEB/Translate/specials/SpecialExportTranslations.php
@@ -0,0 +1,263 @@
+<?php
+/**
+ * @license GPL-2.0-or-later
+ * @ingroup SpecialPage TranslateSpecialPage
+ */
+class SpecialExportTranslations extends SpecialPage {
+ /** @var string */
+ protected $language;
+
+ /** @var string */
+ protected $format;
+
+ /** @var string */
+ protected $groupId;
+
+ /** @var string[] */
+ public static $validFormats = [ 'export-as-po', 'export-to-file' ];
+
+ public function __construct() {
+ parent::__construct( 'ExportTranslations' );
+ }
+
+ /**
+ * @param null|string $par
+ */
+ public function execute( $par ) {
+ $out = $this->getOutput();
+ $request = $this->getRequest();
+ $lang = $this->getLanguage();
+
+ $this->setHeaders();
+
+ $this->groupId = $request->getText( 'group', $par );
+ $this->language = $request->getVal( 'language', $lang->getCode() );
+ $this->format = $request->getText( 'format' );
+
+ $this->outputForm();
+
+ if ( $this->groupId ) {
+ $status = $this->checkInput();
+ if ( !$status->isGood() ) {
+ $errors = $out->parse( $status->getWikiText( false, false, $lang ) );
+ $out->addHTML( Html::rawElement( 'div', [ 'class' => 'error' ], $errors ) );
+ return;
+ }
+
+ $this->doExport();
+ }
+ }
+
+ protected function outputForm() {
+ $fields = [
+ 'group' => [
+ 'type' => 'select',
+ 'name' => 'group',
+ 'id' => 'group',
+ 'label-message' => 'translate-page-group',
+ 'options' => $this->getGroupOptions(),
+ 'default' => $this->groupId,
+ ],
+ 'language' => [
+ // @todo Apply ULS to this field
+ 'type' => 'select',
+ 'name' => 'language',
+ 'id' => 'language',
+ 'label-message' => 'translate-page-language',
+ 'options' => $this->getLanguageOptions(),
+ 'default' => $this->language,
+ ],
+ 'format' => [
+ 'type' => 'radio',
+ 'name' => 'format',
+ 'id' => 'format',
+ 'label-message' => 'translate-export-form-format',
+ 'flatlist' => true,
+ 'options' => $this->getFormatOptions(),
+ 'default' => $this->format,
+ ],
+ ];
+ $form = HTMLForm::factory( 'ooui', $fields, $this->getContext() );
+ $form
+ ->setMethod( 'get' )
+ ->setWrapperLegendMsg( 'translate-page-settings-legend' )
+ ->setSubmitTextMsg( 'translate-submit' )
+ ->prepareForm()
+ ->displayForm( false );
+ }
+
+ /**
+ * @return array
+ */
+ protected function getGroupOptions() {
+ $selected = $this->groupId;
+ $groups = MessageGroups::getAllGroups();
+ uasort( $groups, [ 'MessageGroups', 'groupLabelSort' ] );
+
+ $options = [];
+ foreach ( $groups as $id => $group ) {
+ if ( !$group->exists()
+ || ( MessageGroups::getPriority( $group ) === 'discouraged' && $id !== $selected )
+ ) {
+ continue;
+ }
+
+ $options[$group->getLabel()] = $id;
+ }
+
+ return $options;
+ }
+
+ /**
+ * @return array
+ */
+ protected function getLanguageOptions() {
+ $languages = TranslateUtils::getLanguageNames( 'en' );
+ $options = [];
+ foreach ( $languages as $code => $name ) {
+ $options["$code - $name"] = $code;
+ }
+
+ return $options;
+ }
+
+ /**
+ * @return array
+ */
+ protected function getFormatOptions() {
+ $options = [];
+ foreach ( self::$validFormats as $format ) {
+ // translate-taskui-export-to-file, translate-taskui-export-as-po
+ $options[ $this->msg( "translate-taskui-$format" )->escaped() ] = $format;
+ }
+ return $options;
+ }
+
+ /**
+ * @return Status
+ */
+ protected function checkInput() {
+ $status = Status::newGood();
+
+ $msgGroup = MessageGroups::getGroup( $this->groupId );
+ if ( $msgGroup === null ) {
+ $status->fatal( 'translate-page-no-such-group' );
+ } elseif ( MessageGroups::isDynamic( $msgGroup ) ) {
+ $status->fatal( 'translate-export-not-supported' );
+ }
+
+ $langNames = TranslateUtils::getLanguageNames( 'en' );
+ if ( !isset( $langNames[$this->language] ) ) {
+ $status->fatal( 'translate-page-no-such-language' );
+ }
+
+ // Do not show this error if no/invalid format is specified for translatable
+ // page groups as we can show a textarea box containing the translation page text
+ // (however it's not currently supported for other groups).
+ if ( !$msgGroup instanceof WikiPageMessageGroup
+ && !in_array( $this->format, self::$validFormats )
+ ) {
+ $status->fatal( 'translate-export-invalid-format' );
+ }
+
+ if ( $this->format === 'export-to-file'
+ && !$msgGroup instanceof FileBasedMessageGroup
+ ) {
+ $status->fatal( 'translate-export-format-notsupported' );
+ }
+
+ return $status;
+ }
+
+ protected function doExport() {
+ $out = $this->getOutput();
+ $group = MessageGroups::getGroup( $this->groupId );
+ $collection = $this->setupCollection( $group );
+
+ switch ( $this->format ) {
+ case 'export-as-po':
+ $out->disable();
+
+ $ffs = null;
+ if ( $group instanceof FileBasedMessageGroup ) {
+ $ffs = $group->getFFS();
+ }
+
+ if ( !$ffs instanceof GettextFFS ) {
+ $group = FileBasedMessageGroup::newFromMessageGroup( $group );
+ $ffs = new GettextFFS( $group );
+ }
+
+ $ffs->setOfflineMode( true );
+
+ $filename = "{$group->getId()}_{$this->language}.po";
+ $this->sendExportHeaders( $filename );
+
+ echo $ffs->writeIntoVariable( $collection );
+ break;
+
+ case 'export-to-file':
+ $out->disable();
+
+ $filename = basename( $group->getSourceFilePath( $collection->getLanguage() ) );
+ $this->sendExportHeaders( $filename );
+
+ echo $group->getFFS()->writeIntoVariable( $collection );
+ break;
+
+ default:
+ // @todo Add web viewing for groups other than WikiPageMessageGroup
+ $pageTranslation = $this->getConfig()->get( 'EnablePageTranslation' );
+ if ( $pageTranslation && $group instanceof WikiPageMessageGroup ) {
+ $collection->loadTranslations();
+ $page = TranslatablePage::newFromTitle( $group->getTitle() );
+ $text = $page->getParse()->getTranslationPageText( $collection );
+ $displayTitle = $page->getPageDisplayTitle( $this->language );
+ if ( $displayTitle ) {
+ $text = "{{DISPLAYTITLE:$displayTitle}}$text";
+ }
+ $box = Html::element(
+ 'textarea',
+ [ 'id' => 'wpTextbox', 'rows' => 40, ],
+ $text
+ );
+ $out->addHTML( $box );
+ return;
+ }
+
+ // This should have been prevented at validation. See checkInput().
+ throw new Exception( 'Unexpected export format.' );
+ }
+ }
+
+ private function setupCollection( MessageGroup $group ) {
+ $collection = $group->initCollection( $this->language );
+
+ // Don't export ignored, unless it is the source language or message documentation
+ $translateDocCode = $this->getConfig()->get( 'TranslateDocumentationLanguageCode' );
+ if ( $this->language !== $translateDocCode
+ && $this->language !== $group->getSourceLanguage()
+ ) {
+ $collection->filter( 'ignored' );
+ }
+
+ $collection->loadTranslations();
+
+ return $collection;
+ }
+
+ /**
+ * Send the appropriate response headers for the export
+ *
+ * @param string $fileName
+ */
+ protected function sendExportHeaders( $fileName ) {
+ $response = $this->getRequest()->response();
+ $response->header( 'Content-Type: text/plain; charset=UTF-8' );
+ $response->header( "Content-Disposition: attachment; filename=\"$fileName\"" );
+ }
+
+ protected function getGroupName() {
+ return 'wiki';
+ }
+}
diff --git a/MLEB/Translate/sql/translate_reviews-patch-01-primary-key.sql b/MLEB/Translate/sql/translate_reviews-patch-01-primary-key.sql
new file mode 100644
index 00000000..9017fb53
--- /dev/null
+++ b/MLEB/Translate/sql/translate_reviews-patch-01-primary-key.sql
@@ -0,0 +1,3 @@
+ALTER TABLE /*_*/translate_reviews
+ ADD PRIMARY KEY (trr_page, trr_revision, trr_user),
+ DROP INDEX /*i*/trr_user_page_revision;
diff --git a/MLEB/Translate/tag/SpecialPageMigration.php b/MLEB/Translate/tag/SpecialPageMigration.php
new file mode 100644
index 00000000..e339f4f1
--- /dev/null
+++ b/MLEB/Translate/tag/SpecialPageMigration.php
@@ -0,0 +1,80 @@
+<?php
+/**
+ * Contains code for special page Special:PageMigration
+ *
+ * @file
+ * @author Pratik Lahoti
+ * @copyright Copyright © 2014-2015 Pratik Lahoti
+ * @license GPL-2.0-or-later
+ */
+
+class SpecialPageMigration extends SpecialPage {
+ public function __construct() {
+ parent::__construct( 'PageMigration', 'pagetranslation' );
+ }
+
+ protected function getGroupName() {
+ return 'wiki';
+ }
+
+ function getDescription() {
+ return $this->msg( 'pagemigration' )->text();
+ }
+
+ public function execute( $par ) {
+ $request = $this->getRequest();
+ $output = $this->getOutput();
+ $this->setHeaders();
+ $this->checkPermissions();
+ $this->outputHeader( 'pagemigration-summary' );
+ $output->addModules( 'ext.translate.special.pagemigration' );
+ $output->addModuleStyles( [
+ 'ext.translate.special.pagemigration.styles',
+ 'jquery.uls.grid'
+ ] );
+ # Get request data from, e.g.
+ $param = $request->getText( 'param' );
+ # Do stuff
+ # ...
+ $out = '';
+ $out .= Html::openElement( 'div', [ 'class' => 'grid' ] );
+ $out .= Html::openElement( 'div', [ 'class' => 'mw-tpm-sp-error row',
+ 'id' => 'mw-tpm-sp-error-div' ] );
+ $out .= Html::element( 'div',
+ [ 'class' => 'mw-tpm-sp-error__message five columns hide' ] );
+ $out .= Html::closeElement( 'div' );
+ $out .= Html::openElement( 'form', [ 'class' => 'mw-tpm-sp-form row',
+ 'id' => 'mw-tpm-sp-primary-form' ] );
+ $out .= Html::element( 'input', [ 'id' => 'pm-summary', 'type' => 'hidden',
+ 'value' => $this->msg( 'pm-summary-import' )->inContentLanguage()->text() ] );
+ $out .= "\n";
+ $out .= Html::element( 'input', [ 'id' => 'title', 'class' => 'mw-searchInput mw-ui-input',
+ 'placeholder' => $this->msg( 'pm-pagetitle-placeholder' )->text() ] );
+ $out .= "\n";
+ $out .= Html::element( 'input', [ 'id' => 'action-import',
+ 'class' => 'mw-ui-button mw-ui-progressive', 'type' => 'button',
+ 'value' => $this->msg( 'pm-import-button-label' )->text() ] );
+ $out .= "\n";
+ $out .= Html::element( 'input', [ 'id' => 'action-save',
+ 'class' => 'mw-ui-button mw-ui-progressive hide', 'type' => 'button',
+ 'value' => $this->msg( 'pm-savepages-button-label' )->text() ] );
+ $out .= "\n";
+ $out .= Html::element( 'input', [ 'id' => 'action-cancel',
+ 'class' => 'mw-ui-button mw-ui-quiet hide', 'type' => 'button',
+ 'value' => $this->msg( 'pm-cancel-button-label' )->text() ] );
+ $out .= Html::closeElement( 'form' );
+ $out .= Html::element( 'div', [ 'class' => 'mw-tpm-sp-instructions hide' ] );
+ $out .= Html::openElement( 'div', [ 'class' => 'mw-tpm-sp-unit-listing' ] );
+ $out .= Html::closeElement( 'div' );
+ $out .= Html::closeElement( 'div' );
+
+ $output->addHTML( $out );
+
+ $nojs = Html::element(
+ 'div',
+ [ 'class' => 'tux-nojs errorbox' ],
+ $this->msg( 'tux-nojs' )->plain()
+ );
+ $output->addHTML( $nojs );
+ }
+}
diff --git a/MLEB/Translate/tag/SpecialPagePreparation.php b/MLEB/Translate/tag/SpecialPagePreparation.php
new file mode 100644
index 00000000..cd854e06
--- /dev/null
+++ b/MLEB/Translate/tag/SpecialPagePreparation.php
@@ -0,0 +1,71 @@
+<?php
+/**
+ * Contains code for special page Special:PagePreparation
+ *
+ * @file
+ * @author Pratik Lahoti
+ * @copyright Copyright © 2014 Pratik Lahoti
+ * @license GPL-2.0-or-later
+ */
+
+class SpecialPagePreparation extends SpecialPage {
+ public function __construct() {
+ parent::__construct( 'PagePreparation', 'pagetranslation' );
+ }
+
+ protected function getGroupName() {
+ return 'wiki';
+ }
+
+ public function execute( $par ) {
+ $request = $this->getRequest();
+ $output = $this->getOutput();
+ $this->setHeaders();
+ $this->checkPermissions();
+
+ $inputValue = htmlspecialchars( $request->getText( 'page', $par ) );
+ $pagenamePlaceholder = $this->msg( 'pp-pagename-placeholder' )->escaped();
+ $prepareButtonValue = $this->msg( 'pp-prepare-button-label' )->escaped();
+ $saveButtonValue = $this->msg( 'pp-save-button-label' )->escaped();
+ $cancelButtonValue = $this->msg( 'pp-cancel-button-label' )->escaped();
+ $summaryValue = $this->msg( 'pp-save-summary' )->inContentLanguage()->escaped();
+ $output->addModules( 'ext.translate.special.pagepreparation' );
+ $output->addModuleStyles( [
+ 'ext.translate.special.pagepreparation.styles',
+ 'jquery.uls.grid'
+ ] );
+
+ $out = '';
+ $diff = new DifferenceEngine( $this->getContext() );
+ $diffHeader = $diff->addHeader( ' ', $this->msg( 'pp-diff-old-header' )->escaped(),
+ $this->msg( 'pp-diff-new-header' )->escaped() );
+
+ $out = <<<HTML
+<div class="grid">
+ <form class="mw-tpp-sp-form row" name="mw-tpp-sp-input-form" action="">
+ <input id="pp-summary" type="hidden" value="{$summaryValue}" />
+ <input name="page" id="page" class="mw-searchInput mw-ui-input"
+ placeholder="{$pagenamePlaceholder}" value="{$inputValue}"/>
+ <button id="action-prepare" class="mw-ui-button mw-ui-progressive" type="button">
+ {$prepareButtonValue}</button>
+ <button id="action-save" class="mw-ui-button mw-ui-progressive hide" type="button">
+ {$saveButtonValue}</button>
+ <button id="action-cancel" class="mw-ui-button mw-ui-quiet hide" type="button">
+ {$cancelButtonValue}</button>
+ </form>
+ <div class="messageDiv hide"></div>
+ <div class="divDiff hide">
+ {$diffHeader}
+ </div>
+</div>
+HTML;
+ $output->addHTML( $out );
+
+ $nojs = Html::element(
+ 'div',
+ [ 'class' => 'tux-nojs errorbox' ],
+ $this->msg( 'tux-nojs' )->plain()
+ );
+ $output->addHTML( $nojs );
+ }
+}
diff --git a/MLEB/Translate/tag/TranslatablePageMoveJob.php b/MLEB/Translate/tag/TranslatablePageMoveJob.php
new file mode 100644
index 00000000..47f71d48
--- /dev/null
+++ b/MLEB/Translate/tag/TranslatablePageMoveJob.php
@@ -0,0 +1,170 @@
+<?php
+/**
+ * Contains class with job for moving translation pages.
+ *
+ * @file
+ * @author Niklas Laxström
+ * @license GPL-2.0-or-later
+ */
+
+/**
+ * Contains class with job for moving translation pages. Used together with
+ * SpecialPageTranslationMovePage class.
+ *
+ * @ingroup PageTranslation JobQueue
+ */
+class TranslatablePageMoveJob extends Job {
+
+ /**
+ * @param Title $source
+ * @param Title $target
+ * @param array $moves should include base-source and base-target
+ * @param string $summary
+ * @param User $performer
+ * @return TranslateMoveJob
+ */
+ public static function newJob(
+ Title $source, Title $target, array $moves, $summary, User $performer
+ ) {
+ $params = [
+ 'source' => $source->getPrefixedText(),
+ 'target' => $target->getPrefixedText(),
+ 'moves' => $moves,
+ 'summary' => $summary,
+ 'performer' => $performer->getName(),
+ ];
+
+ $self = new self( $target, $params );
+ $self->lock( array_keys( $moves ) );
+ $self->lock( array_values( $moves ) );
+
+ return $self;
+ }
+
+ public function __construct( $title, $params = [] ) {
+ parent::__construct( __CLASS__, $title, $params );
+ $this->params = $params;
+ }
+
+ public function run() {
+ $sourceTitle = Title::newFromText( $this->params['source'] );
+ $targetTitle = Title::newFromText( $this->params['target'] );
+ $sourcePage = TranslatablePage::newFromTitle( $sourceTitle );
+ $targetPage = TranslatablePage::newFromTitle( $targetTitle );
+
+ $this->doMoves();
+
+ $this->moveMetadata(
+ $sourcePage->getMessageGroupId(),
+ $targetPage->getMessageGroupId()
+ );
+
+ $entry = new ManualLogEntry( 'pagetranslation', 'moveok' );
+ $entry->setPerformer( User::newFromName( $this->params['performer'] ) );
+ $entry->setParameters( [ 'target' => $this->params['target'] ] );
+ $entry->setTarget( $sourceTitle );
+ $logid = $entry->insert();
+ $entry->publish( $logid );
+
+ // Re-render the pages to get everything in sync
+ MessageGroups::singleton()->recache();
+ // Update message index now so that, when after this job the MoveTranslationUnits hook
+ // runs in deferred updates, it will not run MessageIndexRebuildJob (T175834).
+ MessageIndex::singleton()->rebuild();
+
+ $job = TranslationsUpdateJob::newFromPage( $targetPage );
+ JobQueueGroup::singleton()->push( $job );
+
+ return true;
+ }
+
+ protected function doMoves() {
+ $fuzzybot = FuzzyBot::getUser();
+ $performer = User::newFromName( $this->params['performer'] );
+
+ PageTranslationHooks::$allowTargetEdit = true;
+
+ foreach ( $this->params['moves'] as $source => $target ) {
+ $sourceTitle = Title::newFromText( $source );
+ $targetTitle = Title::newFromText( $target );
+
+ if ( $source === $this->params['source'] ) {
+ $user = $performer;
+ } else {
+ $user = $fuzzybot;
+ }
+
+ $mover = new MovePage( $sourceTitle, $targetTitle );
+ $status = $mover->move( $user, $this->params['summary'], false );
+ if ( !$status->isOK() ) {
+ $entry = new ManualLogEntry( 'pagetranslation', 'movenok' );
+ $entry->setPerformer( $performer );
+ $entry->setTarget( $sourceTitle );
+ $entry->setParameters( [
+ 'target' => $target,
+ 'error' => $status->getErrorsArray(),
+ ] );
+ $logid = $entry->insert();
+ $entry->publish( $logid );
+ }
+
+ $this->unlock( [ $source, $target ] );
+ }
+
+ PageTranslationHooks::$allowTargetEdit = false;
+ }
+
+ protected function moveMetadata( $oldGroupId, $newGroupId ) {
+ $types = [ 'prioritylangs', 'priorityforce', 'priorityreason' ];
+
+ foreach ( $types as $type ) {
+ $value = TranslateMetadata::get( $oldGroupId, $type );
+ if ( $value !== false ) {
+ TranslateMetadata::set( $oldGroupId, $type, false );
+ TranslateMetadata::set( $newGroupId, $type, $value );
+ }
+ }
+
+ // Make the changes in aggregate groups metadata, if present in any of them.
+ $groups = MessageGroups::getAllGroups();
+ foreach ( $groups as $group ) {
+ if ( !$group instanceof AggregateMessageGroup ) {
+ continue;
+ }
+
+ $subgroups = TranslateMetadata::get( $group->getId(), 'subgroups' );
+ if ( $subgroups === false ) {
+ continue;
+ }
+
+ $subgroups = explode( ',', $subgroups );
+ $subgroups = array_flip( $subgroups );
+ if ( isset( $subgroups[$oldGroupId] ) ) {
+ $subgroups[$newGroupId] = $subgroups[$oldGroupId];
+ unset( $subgroups[$oldGroupId] );
+ $subgroups = array_flip( $subgroups );
+ TranslateMetadata::set(
+ $group->getId(),
+ 'subgroups',
+ implode( ',', $subgroups )
+ );
+ }
+ }
+ }
+
+ private function lock( array $titles ) {
+ $cache = wfGetCache( CACHE_ANYTHING );
+ $data = [];
+ foreach ( $titles as $title ) {
+ $data[wfMemcKey( 'pt-lock', sha1( $title ) )] = 'locked';
+ }
+ $cache->setMulti( $data );
+ }
+
+ private function unlock( array $titles ) {
+ $cache = wfGetCache( CACHE_ANYTHING );
+ foreach ( $titles as $title ) {
+ $cache->delete( wfMemcKey( 'pt-lock', sha1( $title ) ) );
+ }
+ }
+}
diff --git a/MLEB/Translate/tag/TranslationsUpdateJob.php b/MLEB/Translate/tag/TranslationsUpdateJob.php
new file mode 100644
index 00000000..41670995
--- /dev/null
+++ b/MLEB/Translate/tag/TranslationsUpdateJob.php
@@ -0,0 +1,127 @@
+<?php
+/**
+ * Job for updating translation units and translation pages when
+ * a translatable page is marked for translation.
+ *
+ * @note MessageUpdateJobs from getTranslationUnitJobs() should be run
+ * before the TranslateRenderJobs are run so that the latest changes can
+ * take effect on the translation pages.
+ *
+ * @since 2016.03
+ */
+class TranslationsUpdateJob extends Job {
+ /**
+ * @inheritDoc
+ */
+ public function __construct( Title $title, $params = [] ) {
+ parent::__construct( __CLASS__, $title, $params );
+ }
+
+ /**
+ * Create a job that updates a translation page.
+ *
+ * If a list of sections is provided, then the job will also update translation
+ * unit pages.
+ *
+ * @param Title $page
+ * @param TPSection[] $sections
+ * @return TranslationsUpdateJob
+ * @since 2018.07
+ */
+ public static function newFromPage( TranslatablePage $page, array $sections = [] ) {
+ $params = [];
+ $params[ 'sections' ] = [];
+ foreach ( $sections as $section ) {
+ $params[ 'sections' ][] = $section->serializeToArray();
+ }
+
+ return new self( $page->getTitle(), $params );
+ }
+
+ public function run() {
+ $page = TranslatablePage::newFromTitle( $this->title );
+ $sections = $this->params[ 'sections' ];
+ foreach ( $sections as $index => $section ) {
+ // Old jobs stored sections as objects because they were serialized and
+ // unserialized transparently. That is no longer supported, so we
+ // convert manually to primitive types first (to an PHP array).
+ if ( is_array( $section ) ) {
+ $sections[ $index ] = TPSection::unserializeFromArray( $section );
+ }
+ }
+
+ // Units should be updated before the render jobs are run
+ $unitJobs = self::getTranslationUnitJobs( $page, $sections );
+ foreach ( $unitJobs as $job ) {
+ $job->run();
+ }
+
+ // Ensure we are using the latest group definitions. This is needed so
+ // that in long running scripts we do see the page which was just
+ // marked for translation. Otherwise getMessageGroup in the next line
+ // returns null. There is no need to regenerate the global cache.
+ MessageGroups::singleton()->clearProcessCache();
+ // Ensure fresh definitions for MessageIndex and stats
+ $page->getMessageGroup()->clearCaches();
+
+ MessageIndex::singleton()->rebuild();
+
+ // Refresh translations statistics
+ $id = $page->getMessageGroupId();
+ MessageGroupStats::forGroup( $id, MessageGroupStats::FLAG_NO_CACHE );
+
+ $wikiPage = WikiPage::factory( $page->getTitle() );
+ $wikiPage->doPurge();
+
+ $renderJobs = self::getRenderJobs( $page );
+ JobQueueGroup::singleton()->push( $renderJobs );
+ return true;
+ }
+
+ /**
+ * Creates jobs needed to create or update all translation page definitions.
+ * @param TranslatablePage $page
+ * @param TPSection[] $sections
+ * @return Job[]
+ * @since 2013-01-28
+ */
+ public static function getTranslationUnitJobs( TranslatablePage $page, array $sections ) {
+ $jobs = [];
+
+ $code = $page->getSourceLanguageCode();
+ $prefix = $page->getTitle()->getPrefixedText();
+
+ foreach ( $sections as $s ) {
+ $unit = $s->name;
+ $title = Title::makeTitle( NS_TRANSLATIONS, "$prefix/$unit/$code" );
+
+ $fuzzy = $s->type === 'changed';
+ $jobs[] = MessageUpdateJob::newJob( $title, $s->getTextWithVariables(), $fuzzy );
+ }
+
+ return $jobs;
+ }
+
+ /**
+ * Creates jobs needed to create or update all translation pages.
+ * @param TranslatablePage $page
+ * @return Job[]
+ * @since 2013-01-28
+ */
+ public static function getRenderJobs( TranslatablePage $page ) {
+ $jobs = [];
+
+ $jobTitles = $page->getTranslationPages();
+ // $jobTitles may have the source language title already but duplicate TranslateRenderJobs
+ // are not executed so it's not run twice for the source language page present. This is
+ // added to ensure that we create the source language page from the very beginning.
+ $sourceLangTitle = $page->getTitle()->getSubpage( $page->getSourceLanguageCode() );
+ $jobTitles[] = $sourceLangTitle;
+ foreach ( $jobTitles as $t ) {
+ $jobs[] = TranslateRenderJob::newJob( $t );
+ }
+
+ return $jobs;
+ }
+
+}
diff --git a/MLEB/Translate/tests/phpunit/TPSectionTest.php b/MLEB/Translate/tests/phpunit/TPSectionTest.php
new file mode 100644
index 00000000..1624c738
--- /dev/null
+++ b/MLEB/Translate/tests/phpunit/TPSectionTest.php
@@ -0,0 +1,123 @@
+<?php
+/**
+ * Unit tests for class TPSection
+ *
+ * @author Niklas Laxström
+ * @license GPL-2.0-or-later
+ * @file
+ */
+
+/**
+ * Unit tests for class TPSection
+ * @ingroup PageTranslation
+ */
+class TPSectionTest extends PHPUnit\Framework\TestCase {
+ /**
+ * @dataProvider providerTestGetMarkedText
+ */
+ public function testGetMarkedText( $name, $text, $inline, $expected ) {
+ $section = new TPSection();
+ $section->name = $name;
+ $section->text = $text;
+ $section->setIsInline( $inline );
+
+ $output = $section->getMarkedText();
+
+ $this->assertEquals( $expected, $output );
+ }
+
+ /**
+ * @dataProvider providerTestGetTextWithVariables
+ */
+ public function testGetTextWithVariables( $text, $expected ) {
+ $section = new TPSection();
+ $section->text = $text;
+
+ $output = $section->getTextWithVariables();
+
+ $this->assertEquals( $expected, $output );
+ }
+
+ /**
+ * @dataProvider providerTestGetTextForTrans
+ */
+ public function testGetTextForTrans( $text, $expected ) {
+ $section = new TPSection();
+ $section->text = $text;
+
+ $output = $section->getTextForTrans();
+
+ $this->assertEquals( $expected, $output );
+ }
+
+ public static function providerTestGetMarkedText() {
+ $cases = [];
+
+ // Inline syntax
+ $cases[] = [
+ 'name',
+ 'Hello',
+ true,
+ '<!--T:name--> Hello',
+ ];
+
+ // Normal syntax
+ $cases[] = [
+ 'name',
+ 'Hello',
+ false,
+ "<!--T:name-->\nHello",
+ ];
+
+ // Inline should not matter for headings, which have special syntax, but test both values
+ $cases[] = [
+ 'name',
+ '== Hello ==',
+ true,
+ '== Hello == <!--T:name-->',
+ ];
+
+ $cases[] = [
+ 'name',
+ '====== Hello ======',
+ false,
+ '====== Hello ====== <!--T:name-->',
+ ];
+
+ return $cases;
+ }
+
+ public static function providerTestGetTextWithVariables() {
+ $cases = [];
+
+ // syntax
+ $cases[] = [
+ "<tvar|abc>Peter\n cat!</>",
+ '$abc',
+ ];
+
+ $cases[] = [
+ "<tvar|1>Hello</>\n<tvar|2>Hello</>",
+ "$1\n$2",
+ ];
+
+ return $cases;
+ }
+
+ public static function providerTestGetTextForTrans() {
+ $cases = [];
+
+ // syntax
+ $cases[] = [
+ "<tvar|abc>Peter\n cat!</>",
+ "Peter\n cat!",
+ ];
+
+ $cases[] = [
+ "<tvar|1>Hello</>\n<tvar|2>Hello</>",
+ "Hello\nHello",
+ ];
+
+ return $cases;
+ }
+}
diff --git a/MLEB/Translate/tests/phpunit/TTMServerMessageUpdateJobTest.php b/MLEB/Translate/tests/phpunit/TTMServerMessageUpdateJobTest.php
new file mode 100644
index 00000000..7cc478c0
--- /dev/null
+++ b/MLEB/Translate/tests/phpunit/TTMServerMessageUpdateJobTest.php
@@ -0,0 +1,441 @@
+<?php
+/**
+ * Tests for TTMServerMessageUpdateJob
+ *
+ * @file
+ * @author David Causse
+ * @license GPL-2.0-or-later
+ */
+
+/**
+ * Unit test for TTMServerMessageUpdateJob.
+ * Mostly test mirroring and failure modes.
+ */
+class TTMServerMessageUpdateJobTest extends MediaWikiTestCase {
+ /**
+ * @var WritableTTMServer[] used to link our mocks with TestableTTMServer built by the
+ * factory
+ */
+ public static $mockups = [];
+
+ public function setUp() {
+ parent::setUp();
+ self::$mockups = [];
+ $this->setMwGlobals( [
+ 'wgTranslateTranslationServices' => [
+ 'primary' => [
+ 'class' => TestableTTMServer::class,
+ // will be used as the key in static::$mockups to attach the
+ // mock to the newly created TestableTTMServer instance
+ 'name' => 'primary',
+ 'mirrors' => [ 'secondary' ],
+ ],
+ 'secondary' => [
+ 'class' => TestableTTMServer::class,
+ 'name' => 'secondary',
+ ]
+ ],
+ 'wgTranslateTranslationDefaultService' => 'primary'
+ ] );
+ }
+
+ public function tearDown() {
+ parent::tearDown();
+ self::$mockups = [];
+ }
+
+ /**
+ * Normal mode, we ensure that update is called on primary and its mirror without any resent
+ * jobs
+ */
+ public function testReplication() {
+ $mock = $this->getMockBuilder( WritableTTMServer::class )
+ ->getMock();
+ $mock->expects( $this->atLeastOnce() )
+ ->method( 'update' );
+ static::$mockups['primary'] = $mock;
+ $mock = $this->getMockBuilder( WritableTTMServer::class )
+ ->getMock();
+ $mock->expects( $this->atLeastOnce() )
+ ->method( 'update' );
+ static::$mockups['secondary'] = $mock;
+
+ $job = new TestableTTMServerMessageUpdateJob(
+ Title::makeTitle( NS_MAIN, 'Main Page' ),
+ [ 'command' => 'refresh' ],
+ $this->getMockBuilder( MessageHandle::class )
+ ->disableOriginalConstructor()
+ ->getMock()
+ );
+ $job->run();
+ $this->assertEmpty( $job->getResentJobs() );
+ }
+
+ /**
+ * The mirror failed, we ensure that we resend a job
+ * with the appropriate params.
+ */
+ public function testReplicationError() {
+ $mock = $this->getMockBuilder( WritableTTMServer::class )
+ ->getMock();
+ $mock->expects( $this->atLeastOnce() )
+ ->method( 'update' );
+ static::$mockups['primary'] = $mock;
+ $mock = $this->getMockBuilder( WritableTTMServer::class )
+ ->getMock();
+ $mock->expects( $this->atLeastOnce() )
+ ->method( 'update' )
+ ->will( $this->throwException( new TTMServerException ) );
+ static::$mockups['secondary'] = $mock;
+
+ $job = new TestableTTMServerMessageUpdateJob(
+ Title::makeTitle( NS_MAIN, 'Main Page' ),
+ [ 'command' => 'refresh' ],
+ $this->getMockBuilder( MessageHandle::class )
+ ->disableOriginalConstructor()
+ ->getMock()
+ );
+ $job->run();
+ $this->assertEquals( 1, count( $job->getResentJobs() ) );
+ $expectedParams = [
+ 'errorCount' => 1,
+ 'service' => 'secondary',
+ 'command' => 'refresh'
+ ];
+ $actualParams = array_intersect_key(
+ $job->getResentJobs()[0]->getParams(),
+ $expectedParams
+ );
+ $this->assertEquals( $expectedParams, $actualParams );
+ }
+
+ /**
+ * All services failed, we ensure that we resend 2 jobs for
+ * each services
+ */
+ public function testAllServicesInError() {
+ $mock = $this->getMockBuilder( WritableTTMServer::class )
+ ->getMock();
+ $mock->expects( $this->atLeastOnce() )
+ ->method( 'update' )
+ ->will( $this->throwException( new TTMServerException ) );
+ static::$mockups['primary'] = $mock;
+ $mock = $this->getMockBuilder( WritableTTMServer::class )
+ ->getMock();
+ $mock->expects( $this->atLeastOnce() )
+ ->method( 'update' )
+ ->will( $this->throwException( new TTMServerException ) );
+ static::$mockups['secondary'] = $mock;
+
+ $job = new TestableTTMServerMessageUpdateJob(
+ Title::makeTitle( NS_MAIN, 'Main Page' ),
+ [ 'command' => 'refresh' ],
+ $this->getMockBuilder( MessageHandle::class )
+ ->disableOriginalConstructor()
+ ->getMock()
+ );
+ $job->run();
+ $this->assertEquals( 2, count( $job->getResentJobs() ) );
+ $expectedParams = [
+ 'errorCount' => 1,
+ 'service' => 'primary',
+ 'command' => 'refresh'
+ ];
+ $actualParams = array_intersect_key(
+ $job->getResentJobs()[0]->getParams(),
+ $expectedParams
+ );
+ $this->assertEquals( $expectedParams, $actualParams );
+
+ $expectedParams = [
+ 'errorCount' => 1,
+ 'service' => 'secondary',
+ 'command' => 'refresh'
+ ];
+ $actualParams = array_intersect_key(
+ $job->getResentJobs()[1]->getParams(),
+ $expectedParams
+ );
+ $this->assertEquals( $expectedParams, $actualParams );
+ }
+
+ /**
+ * We simulate a resent job after a failure, this job is directed to a specific service, we
+ * ensure that we do not replicate the write to its mirror
+ */
+ public function testJobOnSingleService() {
+ $mock = $this->getMockBuilder( WritableTTMServer::class )
+ ->getMock();
+ $mock->expects( $this->atLeastOnce() )
+ ->method( 'update' );
+ static::$mockups['primary'] = $mock;
+ $mock = $this->getMockBuilder( WritableTTMServer::class )
+ ->getMock();
+ $mock->expects( $this->never() )
+ ->method( 'update' );
+ static::$mockups['secondary'] = $mock;
+
+ $job = new TestableTTMServerMessageUpdateJob(
+ Title::makeTitle( NS_MAIN, 'Main Page' ),
+ [
+ 'errorCount' => 1,
+ 'service' => 'primary',
+ 'command' => 'refresh'
+ ],
+ $this->getMockBuilder( MessageHandle::class )
+ ->disableOriginalConstructor()
+ ->getMock()
+ );
+ $job->run();
+ $this->assertEmpty( $job->getResentJobs() );
+ }
+
+ /**
+ * We simulate a job that failed multiple times and we fail again, we encure that we adandon
+ * the job by not resending it to queue
+ */
+ public function testAbandonedJob() {
+ $mock = $this->getMockBuilder( WritableTTMServer::class )
+ ->getMock();
+ $mock->expects( $this->atLeastOnce() )
+ ->method( 'update' )
+ ->will( $this->throwException( new TTMServerException ) );
+ static::$mockups['primary'] = $mock;
+ $mock = $this->getMockBuilder( WritableTTMServer::class )
+ ->getMock();
+ $mock->expects( $this->never() )
+ ->method( 'update' );
+ static::$mockups['secondary'] = $mock;
+
+ $job = new TestableTTMServerMessageUpdateJob(
+ Title::makeTitle( NS_MAIN, 'Main Page' ),
+ [
+ 'errorCount' => 4,
+ 'service' => 'primary',
+ 'command' => 'refresh'
+ ],
+ $this->getMockBuilder( MessageHandle::class )
+ ->disableOriginalConstructor()
+ ->getMock()
+ );
+ $job->run();
+ $this->assertEmpty( $job->getResentJobs() );
+ }
+
+ /**
+ * One service is frozen
+ */
+ public function testOneServiceFrozen() {
+ $mock = $this->getMockBuilder( WritableTTMServer::class )
+ ->getMock();
+ $mock->expects( $this->atLeastOnce() )
+ ->method( 'update' );
+ static::$mockups['primary'] = $mock;
+ $mock = $this->getMockBuilder( WritableTTMServer::class )
+ ->getMock();
+ $mock->expects( $this->never() )
+ ->method( 'update' );
+ $mock->expects( $this->atLeastOnce() )
+ ->method( 'isFrozen' )
+ ->willReturn( true );
+ static::$mockups['secondary'] = $mock;
+
+ $now = time();
+ $job = new TestableTTMServerMessageUpdateJob(
+ Title::makeTitle( NS_MAIN, 'Main Page' ),
+ [
+ 'command' => 'refresh',
+ 'createdAt' => $now
+ ],
+ $this->getMockBuilder( MessageHandle::class )
+ ->disableOriginalConstructor()
+ ->getMock()
+ );
+ $job->run();
+ $this->assertEquals( 1, count( $job->getResentJobs() ) );
+ $expectedParams = [
+ 'errorCount' => 0,
+ 'retryCount' => 1,
+ 'createdAt' => $now,
+ 'service' => 'secondary',
+ 'command' => 'refresh'
+ ];
+ $actualParams = array_intersect_key(
+ $job->getResentJobs()[0]->getParams(),
+ $expectedParams
+ );
+ $this->assertEquals( $expectedParams, $actualParams );
+ }
+
+ /**
+ * One is broken
+ * One is frozen
+ */
+ public function testOneBrokenOneFrozen() {
+ $mock = $this->getMockBuilder( WritableTTMServer::class )
+ ->getMock();
+ $mock->expects( $this->atLeastOnce() )
+ ->method( 'update' )
+ ->will( $this->throwException( new TTMServerException ) );
+ static::$mockups['primary'] = $mock;
+ $mock = $this->getMockBuilder( WritableTTMServer::class )
+ ->getMock();
+ $mock->expects( $this->never() )
+ ->method( 'update' );
+ $mock->expects( $this->atLeastOnce() )
+ ->method( 'isFrozen' )
+ ->willReturn( true );
+ static::$mockups['secondary'] = $mock;
+
+ $now = time();
+ $job = new TestableTTMServerMessageUpdateJob(
+ Title::makeTitle( NS_MAIN, 'Main Page' ),
+ [
+ 'command' => 'refresh',
+ 'createdAt' => $now
+ ],
+ $this->getMockBuilder( MessageHandle::class )
+ ->disableOriginalConstructor()
+ ->getMock()
+ );
+ $job->run();
+ $this->assertEquals( 2, count( $job->getResentJobs() ) );
+ $expectedParams = [
+ 'errorCount' => 1,
+ 'retryCount' => 0,
+ 'createdAt' => $now,
+ 'service' => 'primary',
+ 'command' => 'refresh'
+ ];
+ $actualParams = array_intersect_key(
+ $job->getResentJobs()[0]->getParams(),
+ $expectedParams
+ );
+ $this->assertEquals( $expectedParams, $actualParams );
+
+ $expectedParams = [
+ 'errorCount' => 0,
+ 'retryCount' => 1,
+ 'createdAt' => $now,
+ 'service' => 'secondary',
+ 'command' => 'refresh'
+ ];
+ $actualParams = array_intersect_key(
+ $job->getResentJobs()[1]->getParams(),
+ $expectedParams
+ );
+ $this->assertEquals( $expectedParams, $actualParams );
+ }
+
+ /**
+ * Old jobs are abandoned
+ */
+ public function testAbandonedOldJob() {
+ $mock = $this->getMockBuilder( WritableTTMServer::class )
+ ->getMock();
+ $mock->expects( $this->never() )
+ ->method( 'update' );
+ $mock->expects( $this->never() )
+ ->method( 'isFrozen' );
+ static::$mockups['primary'] = $mock;
+ $mock = $this->getMockBuilder( WritableTTMServer::class )
+ ->getMock();
+ $mock->expects( $this->never() )
+ ->method( 'update' );
+ $mock->expects( $this->atLeastOnce() )
+ ->method( 'isFrozen' )
+ ->willReturn( true );
+ static::$mockups['secondary'] = $mock;
+
+ $job = new TestableTTMServerMessageUpdateJob(
+ Title::makeTitle( NS_MAIN, 'Main Page' ),
+ [
+ 'command' => 'refresh',
+ 'retryCount' => 10,
+ 'service' => 'secondary',
+ 'createdAt' => time() - TTMServerMessageUpdateJob::DROP_DELAYED_JOBS_AFTER - 1,
+ ],
+ $this->getMockBuilder( MessageHandle::class )
+ ->disableOriginalConstructor()
+ ->getMock()
+ );
+ $job->run();
+ $this->assertEquals( 0, count( $job->getResentJobs() ) );
+ }
+}
+
+/**
+ * Test subclass to override methods that we are not able to mock
+ * easily.
+ * For the context of the test we can only test the 'refresh' command
+ * because other ones would need to have a more complex context to prepare
+ */
+class TestableTTMServerMessageUpdateJob extends TTMServerMessageUpdateJob {
+ private $resentJobs = [];
+ private $handleMock;
+ public function __construct( Title $title, $params, $handleMock ) {
+ parent::__construct( $title, $params );
+ $this->handleMock = $handleMock;
+ }
+ public function resend( TTMServerMessageUpdateJob $job ) {
+ $this->resentJobs[] = $job;
+ }
+
+ protected function getHandle() {
+ return $this->handleMock;
+ }
+
+ protected function getTranslation( MessageHandle $handle ) {
+ return 'random text';
+ }
+
+ public function getResentJobs() {
+ return $this->resentJobs;
+ }
+}
+
+/**
+ * This "testable" TTMServer implementation allows to:
+ * - test TTMServer specific methods
+ * - attach our mocks to the Test static context, this is needed because
+ * the factory always creates a new instance of the service
+ */
+class TestableTTMServer extends TTMServer implements WritableTTMServer {
+ private $delegate;
+ public function __construct( array $config ) {
+ parent::__construct( $config );
+ $this->delegate = TTMServerMessageUpdateJobTest::$mockups[$config['name']];
+ }
+
+ public function update( MessageHandle $handle, $targetText ) {
+ $this->delegate->update( $handle, $targetText );
+ }
+
+ public function beginBootstrap() {
+ $this->delegate->beginBootstrap();
+ }
+
+ public function beginBatch() {
+ $this->delegate->beginBatch();
+ }
+
+ public function batchInsertDefinitions( array $batch ) {
+ $this->delegate->batchInsertDefinitions( $batch );
+ }
+
+ public function batchInsertTranslations( array $batch ) {
+ $this->delegate->batchInsertTranslations( $batch );
+ }
+
+ public function endBatch() {
+ $this->delegate->endBatch();
+ }
+
+ public function endBootstrap() {
+ $this->delegate->endBootstrap();
+ }
+
+ public function isFrozen() {
+ return $this->delegate->isFrozen();
+ }
+}
diff --git a/MLEB/Translate/tests/phpunit/TranslatablePageTest.php b/MLEB/Translate/tests/phpunit/TranslatablePageTest.php
new file mode 100644
index 00000000..9984facb
--- /dev/null
+++ b/MLEB/Translate/tests/phpunit/TranslatablePageTest.php
@@ -0,0 +1,112 @@
+<?php
+/**
+ * Unit tests for class TPSection
+ *
+ * @author Niklas Laxström
+ * @license GPL-2.0-or-later
+ * @file
+ */
+
+/**
+ * Unit tests for class TPSection
+ * @ingroup PageTranslation
+ */
+class TranslatablePageTest extends PHPUnit\Framework\TestCase {
+ /**
+ * @dataProvider provideTestSectionise
+ */
+ public function testSectionise( $input, $pattern, $comment ) {
+ $result = TranslatablePage::sectionise( $input );
+ $pattern = addcslashes( $pattern, '~' );
+ $this->assertRegExp( "~^$pattern$~", $result['template'], $comment );
+ }
+
+ public static function provideTestSectionise() {
+ // Ugly implicit assumption
+ $ph = "\x7fUNIQ[a-z0-9]{8,16}-\d+";
+
+ $cases = [];
+
+ $cases[] = [
+ 'Hello',
+ "$ph",
+ 'No surrounding whitespace',
+ ];
+
+ $cases[] = [
+ "\nHello",
+ "\n$ph",
+ 'With surrounding whitespace',
+ ];
+
+ $cases[] = [
+ "\nHello world\n\nBunny\n",
+ "\n$ph\n\n$ph\n",
+ 'Splitting at one empty line',
+ ];
+
+ $cases[] = [
+ "First\n\n\n\n\nSecond\n\nThird",
+ "$ph\n\n\n\n\n$ph\n\n$ph",
+ 'Splitting with multiple empty lines',
+ ];
+
+ return $cases;
+ }
+
+ /**
+ * @dataProvider provideTestCleanupTags
+ */
+ public function testCleanupTags( $input, $expected, $comment ) {
+ $output = TranslatablePage::cleanupTags( $input );
+ $this->assertEquals( $expected, $output, $comment );
+ }
+
+ public static function provideTestCleanupTags() {
+ $cases = [];
+
+ $cases[] = [
+ "== Hello ==\n</translate>",
+ '== Hello ==',
+ 'Unbalanced tag in a section preview',
+ ];
+
+ $cases[] = [
+ "</translate><translate>",
+ '',
+ 'Unbalanced tags, no whitespace',
+ ];
+
+ $cases[] = [
+ "1\n2<translate>3\n4</translate>5\n6",
+ "1\n23\n45\n6",
+ 'Unbalanced tags, non-removable whitespace',
+ ];
+
+ $cases[] = [
+ "1<translate>\n\n</translate>2",
+ '12',
+ 'Unbalanced tags, removable whitespace',
+ ];
+
+ $cases[] = [
+ '[[<tvar|wmf>Special:MyLanguage/Wikimedia Foundation</>|Wikimedia Foundation]].',
+ '[[Special:MyLanguage/Wikimedia Foundation|Wikimedia Foundation]].',
+ 'TVAR tag is collapsed',
+ ];
+
+ $cases[] = [
+ 'You can use the <nowiki><translate></nowiki> tag.',
+ 'You can use the <nowiki><translate></nowiki> tag.',
+ 'Tag inside a nowiki is retained',
+ ];
+
+ $cases[] = [
+ 'What if I <translate and </translate>.',
+ 'What if I <translate and .',
+ 'Broken tag is retained',
+ ];
+
+ return $cases;
+ }
+}
diff --git a/MLEB/Translate/tests/phpunit/TranslateYamlTest.php b/MLEB/Translate/tests/phpunit/TranslateYamlTest.php
new file mode 100644
index 00000000..f0259528
--- /dev/null
+++ b/MLEB/Translate/tests/phpunit/TranslateYamlTest.php
@@ -0,0 +1,72 @@
+<?php
+/**
+ * Tests for yaml wrapper.
+ *
+ * @author Niklas Laxström
+ * @license GPL-2.0-or-later
+ */
+
+class TranslateYamlTest extends MediaWikiTestCase {
+ protected function setUp() {
+ parent::setUp();
+
+ $this->setMwGlobals( [
+ 'wgTranslateYamlLibrary' => 'phpyaml',
+ ] );
+ }
+
+ /**
+ * TODO: test other drivers too.
+ * @requires function yaml_parse
+ * @dataProvider provideTestLoadString
+ */
+ public function testLoadStringPhpyaml( $input, $expected, $comment ) {
+ $output = TranslateYaml::loadString( $input );
+ $this->assertEquals( $expected, $output, $comment );
+ }
+
+ public function provideTestLoadString() {
+ $tests = [];
+ $tests[] = [
+ 'a: b',
+ [ 'a' => 'b' ],
+ 'Simple key-value'
+ ];
+
+ $tests[] = [
+ 'a: !php/object "O:8:\"stdClass\":1:{s:1:\"a\";s:1:\"b\";}"',
+ [ 'a' => 'O:8:"stdClass":1:{s:1:"a";s:1:"b";}' ],
+ 'PHP objects must not be unserialized'
+ ];
+
+ return $tests;
+ }
+
+ /**
+ * Tests workaround for https://bugs.php.net/bug.php?id=76309
+ * @requires function yaml_emit
+ */
+ public function testBug76309() {
+ $input = [
+ 'a' => '2.',
+ 'b' => '22222222222222222222222222222222222222222222222222222222222222.',
+ 'c' => 2.0,
+ 'd' => "2.0"
+ ];
+
+ $expected = <<<YAML
+---
+a: "2."
+b: "22222222222222222222222222222222222222222222222222222222222222."
+c: 2.000000
+d: "2.0"
+...
+
+YAML;
+
+ $output = TranslateYaml::dump( $input );
+ $this->assertEquals( $expected, $output, "Floaty strings outputted as strings" );
+ $parsed = TranslateYaml::loadString( $output );
+ $this->assertEquals( $input, $parsed, "Floaty strings roundtrip" );
+ }
+}
diff --git a/MLEB/Translate/tests/phpunit/ffs/MediaWikiExtensionsTest.php b/MLEB/Translate/tests/phpunit/ffs/MediaWikiExtensionsTest.php
new file mode 100644
index 00000000..f0a27a2d
--- /dev/null
+++ b/MLEB/Translate/tests/phpunit/ffs/MediaWikiExtensionsTest.php
@@ -0,0 +1,45 @@
+<?php
+/**
+ * Test for parsing the special definition file for mediawiki-extensions
+ * @author Niklas Laxström
+ * @license GPL-2.0-or-later
+ */
+class MediaWikiExtensionsTest extends PHPUnit\Framework\TestCase {
+ /**
+ * @requires function yaml_parse
+ * @covers PremadeMediawikiExtensionGroups
+ */
+ public function testParsing() {
+ $defs = __DIR__ . '/../data/mediawiki-extensions.txt';
+ $path = '%GROUPROOT%/mediawiki-extensions/extensions';
+ $foo = new PremadeMediawikiExtensionGroups( $defs, $path );
+ $list = $deps = $autoload = [];
+ $foo->register( $list, $deps, $autoload );
+
+ $this->assertEquals( 1, count( $deps ), 'A dependency to definition file was added' );
+ $this->assertEquals( 5, count( $list ), 'Right number of groups were created' );
+
+ $this->assertArrayHasKey( 'ext-wikimediamessages', $list );
+ $expected = TranslateYaml::load( __DIR__ . '/../data/MediaWikiExtensionTest-conf2.yaml' );
+ $this->assertEquals( $expected, $list['ext-wikimediamessages']->getConfiguration() );
+
+ $this->assertArrayHasKey( 'ext-examplejsonextension', $list );
+ $expected = TranslateYaml::load( __DIR__ . '/../data/MediaWikiExtensionTest-conf3.yaml' );
+ $this->assertEquals( $expected, $list['ext-examplejsonextension']->getConfiguration() );
+
+ $this->assertArrayHasKey( 'ext-exampleextension2', $list );
+ $expected = TranslateYaml::load( __DIR__ . '/../data/MediaWikiExtensionTest-conf4.yaml' );
+ $this->assertEquals( $expected, $list['ext-exampleextension2']->getConfiguration() );
+
+ $this->assertArrayHasKey( 'ext-languagesmodified', $list );
+ $languages = $list['ext-languagesmodified']->getTranslatableLanguages();
+ $this->assertArrayHasKey( 'foo', $languages, 'Whitelisted language is available' );
+ $this->assertArrayNotHasKey( 'bar', $languages, 'Blacklisted language is not available' );
+ $this->assertArrayHasKey( 'de', $languages, 'Default language is available' );
+
+ $this->assertArrayHasKey( 'ext-languagesset', $list );
+ $languages = $list['ext-languagesset']->getTranslatableLanguages();
+ $this->assertArrayHasKey( 'foo', $languages, 'Set language is available' );
+ $this->assertArrayNotHasKey( 'de', $languages, 'Unset language is not available' );
+ }
+}
diff --git a/MLEB/Translate/tests/phpunit/insertables/CombinedInsertablesSuggesterTest.php b/MLEB/Translate/tests/phpunit/insertables/CombinedInsertablesSuggesterTest.php
new file mode 100644
index 00000000..e12092ef
--- /dev/null
+++ b/MLEB/Translate/tests/phpunit/insertables/CombinedInsertablesSuggesterTest.php
@@ -0,0 +1,90 @@
+<?php
+
+/**
+ * Tests for class CombinedInsertablesSuggester
+ *
+ * @file
+ * @author Geoffrey Mon
+ * @license GPL-2.0-or-later
+ */
+class CombinedInsertablesSuggesterTest extends MediaWikiTestCase {
+
+ /**
+ * @dataProvider getInsertablesProvider
+ */
+ public function testGetInsertables( $suggesters, $input, $expected ) {
+ $suggester = new CombinedInsertablesSuggester( $suggesters );
+ $this->assertArrayEquals( $expected, $suggester->getInsertables( $input ) );
+ }
+
+ public function getInsertablesProvider() {
+ return [
+ // Test basic combination of multiple InsertablesSuggesters
+ [
+ [
+ new TestingInsertablesSuggester(),
+ new NumericalParameterInsertablesSuggester(),
+ ],
+ 'test $1 foo $2 bar $3spam eggs',
+ [
+ new Insertable( 'Test', 'Test', '' ),
+ new Insertable( '$1', '$1', '' ),
+ new Insertable( '$2', '$2', '' ),
+ new Insertable( '$3', '$3', '' ),
+ ]
+ ],
+ // Test removal of duplicate suggestions
+ [
+ [
+ new NumericalParameterInsertablesSuggester(),
+ new NumericalParameterInsertablesSuggester(),
+ ],
+ 'test $1 duplicates $2 $3',
+ [
+ new Insertable( '$1', '$1', '' ),
+ new Insertable( '$2', '$2', '' ),
+ new Insertable( '$3', '$3', '' ),
+ ]
+ ],
+ // Test removal of duplicate suggestions
+ [
+ [
+ new TestingDuplicateInsertablesSuggester(),
+ new NumericalParameterInsertablesSuggester(),
+ ],
+ 'test $1 duplicates $2 $3',
+ [
+ new Insertable( '$1', '$1', '' ),
+ new Insertable( '$2', '$2', '' ),
+ new Insertable( '$3', '$3', '' ),
+ new Insertable( 'Test', 'Test', '' ),
+ new Insertable( '', 'Test', 'Test' ),
+ ]
+ ],
+ // Test no InsertablesSuggesters
+ [
+ [],
+ 'test $1 duplicates $2 $3',
+ []
+ ],
+ ];
+ }
+}
+
+class TestingInsertablesSuggester implements InsertablesSuggester {
+ public function getInsertables( $text ) {
+ return [ new Insertable( 'Test', 'Test', '' ) ];
+ }
+}
+
+class TestingDuplicateInsertablesSuggester implements InsertablesSuggester {
+ public function getInsertables( $text ) {
+ return [
+ new Insertable( '$1', '$1', '' ),
+ new Insertable( '$1', '$1', '' ),
+ new Insertable( 'Test', 'Test', '' ),
+ new Insertable( 'Test', 'Test', '' ),
+ new Insertable( '', 'Test', 'Test' ),
+ ];
+ }
+}
diff --git a/MLEB/Translate/tests/phpunit/insertables/NumericalParameterInsertablesSuggesterTest.php b/MLEB/Translate/tests/phpunit/insertables/NumericalParameterInsertablesSuggesterTest.php
new file mode 100644
index 00000000..d24accac
--- /dev/null
+++ b/MLEB/Translate/tests/phpunit/insertables/NumericalParameterInsertablesSuggesterTest.php
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * Tests for class NumericalParameterInsertablesSuggester
+ *
+ * @file
+ * @author Geoffrey Mon
+ * @license GPL-2.0-or-later
+ */
+class NumericalParameterInsertablesSuggesterTest extends PHPUnit\Framework\TestCase {
+
+ /**
+ * @dataProvider getInsertablesProvider
+ */
+ public function testGetInsertables( $input, $expected ) {
+ $suggester = new MediaWikiInsertablesSuggester();
+ $this->assertEquals( $expected, $suggester->getInsertables( $input ) );
+ }
+
+ public function getInsertablesProvider() {
+ return [
+ [ '$1 $2 $3', [
+ new Insertable( '$1', '$1', '' ),
+ new Insertable( '$2', '$2', '' ),
+ new Insertable( '$3', '$3', '' ),
+ ] ],
+ [ 'test $1 foo $2 bar $3spam eggs', [
+ new Insertable( '$1', '$1', '' ),
+ new Insertable( '$2', '$2', '' ),
+ new Insertable( '$3', '$3', '' ),
+ ] ],
+ [ '$1 or $2, $15!', [
+ new Insertable( '$1', '$1', '' ),
+ new Insertable( '$2', '$2', '' ),
+ new Insertable( '$15', '$15', '' ),
+ ] ],
+ ];
+ }
+}
diff --git a/MLEB/Translate/tests/phpunit/pagetranslation/Inline.ptsource b/MLEB/Translate/tests/phpunit/pagetranslation/Inline.ptsource
new file mode 100644
index 00000000..78c4146e
--- /dev/null
+++ b/MLEB/Translate/tests/phpunit/pagetranslation/Inline.ptsource
@@ -0,0 +1 @@
+We had a nice <translate><!--T:-1--> day</translate> today.
diff --git a/MLEB/Translate/tests/phpunit/pagetranslation/Whitespace.ptsource b/MLEB/Translate/tests/phpunit/pagetranslation/Whitespace.ptsource
new file mode 100644
index 00000000..e6773415
--- /dev/null
+++ b/MLEB/Translate/tests/phpunit/pagetranslation/Whitespace.ptsource
@@ -0,0 +1,19 @@
+There is a two new lines after this line.
+
+<translate>
+
+<!--T:-1-->
+There is a two new lines before and after this line.
+
+</translate>
+
+There is a two new lines before and after this line also.
+
+
+There is three spaces trailing on this line: <translate>
+
+ <!--T:-1-->
+This line is prefixed with a space, with two trailing spaces </translate>
+
+<translate><!--T:-1--> line1</translate>
+<translate><!--T:-1--> line2</translate>
diff --git a/MLEB/Translate/tests/phpunit/tag/PageTranslationHooksTest.php b/MLEB/Translate/tests/phpunit/tag/PageTranslationHooksTest.php
new file mode 100644
index 00000000..375458b0
--- /dev/null
+++ b/MLEB/Translate/tests/phpunit/tag/PageTranslationHooksTest.php
@@ -0,0 +1,97 @@
+<?php
+/**
+ * Test for various code using hooks.
+ *
+ * @file
+ * @author Niklas Laxström
+ * @license GPL-2.0-or-later
+ */
+
+/**
+ * @group Database
+ * @group medium
+ */
+class PageTranslationHooksTest extends MediaWikiTestCase {
+ protected function setUp() {
+ parent::setUp();
+
+ global $wgHooks;
+ $this->setMwGlobals( [
+ 'wgHooks' => [],
+ 'wgEnablePageTranslation' => true,
+ 'wgTranslateTranslationServices' => [],
+ ] );
+ TranslateHooks::setupTranslate();
+ $wgHooks['TranslatePostInitGroups'] = [ 'MessageGroups::getTranslatablePages' ];
+
+ $mg = MessageGroups::singleton();
+ $mg->setCache( wfGetCache( 'hash' ) );
+ $mg->recache();
+
+ MessageIndex::setInstance( new HashMessageIndex() );
+ MessageIndex::singleton()->rebuild();
+ }
+
+ public function testRenderTagPage() {
+ global $wgParser;
+
+ // Setup objects
+ $superUser = new MockSuperUser();
+ $translatablePageTitle = Title::newFromText( 'Vuosaari' );
+ $page = WikiPage::factory( $translatablePageTitle );
+ $text = '<translate>pupu</translate>';
+ $content = ContentHandler::makeContent( $text, $translatablePageTitle );
+ $translatablePage = TranslatablePage::newFromTitle( $translatablePageTitle );
+ $parser = $wgParser->getFreshParser();
+ $options = ParserOptions::newFromUser( $superUser );
+ $messageGroups = MessageGroups::singleton();
+
+ // Create the page
+ $editStatus = $page->doEditContent( $content, __METHOD__, 0, false, $superUser );
+ $messageGroups->recache();
+
+ // Check that we don't interfere with non-translatable pages at all
+ $parserOutput = $parser->parse( $text, $translatablePageTitle, $options );
+ $actual = $parserOutput->getExtensionData( 'translate-translation-page' );
+ $expected = null;
+ $this->assertSame( $expected, $actual, 'Extension data is not set on unmarked source page' );
+
+ // Mark the page for translation
+ $latestRevisionId = $editStatus->value['revision']->getId();
+ $translatablePage->addMarkedTag( $latestRevisionId );
+ $messageGroups->recache();
+ $translationPageTitle = Title::newFromText( 'Vuosaari/fi' );
+ TranslateRenderJob::newJob( $translationPageTitle )->run();
+
+ // Check that we don't add data to translatable pages
+ $parserOutput = $parser->parse( $text, $translatablePageTitle, $options );
+ $actual = $parserOutput->getExtensionData( 'translate-translation-page' );
+ $expected = null;
+ $this->assertSame( $expected, $actual, 'Extension data is not set on marked source page' );
+
+ // Check that our code works for translation pages
+ $parserOutput = $parser->parse( 'fi-pupu', $translationPageTitle, $options );
+ $actual = $parserOutput->getExtensionData( 'translate-translation-page' );
+ $expected = [
+ 'sourcepagetitle' => $translatablePageTitle,
+ 'languagecode' => 'fi',
+ 'messagegroupid' => 'page-Vuosaari',
+ ];
+ $this->assertTrue( is_array( $actual ), 'Extension data is set on marked page' );
+ $this->assertSame(
+ 'Vuosaari',
+ $actual[ 'sourcepagetitle' ]->getPrefixedText(),
+ 'Source page title is correct'
+ );
+ $this->assertSame(
+ 'fi',
+ $actual[ 'languagecode' ],
+ 'Language code is correct'
+ );
+ $this->assertSame(
+ 'page-Vuosaari',
+ $actual[ 'messagegroupid' ],
+ 'Message group id is correct'
+ );
+ }
+}
diff --git a/MLEB/Translate/tests/phpunit/utils/ArrayFlattenerTest.php b/MLEB/Translate/tests/phpunit/utils/ArrayFlattenerTest.php
new file mode 100644
index 00000000..2edf9137
--- /dev/null
+++ b/MLEB/Translate/tests/phpunit/utils/ArrayFlattenerTest.php
@@ -0,0 +1,249 @@
+<?php
+/**
+ * Unit tests.
+ *
+ * @author Niklas Laxström
+ * @file
+ * @license GPL-2.0-or-later
+ */
+
+class ArrayFlattenerTest extends PHPUnit\Framework\TestCase {
+ /**
+ * @dataProvider provideTestFlatten
+ */
+ public function testFlatten( $sep, $input, $expected ) {
+ $flattener = new ArrayFlattener( $sep );
+ $output = $flattener->flatten( $input );
+ $this->assertEquals( $expected, $output );
+ }
+
+ /**
+ * @dataProvider provideTestFlatten
+ */
+ public function testUnflatten( $sep, $expected, $input ) {
+ $flattener = new ArrayFlattener( $sep );
+ $output = $flattener->unflatten( $input );
+ $this->assertEquals( $expected, $output );
+ }
+
+ /**
+ * @dataProvider provideTestCLDRPlurals
+ */
+ public function testFlattenCLDRPlurals( $sep, $input, $expected ) {
+ $flattener = new ArrayFlattener( $sep, true );
+ $output = $flattener->flatten( $input );
+ $this->assertEquals( $expected, $output );
+ }
+
+ /**
+ * @dataProvider provideTestCLDRPlurals
+ */
+ public function testUnflattenCLDRPlurals( $sep, $expected, $input ) {
+ $flattener = new ArrayFlattener( $sep, true );
+ $output = $flattener->unflatten( $input );
+ $this->assertEquals( $expected, $output );
+ }
+
+ /**
+ * @expectedException MWException
+ * @expectedExceptionMessage Reserved plural keywords mixed with other keys
+ * @dataProvider provideTestMixedCLDRPlurals
+ */
+ public function testFlattenMixedCLDRPlurals( $input ) {
+ $flattener = new ArrayFlattener( '.', true );
+ $flattener->flatten( $input );
+ }
+
+ public static function provideTestFlatten() {
+ $cases = [];
+ $cases[] = [
+ '.',
+ [ 'a' => 1 ],
+ [ 'a' => 1 ],
+ ];
+
+ $cases[] = [
+ '.',
+ [ 'a' => [ 'b' => [ 'c' => 1, 'd' => 2 ] ] ],
+ [ 'a.b.c' => 1, 'a.b.d' => 2 ],
+ ];
+
+ // By default, CLDR plural keywords should be treated like any other key
+ $cases[] = [
+ '/',
+ [ 'number' => [ 'one' => '1', 'other' => '999' ] ],
+ [ 'number/one' => '1', 'number/other' => '999' ]
+ ];
+
+ return $cases;
+ }
+
+ public static function provideTestCLDRPlurals() {
+ $cases = [];
+
+ // We include some non-plural data to ensure it is processed correctly
+ $cases[] = [
+ '/',
+ [
+ 'cat' => 'An amount of cats',
+ 'mice' => [
+ 'Frankie',
+ 'Benjy'
+ ],
+ 'dog or dogs' => [
+ 'one' => 'One dog',
+ 'two' => 'Two doggies',
+ 'other' => 'Some dogs'
+ ]
+ ],
+ [
+ 'cat' => 'An amount of cats',
+ 'mice/0' => 'Frankie',
+ 'mice/1' => 'Benjy',
+ 'dog or dogs' => '{{PLURAL|one=One dog|two=Two doggies|Some dogs}}'
+ ],
+ ];
+
+ $cases[] = [
+ '/',
+ [
+ 'dog or dogs' => [
+ 'zero' => 'No dogs',
+ 'one' => 'One dog',
+ 'two' => 'A couple doggies',
+ 'few' => 'A few dogs',
+ 'many' => '%1 dogs',
+ 'other' => 'Some dogs'
+ ]
+ ],
+ [
+ 'dog or dogs' => '{{PLURAL|zero=No dogs|one=One dog|two=A couple doggies|' .
+ 'few=A few dogs|many=%1 dogs|Some dogs}}'
+ ],
+ ];
+
+ $cases[] = [
+ '/',
+ [
+ 'math is hard' => [
+ 'one' => 'a=400',
+ 'other' => 'a=999'
+ ]
+ ],
+ [ 'math is hard' => '{{PLURAL|one=a=400|a=999}}' ],
+ ];
+
+ return $cases;
+ }
+
+ // Separate provider because the input throws an exception
+ public static function provideTestMixedCLDRPlurals() {
+ $cases = [];
+ $cases[] = [
+ [
+ 'dog or dogs' => [
+ 'one' => 'One dog',
+ 'two' => 'Two doggies',
+ 'other' => 'Some dogs',
+ 'Pluto' => 'A specific dog'
+ ]
+ ]
+ ];
+
+ $cases[] = [
+ [
+ 'dog or dogs' => [
+ 'Pluto' => 'A specific dog',
+ 'one' => 'One dog',
+ 'two' => 'Two doggies',
+ 'other' => 'Some dogs',
+ ]
+ ]
+ ];
+ return $cases;
+ }
+
+ /**
+ * @dataProvider provideMatchingValues
+ */
+ public function testCompareTrue( $input1, $input2 ) {
+ $flattener = new ArrayFlattener( '.', true );
+
+ $this->assertTrue(
+ $flattener->compareContent( $input1, $input2, $flattener )
+ );
+ }
+
+ /**
+ * @dataProvider provideNonMatchingValues
+ */
+ public function testCompareFalse( $input1, $input2 ) {
+ $flattener = new ArrayFlattener( '.', true );
+
+ $this->assertfalse(
+ $flattener->compareContent( $input1, $input2, $flattener )
+ );
+ }
+
+ public static function provideMatchingValues() {
+ $cases = [];
+
+ // We include some non-plural data to ensure it is processed correctly
+ $cases[] = [
+ 'a',
+ 'a'
+ ];
+
+ $cases[] = [
+ '{{PLURAL|one=cat|cats}}',
+ '{{PLURAL|one=cat|cats}}',
+ ];
+
+ $cases[] = [
+ 'Give me {{PLURAL|one=a cat|cats}}',
+ '{{PLURAL|one=Give me a cat|Give me cats}}',
+ ];
+
+ // Order should not matter
+ $cases[] = [
+ '{{PLURAL|one=Give me a cat|Give me cats}}',
+ 'Give me {{PLURAL|one=a cat|cats}}',
+ ];
+
+ // Multiple inlines
+ $cases[] = [
+ 'Test {{PLURAL|one=one|other}} and {{PLURAL|one=one|other}} and {{PLURAL|one=one|other}}!',
+ '{{PLURAL|one=Test one and one and one|Test other and other and other}}!',
+ ];
+
+ // Lots of keys
+ $cases[] = [
+ 'Is {{PLURAL|zero=zero|one=one|two=two|few=few|many=many|other}}',
+ '{{PLURAL|zero=Is zero|one=Is one|two=Is two|few=Is few|many=Is many|Is other}}',
+ ];
+
+ return $cases;
+ }
+
+ public static function provideNonMatchingValues() {
+ $cases = [];
+
+ $cases[] = [
+ 'a',
+ 'b'
+ ];
+
+ $cases[] = [
+ '{{PLURAL|one=cat|cats}}',
+ '{{PLURAL|one=dog|dogs}}',
+ ];
+
+ // Different set of keys
+ $cases[] = [
+ 'Is {{PLURAL|zero=zero|one=one|two=two|few=few|other}}',
+ '{{PLURAL|zero=Is zero|two=Is two|few=Is few|many=Is many|Is other}}',
+ ];
+
+ return $cases;
+ }
+}
diff --git a/MLEB/Translate/tests/phpunit/utils/MessageGroupStatsTest.php b/MLEB/Translate/tests/phpunit/utils/MessageGroupStatsTest.php
new file mode 100644
index 00000000..54c9f5d3
--- /dev/null
+++ b/MLEB/Translate/tests/phpunit/utils/MessageGroupStatsTest.php
@@ -0,0 +1,36 @@
+<?php
+/**
+ * Unit tests.
+ *
+ * @author Niklas Laxström
+ * @file
+ * @license GPL-2.0-or-later
+ */
+
+class MessageGroupStatsTest extends PHPUnit\Framework\TestCase {
+ public function testGetDatabaseIdForGroupId() {
+ $shortId = 'abab';
+ $longId = str_repeat( 'ab', 100 );
+
+ $this->assertLessThanOrEqual(
+ 100,
+ strlen( MessageGroupStats::getDatabaseIdForGroupId( $shortId ) ),
+ 'Short id is <= 100 bytes long'
+ );
+
+ $this->assertLessThanOrEqual(
+ 100,
+ strlen( MessageGroupStats::getDatabaseIdForGroupId( $longId ) ),
+ 'Long id is <= 100 bytes long'
+ );
+
+ $longId1 = str_repeat( 'ab', 100 ) . '1';
+ $longId2 = str_repeat( 'ab', 100 ) . '2';
+
+ $this->assertNotEquals(
+ MessageGroupStats::getDatabaseIdForGroupId( $longId1 ),
+ MessageGroupStats::getDatabaseIdForGroupId( $longId2 ),
+ 'Two long ids with the same prefix do not collide'
+ );
+ }
+}
diff --git a/MLEB/Translate/translationaids/QueryAggregatorAwareTranslationAid.php b/MLEB/Translate/translationaids/QueryAggregatorAwareTranslationAid.php
new file mode 100644
index 00000000..11358315
--- /dev/null
+++ b/MLEB/Translate/translationaids/QueryAggregatorAwareTranslationAid.php
@@ -0,0 +1,83 @@
+<?php
+/**
+ * Translation aid helper class.
+ *
+ * @file
+ * @author Niklas Laxström
+ * @license GPL-2.0-or-later
+ */
+
+/**
+ * Helper class for translation aids which use web services.
+ *
+ * @ingroup TranslationAids
+ * @since 2015.02
+ */
+abstract class QueryAggregatorAwareTranslationAid
+ extends TranslationAid
+ implements QueryAggregatorAware
+{
+ private $queries = [];
+ private $aggregator;
+
+ // Interface: QueryAggregatorAware
+ public function setQueryAggregator( QueryAggregator $aggregator ) {
+ $this->aggregator = $aggregator;
+ }
+
+ /**
+ * Stores a web service query for later execution.
+ * @param TranslationWebService $service
+ * @param string $from Source language
+ * @param string $to Target language
+ * @param string $text Source text
+ */
+ protected function storeQuery( TranslationWebService $service, $from, $to, $text ) {
+ $queries = $service->getQueries( $text, $from, $to );
+ foreach ( $queries as $query ) {
+ $this->queries[] = [
+ 'id' => $this->aggregator->addQuery( $query ),
+ 'language' => $from,
+ 'text' => $text,
+ 'service' => $service,
+ ];
+ }
+ }
+
+ /**
+ * Returns all stored queries.
+ * @return array Map of executed queries:
+ * - language: string: source language
+ * - text: string: source text
+ * - response: TranslationQueryResponse
+ */
+ protected function getQueryData() {
+ foreach ( $this->queries as &$queryData ) {
+ $queryData['response'] = $this->aggregator->getResponse( $queryData['id'] );
+ unset( $queryData['id'] );
+ }
+
+ return $this->queries;
+ }
+
+ /**
+ * Returns all web services of given type.
+ * @param string $type
+ * @return TranslationWebService[]
+ */
+ protected function getWebServices( $type ) {
+ global $wgTranslateTranslationServices;
+
+ $services = [];
+ foreach ( $wgTranslateTranslationServices as $name => $config ) {
+ $service = TranslationWebService::factory( $name, $config );
+ if ( !$service || $service->getType() !== $type ) {
+ continue;
+ }
+
+ $services[$name] = $service;
+ }
+
+ return $services;
+ }
+}
diff --git a/MLEB/Translate/translationaids/TranslationAidDataProvider.php b/MLEB/Translate/translationaids/TranslationAidDataProvider.php
new file mode 100644
index 00000000..2f6672ef
--- /dev/null
+++ b/MLEB/Translate/translationaids/TranslationAidDataProvider.php
@@ -0,0 +1,135 @@
+<?php
+/**
+ * Translation aid code.
+ *
+ * @file
+ * @author Niklas Laxström
+ * @license GPL-2.0-or-later
+ */
+
+use Wikimedia\Rdbms\IDatabase;
+
+/**
+ * @since 2018.01
+ */
+class TranslationAidDataProvider {
+ private $handle;
+ private $group;
+
+ private $definition;
+ private $translations;
+
+ public function __construct( MessageHandle $handle ) {
+ $this->handle = $handle;
+ $this->group = $handle->getGroup();
+ }
+
+ /**
+ * Get the message definition. Cached for performance.
+ *
+ * @return string
+ */
+ public function getDefinition() {
+ if ( $this->definition !== null ) {
+ return $this->definition;
+ }
+
+ // Optional performance optimization
+ if ( method_exists( $this->group, 'getMessageContent' ) ) {
+ $this->definition = $this->group->getMessageContent( $this->handle );
+ } else {
+ $this->definition = $this->group->getMessage(
+ $this->handle->getKey(),
+ $this->group->getSourceLanguage()
+ );
+ }
+
+ return $this->definition;
+ }
+
+ /**
+ * @return Content
+ */
+ public function getDefinitionContent() {
+ return ContentHandler::makeContent( $this->getDefinition(), $this->handle->getTitle() );
+ }
+
+ /**
+ * Get the translations in all languages. Cached for performance.
+ * Fuzzy translation are not included.
+ *
+ * @return array Language code => Translation
+ */
+ public function getGoodTranslations() {
+ if ( $this->translations !== null ) {
+ return $this->translations;
+ }
+
+ $data = self::loadTranslationData( wfGetDB( DB_REPLICA ), $this->handle );
+ $translations = [];
+ $prefixLength = strlen( $this->handle->getTitleForBase()->getDBKey() . '/' );
+
+ foreach ( $data as $page => $translation ) {
+ // Could use MessageHandle here, but that queries the message index.
+ // Instead we can get away with simple string manipulation.
+ $code = substr( $page, $prefixLength );
+ if ( !Language::isKnownLanguageTag( $code ) ) {
+ continue;
+ }
+
+ $translations[ $code ] = $translation;
+ }
+
+ $this->translations = $translations;
+
+ return $translations;
+ }
+
+ private static function loadTranslationData( IDatabase $db, MessageHandle $handle ) {
+ if ( method_exists( 'Revision', 'getQueryInfo' ) ) {
+ $queryInfo = Revision::getQueryInfo( [ 'page', 'text' ] );
+ $tables = $queryInfo[ 'tables' ];
+ $fields = $queryInfo[ 'fields' ];
+ $conds = [];
+ $options = [];
+ $joins = $queryInfo[ 'joins' ];
+ } else {
+ // BC for <= MW 1.31
+ $tables = [ 'page', 'text', 'revision' ];
+ $fields = array_merge(
+ Revision::selectFields(),
+ Revision::selectPageFields(),
+ Revision::selectTextFields()
+ );
+ $conds = [];
+ $options = [];
+ $joins = [
+ 'page' => Revision::pageJoinCond(),
+ 'text' => [ 'INNER JOIN', [ 'rev_text_id=old_id' ] ]
+ ];
+ }
+
+ // The list of pages we want to select, and their latest versions
+ $conds['page_namespace'] = $handle->getTitle()->getNamespace();
+ $base = $handle->getKey();
+ $conds[] = 'page_title ' . $db->buildLike( "$base/", $db->anyString() );
+ $conds[] = 'rev_id=page_latest';
+
+ // For fuzzy tags we also need:
+ $tables[] = 'revtag';
+ $conds[ 'rt_type' ] = null;
+ $joins[ 'revtag' ] = [
+ 'LEFT JOIN',
+ [ 'page_id=rt_page', 'page_latest=rt_revision', 'rt_type' => 'fuzzy' ]
+ ];
+
+ $rows = $db->select( $tables, $fields, $conds, __METHOD__, $options, $joins );
+
+ $pages = [];
+ foreach ( $rows as $row ) {
+ $pages[$row->page_title] = Revision::getRevisionText( $row );
+ }
+
+ return $pages;
+ }
+}
diff --git a/MLEB/Translate/ttmserver/CrossLanguageTranslationSearchQuery.php b/MLEB/Translate/ttmserver/CrossLanguageTranslationSearchQuery.php
new file mode 100644
index 00000000..ba620e40
--- /dev/null
+++ b/MLEB/Translate/ttmserver/CrossLanguageTranslationSearchQuery.php
@@ -0,0 +1,152 @@
+<?php
+/**
+ * Cross Language Translation Search.
+ * @since 2015.08
+ */
+class CrossLanguageTranslationSearchQuery {
+ /** @var TTMServer */
+ protected $server;
+
+ /** @var array */
+ protected $params;
+
+ /** @var ResultSet */
+ protected $resultset;
+
+ /** @var int */
+ protected $total = 0;
+
+ protected $hl = [ '', '' ];
+
+ public function __construct( array $params, SearchableTTMServer $server ) {
+ $this->params = $params;
+ $this->server = $server;
+ }
+
+ public function getDocuments() {
+ $documents = [];
+ $total = $start = 0;
+ $queryString = $this->params['query'];
+ $offset = $this->params['offset'];
+ $limit = $this->params['limit'];
+ $size = 1000;
+
+ $options = $this->params;
+ $options['limit'] = $size;
+ $options['language'] = $this->params['sourcelanguage'];
+ do {
+ $options['offset'] = $start;
+ $this->resultset = $this->server->search( $queryString, $options, $this->hl );
+
+ list( $results, $offsets ) = $this->extractMessages(
+ $this->resultset,
+ $offset,
+ $limit
+ );
+ $offset = $offsets['start'] + $offsets['left'] - $offsets['total'];
+ $limit = $limit - $offsets['left'];
+ $total = $total + $offsets['total'];
+
+ $documents = array_merge( $documents, $results );
+ $start = $start + $size;
+ } while (
+ $offsets['start'] + $offsets['left'] >= $offsets['total'] &&
+ $this->resultset->getTotalHits() > $start
+ );
+ $this->total = $total;
+
+ return $documents;
+ }
+
+ /**
+ * Extract messages from the resultset and build message definitions.
+ * Create a message collection from the definitions in the target language.
+ * Filter the message collection to get filtered messages.
+ * Slice messages according to limit and offset given.
+ * @param ResultSet $resultset
+ * @param int $offset
+ * @param int $limit
+ * @return array
+ */
+ protected function extractMessages( $resultset, $offset, $limit ) {
+ $messages = $documents = $ret = [];
+
+ $language = $this->params['language'];
+ foreach ( $resultset->getResults() as $document ) {
+ $data = $document->getData();
+
+ if ( !$this->server->isLocalSuggestion( $data ) ) {
+ continue;
+ }
+
+ $title = Title::newFromText( $data['localid'] );
+ if ( !$title ) {
+ continue;
+ }
+
+ $handle = new MessageHandle( $title );
+ if ( !$handle->isValid() ) {
+ continue;
+ }
+
+ $key = $title->getNamespace() . ':' . $title->getDBkey();
+ $messages[$key] = $data['content'];
+ }
+
+ $definitions = new MessageDefinitions( $messages );
+ $collection = MessageCollection::newFromDefinitions( $definitions, $language );
+
+ $filter = $this->params['filter'];
+ if ( $filter === 'untranslated' ) {
+ $collection->filter( 'hastranslation', true );
+ } elseif ( in_array( $filter, $this->getAvailableFilters() ) ) {
+ $collection->filter( $filter, false );
+ }
+
+ $total = count( $collection );
+ $offset = $collection->slice( $offset, $limit );
+ $left = count( $collection );
+
+ $offsets = [
+ 'start' => $offset[2],
+ 'left' => $left,
+ 'total' => $total,
+ ];
+
+ if ( $filter === 'translated' || $filter === 'fuzzy' ) {
+ $collection->loadTranslations();
+ }
+
+ foreach ( $collection->keys() as $mkey => $title ) {
+ $documents[$mkey]['content'] = $messages[$mkey];
+ if ( $filter === 'translated' || $filter === 'fuzzy' ) {
+ $documents[$mkey]['content'] = $collection[$mkey]->translation();
+ }
+ $handle = new MessageHandle( $title );
+ $documents[$mkey]['localid'] = $handle->getTitleForBase()->getPrefixedText();
+ $documents[$mkey]['language'] = $language;
+ $ret[] = $documents[$mkey];
+ }
+
+ return [ $ret, $offsets ];
+ }
+
+ /**
+ * @return array
+ */
+ public function getAvailableFilters() {
+ return [
+ 'translated',
+ 'fuzzy',
+ 'untranslated'
+ ];
+ }
+
+ public function getTotalHits() {
+ return $this->total;
+ }
+
+ public function getResultSet() {
+ return $this->resultset;
+ }
+}
diff --git a/MLEB/Translate/ttmserver/FuzzyLikeThis.php b/MLEB/Translate/ttmserver/FuzzyLikeThis.php
new file mode 100644
index 00000000..143b3222
--- /dev/null
+++ b/MLEB/Translate/ttmserver/FuzzyLikeThis.php
@@ -0,0 +1,222 @@
+<?php
+/**
+ * NOTE: the following class has been copied from elastica 2.3.1 :
+ * https://github.com/ruflin/Elastica/blob/2.3.1/lib/Elastica/Query/FuzzyLikeThis.php
+ * (few modifications have been made to comply with phpcs rules used by this extension)
+ * It is intended to be used as a temporary workaround with the wmf extra
+ * elasticsearch plugin with elasticsearch 2.x.
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014 Nicolas Ruflin
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * (c.f. https://github.com/ruflin/Elastica/blob/2.3.1/LICENSE.txt)
+ *
+ * @file
+ * @license MIT
+ * @ingroup TTMServer
+ */
+
+/**
+ * Fuzzy Like This query.
+ *
+ * @author Raul Martinez, Jr <juneym@gmail.com>
+ *
+ * @link https://www.elastic.co/guide/en/elasticsearch/reference/1.7/query-dsl-flt-query.html
+ *
+ * @since 2016.05
+ * @ingroup TTMServer
+ */
+class FuzzyLikeThis extends \Elastica\Query\AbstractQuery {
+ // phpcs:disable PSR2.Classes.PropertyDeclaration.Underscore
+ /**
+ * Field names.
+ *
+ * @var array Field names
+ */
+ protected $_fields = [];
+
+ /**
+ * Like text.
+ *
+ * @var string Like text
+ */
+ protected $_likeText = '';
+
+ /**
+ * Ignore term frequency.
+ *
+ * @var bool ignore term frequency
+ */
+ protected $_ignoreTF = false;
+
+ /**
+ * Max query terms value.
+ *
+ * @var int Max query terms value
+ */
+ protected $_maxQueryTerms = 25;
+
+ /**
+ * fuzziness.
+ *
+ * @var int fuzziness
+ */
+ protected $_fuzziness = 2;
+
+ /**
+ * Prefix Length.
+ *
+ * @var int Prefix Length
+ */
+ protected $_prefixLength = 0;
+
+ /**
+ * Analyzer.
+ *
+ * @var string Analyzer
+ */
+ protected $_analyzer;
+ // phpcs:enable
+
+ /**
+ * Adds field to flt query.
+ *
+ * @param array $fields Field names
+ *
+ * @return $this
+ */
+ public function addFields( array $fields ) {
+ $this->_fields = $fields;
+
+ return $this;
+ }
+
+ /**
+ * Set the "like_text" value.
+ *
+ * @param string $text
+ *
+ * @return $this
+ */
+ public function setLikeText( $text ) {
+ $text = trim( $text );
+ $this->_likeText = $text;
+
+ return $this;
+ }
+
+ /**
+ * Set the "ignore_tf" value (ignore term frequency).
+ *
+ * @param bool $ignoreTF
+ *
+ * @return $this
+ */
+ public function setIgnoreTF( $ignoreTF ) {
+ $this->_ignoreTF = (bool)$ignoreTF;
+
+ return $this;
+ }
+
+ /**
+ * Set the minimum similarity.
+ *
+ * @param int $value
+ *
+ * @return $this
+ */
+ public function setFuzziness( $value ) {
+ $value = (int)$value;
+ $this->_fuzziness = $value;
+
+ return $this;
+ }
+
+ /**
+ * Set Prefix Length.
+ *
+ * @param int $value Prefix length
+ *
+ * @return $this
+ */
+ public function setPrefixLength( $value ) {
+ $this->_prefixLength = (int)$value;
+
+ return $this;
+ }
+
+ /**
+ * Set max_query_terms.
+ *
+ * @param int $value Max query terms value
+ *
+ * @return $this
+ */
+ public function setMaxQueryTerms( $value ) {
+ $this->_maxQueryTerms = (int)$value;
+
+ return $this;
+ }
+
+ /**
+ * Set analyzer.
+ *
+ * @param string $text Analyzer text
+ *
+ * @return $this
+ */
+ public function setAnalyzer( $text ) {
+ $text = trim( $text );
+ $this->_analyzer = $text;
+
+ return $this;
+ }
+
+ /**
+ * Converts fuzzy like this query to array.
+ *
+ * @return array Query array
+ *
+ * @see \Elastica\Query\AbstractQuery::toArray()
+ */
+ public function toArray() {
+ if ( !empty( $this->_fields ) ) {
+ $args['fields'] = $this->_fields;
+ }
+
+ if ( !empty( $this->_analyzer ) ) {
+ $args['analyzer'] = $this->_analyzer;
+ }
+
+ $args['fuzziness'] = ( $this->_fuzziness > 0 ) ? $this->_fuzziness : 0;
+
+ $args['like_text'] = $this->_likeText;
+ $args['prefix_length'] = $this->_prefixLength;
+ $args['ignore_tf'] = $this->_ignoreTF;
+ $args['max_query_terms'] = $this->_maxQueryTerms;
+
+ $data = parent::toArray();
+ $args = array_merge( $args, $data['fuzzy_like_this'] );
+
+ return [ 'fuzzy_like_this' => $args ];
+ }
+}
diff --git a/MLEB/Translate/utils/ArrayFlattener.php b/MLEB/Translate/utils/ArrayFlattener.php
new file mode 100644
index 00000000..c5e61769
--- /dev/null
+++ b/MLEB/Translate/utils/ArrayFlattener.php
@@ -0,0 +1,297 @@
+<?php
+/**
+ * Flattens message arrays for further processing. Supports parsing CLDR
+ * plural messages and converting them into MediaWiki's {{PLURAL}} syntax
+ * in a single message.
+ *
+ * @file
+ * @author Niklas Laxström
+ * @author Erik Moeller
+ * @license GPL-2.0-or-later
+ * @since 2016.01
+ */
+
+class ArrayFlattener {
+ protected $sep;
+ protected $parseCLDRPlurals;
+
+ // For CLDR pluralization rules
+ protected static $pluralWords = [
+ 'zero' => 1,
+ 'one' => 1,
+ 'many' => 1,
+ 'few' => 1,
+ 'other' => 1,
+ 'two' => 1
+ ];
+
+ public function __construct( $sep = '.', $parseCLDRPlurals = false ) {
+ $this->sep = $sep;
+ $this->parseCLDRPlurals = $parseCLDRPlurals;
+ }
+
+ /**
+ * Flattens multidimensional array.
+ *
+ * @param array $unflat Array of messages
+ * @return array
+ */
+ public function flatten( array $unflat ) {
+ $flat = [];
+
+ foreach ( $unflat as $key => $value ) {
+ if ( !is_array( $value ) ) {
+ $flat[$key] = $value;
+ continue;
+ }
+
+ $plurals = false;
+ if ( $this->parseCLDRPlurals ) {
+ $plurals = $this->flattenCLDRPlurals( $value );
+ }
+
+ if ( $this->parseCLDRPlurals && $plurals ) {
+ $flat[$key] = $plurals;
+ } else {
+ $temp = [];
+ foreach ( $value as $subKey => $subValue ) {
+ $newKey = "$key{$this->sep}$subKey";
+ $temp[$newKey] = $subValue;
+ }
+ $flat += $this->flatten( $temp );
+ }
+
+ // Can as well keep only one copy around.
+ unset( $unflat[$key] );
+ }
+
+ return $flat;
+ }
+
+ /**
+ * Flattens arrays that contain CLDR plural keywords into single values using
+ * MediaWiki's plural syntax.
+ *
+ * @param array $messages Array of messages
+ *
+ * @throws MWException
+ * @return bool|string
+ */
+ public function flattenCLDRPlurals( $messages ) {
+ $pluralKeys = false;
+ $nonPluralKeys = false;
+ foreach ( $messages as $key => $value ) {
+ if ( is_array( $value ) ) {
+ // Plurals can only happen in the lowest level of the structure
+ return false;
+ }
+
+ // Check if we find any reserved plural keyword
+ if ( isset( self::$pluralWords[$key] ) ) {
+ $pluralKeys = true;
+ } else {
+ $nonPluralKeys = true;
+ }
+ }
+
+ // No plural keys at all, we can skip
+ if ( !$pluralKeys ) {
+ return false;
+ }
+
+ // Mixed plural keys with other keys, should not happen
+ if ( $nonPluralKeys ) {
+ $keys = implode( ', ', array_keys( $messages ) );
+ throw new MWException( "Reserved plural keywords mixed with other keys: $keys." );
+ }
+
+ $pls = '{{PLURAL';
+ foreach ( $messages as $key => $value ) {
+ if ( $key === 'other' ) {
+ continue;
+ }
+
+ $pls .= "|$key=$value";
+ }
+
+ // Put the "other" alternative last, without other= prefix.
+ $other = isset( $messages['other'] ) ? '|' . $messages['other'] : '';
+ $pls .= "$other}}";
+
+ return $pls;
+ }
+
+ /**
+ * Performs the reverse operation of flatten.
+ *
+ * @param array $flat Array of messages
+ * @return array
+ */
+ public function unflatten( $flat ) {
+ $unflat = [];
+
+ if ( $this->parseCLDRPlurals ) {
+ $unflattenedPlurals = [];
+ foreach ( $flat as $key => $value ) {
+ $plurals = false;
+ if ( !is_array( $value ) ) {
+ $plurals = $this->unflattenCLDRPlurals( $key, $value );
+ }
+ if ( $plurals ) {
+ $unflattenedPlurals += $plurals;
+ } else {
+ $unflattenedPlurals[$key] = $value;
+ }
+ }
+ $flat = $unflattenedPlurals;
+ }
+
+ foreach ( $flat as $key => $value ) {
+ $path = explode( $this->sep, $key );
+ if ( count( $path ) === 1 ) {
+ $unflat[$key] = $value;
+ continue;
+ }
+
+ $pointer = &$unflat;
+ do {
+ /// Extract the level and make sure it exists.
+ $level = array_shift( $path );
+ if ( !isset( $pointer[$level] ) ) {
+ $pointer[$level] = [];
+ }
+
+ /// Update the pointer to the new reference.
+ $tmpPointer = &$pointer[$level];
+ unset( $pointer );
+ $pointer = &$tmpPointer;
+ unset( $tmpPointer );
+
+ /// If next level is the last, add it into the array.
+ if ( count( $path ) === 1 ) {
+ $lastKey = array_shift( $path );
+ $pointer[$lastKey] = $value;
+ }
+ } while ( count( $path ) );
+ }
+
+ return $unflat;
+ }
+
+ /**
+ * Converts the MediaWiki plural syntax to array of CLDR style plurals
+ *
+ * @param string $key Message key prefix
+ * @param string $message The plural string
+ *
+ * @return bool|array
+ */
+ public function unflattenCLDRPlurals( $key, $message ) {
+ // Quick escape.
+ if ( strpos( $message, '{{PLURAL' ) === false ) {
+ return false;
+ }
+
+ /*
+ * Replace all variables with placeholders. Possible source of bugs
+ * if other characters that given below are used.
+ */
+ $regex = '~\{[a-zA-Z_-]+}~';
+ $placeholders = [];
+ $match = [];
+
+ while ( preg_match( $regex, $message, $match ) ) {
+ $uniqkey = TranslateUtils::getPlaceholder();
+ $placeholders[$uniqkey] = $match[0];
+ $search = preg_quote( $match[0], '~' );
+ $message = preg_replace( "~$search~", $uniqkey, $message );
+ }
+
+ // Then replace (possible multiple) plural instances into placeholders.
+ $regex = '~\{\{PLURAL\|(.*?)}}~s';
+ $matches = [];
+ $match = [];
+
+ while ( preg_match( $regex, $message, $match ) ) {
+ $uniqkey = TranslateUtils::getPlaceholder();
+ $matches[$uniqkey] = $match;
+ $message = preg_replace( $regex, $uniqkey, $message, 1 );
+ }
+
+ // No plurals, should not happen.
+ if ( !count( $matches ) ) {
+ return false;
+ }
+
+ // The final array of alternative plurals forms.
+ $alts = [];
+
+ /*
+ * Then loop trough each plural block and replacing the placeholders
+ * to construct the alternatives. Produces invalid output if there is
+ * multiple plural bocks which don't have the same set of keys.
+ */
+ $pluralChoice = implode( '|', array_keys( self::$pluralWords ) );
+ $regex = "~($pluralChoice)\s*=\s*(.+)~s";
+ foreach ( $matches as $ph => $plu ) {
+ $forms = explode( '|', $plu[1] );
+
+ foreach ( $forms as $form ) {
+ if ( $form === '' ) {
+ continue;
+ }
+
+ $match = [];
+ if ( preg_match( $regex, $form, $match ) ) {
+ $formWord = "$key{$this->sep}{$match[1]}";
+ $value = $match[2];
+ } else {
+ $formWord = "$key{$this->sep}other";
+ $value = $form;
+ }
+
+ if ( !isset( $alts[$formWord] ) ) {
+ $alts[$formWord] = $message;
+ }
+
+ $string = $alts[$formWord];
+ $alts[$formWord] = str_replace( $ph, $value, $string );
+ }
+ }
+
+ // Replace other variables.
+ foreach ( $alts as &$value ) {
+ $value = str_replace( array_keys( $placeholders ), array_values( $placeholders ), $value );
+ }
+
+ if ( !isset( $alts["$key{$this->sep}other"] ) ) {
+ wfWarn( "Other not set for key $key" );
+ }
+
+ return $alts;
+ }
+
+ /**
+ * Compares two strings for equal content, taking PLURAL expansion into account.
+ *
+ * @param string $a
+ * @param string $b
+ * @return bool Whether two strings are equal
+ */
+ public function compareContent( $a, $b ) {
+ if ( !$this->parseCLDRPlurals ) {
+ return $a === $b;
+ }
+
+ $a2 = $this->unflattenCLDRPlurals( 'prefix', $a );
+ $b2 = $this->unflattenCLDRPlurals( 'prefix', $b );
+
+ // Fall back to regular comparison if parsing fails.
+ if ( $a2 === false || $b2 === false ) {
+ return $a === $b;
+ }
+
+ // Require key-value pairs to match, but ignore order and types (all should be strings).
+ return $a2 == $b2;
+ }
+}
diff --git a/MLEB/Translate/utils/ExternalMessageSourceStateImporter.php b/MLEB/Translate/utils/ExternalMessageSourceStateImporter.php
new file mode 100644
index 00000000..495c3fd7
--- /dev/null
+++ b/MLEB/Translate/utils/ExternalMessageSourceStateImporter.php
@@ -0,0 +1,84 @@
+<?php
+
+/**
+ * Finds external changes for file based message groups.
+ *
+ * @author Niklas Laxström
+ * @license GPL-2.0-or-later
+ * @since 2016.02
+ */
+class ExternalMessageSourceStateImporter {
+
+ public function importSafe( $changeData ) {
+ $processed = [];
+ $skipped = [];
+ $jobs = [];
+ $jobs[] = MessageIndexRebuildJob::newJob();
+
+ foreach ( $changeData as $groupId => $changesForGroup ) {
+ $group = MessageGroups::getGroup( $groupId );
+ if ( !$group ) {
+ unset( $changeData[$groupId] );
+ continue;
+ }
+
+ $processed[$groupId] = 0;
+
+ foreach ( $changesForGroup as $languageCode => $changesForLanguage ) {
+ if ( !self::isSafe( $changesForLanguage ) ) {
+ $skipped[$groupId] = true;
+ continue;
+ }
+
+ if ( !isset( $changesForLanguage['addition'] ) ) {
+ continue;
+ }
+
+ foreach ( $changesForLanguage['addition'] as $addition ) {
+ $namespace = $group->getNamespace();
+ $name = "{$addition['key']}/$languageCode";
+
+ $title = Title::makeTitleSafe( $namespace, $name );
+ if ( !$title ) {
+ wfWarn( "Invalid title for group $groupId key {$addition['key']}" );
+ continue;
+ }
+
+ $jobs[] = MessageUpdateJob::newJob( $title, $addition['content'] );
+ $processed[$groupId]++;
+ }
+
+ unset( $changeData[$groupId][$languageCode] );
+
+ $cache = new MessageGroupCache( $groupId, $languageCode );
+ $cache->create();
+ }
+ }
+
+ // Remove groups where everything was imported
+ $changeData = array_filter( $changeData );
+ // Remove groups with no imports
+ $processed = array_filter( $processed );
+
+ $name = 'unattended';
+ $file = MessageChangeStorage::getCdbPath( $name );
+ MessageChangeStorage::writeChanges( $changeData, $file );
+ JobQueueGroup::singleton()->push( $jobs );
+
+ return [
+ 'processed' => $processed,
+ 'skipped' => $skipped,
+ 'name' => $name,
+ ];
+ }
+
+ protected static function isSafe( array $changesForLanguage ) {
+ foreach ( array_keys( $changesForLanguage ) as $changeType ) {
+ if ( $changeType !== 'addition' ) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/MLEB/Translate/utils/MessageChangeStorage.php b/MLEB/Translate/utils/MessageChangeStorage.php
new file mode 100644
index 00000000..5c23a3a6
--- /dev/null
+++ b/MLEB/Translate/utils/MessageChangeStorage.php
@@ -0,0 +1,52 @@
+<?php
+/**
+ * Handles storage of message change files.
+ *
+ * @author Niklas Laxström
+ * @license GPL-2.0-or-later
+ * @since 2016.02
+ * @file
+ */
+
+class MessageChangeStorage {
+ const DEFAULT_NAME = 'default';
+
+ /**
+ * Writes change array as a serialized file.
+ *
+ * @param array $array Array of changes as returned by processGroup
+ * indexed by message group id.
+ * @param string $file Which file to use.
+ */
+ public static function writeChanges( $array, $file ) {
+ $cache = \Cdb\Writer::open( $file );
+ $keys = array_keys( $array );
+ $cache->set( '#keys', serialize( $keys ) );
+
+ foreach ( $array as $key => $value ) {
+ $value = serialize( $value );
+ $cache->set( $key, $value );
+ }
+ $cache->close();
+ }
+
+ /**
+ * Validate a name.
+ *
+ * @param string $name Which file to use.
+ * @return bool
+ */
+ public static function isValidCdbName( $name ) {
+ return preg_match( '/^[a-zA-Z_-]{1,100}$/', $name );
+ }
+
+ /**
+ * Get a full path to file in a known location.
+ *
+ * @param string $name Which file to use.
+ * @return string
+ */
+ public static function getCdbPath( $name ) {
+ return TranslateUtils::cacheFile( "messagechanges.$name.cdb" );
+ }
+}
diff --git a/MLEB/Translate/webservices/CaighdeanWebService.php b/MLEB/Translate/webservices/CaighdeanWebService.php
new file mode 100644
index 00000000..cb472d93
--- /dev/null
+++ b/MLEB/Translate/webservices/CaighdeanWebService.php
@@ -0,0 +1,93 @@
+<?php
+/**
+ * Contains a class for querying external translation service.
+ *
+ * @file
+ * @author Niklas Laxström
+ * @license GPL-2.0-or-later
+ */
+
+/**
+ * Implements support Caighdean translator api.
+ * @see https://github.com/kscanne/caighdean/blob/master/API.md
+ * @ingroup TranslationWebService
+ * @since 2017.04
+ */
+class CaighdeanWebService extends TranslationWebService {
+ public function getType() {
+ return 'mt';
+ }
+
+ public function mapCode( $code ) {
+ return $code;
+ }
+
+ protected function doPairs() {
+ $pairs = [
+ 'gd' => [ 'ga' => true ],
+ 'gv' => [ 'ga' => true ],
+ ];
+
+ return $pairs;
+ }
+
+ protected function getQuery( $text, $from, $to ) {
+ if ( !isset( $this->config['url'] ) ) {
+ throw new TranslationWebServiceConfigurationException( '`url` not set in configuration' );
+ }
+
+ $text = trim( $text );
+ if ( $text === '' ) {
+ throw new TranslationWebServiceInvalidInputException( 'Input is empty' );
+ }
+
+ $data = wfArrayToCgi( [
+ 'foinse' => $from,
+ 'teacs' => $text,
+ ] );
+
+ // Maximum payload is 16 KiB. Based ont testing 16000 bytes is safe by leaving 224
+ // bytes for other things.
+ if ( strlen( $data ) > 16000 ) {
+ throw new TranslationWebServiceInvalidInputException( 'Input is over 16000 bytes long' );
+ }
+
+ return TranslationQuery::factory( $this->config['url'] )
+ ->timeout( $this->config['timeout'] )
+ ->postWithData( $data )
+ ->attachProcessingInstructions( $text );
+ }
+
+ protected function parseResponse( TranslationQueryResponse $reply ) {
+ $body = $reply->getBody();
+ $response = FormatJson::decode( $body );
+ if ( !is_array( $response ) ) {
+ throw new TranslationWebServiceException( 'Invalid json: ' . serialize( $body ) );
+ }
+
+ $text = '';
+ $originalText = $reply->getQuery()->getProcessingInstructions();
+ foreach ( $response as list( $sourceToken, $targetToken ) ) {
+ $separator = ' ';
+ $pos = strpos( $originalText, $sourceToken );
+ // Try to keep the effects local. If we fail to match at token, we could accidentally
+ // scan very far ahead in the text, find a false match and not find matches for all
+ // of the tokens in the between.
+ if ( $pos !== false && $pos < 50 ) {
+ // Remove the portion of text we have processed. $pos should be zero, unless
+ // we failed to match something earlier.
+ $originalText = substr( $originalText, $pos + strlen( $sourceToken ) );
+ if ( preg_match( '/^\s+/', $originalText, $match ) ) {
+ $separator = $match[ 0 ];
+ $originalText = substr( $originalText, strlen( $separator ) );
+ } else {
+ $separator = '';
+ }
+ }
+
+ $text .= $targetToken . $separator;
+ }
+
+ return $text;
+ }
+}
diff --git a/MLEB/Translate/webservices/QueryAggregator.php b/MLEB/Translate/webservices/QueryAggregator.php
new file mode 100644
index 00000000..6cc6465b
--- /dev/null
+++ b/MLEB/Translate/webservices/QueryAggregator.php
@@ -0,0 +1,89 @@
+<?php
+/**
+ * Web service utility class.
+ *
+ * @file
+ * @author Niklas Laxström
+ * @license GPL-2.0-or-later
+ */
+
+/**
+ * Runs multiple web service queries asynchronously to save time.
+ *
+ * @ingroup TranslationWebService
+ * @since 2015.02
+ */
+class QueryAggregator {
+ protected $queries = [];
+ protected $responses = [];
+ protected $timeout = 0;
+ protected $hasRun = false;
+
+ /**
+ * Register a query to be run.
+ * @param TranslationQuery $query
+ * @return mixed Query id that can be used to fetch results.
+ */
+ public function addQuery( TranslationQuery $query ) {
+ $this->queries[] = $query;
+
+ $this->timeout = max( $query->getTimeout(), $this->timeout );
+ return count( $this->queries ) - 1;
+ }
+
+ /**
+ * Returns a response for a query.
+ * @param mixed $id Query id.
+ * @return TranslationQueryResponse
+ * @throws RuntimeException if called before run() has been called.
+ */
+ public function getResponse( $id ) {
+ if ( !$this->hasRun ) {
+ throw new RuntimeException( 'Tried to get response before queries ran' );
+ }
+
+ return TranslationQueryResponse::newFromMultiHttp(
+ $this->responses[$id],
+ $this->queries[$id]
+ );
+ }
+
+ /**
+ * Runs all the queries.
+ */
+ public function run() {
+ global $wgSitename;
+
+ $version = TRANSLATE_VERSION;
+
+ $http = new MultiHttpClient( [
+ 'reqTimeout' => $this->timeout,
+ 'connTimeout' => 3,
+ 'userAgent' => "MediaWiki Translate extension $version for $wgSitename"
+ ] );
+ $responses = $http->runMulti( $this->getMultiHttpQueries( $this->queries ) );
+ foreach ( $responses as $index => $response ) {
+ $this->responses[$index] = $response;
+ }
+ $this->hasRun = true;
+ }
+
+ /**
+ * Formats queries for format used by MultiHttpClient class.
+ * @param TranslationQuery[] $queries
+ * @return array[]
+ */
+ protected function getMultiHttpQueries( $queries ) {
+ $converter = function ( TranslationQuery $q ) {
+ return [
+ 'url' => $q->getUrl(),
+ 'method' => $q->getMethod(),
+ 'query' => $q->getQueryParameters(),
+ 'body' => $q->getBody(),
+ 'headers' => $q->getHeaders(),
+ ];
+ };
+
+ return array_map( $converter, $queries );
+ }
+}
diff --git a/MLEB/Translate/webservices/QueryAggregatorAware.php b/MLEB/Translate/webservices/QueryAggregatorAware.php
new file mode 100644
index 00000000..c5c0e9a5
--- /dev/null
+++ b/MLEB/Translate/webservices/QueryAggregatorAware.php
@@ -0,0 +1,17 @@
+<?php
+/**
+ * Web service utility interface.
+ *
+ * @file
+ * @author Niklas Laxström
+ * @license GPL-2.0-or-later
+ */
+
+/**
+ * Interface for classes that want to use QueryAggregator.
+ * @since 2015.12
+ */
+interface QueryAggregatorAware {
+ public function setQueryAggregator( QueryAggregator $aggregator );
+ public function populateQueries();
+}
diff --git a/MLEB/Translate/webservices/RESTBaseWebService.php b/MLEB/Translate/webservices/RESTBaseWebService.php
new file mode 100644
index 00000000..2ff80c43
--- /dev/null
+++ b/MLEB/Translate/webservices/RESTBaseWebService.php
@@ -0,0 +1,80 @@
+<?php
+/**
+ * Contains a class for querying external translation service.
+ *
+ * @file
+ * @author Niklas Laxström
+ * @license GPL-2.0-or-later
+ */
+
+/**
+ * Implements support for cxserver proxied through RESTBase
+ * @ingroup TranslationWebService
+ * @since 2017.10
+ */
+class RESTBaseWebService extends TranslationWebService {
+ public function getType() {
+ return 'mt';
+ }
+
+ protected function mapCode( $code ) {
+ return $code;
+ }
+
+ protected function doPairs() {
+ if ( !isset( $this->config['host'] ) ) {
+ throw new TranslationWebServiceConfigurationException( 'RESTBase host not set' );
+ }
+
+ $pairs = [];
+
+ $url = $this->config['host'] . '/rest_v1/transform/list/tool/mt/';
+ $json = Http::get(
+ $url,
+ [ $this->config['timeout'] ],
+ __METHOD__
+ );
+ $response = FormatJson::decode( $json, true );
+
+ if ( !is_array( $response ) ) {
+ $exception = 'Malformed reply from remote server: ' . $url . ' ' . (string)$json;
+ throw new TranslationWebServiceException( $exception );
+ }
+
+ foreach ( $response['Apertium'] as $source => $targets ) {
+ foreach ( $targets as $target ) {
+ $pairs[$source][$target] = true;
+ }
+ }
+
+ return $pairs;
+ }
+
+ protected function getQuery( $text, $from, $to ) {
+ if ( !isset( $this->config['host'] ) ) {
+ throw new TranslationWebServiceConfigurationException( 'RESTBase host not set' );
+ }
+
+ $text = trim( $text );
+ $text = $this->wrapUntranslatable( $text );
+ $url = $this->config['host'] . "/rest_v1/transform/html/from/$from/to/$to/Apertium";
+
+ return TranslationQuery::factory( $url )
+ ->timeout( $this->config['timeout'] )
+ ->postWithData( wfArrayToCgi( [ 'html' => $text ] ) );
+ }
+
+ protected function parseResponse( TranslationQueryResponse $reply ) {
+ $body = $reply->getBody();
+
+ $response = FormatJson::decode( $body );
+ if ( !is_object( $response ) ) {
+ throw new TranslationWebServiceException( 'Invalid json: ' . serialize( $body ) );
+ }
+
+ $text = $response->contents;
+ $text = $this->unwrapUntranslatable( $text );
+
+ return trim( $text );
+ }
+}
diff --git a/MLEB/Translate/webservices/TranslationQuery.php b/MLEB/Translate/webservices/TranslationQuery.php
new file mode 100644
index 00000000..5b6a8174
--- /dev/null
+++ b/MLEB/Translate/webservices/TranslationQuery.php
@@ -0,0 +1,105 @@
+<?php
+/**
+ * Contains code related to web services support.
+ *
+ * @file
+ * @author Niklas Laxström
+ * @license GPL-2.0-or-later
+ */
+
+/**
+ * Mutable objects that represents a HTTP(S) query.
+ * NB: Too lazy to make TranslationQueryFactory to make this class immutable.
+ * @since 2015.02
+ */
+class TranslationQuery {
+ protected $url;
+ protected $timeout = 0;
+ protected $method = 'GET';
+ protected $params = [];
+ protected $body;
+ protected $headers = [];
+
+ /**
+ * @var mixed Arbitrary data that is returned with TranslationQueryResponse
+ */
+ protected $instructions;
+
+ // URL is mandatory, so using it here
+ public static function factory( $url ) {
+ $obj = new TranslationQuery();
+ $obj->url = $url;
+ return $obj;
+ }
+
+ /**
+ * Make this a POST request with given data.
+ *
+ * @param string $data
+ * @return $this
+ */
+ public function postWithData( $data ) {
+ $this->method = 'POST';
+ $this->body = $data;
+ return $this;
+ }
+
+ public function queryParameters( array $params ) {
+ $this->params = $params;
+ return $this;
+ }
+
+ public function queryHeaders( array $headers ) {
+ $this->headers = $headers;
+ return $this;
+ }
+
+ public function timeout( $timeout ) {
+ $this->timeout = $timeout;
+ return $this;
+ }
+
+ /**
+ * Attach arbitrary data that is necessary to process the results.
+ * @param mixed $data
+ * @return self
+ * @since 2017.04
+ */
+ public function attachProcessingInstructions( $data ) {
+ $this->instructions = $data;
+ return $this;
+ }
+
+ public function getTimeout() {
+ return $this->timeout;
+ }
+
+ public function getUrl() {
+ return $this->url;
+ }
+
+ public function getMethod() {
+ return $this->method;
+ }
+
+ public function getQueryParameters() {
+ return $this->params;
+ }
+
+ public function getBody() {
+ return $this->body;
+ }
+
+ public function getHeaders() {
+ return $this->headers;
+ }
+
+ /**
+ * Get previously attached result processing instructions.
+ * @return mixed
+ * @since 2017.04
+ */
+ public function getProcessingInstructions() {
+ return $this->instructions;
+ }
+}
diff --git a/MLEB/Translate/webservices/TranslationQueryResponse.php b/MLEB/Translate/webservices/TranslationQueryResponse.php
new file mode 100644
index 00000000..13540eda
--- /dev/null
+++ b/MLEB/Translate/webservices/TranslationQueryResponse.php
@@ -0,0 +1,65 @@
+<?php
+/**
+ * Contains code related to web services support.
+ *
+ * @file
+ * @author Niklas Laxström
+ * @license GPL-2.0-or-later
+ */
+
+/**
+ * Value object that represents a HTTP(S) query response.
+ * @since 2015.02
+ */
+class TranslationQueryResponse {
+ protected $code;
+ protected $reason;
+ protected $headers;
+ protected $body;
+ protected $error;
+
+ /**
+ * @var TranslationQuery
+ */
+ protected $query;
+
+ protected function __construct() {
+ }
+
+ public static function newFromMultiHttp( array $data, TranslationQuery $query ) {
+ $response = $data['response'];
+ $obj = new TranslationQueryResponse();
+ $obj->code = (int)$response['code'];
+ $obj->reason = $response['reason'];
+ $obj->headers = $response['headers'];
+ $obj->body = $response['body'];
+ $obj->error = $response['error'];
+ $obj->query = $query;
+ return $obj;
+ }
+
+ public function getStatusCode() {
+ return $this->code;
+ }
+
+ public function getStatusMessage() {
+ if ( $this->code === 0 ) {
+ return $this->error;
+ } else {
+ return $this->reason;
+ }
+ }
+
+ public function getBody() {
+ return $this->body;
+ }
+
+ /**
+ * Get the TranslationQuery that was made for this request.
+ * @return TranslationQuery
+ * @since 2017.04
+ */
+ public function getQuery() {
+ return $this->query;
+ }
+}
diff --git a/MLEB/Translate/webservices/TranslationWebServiceConfigurationException.php b/MLEB/Translate/webservices/TranslationWebServiceConfigurationException.php
new file mode 100644
index 00000000..509224a4
--- /dev/null
+++ b/MLEB/Translate/webservices/TranslationWebServiceConfigurationException.php
@@ -0,0 +1,18 @@
+<?php
+/**
+ * Contains code related to web service support.
+ *
+ * @file
+ * @author Niklas Laxström
+ * @license GPL-2.0-or-later
+ */
+
+/**
+ * Used to signal a configuration mistake in an external web service. This is in
+ * contrast to TranslationWebServiceException that signals a failure in the web
+ * service itself.
+ * @since 2017.04
+ * @ingroup TranslationWebService
+ */
+class TranslationWebServiceConfigurationException extends Exception {
+}
diff --git a/MLEB/Translate/webservices/TranslationWebServiceInvalidInputException.php b/MLEB/Translate/webservices/TranslationWebServiceInvalidInputException.php
new file mode 100644
index 00000000..e8ef9d08
--- /dev/null
+++ b/MLEB/Translate/webservices/TranslationWebServiceInvalidInputException.php
@@ -0,0 +1,20 @@
+<?php
+/**
+ * Contains code related to web service support.
+ *
+ * @file
+ * @author Niklas Laxström
+ * @license GPL-2.0-or-later
+ */
+
+/**
+ * Used to signal that the requested input is rejected and cannot be used with
+ * an external web service. This is in contrast to a failure in the web service
+ * itself that is not in our control. Most common case for this is input that is
+ * too long.
+ * service itself.
+ * @since 2017.04
+ * @ingroup TranslationWebService
+ */
+class TranslationWebServiceInvalidInputException extends Exception {
+}