diff --git a/4dev/locale/en_US.admin.UTF-8.po b/4dev/locale/en_US.admin.UTF-8.po index e6241638..b6c191f3 100644 --- a/4dev/locale/en_US.admin.UTF-8.po +++ b/4dev/locale/en_US.admin.UTF-8.po @@ -3,21 +3,22 @@ # CREATED: 2005/08/09 # SHORT DESCRIPTION: # Backned English Messages file for gettext -# to craete: msgfmt -o ja.mo messages_en.po -# HISTORY: +# to craete: msgfmt -o # ********************************************************************/ msgid "" msgstr "" -"Project-Id-Version: Project Version\n" +"Project-Id-Version: en_US.UTF-8 LC_MESSAGES admin\n" "Report-Msgid-Bugs-To: clemens.schwaighofer@egplusww.com\n" "POT-Creation-Date: 2018-03-28 10:40+0900\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: \n" +"Last-Translator: clemens.schwaighofer@egplusww.co\n" "Language-Team: E-GRAPHICS COMMUNICATIONS Japan \n" +"Language: en_US\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" msgid "Year" msgstr "Year" @@ -28,6 +29,24 @@ msgstr "Month" msgid "INPUT TEST" msgstr "OUTPUT TEST ADMIN EN" +# testing multi +msgid "single" +msgid_plural "multi" +msgstr[0] "Multi Admin en_US 0" +msgstr[1] "Multi Admin en_US 1" +msgstr[2] "Multi Admin en_US 2" + +msgctxt "month name" +msgid "May" +msgstr "May Admin en_US" + +msgctxt "month name" +msgid "single" +msgid_plural "multi" +msgstr[0] "Multi Admin month en_US 0" +msgstr[1] "Multi Admin month en_US 1" +msgstr[2] "Multi Admin month en_US 2" + msgid "I should be translated" msgstr "I should be translated: I WAS TRANSLATED" diff --git a/4dev/locale/en_US.frontend.UTF-8.po b/4dev/locale/en_US.frontend.UTF-8.po index 6e9f76f2..765359d0 100644 --- a/4dev/locale/en_US.frontend.UTF-8.po +++ b/4dev/locale/en_US.frontend.UTF-8.po @@ -1,2 +1,24 @@ +# to craete: msgfmt -o + +msgid "" +msgstr "" +"Project-Id-Version: en_US.UTF-8 LC_MESSAGES frontend\n" +"Report-Msgid-Bugs-To: clemens.schwaighofer@egplusww.com\n" +"POT-Creation-Date: 2018-03-28 10:40+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: clemens.schwaighofer@egplusww.co\n" +"Language-Team: E-GRAPHICS COMMUNICATIONS Japan \n" +"Language: en_US\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + msgid "INPUT TEST" msgstr "OUTPUT TEST FRONTEND EN" + +msgid "single" +msgid_plural "multi" +msgstr[0] "Multi frontend en_US 0" +msgstr[1] "Multi frontend en_US 1" +msgstr[2] "Multi frontend en_US 2" diff --git a/4dev/locale/ja_JP.admin.UTF-8.po b/4dev/locale/ja_JP.admin.UTF-8.po index 4d49c4a9..35e08f80 100644 --- a/4dev/locale/ja_JP.admin.UTF-8.po +++ b/4dev/locale/ja_JP.admin.UTF-8.po @@ -3,21 +3,22 @@ # CREATED: 2018/03/28 # SHORT DESCRIPTION: # Backend Japanese Messages file for gettext -# to craete: msgfmt -o ja.mo messages_ja.po -# HISTORY: +# to craete: msgfmt -o # ********************************************************************/ msgid "" msgstr "" -"Project-Id-Version: Project Version\n" +"Project-Id-Version: ja_JP.UTF-8 LC_MESSAGES admin\n" "Report-Msgid-Bugs-To: clemens.schwaighofer@egplusww.com\n" "POT-Creation-Date: 2018-03-28 10:40+0900\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: \n" +"Last-Translator: clemens.schwaighofer@egplusww.com\n" "Language-Team: E-GRAPHICS COMMUNICATIONS Japan \n" +"Language: ja\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" msgid "Yes" msgstr "はい" @@ -65,6 +66,13 @@ msgstr "日" msgid "INPUT TEST" msgstr "OUTPUT TEST ADMIN JA" +# testing multi +msgid "single" +msgid_plural "multi" +msgstr[0] "Multi Admin ja_JP 0" +msgstr[1] "Multi Admin ja_JP 1" +msgstr[2] "Multi Admin ja_JP 2" + # login string msgid "Hello %s" msgstr "こにちは %s" diff --git a/4dev/locale/ja_JP.frontend.UTF-8.po b/4dev/locale/ja_JP.frontend.UTF-8.po index 91c739b2..049707a1 100644 --- a/4dev/locale/ja_JP.frontend.UTF-8.po +++ b/4dev/locale/ja_JP.frontend.UTF-8.po @@ -1,2 +1,8 @@ +# to craete: msgfmt -o + +msgid "" +msgstr "" +"Plural-Forms: nplurals=2; plural=(n != 1)\n" + msgid "INPUT TEST" msgstr "OUTPUT TEST FRONTEND JA" diff --git a/4dev/tests/CoreLibsLanguageL10nTest.php b/4dev/tests/CoreLibsLanguageL10nTest.php index 57adf084..ac866c06 100644 --- a/4dev/tests/CoreLibsLanguageL10nTest.php +++ b/4dev/tests/CoreLibsLanguageL10nTest.php @@ -1,4 +1,4 @@ -assertTrue(true, 'Language L10n Tests not implemented'); - $this->markTestIncomplete( - 'Language\L10n Tests have not yet been implemented' + define('DEFAULT_LANG', 'en_US'); + // default web page encoding setting + define('DEFAULT_ENCODING', 'UTF-8'); + // default lang + encoding + define('DEFAULT_LOCALE', 'en_US.UTF-8'); + // site + define('SITE_LANG', DEFAULT_LANG); + // just set + define('BASE', str_replace('/configs', '', __DIR__) . DIRECTORY_SEPARATOR); + define('INCLUDES', 'includes' . DIRECTORY_SEPARATOR); + define('LANG', 'lang' . DIRECTORY_SEPARATOR); + define('LOCALE', 'locale' . DIRECTORY_SEPARATOR); + define('CONTENT_PATH', 'frontend' . DIRECTORY_SEPARATOR); + } + + /** + * get instance self type check + * + * @covers ::getInstance + * @testdox check that getInstance() returns valid instance + * + * @return void + */ + public function testGetInstance(): void + { + $l10n_obj = \CoreLibs\Language\L10n::getInstance(); + $this->assertIsObject( + $l10n_obj + ); + $this->assertInstanceOf( + '\CoreLibs\Language\L10n', + $l10n_obj + ); + } + + /** + * get current translator class type check + * + * @covers ::getTranslatorClass + * @testdox check that getTranslatorClass() returns valid instance + * + * @return void + */ + public function testGetTranslatorClass(): void + { + $l10n = new \CoreLibs\Language\L10n(); + $translator = $l10n->getTranslatorClass(); + $this->assertIsObject( + $translator + ); + $this->assertInstanceOf( + '\CoreLibs\Language\Core\GetTextReader', + $translator + ); + } + + /** + * provider for class load parameters + * + * @return array + */ + public function l10nObjectProvider(): array + { + return [ + // 0: locale + // 1: path + // 2: domain + // 3: legacy load (Default true) + // 4: locale expected + // 5: domain exepcted + // 6: context (null for none) + // 7: test string in + // 8: test translated + 'legacy load en' => [ + 'en_utf8', + null, + null, + null, + // + 'en_utf8', + '', + // + null, + 'Original', + 'Translated frontend en_US', + ], + 'legacy load ja' => [ + 'ja_utf8', + null, + null, + null, + 'ja_utf8', + '', + null, + 'Original', + 'Translated frontend ja_JP', + ], + // new style load + 'gettext load en' => [ + 'en_US.UTF-8', + __DIR__ . 'includes/locale/', + 'frontend', + false, + 'en_US.UTF-8', + 'frontend', + null, + 'Original', + 'Translated frontend en_US', + ], + 'gettext load en' => [ + 'en_US.UTF-8', + __DIR__ . 'includes/locale/', + 'frontend', + false, + 'en_US.UTF-8', + 'frontend', + 'context', + 'Original', + 'Original context frontend en_US', + ], + 'gettext load ja' => [ + 'ja_JP.UTF-8', + __DIR__ . 'includes/locale/', + 'admin', + false, + 'ja_JP.UTF-8', + 'admin', + null, + 'Original', + 'Translated admin ja_JP', + ], + // null set locale legacy + 'empty load legacy' => [ + null, + null, + null, + null, + '', + '', + null, + 'Original', + 'Original', + ], + 'empty load new ' => [ + '', + '', + '', + false, + '', + '', + null, + 'Original', + 'Original', + ] + ]; + } + + /** + * new class load test (basic test) + * + * @covers ::__construct + * @dataProvider l10nObjectProvider + * @testdox check l10n init with Locale $locale, Path $path, Domain $domain, Legacy: $legacy with $context [$_dataName] + * + * @param string|null $locale + * @param string|null $path + * @param string|null $domain + * @param bool|null $legacy + * @param string $locale_expected + * @param string $domain_expected + * @param ?string $context + * @param string $original + * @param string $translated + * @return void + */ + public function testL10nObject( + ?string $locale, + ?string $path, + ?string $domain, + ?bool $legacy, + string $locale_expected, + string $domain_expected, + ?string $context, + string $original, + string $translated, + ): void { + if ($locale === null) { + $l10n = new \CoreLibs\Language\L10n(); + } elseif ($path === null) { + $l10n = new \CoreLibs\Language\L10n($locale); + } elseif ($domain === null) { + $l10n = new \CoreLibs\Language\L10n($locale, $path); + } elseif ($legacy === null) { + $l10n = new \CoreLibs\Language\L10n($locale, $path, $domain); + } else { + $l10n = new \CoreLibs\Language\L10n($locale, $path, $domain, $legacy); + } + // print "LOC: " . $locale . ", " . $l10n->getLocale() . ", " . $locale_expected . "\n"; + // print "MO: " . $l10n->getMoFile() . "\n"; + $this->assertEquals( + $locale_expected, + $l10n->getLocale(), + 'Locale assert failed' + ); + $this->assertEquals( + $domain_expected, + $l10n->getDomain(), + 'Domain assert failed' + ); + if (empty($context)) { + $this->assertEquals( + $translated, + $l10n->__($original), + 'Translated string assert failed' + ); + } else { + $this->assertEquals( + $translated, + $l10n->__p($context, $original), + 'Translated string assert failed in context: ' . $context + ); + } + } + + // l10nReloadMOfile and getTranslator + // null init with loader + // loader with reload (change type) + + /** + * Undocumented function + * + * @return array + */ + public function getTranslatorProvider(): array + { + return [ + // 0: locale + // 1: path + // 2: domain + // 3: legacy flag + // 4: input string to translated + // 5: expected locale + // 6: expected domain + // 7: expected translation + // 8: change locale + // 9: change path + // 10: change domain + // 11: legacy flag + // 12: expected locale + // 13: expected domain + // 14: expected translation + 'legacy load and change (en->ja)' => [ + // set 0-3 + 'en_utf8', + null, + null, + null, + // status 4 + false, + // to translate 5 + 'Original', + // check setter 6-8 + 'en_utf8', + '', + 'Translated frontend en_US', + // set new 9-12 + 'ja_utf8', + null, + null, + null, + // status new 13 + false, + // check new setter 14-16 + 'ja_utf8', + '', + 'Translated frontend ja_JP', + ], + 'load and change (en->ja)' => [ + // set 0-3 + 'en_US.UTF-8', + __DIR__ . 'includes/locale/', + 'frontend', + false, + // status 4 + false, + // to translate 5 + 'Original', + // check setter 6-8 + 'en_US.UTF-8', + 'frontend', + 'Translated frontend en_US', + // set new 9-12 + 'ja_JP.UTF-8', + __DIR__ . 'includes/locale/', + 'frontend', + false, + // status new 13 + false, + // check new setter 14-16 + 'ja_JP.UTF-8', + 'frontend', + 'Translated frontend ja_JP', + ], + 'empty load and change to en' => [ + // set 0-3 + '', + '', + '', + false, + // status 4 + false, + // to translate 5 + 'Original', + // check setter 6-8 + '', + '', + 'Original', + // set new 9-12 + 'en_US.UTF-8', + __DIR__ . 'includes/locale/', + 'frontend', + false, + // status new 13 + false, + // check new setter 14-16 + 'en_US.UTF-8', + 'frontend', + 'Translated frontend en_US', + ] + ]; + } + + /** + * init check and connected change translation + * + * @covers ::getTranslator + * @covers ::l10nReloadMOfile + * @dataProvider getTranslatorProvider + * @testdox change locale from $locale and domain $domain to locale $locale_new and domain $domain_new [$_dataName] + * + * @param string|null $locale + * @param string|null $path + * @param string|null $domain + * @param bool|null $legacy + * @param bool $load_error + * @param string $original + * @param string $locale_expected_a + * @param string $domain_expected_a + * @param string $translated_a + * @param string|null $locale_new + * @param string|null $path_new + * @param string|null $domain_new + * @param bool|null $legacy_new + * @param bool $load_error_new + * @param string $locale_expected_b + * @param string $domain_expected_b + * @param string $translated_b + * @return void + */ + public function testGetTranslator( + // 0-3 + ?string $locale, + ?string $path, + ?string $domain, + ?bool $legacy, + // 4 + bool $load_error, + // 5 + string $original, + // 6-8 + string $locale_expected_a, + string $domain_expected_a, + string $translated_a, + // 9-12 + ?string $locale_new, + ?string $path_new, + ?string $domain_new, + ?bool $legacy_new, + // 13 + bool $load_error_new, + // 14-16 + string $locale_expected_b, + string $domain_expected_b, + string $translated_b, + ): void { + if ($locale === null) { + $l10n = new \CoreLibs\Language\L10n(); + } elseif ($path === null) { + $l10n = new \CoreLibs\Language\L10n($locale); + } elseif ($domain === null) { + $l10n = new \CoreLibs\Language\L10n($locale, $path); + } elseif ($legacy === null) { + $l10n = new \CoreLibs\Language\L10n($locale, $path, $domain); + } else { + $l10n = new \CoreLibs\Language\L10n($locale, $path, $domain, $legacy); + } + // print "LOC: " . $locale . ", " . $l10n->getLocale() . ", " . $locale_expected . "\n"; + // status check + $this->assertEquals( + $load_error, + $l10n->getLoadError(), + 'Legacy method load error init check' + ); + $this->assertEquals( + $locale_expected_a, + $l10n->getLocale(), + 'Locale init assert failed' + ); + $this->assertEquals( + $domain_expected_a, + $l10n->getDomain(), + 'Domain init assert failed' + ); + $this->assertEquals( + $translated_a, + $l10n->__($original), + 'Translated string init assert failed' + ); + + // do reload with legacy l10nReloadMOfile IF legacy is null or true + // use getTranslator if legacy is false + if ($legacy === null || $legacy === true) { + // there is no null/empty locale allowed directly + // if empty will set previous one + if ($path_new === null) { + $ret_status = $l10n->l10nReloadMOfile($locale_new); + } elseif ($domain_new === null) { + $ret_status = $l10n->l10nReloadMOfile($locale_new, $path_new); + } elseif ($legacy_new === null) { + $ret_status = $l10n->l10nReloadMOfile($locale_new, $path_new, $domain_new); + } else { + $ret_status = $l10n->l10nReloadMOfile($locale_new, $path_new, $domain_new, $legacy_new); + } + // status check + $this->assertEquals( + $load_error_new, + $l10n->getLoadError(), + 'Legacy method load error change check' + ); + // retun status check is inverted to load error check + $this->assertEquals( + $load_error_new ? false : true, + $ret_status, + 'Legacy return load error change check' + ); + } else { + if ($locale_new === null) { + $translator = $l10n->getTranslator(); + } elseif ($path_new === null) { + $translator = $l10n->getTranslator($locale_new); + } elseif ($domain_new === null) { + $translator = $l10n->getTranslator($locale_new, $path_new); + } elseif ($legacy_new === null) { + $translator = $l10n->getTranslator($locale_new, $path_new, $domain_new); + } else { + $translator = $l10n->getTranslator($locale_new, $path_new, $domain_new, $legacy_new); + } + // status check + $this->assertEquals( + $load_error_new, + $l10n->getLoadError(), + 'Translate method load error change check' + ); + // check that returned is class GetTextReader and object + $this->assertIsObject( + $translator, + 'translater class is object assert failed' + ); + $this->assertInstanceOf( + '\CoreLibs\Language\Core\GetTextReader', + $translator, + 'translator class is correct instance assert failed' + ); + // translator class + $this->assertEquals( + $translated_b, + $translator->gettext($original), + 'Translated string change assert failed from returned class' + ); + } + // new set check + $this->assertEquals( + $locale_expected_b, + $l10n->getLocale(), + 'Locale change assert failed' + ); + $this->assertEquals( + $domain_expected_b, + $l10n->getDomain(), + 'Domain change assert failed' + ); + $this->assertEquals( + $translated_b, + $l10n->__($original), + 'Translated string change assert failed' + ); + } + + // TODO: test all translation types + // __/gettext + // __n/ngettext + // ->dgettext + // ->dngettext + // ->dpgettext + // ->dpngettext + + /** + * Undocumented function + * + * @return array + */ + public function ngettextProvider(): array + { + return [ + // 0: locale + // 1: path + // 2: domain + // 3: context (null for none) + // 4: single string + // 5: plural string + // 6: array for each n value expected string + 'plural text en' => [ + 'en_US', + __DIR__ . 'includes/locale/', + 'admin', + // context + null, + // text single/multi in + 'single', + 'multi', + // confirm translation, pos in array equal n + [ + 0 => 'Multi admin en_US 1', + 1 => 'Multi admin en_US 0', + 2 => 'Multi admin en_US 1', + ] + ], + 'plural text context en' => [ + 'en_US', + __DIR__ . 'includes/locale/', + 'admin', + // context + 'context', + // text single/multi in + 'single', + 'multi', + // confirm translation, pos in array equal n + [ + 0 => 'Multi context admin en_US 1', + 1 => 'Multi context admin en_US 0', + 2 => 'Multi context admin en_US 1', + ] + ], + ]; + } + + /** + * Undocumented function + * + * @covers ::__n + * @dataProvider ngettextProvider + * @testdox plural string test for locale $locale and domain $domain with $context [$_dataName] + * + * @param string $locale + * @param string $path + * @param string $domain + * @param ?string $context + * @param string $original_single + * @param string $original_plural + * @param array $expected_strings + * @return void + */ + public function testNgettext( + // config 0-3 + string $locale, + string $path, + string $domain, + // context string + ?string $context, + // input strings + string $original_single, + string $original_plural, + // expected + array $expected_strings + ): void { + $l10n = new \CoreLibs\Language\L10n($locale, $path, $domain, false); + + foreach ($expected_strings as $n => $expected) { + if (empty($context)) { + $this->assertEquals( + $expected, + $l10n->__n($original_single, $original_plural, $n), + 'assert failed for plural: ' . $n + ); + } else { + $this->assertEquals( + $expected, + $l10n->__pn($context, $original_single, $original_plural, $n), + 'assert failed for plural: ' . $n . ' in context: ' . $context + ); + } + } + } + + /** + * locales list for testing locale folder lookup + * + * @return array + */ + public function localesProvider(): array + { + return [ + // 0: locale + // 1: return array + 'en' => [ + 'en', + [ + 'en', + ] + ], + 'en.UTF-8' => [ + 'en.UTF-8', + [ + 'en.UTF-8', + 'en', + ] + ], + 'en_US' => [ + 'en_US', + [ + 'en_US', + 'en', + ] + ], + 'en_US.UTF-8' => [ + 'en_US.UTF-8', + [ + 'en_US.UTF-8', + 'en_US', + 'en', + ] + ], + 'en_US@subtext' => [ + 'en_US@subtext', + [ + 'en_US@subtext', + 'en@subtext', + 'en_US', + 'en', + ] + ], + 'en_US.UTF-8@subtext' => [ + 'en_US.UTF-8@subtext', + [ + 'en_US.UTF-8@subtext', + 'en_US@subtext', + 'en@subtext', + 'en_US.UTF-8', + 'en_US', + 'en', + ] + ] + ]; + } + + /** + * test locales array return + * + * @covers ::listLocales + * @dataProvider localesProvider + * @testdox check $locale [$_dataName] + * + * @param string $locale + * @param array $expected + * @return void + */ + public function testListLocales(string $locale, array $expected): void + { + $locale_list = \CoreLibs\Language\L10n::listLocales($locale); + // print "LOCALES: " . print_r($locale_list, true) . "\n"; + $this->assertEquals( + $expected, + $locale_list + ); + } + + // @covers ::detectLocale + + /** + * Undocumented function + * + * @return array + */ + public function detectLocaleProvider(): array + { + return [ + // 0: type: global | env + // 1: global variable name or enviroment var + // 2: value to set + // 3: value to expect back + 'global locale' => [ + 'global', + 'LOCALE', + 'ja_JP.UTF-8', + 'ja_JP.UTF-8', + ], + 'env LC_ALL' => [ + 'env', + 'LC_ALL', + 'ja_JP.UTF-8', + 'ja_JP.UTF-8', + ], + 'env LANG' => [ + 'env', + 'LANG', + 'ja_JP.UTF-8', + 'ja_JP.UTF-8', + ], + 'default return' => [ + 'env', + 'LC_ALL', + '', + 'en', + ] + ]; + } + + /** + * Undocumented function + * @covers ::detectLocale + * @dataProvider detectLocaleProvider + * @testdox check detectLocale for $type with $var and $value is $expected [$_dataName] + * + * @return void + */ + public function testDetectLocale( + string $type, + string $var, + string $value, + string $expected, + ): void { + switch ($type) { + case 'global': + $GLOBALS[$var] = $value; + break; + case 'env': + $old_value = getenv("$var"); + putenv("$var=$value"); + // unset all other env vars + foreach (['LC_ALL', 'LC_MESSAGES', 'LANG'] as $env) { + if ($env != $var) { + putenv("$env="); + } + } + break; + } + $locale = \CoreLibs\Language\L10n::detectLocale(); + $this->assertEquals( + $expected, + $locale + ); + // reset post run + switch ($type) { + case 'global': + unset($GLOBALS[$var]); + break; + case 'env': + putenv("$var=$old_value"); + break; + } + } + + // set/get text domain, domain, locale + + /** + * Undocumented function + * + * @return array + */ + public function textDomainProvider(): array + { + return [ + // 0: set domain + // 1: set path + // 2: get domain + // 3: expected path + 'valid set and get' => [ + 'foo', + 'foo/bar', + 'foo', + 'foo/bar', + ], + 'invalid set and get' => [ + 'foo', + 'foo/bar', + 'iamnotset', + false + ] + ]; + } + + /** + * Undocumented function + * + * @covers ::setTextDomain + * @covers ::getTextDomain + * @dataProvider textDomainProvider + * @testdox set $domain with $path and get $get_domain and expect $expected [$_dataName] + * + * @param string $domain + * @param string $path + * @param string $get_domain + * @param string|bool $expected + * @return void + */ + public function testSetGetTextDomain(string $domain, string $path, string $get_domain, $expected): void + { + $l10n = new \CoreLibs\Language\L10n(); + $l10n->setTextDomain($domain, $path); + $this->assertEquals( + $expected, + $l10n->getTextDomain($get_domain) + ); + } + + /** + * Undocumented function + * + * @return array + */ + public function domainProvider(): array + { + return [ + // 0: set domain + // 1: expected domain from get + 'valid domain' => [ + 'foo', + 'foo', + ], + 'empty domain' => [ + '', + '', + ] + ]; + } + + /** + * Undocumented function + * + * @covers ::setDomain + * @covers ::getDomain + * @dataProvider domainProvider + * @testdox set $domain and expect $expected [$_dataName] + * + * @param string $domain + * @param string $expected + * @return void + */ + public function testSetGetDomain(string $domain, string $expected): void + { + $l10n = new \CoreLibs\Language\L10n(); + $l10n->setDomain($domain); + $this->assertEquals( + $expected, + $l10n->getDomain() + ); + } + + /** + * Undocumented function + * + * @return array + */ + public function localeProvider(): array + { + return [ + // 0: set locale + // 1: pre set if not null or not empty + // 2: expected return from set + // 3: expected from get + 'valid locale' => [ + 'foo', + null, + 'foo', + 'foo', + ], + 'empty locale' => [ + '', + null, + '', + '', + ], + 'empty locale, pre set' => [ + '', + 'foo', + 'foo', + 'foo', + ], + ]; + } + + /** + * Undocumented function + * + * @covers ::setLocale + * @covers ::getLocale + * @dataProvider localeProvider + * @testdox set $locale with $expected_return and expect $expected [$_dataName] + * + * @param string $locale + * @param string $pre_locale + * @param string $expected_return + * @param string $expected + * @return void + */ + public function testSetGetLocale( + string $locale, + ?string $pre_locale, + string $expected_return, + string $expected + ): void { + $l10n = new \CoreLibs\Language\L10n(); + if (!empty($pre_locale)) { + $l10n->setLocale($pre_locale); + } + $returned = $l10n->setLocale($locale); + $this->assertEquals( + $expected_return, + $returned, + 'Set locale return assert failed' + ); + $this->assertEquals( + $expected, + $l10n->getLocale(), + 'Get locale aszert failed' + ); + } + + // static load + + /** + * Undocumented function + * + * @return array + */ + public function functionsProvider(): array + { + return [ + // 0: lang/locale + // 1: path + // 2: domain + // 3: encoding + // 4: string + // 5: translated string + 'standard en' => [ + 'en_US.UTF-8', + __DIR__ . 'includes/locale/', + 'frontend', + 'UTF-8', + 'Original', + 'Translated frontend en_US', + ], + 'standard ja' => [ + 'ja_JP.UTF-8', + __DIR__ . 'includes/locale/', + 'admin', + 'UTF-8', + 'Original', + 'Translated admin ja_JP', + ] + ]; + } + + /** + * fuctions check + * TODO: others d/dn/dp/dpn gettext functions + * + * @covers __setlocale + * @covers __bindtextdomain + * @covers __bind_textdomain_codeset + * @covers __textdomain + * @covers __gettext + * @covers __ + * @dataProvider functionsProvider + * @testdox check functions with locale $locale and domain $domain [$_dataName] + * @param string $locale + * @param string $path + * @param string $domain + * @param string $encoding + * @param string $original + * @param string $translated + * @return void + */ + public function testFunctions( + string $locale, + string $path, + string $domain, + string $encoding, + string $original, + string $translated + ): void { + \CoreLibs\Language\L10n::loadFunctions(); + __setlocale(LC_MESSAGES, $locale); + __textdomain($domain); + __bindtextdomain($domain, $path); + __bind_textdomain_codeset($domain, $encoding); + $this->assertEquals( + $translated, + __($original), + 'function __ assert failed' + ); + $this->assertEquals( + $translated, + __gettext($original), + 'function gettext assert failed' ); } } diff --git a/4dev/tests/includes/create_po.sh b/4dev/tests/includes/create_po.sh new file mode 100755 index 00000000..d549d19a --- /dev/null +++ b/4dev/tests/includes/create_po.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +# if we don't have one base file we are in the wrong folder +if [ ! -f "locale/en_US/LC_MESSAGES/admin.mo" ]; then + echo "Locale file is missing, wrong base folder?" + echo "Should be: 4dev/tests/includes/" + exit; +fi; + +msgfmt -o locale/en_US/LC_MESSAGES/admin.mo locale/en_US.admin.UTF-8.po +msgfmt -o locale/en_US/LC_MESSAGES/frontend.mo locale/en_US.frontend.UTF-8.po +msgfmt -o locale/ja_JP/LC_MESSAGES/admin.mo locale/ja_JP.admin.UTF-8.po +msgfmt -o locale/ja_JP/LC_MESSAGES/frontend.mo locale/ja_JP.frontend.UTF-8.po diff --git a/4dev/tests/includes/locale/en_US.admin.UTF-8.po b/4dev/tests/includes/locale/en_US.admin.UTF-8.po index af91eed8..c35caf23 100644 --- a/4dev/tests/includes/locale/en_US.admin.UTF-8.po +++ b/4dev/tests/includes/locale/en_US.admin.UTF-8.po @@ -1,2 +1,34 @@ + +msgid "" +msgstr "" +"Project-Id-Version: en_US.UTF-8 LC_MESSAGES admin\n" +"Report-Msgid-Bugs-To: clemens.schwaighofer@egplusww.com\n" +"POT-Creation-Date: 2018-03-28 10:40+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: clemens.schwaighofer@egplusww.co\n" +"Language-Team: E-GRAPHICS COMMUNICATIONS Japan \n" +"Language: en_US\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + msgid "Original" msgstr "Translated admin en_US" + +msgid "single" +msgid_plural "multi" +msgstr[0] "Multi admin en_US 0" +msgstr[1] "Multi admin en_US 1" +msgstr[2] "Multi admin en_US 2" + +msgctxt "context" +msgid "Original" +msgstr "Original context admin en_US" + +msgctxt "context" +msgid "single" +msgid_plural "multi" +msgstr[0] "Multi context admin en_US 0" +msgstr[1] "Multi context admin en_US 1" +msgstr[2] "Multi context admin en_US 2" diff --git a/4dev/tests/includes/locale/en_US.frontend.UTF-8.po b/4dev/tests/includes/locale/en_US.frontend.UTF-8.po index f955356b..a2f58edb 100644 --- a/4dev/tests/includes/locale/en_US.frontend.UTF-8.po +++ b/4dev/tests/includes/locale/en_US.frontend.UTF-8.po @@ -1,2 +1,34 @@ + +msgid "" +msgstr "" +"Project-Id-Version: en_US.UTF-8 LC_MESSAGES frontend\n" +"Report-Msgid-Bugs-To: clemens.schwaighofer@egplusww.com\n" +"POT-Creation-Date: 2018-03-28 10:40+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: clemens.schwaighofer@egplusww.co\n" +"Language-Team: E-GRAPHICS COMMUNICATIONS Japan \n" +"Language: en_US\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + msgid "Original" msgstr "Translated frontend en_US" + +msgid "single" +msgid_plural "multi" +msgstr[0] "Multi frontend en_US 0" +msgstr[1] "Multi frontend en_US 1" +msgstr[2] "Multi frontend en_US 2" + +msgctxt "context" +msgid "Original" +msgstr "Original context frontend en_US" + +msgctxt "context" +msgid "single" +msgid_plural "multi" +msgstr[0] "Multi context frontend en_US 0" +msgstr[1] "Multi context frontend en_US 1" +msgstr[2] "Multi context frontend en_US 2" diff --git a/4dev/tests/includes/locale/en_US/LC_MESSAGES/admin.mo b/4dev/tests/includes/locale/en_US/LC_MESSAGES/admin.mo index f52560b8..c681b0c4 100644 Binary files a/4dev/tests/includes/locale/en_US/LC_MESSAGES/admin.mo and b/4dev/tests/includes/locale/en_US/LC_MESSAGES/admin.mo differ diff --git a/4dev/tests/includes/locale/en_US/LC_MESSAGES/frontend.mo b/4dev/tests/includes/locale/en_US/LC_MESSAGES/frontend.mo index 1d8fb44f..b63ffe74 100644 Binary files a/4dev/tests/includes/locale/en_US/LC_MESSAGES/frontend.mo and b/4dev/tests/includes/locale/en_US/LC_MESSAGES/frontend.mo differ diff --git a/4dev/tests/includes/locale/ja_JP.admin.UTF-8.po b/4dev/tests/includes/locale/ja_JP.admin.UTF-8.po index 3f4dccbe..e2a8795c 100644 --- a/4dev/tests/includes/locale/ja_JP.admin.UTF-8.po +++ b/4dev/tests/includes/locale/ja_JP.admin.UTF-8.po @@ -1,2 +1,35 @@ + +msgid "" +msgstr "" +"Project-Id-Version: ja_JP.UTF-8 LC_MESSAGES admin\n" +"Report-Msgid-Bugs-To: clemens.schwaighofer@egplusww.com\n" +"POT-Creation-Date: 2018-03-28 10:40+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: clemens.schwaighofer@egplusww.co\n" +"Language-Team: E-GRAPHICS COMMUNICATIONS Japan \n" +"Language: ja\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2;\n" +# 0, 1, 2 plural + msgid "Original" msgstr "Translated admin ja_JP" + +msgid "single" +msgid_plural "multi" +msgstr[0] "Multi admin ja_JP 0" +msgstr[1] "Multi admin ja_JP 1" +msgstr[2] "Multi admin ja_JP 2" + +msgctxt "context" +msgid "Original" +msgstr "Original context admin ja_JP" + +msgctxt "context" +msgid "single" +msgid_plural "multi" +msgstr[0] "Multi context admin ja_JP 0" +msgstr[1] "Multi context admin ja_JP 1" +msgstr[2] "Multi context admin ja_JP 2" diff --git a/4dev/tests/includes/locale/ja_JP.frontend.UTF-8.po b/4dev/tests/includes/locale/ja_JP.frontend.UTF-8.po index 3d7318ff..ccdba7a6 100644 --- a/4dev/tests/includes/locale/ja_JP.frontend.UTF-8.po +++ b/4dev/tests/includes/locale/ja_JP.frontend.UTF-8.po @@ -1,2 +1,35 @@ + +msgid "" +msgstr "" +"Project-Id-Version: ja_JP.UTF-8 LC_MESSAGES frontend\n" +"Report-Msgid-Bugs-To: clemens.schwaighofer@egplusww.com\n" +"POT-Creation-Date: 2018-03-28 10:40+0900\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: clemens.schwaighofer@egplusww.co\n" +"Language-Team: E-GRAPHICS COMMUNICATIONS Japan \n" +"Language: ja\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2;\n" +# 0, 1, 2 plural + msgid "Original" msgstr "Translated frontend ja_JP" + +msgid "single" +msgid_plural "multi" +msgstr[0] "Multi frontend ja_JP 0" +msgstr[1] "Multi frontend ja_JP 1" +msgstr[2] "Multi frontend ja_JP 2" + +msgctxt "context" +msgid "Original" +msgstr "Original context frontend ja_JP" + +msgctxt "context" +msgid "single" +msgid_plural "multi" +msgstr[0] "Multi context frontend ja_JP 0" +msgstr[1] "Multi context frontend ja_JP 1" +msgstr[2] "Multi context frontend ja_JP 2" diff --git a/4dev/tests/includes/locale/ja_JP/LC_MESSAGES/admin.mo b/4dev/tests/includes/locale/ja_JP/LC_MESSAGES/admin.mo index f4985b59..df8d579c 100644 Binary files a/4dev/tests/includes/locale/ja_JP/LC_MESSAGES/admin.mo and b/4dev/tests/includes/locale/ja_JP/LC_MESSAGES/admin.mo differ diff --git a/4dev/tests/includes/locale/ja_JP/LC_MESSAGES/frontend.mo b/4dev/tests/includes/locale/ja_JP/LC_MESSAGES/frontend.mo index 3e45df2d..c8b84144 100644 Binary files a/4dev/tests/includes/locale/ja_JP/LC_MESSAGES/frontend.mo and b/4dev/tests/includes/locale/ja_JP/LC_MESSAGES/frontend.mo differ diff --git a/www/admin/class_test.lang.php b/www/admin/class_test.lang.php index c2b936d2..b672478c 100644 --- a/www/admin/class_test.lang.php +++ b/www/admin/class_test.lang.php @@ -40,15 +40,35 @@ use CoreLibs\Language\L10n; $string = 'INPUT TEST'; +echo "
LEGACY TEST
"; + +$lang = 'en_utf8'; $l = new CoreLibs\Language\L10n($lang); +echo "*
"; echo "LANGUAGE WANT/SET: " . $lang . '/' . $l->getLocale() . "
"; echo "LANGUAGE FILE: " . $l->getMoFile() . "
"; echo "LOAD ERROR: " . $l->getLoadError() . "
"; echo "INPUT TEST: " . $string . " => " . $l->__($string) . "
"; +$single_string = 'single'; +$multi_string = 'multi'; +for ($n = 0; $n <= 3; $n++) { + echo "MULTI TEST $n: " . $single_string . "/" . $multi_string . " => " + . $l->__n($single_string, $multi_string, $n) . "
"; +} +$context = "month name"; +$context_string = "May"; +echo "CONTEXT TRANSLATION: " . $context_string . " => " . $l->__p($context, $context_string) . "
"; +$single_string = 'single'; +$multi_string = 'multi'; +for ($n = 0; $n <= 3; $n++) { + echo "CONTEXT MULTI TEST $n: " . $single_string . "/" . $multi_string . " => " + . $l->__pn($context, $single_string, $multi_string, $n) . "
"; +} // switch to other language $lang = 'ja_utf8'; $l->l10nReloadMOfile($lang); +echo "*
"; echo "LANGUAGE WANT/SET: " . $lang . '/' . $l->getLocale() . "
"; echo "LANGUAGE FILE: " . $l->getMoFile() . "
"; echo "LOAD ERROR: " . $l->getLoadError() . "
"; @@ -56,6 +76,7 @@ echo "INPUT TEST: " . $string . " => " . $l->__($string) . "
"; // switch to non existing language $lang = 'tr_utf8'; $l->l10nReloadMOfile($lang); +echo "*
"; echo "LANGUAGE WANT/SET: " . $lang . '/' . $l->getLocale() . "
"; echo "LANGUAGE FILE: " . $l->getMoFile() . "
"; echo "LOAD ERROR: " . $l->getLoadError() . "
"; @@ -79,33 +100,64 @@ $encoding = 'UTF-8'; $path = BASE . INCLUDES . LOCALE; $l = new CoreLibs\Language\L10n($lang, $path, $domain, false); +echo "*
"; echo "LANGUAGE WANT/SET: " . $lang . '/' . $l->getLocale() . "
"; echo "DOMAIN WANT/SET: " . $domain . '/' . $l->getDomain() . "
"; echo "LANGUAGE FILE: " . $l->getMoFile() . "
"; +echo "CONTENT PATH: " . $l->getBaseContentPath() . "
"; +echo "DOMAIN PATH: " . $l->getTextDomain($domain) . "
"; +echo "BASE PATH: " . $l->getBaseLocalePath() . "
"; +echo "LOAD ERROR: " . $l->getLoadError() . "
"; +echo "INPUT TEST: " . $string . " => " . $l->__($string) . "
"; +echo "TROUGH LOAD: " . $l->getTranslatorClass()->gettext($string) . "
"; +$single_string = 'single'; +$multi_string = 'multi'; +for ($n = 0; $n <= 3; $n++) { + echo "MULTI TEST $n: " . $single_string . "/" . $multi_string . " => " + . $l->__n($single_string, $multi_string, $n) . "
"; +} + +$domain = 'frontend'; +$l->getTranslator('', $path, $domain); +echo "*
"; +echo "LANGUAGE WANT/SET: " . $lang . '/' . $l->getLocale() . "
"; +echo "DOMAIN WANT/SET: " . $domain . '/' . $l->getDomain() . "
"; +echo "LANGUAGE FILE: " . $l->getMoFile() . "
"; +echo "CONTENT PATH: " . $l->getBaseContentPath() . "
"; +echo "DOMAIN PATH: " . $l->getTextDomain($domain) . "
"; +echo "BASE PATH: " . $l->getBaseLocalePath() . "
"; echo "LOAD ERROR: " . $l->getLoadError() . "
"; echo "INPUT TEST: " . $string . " => " . $l->__($string) . "
"; echo "TROUGH LOAD: " . $l->getTranslatorClass()->gettext($string) . "
"; - -echo "
STATIC TYPE
"; +$domain = 'admin'; +echo "
STATIC TYPE TEST
"; // static tests from l10n_load L10n::getInstance()->setLocale($lang); echo "SET LOCALE: " . L10n::getInstance()->getLocale() . "
"; L10n::getInstance()->setDomain($domain); echo "SET DOMAIN: " . L10n::getInstance()->getDomain() . "
"; L10n::getInstance()->setTextDomain($domain, $path); -echo "SET DOMAIN: " . L10n::getInstance()->getTextDomain($domain) . "
"; +echo "SET TEXT DOMAIN: " . L10n::getInstance()->getTextDomain($domain) . "
"; // null call __bind_textdomain_codeset echo "INPUT TEST: " . $string . " => " . L10n::getInstance()->getTranslator()->gettext($string) . "
"; echo "
FUNCTIONS
"; // real statisc test L10n::loadFunctions(); -__setlocale(LC_MESSAGES, $lang); +$locale = 'ja'; +__setlocale(LC_MESSAGES, $locale); __textdomain($domain); __bindtextdomain($domain, $path); __bind_textdomain_codeset($domain, $encoding); -echo "INPUT TEST: " . $string . " => " . __($string) . "
"; +echo "INPUT TEST $locale: " . $string . " => " . __($string) . "
"; + +$locale = 'en_US.UTF-8'; +__setlocale(LC_MESSAGES, $locale); +__textdomain($domain); +__bindtextdomain($domain, $path); +__bind_textdomain_codeset($domain, $encoding); +echo "INPUT TEST $locale: " . $string . " => " . __($string) . "
"; print ""; diff --git a/www/includes/locale/en_US/LC_MESSAGES/admin.mo b/www/includes/locale/en_US/LC_MESSAGES/admin.mo index da820bb0..6407f2e0 100644 Binary files a/www/includes/locale/en_US/LC_MESSAGES/admin.mo and b/www/includes/locale/en_US/LC_MESSAGES/admin.mo differ diff --git a/www/includes/locale/ja/LC_MESSAGES/admin.mo b/www/includes/locale/ja/LC_MESSAGES/admin.mo index 6d2ebed8..76cd4a38 100644 Binary files a/www/includes/locale/ja/LC_MESSAGES/admin.mo and b/www/includes/locale/ja/LC_MESSAGES/admin.mo differ diff --git a/www/lib/CoreLibs/Basic.php b/www/lib/CoreLibs/Basic.php index 20fbe1b7..1ff06b44 100644 --- a/www/lib/CoreLibs/Basic.php +++ b/www/lib/CoreLibs/Basic.php @@ -715,20 +715,20 @@ class Basic // *** ARRAY HANDLING END // [!!! DEPRECATED !!!] - // Moved to \CoreLibs\Language\Encoding + // Moved to \CoreLibs\Convert\MimeEncode /** * wrapper function for mb mime convert, for correct conversion with long strings * @param string $string string to encode * @param string $encoding target encoding * @return string encoded string - * @deprecated Use \CoreLibs\Language\Encoding::__mbMimeEncode() instead + * @deprecated Use \CoreLibs\Convert\MimeEncode::__mbMimeEncode() instead */ public static function __mbMimeEncode(string $string, string $encoding): string { - trigger_error('Method ' . __METHOD__ . ' is deprecated, use \CoreLibs\Language\Encoding::__mbMimeEncode()', E_USER_DEPRECATED); - return \CoreLibs\Language\Encoding::__mbMimeEncode($string, $encoding); + trigger_error('Method ' . __METHOD__ . ' is deprecated, use \CoreLibs\Convert\MimeEncode::__mbMimeEncode()', E_USER_DEPRECATED); + return \CoreLibs\Convert\MimeEncode::__mbMimeEncode($string, $encoding); } // *** HUMAND BYTE READABLE CONVERT @@ -1009,7 +1009,7 @@ class Basic // *** ENCODING FUNCTIONS // [!!! DEPRECATED !!!] - // Moved to \CoreLibs\Language\Encoding + // Moved to \CoreLibs\Check\Encoding and \CoreLibs\Convert\Encoding /** * test if a string can be safely convert between encodings. mostly utf8 to shift jis @@ -1026,12 +1026,12 @@ class Basic * @param string $from_encoding encoding of string to test * @param string $to_encoding target encoding * @return bool|array false if no error or array with failed characters - * @deprecated use \CoreLibs\Language\Encoding::checkConvertEncoding() instead + * @deprecated use \CoreLibs\Check\Encoding::checkConvertEncoding() instead */ public function checkConvertEncoding(string $string, string $from_encoding, string $to_encoding) { - trigger_error('Method ' . __METHOD__ . ' is deprecated, use \CoreLibs\Language\Encoding::checkConvertEncoding()', E_USER_DEPRECATED); - return \CoreLibs\Language\Encoding::checkConvertEncoding($string, $from_encoding, $to_encoding); + trigger_error('Method ' . __METHOD__ . ' is deprecated, use \CoreLibs\Check\Encoding::checkConvertEncoding()', E_USER_DEPRECATED); + return \CoreLibs\Check\Encoding::checkConvertEncoding($string, $from_encoding, $to_encoding); } /** @@ -1047,12 +1047,12 @@ class Basic * check that the source is actually matching * to what we sav the source is * @return string encoding converted string - * @deprecated use \CoreLibs\Language\Encoding::convertEncoding() instead + * @deprecated use \CoreLibs\Convert\Encoding::convertEncoding() instead */ public static function convertEncoding(string $string, string $to_encoding, string $source_encoding = '', bool $auto_check = true): string { - trigger_error('Method ' . __METHOD__ . ' is deprecated, use \CoreLibs\Language\Encoding::convertEncoding()', E_USER_DEPRECATED); - return \CoreLibs\Language\Encoding::convertEncoding($string, $to_encoding, $source_encoding, $auto_check); + trigger_error('Method ' . __METHOD__ . ' is deprecated, use \CoreLibs\Convert\Encoding::convertEncoding()', E_USER_DEPRECATED); + return \CoreLibs\Convert\Encoding::convertEncoding($string, $to_encoding, $source_encoding, $auto_check); } // *** ENCODING FUNCTIONS END diff --git a/www/lib/CoreLibs/Language/Core/FileReader.php b/www/lib/CoreLibs/Language/Core/FileReader.php index 17bf0dce..cbee1b04 100644 --- a/www/lib/CoreLibs/Language/Core/FileReader.php +++ b/www/lib/CoreLibs/Language/Core/FileReader.php @@ -39,7 +39,7 @@ class FileReader * file read constructor * @param string $filename file name to load */ - public function __construct($filename) + public function __construct(string $filename) { if (file_exists($filename)) { $this->fr_length = filesize($filename) ?: 0; @@ -58,7 +58,7 @@ class FileReader * @param int $bytes how many bytes to read * @return string read data as string */ - public function read($bytes) + public function read(int $bytes): string { if (!$bytes || !is_resource($this->fr_fd)) { return ''; @@ -86,7 +86,7 @@ class FileReader * @param int $pos position where to go to * @return int file position after seek done */ - public function seekto($pos) + public function seekto(int $pos): int { if (!is_resource($this->fr_fd)) { return 0; @@ -100,7 +100,7 @@ class FileReader * get current position in file * @return int current position in bytes */ - public function currentpos() + public function currentpos(): int { return $this->fr_pos; } @@ -109,7 +109,7 @@ class FileReader * file length/size * @return int file size in bytes */ - public function length() + public function length(): int { return $this->fr_length; } diff --git a/www/lib/CoreLibs/Language/Core/GetTextReader.php b/www/lib/CoreLibs/Language/Core/GetTextReader.php index 550a1125..f15576e4 100644 --- a/www/lib/CoreLibs/Language/Core/GetTextReader.php +++ b/www/lib/CoreLibs/Language/Core/GetTextReader.php @@ -67,17 +67,15 @@ class GetTextReader /** @var array */ private $cache_translations = []; // original -> translation mapping - /* Methods */ - /** * Reads a 32bit Integer from the Stream * * @access private * @return int Integer from the Stream */ - private function readint() + private function readint(): int { if ($this->BYTEORDER == 0) { // low endian @@ -91,10 +89,11 @@ class GetTextReader /** * read bytes + * * @param int $bytes byte length to read * @return string return data, possible string */ - public function read($bytes) + public function read(int $bytes): string { return $this->STREAM->read($bytes); } @@ -105,7 +104,7 @@ class GetTextReader * @param int $count How many elements should be read * @return array Array of Integers */ - public function readintarray($count) + public function readintarray(int $count): array { if ($this->BYTEORDER == 0) { // low endian @@ -120,9 +119,10 @@ class GetTextReader * Constructor * * @param FileReader|bool $Reader the StreamReader object - * @param bool $enable_cache Enable or disable caching of strings (default on) + * @param bool $enable_cache Enable or disable caching + * of strings (default on) */ - public function __construct($Reader, $enable_cache = true) + public function __construct($Reader, bool $enable_cache = true) { // If there isn't a StreamReader, turn on short circuit mode. if ((!is_object($Reader) && !$Reader) || (is_object($Reader) && $Reader->error)) { @@ -159,6 +159,26 @@ class GetTextReader $this->translations = $this->readint(); } + /** + * Get current short circuit, equals to no translator running + * + * @return bool + */ + public function getShortCircuit(): bool + { + return $this->short_circuit; + } + + /** + * get the current cache enabled status + * + * @return bool + */ + public function getEnableCache(): bool + { + return $this->enable_cache; + } + /** * Loads the translation tables from the MO file into the cache * If caching is enabled, also loads all strings into a cache @@ -207,7 +227,7 @@ class GetTextReader * @param int $num Offset number of original string * @return string Requested string if found, otherwise '' */ - private function getOriginalString($num) + private function getOriginalString(int $num): string { $length = $this->table_originals[$num * 2 + 1] ?? 0; $offset = $this->table_originals[$num * 2 + 2] ?? 0; @@ -226,7 +246,7 @@ class GetTextReader * @param int $num Offset number of original string * @return string Requested string if found, otherwise '' */ - private function getTranslationString($num) + private function getTranslationString(int $num): string { $length = $this->table_translations[$num * 2 + 1] ?? 0; $offset = $this->table_translations[$num * 2 + 2] ?? 0; @@ -242,12 +262,12 @@ class GetTextReader * Binary search for string * * @access private - * @param string $string string to find - * @param int $start (internally used in recursive function) - * @param int $end (internally used in recursive function) - * @return int (offset in originals table) + * @param string $string string to find + * @param int $start (internally used in recursive function) + * @param int $end (internally used in recursive function) + * @return int (offset in originals table) */ - private function findString($string, $start = -1, $end = -1) + private function findString(string $string, int $start = -1, int $end = -1): int { if (($start == -1) or ($end == -1)) { // findString is called with only one parameter, set start end end @@ -289,7 +309,7 @@ class GetTextReader * @param string $string to be translated * @return string translated string (or original, if not found) */ - public function translate($string) + public function translate(string $string): string { if ($this->short_circuit) { return $string; @@ -298,7 +318,10 @@ class GetTextReader if ($this->enable_cache) { // Caching enabled, get translated string from cache - if (is_array($this->cache_translations) && array_key_exists($string, $this->cache_translations)) { + if ( + is_array($this->cache_translations) && + array_key_exists($string, $this->cache_translations) + ) { return $this->cache_translations[$string]; } else { return $string; @@ -321,7 +344,7 @@ class GetTextReader * @param string $expr an expression to match * @return string sanitized plural form expression */ - private function sanitizePluralExpression($expr) + private function sanitizePluralExpression(string $expr): string { // Get rid of disallowed characters. $expr = preg_replace('@[^a-zA-Z0-9_:;\(\)\?\|\&=!<>+*/\%-]@', '', $expr); @@ -358,7 +381,7 @@ class GetTextReader * @param string $header header search in plurals * @return string verbatim plural form header field */ - private function extractPluralFormsHeaderFromPoHeader($header) + private function extractPluralFormsHeaderFromPoHeader(string $header): string { if (preg_match("/(^|\n)plural-forms: ([^\n]*)\n/i", $header, $regs)) { $expr = $regs[2]; @@ -374,14 +397,14 @@ class GetTextReader * @access private * @return string plural form header */ - private function getPluralForms() + private function getPluralForms(): string { // lets assume message number 0 is header // this is true, right? $this->loadTables(); // cache header field for plural forms - if (! is_string($this->pluralheader)) { + if (empty($this->pluralheader) || !is_string($this->pluralheader)) { if ($this->enable_cache) { $header = $this->cache_translations['']; } else { @@ -397,25 +420,37 @@ class GetTextReader * Detects which plural form to take * * @access private - * @param string $n count - * @return int array index of the right plural form + * @param int $n count + * @return int array index of the right plural form */ - private function selectString($n) + private function selectString(int $n): int { $string = $this->getPluralForms(); $string = str_replace('nplurals', "\$total", $string); - $string = str_replace("n", $n, $string); + $string = str_replace("n", (string)$n, $string); $string = str_replace('plural', "\$plural", $string); $total = 0; $plural = 0; eval("$string"); - /** @phpstan-ignore-next-line */ + /** @phpstan-ignore-next-line 0 >= 0 is always true*/ if ($plural >= $total) { $plural = $total - 1; } - return $plural; + return (int)$plural; + } + + /** + * wrapper for translate() method + * + * @access public + * @param string $string + * @return string + */ + public function gettext(string $string): string + { + return $this->translate($string); } /** @@ -424,10 +459,10 @@ class GetTextReader * @access public * @param string $single * @param string $plural - * @param string $number + * @param int $number * @return string plural form */ - public function ngettext($single, $plural, $number) + public function ngettext(string $single, string $plural, int $number): string { if ($this->short_circuit) { if ($number != 1) { @@ -465,11 +500,12 @@ class GetTextReader /** * p get text + * * @param string $context [description] * @param string $msgid [description] * @return string [description] */ - public function pgettext($context, $msgid) + public function pgettext(string $context, string $msgid): string { $key = $context . chr(4) . $msgid; $ret = $this->translate($key); @@ -482,14 +518,19 @@ class GetTextReader /** * np get text + * * @param string $context [description] * @param string $singular [description] * @param string $plural [description] - * @param string $number [description] + * @param int $number [description] * @return string [description] */ - public function npgettext($context, $singular, $plural, $number) - { + public function npgettext( + string $context, + string $singular, + string $plural, + int $number + ): string { $key = $context . chr(4) . $singular; $ret = $this->ngettext($key, $plural, $number); if (strpos($ret, "\004") !== false) { diff --git a/www/lib/CoreLibs/Language/Core/StreamReader.php b/www/lib/CoreLibs/Language/Core/StreamReader.php index a02450e6..fdd87f0c 100644 --- a/www/lib/CoreLibs/Language/Core/StreamReader.php +++ b/www/lib/CoreLibs/Language/Core/StreamReader.php @@ -41,7 +41,7 @@ class StreamReader * @param int $bytes bytes to read * @return bool dummy false */ - public function read($bytes) + public function read(int $bytes): bool { return false; } @@ -51,7 +51,7 @@ class StreamReader * @param int $position seek to position * @return bool dummy false */ - public function seekto($position) + public function seekto(int $position): bool { return false; } @@ -60,7 +60,7 @@ class StreamReader * returns current position * @return bool dummy false */ - public function currentpos() + public function currentpos(): bool { return false; } @@ -69,7 +69,7 @@ class StreamReader * returns length of entire stream (limit for seekto()s) * @return bool dummy false */ - public function length() + public function length(): bool { return false; } diff --git a/www/lib/CoreLibs/Language/Core/StringReader.php b/www/lib/CoreLibs/Language/Core/StringReader.php index 4bf0a7d3..243795ea 100644 --- a/www/lib/CoreLibs/Language/Core/StringReader.php +++ b/www/lib/CoreLibs/Language/Core/StringReader.php @@ -35,7 +35,7 @@ class StringReader * constructor for string reader * @param string $str basic string */ - public function __construct($str = '') + public function __construct(string $str = '') { $this->sr_str = $str; $this->sr_pos = 0; @@ -46,7 +46,7 @@ class StringReader * @param int $bytes bytes to read in string * @return string data read in length of bytes as string */ - public function read($bytes) + public function read(int $bytes): string { $data = substr($this->sr_str, $this->sr_pos, $bytes); $this->sr_pos += $bytes; @@ -62,7 +62,7 @@ class StringReader * @param int $pos position in string * @return int new position in string after seek */ - public function seekto($pos) + public function seekto(int $pos): int { $this->sr_pos = $pos; if (strlen($this->sr_str) < $this->sr_pos) { @@ -75,7 +75,7 @@ class StringReader * get current position in string * @return int position in string */ - public function currentpos() + public function currentpos(): int { return $this->sr_pos; } @@ -84,7 +84,7 @@ class StringReader * get length of string * @return int return length of assigned string */ - public function length() + public function length(): int { return strlen($this->sr_str); } diff --git a/www/lib/CoreLibs/Language/L10n.php b/www/lib/CoreLibs/Language/L10n.php index 81667a27..6b37ab86 100644 --- a/www/lib/CoreLibs/Language/L10n.php +++ b/www/lib/CoreLibs/Language/L10n.php @@ -40,104 +40,530 @@ use CoreLibs\Language\Core\GetTextReader; class L10n { - /** @var string */ - private $lang = ''; - /** @var string */ + /** @var string the current locale */ + private $locale = ''; + /** @var string the default selected/active domain */ + private $domain = ''; + /** @var array> locale > domain = translator */ + private $domains = []; + /** @var array bound paths for domains */ + private $paths = ['' => './']; + /** @var string the full path to the mo file to loaded */ private $mofile = ''; - /** @var FileReader|bool */ - private $input; - /** @var GetTextReader */ + /** @var string base path to search level */ + private $base_locale_path = ''; + /** @var string dynamic set path to where the mo file is actually */ + private $base_content_path = ''; + /** @var bool if load of mo file was unsuccessful */ + private $load_failure = false; + + /** @var FileReader|bool reader class for file reading, false for short circuit */ + private $input = false; + /** @var GetTextReader reader class for MO data */ private $l10n; + /** + * @static + * @var L10n self class + */ + private static $instance; /** * class constructor call for language getstring - * @param string $lang language name (optional), fallback is en - * @param string $path path, if empty fallback on default internal path + * if locale is not empty will load translation + * else getTranslator needs to be called + * + * @param string $locale language name, default empty string + * will return self instance + * @param string $path path, if empty fallback on default internal path + * @param string $domain override CONTENT_PATH . $encoding name for mo file + * @param bool $legacy default true, if set to true, will look in the old + * folder format lang/ CONTENT_PATH / $lang . mo */ - public function __construct(string $lang = '', string $path = '') - { - if (!$lang) { - $this->lang = 'en'; - } else { - $this->lang = $lang; + public function __construct( + string $locale = '', + string $path = '', + string $domain = '', + bool $legacy = true + ) { + // load the mo file if locale is not empty + if (!empty($locale)) { + $this->getTranslator($locale, $path, $domain, $legacy); } - - // override path check - if (!is_dir($path)) { - $path = BASE . INCLUDES . LANG . CONTENT_PATH; - } - - $this->mofile = $path . $this->lang . ".mo"; - - // check if get a readable mofile - if (is_readable($this->mofile)) { - $this->input = new FileReader($this->mofile); - } else { - $this->input = false; - } - $this->l10n = new GetTextReader($this->input); } /** - * reloads the mofile, if the location of the lang file changes - * @param string $lang language to reload data - * @param string $path optional path, if not set fallback on internal - * @return bool successfull reload true/false + * Returns the singleton L10n object. + * For function wrapper use + * + * @return L10n object */ - public function l10nReloadMOfile(string $lang, string $path = ''): bool + public static function getInstance(): L10n { - $success = false; - $old_mofile = $this->mofile; - $old_lang = $this->lang; - - $this->lang = $lang; - - // override path check - if (!is_dir($path)) { - $path = BASE . INCLUDES . LANG . CONTENT_PATH; + /** @phpstan-ignore-next-line */ + if (empty(self::$instance)) { + self::$instance = new self(); } + return self::$instance; + } - $this->mofile = $path . $this->lang . ".mo"; + /** + * Loads global localization functions. + * prefixed with double underscore + * eg: gettext -> __gettext + */ + public static function loadFunctions(): void + { + require_once __DIR__ . '/l10n_functions.php'; + } + + /** + * legacy loader name for getTranslator + * instead of returning the GetTextReader object it returns + * true or false for successful load. + * NOTE: some time down the road this will be deprecated + * + * @param string $locale + * @param string $path + * @param string $domain + * @param bool $legacy + * @return bool Returns true for successfull load, false for error + */ + public function l10nReloadMOfile( + string $locale, + string $path = '', + string $domain = '', + bool $legacy = true + ): bool { + $this->getTranslator($locale, $path, $domain, $legacy); + return $this->load_failure ? false : true; + } + + /** + * loads the mo file base on path, locale and domain set + * + * @param string $locale language name (optional), fallback is en + * @param string $path path, if empty fallback on default internal path + * @param string $domain override CONTENT_PATH . $encoding name for mo file + * @param bool $legacy default true, if set to true, will look in the old + * folder format lang/ CONTENT_PATH / $lang . mo + * @return GetTextReader the main gettext reader object + */ + public function getTranslator( + string $locale = '', + string $path = '', + string $domain = '', + bool $legacy = false + ): GetTextReader { + // set local if not from parameter + if (empty($locale)) { + $locale = $this->locale; + } + // set domain if not given + if (empty($domain)) { + $domain = $this->domain; + } + // store old settings + $old_mofile = $this->mofile; + $old_lang = $this->locale; + $old_domain = $this->domain; + $old_base_locale_path = $this->base_locale_path; + $old_base_content_path = $this->base_content_path; + + // legacy or new type + // legacy will use the old lang/content/file.mo type as default + // if path is not set, also locale is the file name + // for new type it follows the gettext spec and path is just the + // base folder where the mo files will be searched + if ($legacy === true) { + if (!is_dir($path)) { + $this->base_locale_path = BASE . INCLUDES . LANG; + $this->base_content_path = CONTENT_PATH; + $path = $this->base_locale_path . $this->base_content_path; + } + $this->mofile = $path . $locale . ".mo"; + } else { + // if new path is a dir + // 1) from a previous set domain + // 2) from method option as is + // 3) fallback if BASE/INCLUDES/LOCALE set + // 4) current dir + if (!empty($this->paths[$domain]) && is_dir($this->paths[$domain])) { + $this->base_locale_path = $this->paths[$domain]; + } elseif (is_dir($path)) { + $this->base_locale_path = $path; + } elseif ( + defined('BASE') && defined('INCLUDES') && defined('LOCALE') + ) { + // set fallback base path if constant set + $this->base_locale_path = BASE . INCLUDES . LOCALE; + } else { + $this->base_locale_path = './'; + } + // now we loop over lang compositions to get the base path + // then we check + $locales = $this->listLocales($locale); + foreach ($locales as $_locale) { + $this->base_content_path = $_locale . DIRECTORY_SEPARATOR + . 'LC_MESSAGES' . DIRECTORY_SEPARATOR; + $this->mofile = $this->base_locale_path + . $this->base_content_path + . $domain . '.mo'; + if (file_exists($this->mofile)) { + break; + } + } + } // check if get a readable mofile if (is_readable($this->mofile)) { + // locale and domain current wanted + $this->locale = $locale; + $this->domain = $domain; + // set empty domains path with current locale + if (empty($this->domains[$locale])) { + $this->domains[$locale] = []; + } + // store current base path (without locale, etc) + if (empty($this->paths[$domain])) { + $this->paths[$domain] = $this->base_locale_path; + } + // file reader and mo reader $this->input = new FileReader($this->mofile); $this->l10n = new GetTextReader($this->input); - // we successfully loaded - $success = true; - } else { + // if short circuit is true, we failed to have a translator loaded + $this->load_failure = $this->l10n->getShortCircuit(); + // below is not used at the moment, but can be to avoid reloading + $this->domains[$this->locale][$domain] = $this->l10n; + } elseif (!empty($old_mofile)) { + // mo file not readable + $this->load_failure = true; // else fall back to the old ones $this->mofile = $old_mofile; - $this->lang = $old_lang; + $this->locale = $old_lang; + $this->domain = $old_domain; + $this->base_locale_path = $old_base_locale_path; + $this->base_content_path = $old_base_content_path; + } else { + // mo file not readable, no previous mo file set, set short circuit + $this->load_failure = true; + // dummy + $this->l10n = new GetTextReader($this->input); } - return $success; + return $this->l10n; } - /** + /** + * return current set GetTextReader or return the one for given + * domain name if set + * This can be used to access all the public methods from the + * GetTextReader + * + * @param string $domain optional domain name + * @return GetTextReader + */ + public function getTranslatorClass(string $domain = ''): GetTextReader + { + if (!empty($domain) && !empty($this->domains[$this->locale][$domain])) { + return $this->domains[$this->locale][$domain]; + } + // if null return short circuit version + if ($this->l10n === null) { + return new GetTextReader($this->input); + } + return $this->l10n; + } + + /** + * original: + * vendor/phpmyadmin/motranslator/src/Loader.php + * + * Returns array with all possible locale combinations based on the + * given locale name + * + * I.e. for sr_CS.UTF-8@latin, look through all of + * sr_CS.UTF-8@latin, sr_CS@latin, sr@latin, sr_CS.UTF-8, sr_CS, sr. + * + * @param string $locale Locale string + * @return array List of locale path parts that can be possible + */ + public static function listLocales(string $locale): array + { + $locale_list = []; + + if (empty($locale)) { + return $locale_list; + } + // is matching regex + if ( + !preg_match( + // language code + '/^(?P[a-z]{2,3})' + // country code + . '(?:_(?P[A-Z]{2}))?' + // charset + . '(?:\\.(?P[-A-Za-z0-9_]+))?' + // @ modifier + . '(?:@(?P[-A-Za-z0-9_]+))?$/', + $locale, + $matches + ) + ) { + // not matching, return as is + return [$locale]; + } + // do matching run + $lang = $matches['lang'] ?? null; + $country = $matches['country'] ?? null; + $charset = $matches['charset'] ?? null; + $modifier = $matches['modifier'] ?? null; + // we need to add all possible cominations from not null set + // entries to the list, from longest to shortest + // %s_%s.%s@%s (lang _ country . encoding @ suffix) + // %s_%s@%s (lang _ country @ suffix) + // %s@%s (lang @ suffix) + // %s_%s.%s (lang _ country . encoding) + // %s_%s (lang _ country) + // %s (lang) + + // if lang is set + if ($lang) { + // modifier group + if ($modifier) { + if ($country) { + if ($charset) { + array_push( + $locale_list, + sprintf('%s_%s.%s@%s', $lang, $country, $charset, $modifier) + ); + } + + array_push( + $locale_list, + sprintf('%s_%s@%s', $lang, $country, $modifier) + ); + } elseif ($charset) { + array_push( + $locale_list, + sprintf('%s.%s@%s', $lang, $charset, $modifier) + ); + } + + array_push( + $locale_list, + sprintf('%s@%s', $lang, $modifier) + ); + } + // country group + if ($country) { + if ($charset) { + array_push( + $locale_list, + sprintf('%s_%s.%s', $lang, $country, $charset) + ); + } + + array_push( + $locale_list, + sprintf('%s_%s', $lang, $country) + ); + } elseif ($charset) { + array_push( + $locale_list, + sprintf('%s.%s', $lang, $charset) + ); + } + // lang only + array_push($locale_list, $lang); + } + + // If the locale name doesn't match POSIX style, just include it as-is. + if (!in_array($locale, $locale_list)) { + array_push($locale_list, $locale); + } + + return $locale_list; + } + + /** + * tries to detect the locale set in the following order: + * - globals: LOCALE + * - globals: LANG + * - env: LC_ALL + * - env: LC_MESSAGES + * - env: LANG + * if nothing set, returns 'en' as default + * + * @return string + */ + public static function detectLocale(): string + { + // globals + foreach (['LOCALE', 'LANG'] as $global) { + if (!empty($GLOBALS[$global])) { + return $GLOBALS[$global]; + } + } + // enviroment + foreach (['LC_ALL', 'LC_MESSAGES', 'LANG'] as $env) { + $locale = getenv($env); + if ($locale !== false && !empty($locale)) { + return $locale; + } + } + return 'en'; + } + + /************ + * INTERNAL VAR SET/GET + */ + + /** + * Sets the path for a domain. + * must be set before running l10nReloadMOfile + * + * @param string $domain Domain name + * @param string $path Path where to find locales + */ + public function setTextDomain(string $domain, string $path): void + { + $this->paths[$domain] = $path; + } + + /** + * return set path for given domain + * if not found return false + * + * @param string $domain + * @return string|bool + */ + public function getTextDomain(string $domain) + { + return $this->paths[$domain] ?? false; + } + + /** + * sets the default domain. + * + * @param string $domain Domain name + */ + public function setDomain(string $domain): void + { + $this->domain = $domain; + } + + /** + * return current set domain name + * + * @return string + */ + public function getDomain(): string + { + return $this->domain; + } + + /** + * sets a requested locale. + * + * @param string $locale Locale name + * @return string Set or current locale + */ + public function setLocale(string $locale): string + { + if (!empty($locale)) { + $this->locale = $locale; + } + return $this->locale; + } + + /** + * get current set locale + * + * @return string + */ + public function getLocale(): string + { + return $this->locale; + } + + /** * get current set language + * * @return string current set language string + * @deprecated Use getLocale() */ public function __getLang(): string { - return $this->lang; + return $this->getLocale(); } /** * get current set mo file + * * @return string current set mo language file */ - public function __getMoFile(): string + public function getMoFile(): string { return $this->mofile; } + /** + * get current set mo file + * + * @return string current set mo language file + * @deprecated Use getMoFile() + */ + public function __getMoFile(): string + { + return $this->getMoFile(); + } + + /** + * get the current base path in which we search + * + * @return string + */ + public function getBaseLocalePath(): string + { + return $this->base_locale_path; + } + + /** + * the path below the base path to where the mo file is located + * + * @return string + */ + public function getBaseContentPath(): string + { + return $this->base_content_path; + } + + /** + * get the current load error status + * if true then the mo file failed to load + * + * @return bool + */ + public function getLoadError(): bool + { + return $this->load_failure; + } + + /************ + * TRANSLATION METHODS + */ + /** * translates a string and returns translated text + * * @param string $text text to translate * @return string translated text */ public function __(string $text): string { + // fallback passthrough + if ($this->l10n === null) { + return $text; + } return $this->l10n->translate($text); } @@ -145,32 +571,75 @@ class L10n * prints translated string out to the screen * @param string $text text to translate * @return void has no return + * @deprecated use echo __() instead */ public function __e(string $text): void { + // fallback passthrough + if ($this->l10n === null) { + echo $text; + } echo $this->l10n->translate($text); } - // Return the plural form. /** * Return the plural form. - * @param string $single string for single word - * @param string $plural string for plural word - * @param int|float $number number value - * @return string translated plural string + * + * @param string $single string for single word + * @param string $plural string for plural word + * @param int $number number value + * @return string translated plural string */ - public function __n(string $single, string $plural, $number): string + public function __n(string $single, string $plural, int $number): string { + // in case nothing got set yet, this is fallback + if ($this->l10n === null) { + return $number > 1 ? $plural : $single; + } return $this->l10n->ngettext($single, $plural, $number); } + /** + * context translation via msgctxt + * + * @param string $context context string + * @param string $text text to translate + * @return string + */ + public function __p(string $context, string $text): string + { + if ($this->l10n === null) { + return $text; + } + return $this->l10n->pgettext($context, $text); + } + + /** + * context translation via msgctxt + * + * @param string $context context string + * @param string $single string for single word + * @param string $plural string for plural word + * @param int $number number value + * @return string + */ + public function __pn(string $context, string $single, string $plural, int $number): string + { + if ($this->l10n === null) { + return $number > 1 ? $plural : $single; + } + return $this->l10n->npgettext($context, $single, $plural, $number); + } + // alias functions to mimic gettext calls /** - * Undocumented function + * alias for gettext, + * calls __ * * @param string $text * @return string + * @deprecated Use __() */ public function gettext(string $text): string { @@ -178,22 +647,24 @@ class L10n } /** - * Undocumented function + * alias for ngettext + * calls __n * * @param string $single * @param string $plural - * @param int|float $number + * @param int $number * @return string + * @deprecated Use __n() */ - public function ngettext(string $single, string $plural, $number): string + public function ngettext(string $single, string $plural, int $number): string { return $this->__n($single, $plural, $number); } // TODO: dgettext(string $domain, string $message): string // TODO: dngettext(string $domain, string $singular, string $plural, int $count): string - // TODO: dcgettext(string $domain, string $message, int $category): string - // TODO: dcngettext(string $domain, string $singular, string $plural, int $count, int $category): string + // TODO: dpgettext(string $domain, string $message, int $category): string + // TODO: dpngettext(string $domain, string $singular, string $plural, int $count, int $category): string } // __END__ diff --git a/www/lib/CoreLibs/Language/l10n_functions.php b/www/lib/CoreLibs/Language/l10n_functions.php new file mode 100644 index 00000000..93a3188c --- /dev/null +++ b/www/lib/CoreLibs/Language/l10n_functions.php @@ -0,0 +1,208 @@ +setLocale($locale); +} + +/** + * Sets the path for a domain. + * + * @param string $domain Domain name + * @param string $path Path where to find locales + */ +function __bindtextdomain(string $domain, string $path): void +{ + L10n::getInstance()->setTextDomain($domain, $path); +} + +/** + * Dummy compatibility function, MoTranslator assumes + * everything is using same character set on input and + * output. + * + * Generally it is wise to output in UTF-8 and have + * mo files in UTF-8. + * + * @param string $domain Domain where to set character set + * @param string $codeset Character set to set + */ +function __bind_textdomain_codeset(string $domain, string $codeset): void +{ +} + +/** + * Sets the default domain. + * + * @param string $domain Domain name + */ +function __textdomain(string $domain): void +{ + L10n::getInstance()->setDomain($domain); +} + +/** + * Translates a string. + * + * @param string $msgid String to be translated + * + * @return string translated string (or original, if not found) + */ +function __gettext(string $msgid): string +{ + return L10n::getInstance()->getTranslator()->gettext( + $msgid + ); +} + +/** + * Translates a string, alias for _gettext. + * + * @param string $msgid String to be translated + * + * @return string translated string (or original, if not found) + */ +function __(string $msgid): string +{ + return L10n::getInstance()->getTranslator()->gettext( + $msgid + ); +} + +/** + * Plural version of gettext. + * + * @param string $msgid Single form + * @param string $msgidPlural Plural form + * @param int $number Number of objects + * + * @return string translated plural form + */ +function __ngettext(string $msgid, string $msgidPlural, int $number): string +{ + return L10n::getInstance()->getTranslator()->ngettext( + $msgid, + $msgidPlural, + $number + ); +} + +/** + * Translate with context. + * + * @param string $msgctxt Context + * @param string $msgid String to be translated + * + * @return string translated plural form + */ +function __pgettext(string $msgctxt, string $msgid): string +{ + return L10n::getInstance()->getTranslator()->pgettext( + $msgctxt, + $msgid + ); +} + +/** + * Plural version of pgettext. + * + * @param string $msgctxt Context + * @param string $msgid Single form + * @param string $msgidPlural Plural form + * @param int $number Number of objects + * + * @return string translated plural form + */ +function __npgettext(string $msgctxt, string $msgid, string $msgidPlural, int $number): string +{ + return L10n::getInstance()->getTranslator()->npgettext( + $msgctxt, + $msgid, + $msgidPlural, + $number + ); +} + +/** + * Translates a string. + * + * @param string $domain Domain to use + * @param string $msgid String to be translated + * + * @return string translated string (or original, if not found) + */ +function __dgettext(string $domain, string $msgid): string +{ + return L10n::getInstance()->getTranslator('', '', $domain)->gettext( + $msgid + ); +} + +/** + * Plural version of gettext. + * + * @param string $domain Domain to use + * @param string $msgid Single form + * @param string $msgidPlural Plural form + * @param int $number Number of objects + * + * @return string translated plural form + */ +function __dngettext(string $domain, string $msgid, string $msgidPlural, int $number): string +{ + return L10n::getInstance()->getTranslator('', '', $domain)->ngettext( + $msgid, + $msgidPlural, + $number + ); +} + +/** + * Translate with context. + * + * @param string $domain Domain to use + * @param string $msgctxt Context + * @param string $msgid String to be translated + * + * @return string translated plural form + */ +function __dpgettext(string $domain, string $msgctxt, string $msgid): string +{ + return L10n::getInstance()->getTranslator('', '', $domain)->pgettext( + $msgctxt, + $msgid + ); +} + +/** + * Plural version of pgettext. + * + * @param string $domain Domain to use + * @param string $msgctxt Context + * @param string $msgid Single form + * @param string $msgidPlural Plural form + * @param int $number Number of objects + * + * @return string translated plural form + */ +function __dnpgettext(string $domain, string $msgctxt, string $msgid, string $msgidPlural, int $number): string +{ + return L10n::getInstance()->getTranslator('', '', $domain)->npgettext( + $msgctxt, + $msgid, + $msgidPlural, + $number + ); +}