diff --git a/build/translation-checker.php b/build/translation-checker.php index 399031c12ae45..b6d947ecbf091 100644 --- a/build/translation-checker.php +++ b/build/translation-checker.php @@ -16,6 +16,35 @@ 'testing', ]; +// Next line only looks messed up, but it works. Don't touch it! +$rtlCharacters = [ + '\x{061C}', // ARABIC LETTER MARK + '\x{0623}', // ARABIC LETTER ALEF WITH HAMZA ABOVE + '\x{200E}', // LEFT-TO-RIGHT MARK + '\x{200F}', // RIGHT-TO-LEFT MARK + '\x{202A}', // LEFT-TO-RIGHT EMBEDDING + '\x{202B}', // RIGHT-TO-LEFT EMBEDDING + '\x{202C}', // POP DIRECTIONAL FORMATTING + '\x{202D}', // LEFT-TO-RIGHT OVERRIDE + '\x{202E}', // RIGHT-TO-LEFT OVERRIDE + '\x{2066}', // LEFT-TO-RIGHT ISOLATE + '\x{2067}', // RIGHT-TO-LEFT ISOLATE + '\x{2068}', // FIRST STRONG ISOLATE + '\x{2069}', // POP DIRECTIONAL ISOLATE + '\x{206C}', // INHIBIT ARABIC FORM SHAPING + '\x{206D}', // ACTIVATE ARABIC FORM SHAPING +]; + +$rtlLanguages = [ + 'ar', // Arabic + 'fa', // Persian + 'he', // Hebrew + 'ps', // Pashto, + 'ug', // 'Uyghurche / Uyghur + 'ur_PK', // Urdu + 'uz', // Uzbek Afghan +]; + $valid = 0; $errors = []; $apps = new \DirectoryIterator(__DIR__ . '/../apps'); @@ -46,6 +75,12 @@ } $content = file_get_contents($file->getPathname()); + + $language = pathinfo($file->getFilename(), PATHINFO_FILENAME); + if (!in_array($language, $rtlLanguages, true) && preg_match('/[' . implode('', $rtlCharacters) . ']/u', $content)) { + $errors[] = $file->getPathname() . "\n" . ' ' . 'Contains a RTL limited character in the translations.' . "\n"; + } + $json = json_decode($content, true); $translations = json_encode($json['translations']); diff --git a/core/templates/layout.user.php b/core/templates/layout.user.php index fc0f3af2d090c..7f5322fee2947 100644 --- a/core/templates/layout.user.php +++ b/core/templates/layout.user.php @@ -51,7 +51,7 @@ - data-themes=> diff --git a/lib/private/L10N/Factory.php b/lib/private/L10N/Factory.php index 7dd0704682d74..a519ae7e7616b 100644 --- a/lib/private/L10N/Factory.php +++ b/lib/private/L10N/Factory.php @@ -58,6 +58,19 @@ class Factory implements IFactory { 'pt_BR', 'pt_PT', 'da', 'fi_FI', 'nb_NO', 'sv', 'tr', 'zh_CN', 'ko' ]; + /** + * Keep in sync with `build/translation-checker.php` + */ + public const RTL_LANGUAGES = [ + 'ar', // Arabic + 'fa', // Persian + 'he', // Hebrew + 'ps', // Pashto, + 'ug', // 'Uyghurche / Uyghur + 'ur_PK', // Urdu + 'uz', // Uzbek Afghan + ]; + private ICache $cache; public function __construct( @@ -364,6 +377,14 @@ public function languageExists($app, $lang) { return in_array($lang, $languages); } + public function getLanguageDirection(string $language): string { + if (in_array($language, self::RTL_LANGUAGES, true)) { + return 'rtl'; + } + + return 'ltr'; + } + public function getLanguageIterator(?IUser $user = null): ILanguageIterator { $user = $user ?? $this->userSession->getUser(); if ($user === null) { diff --git a/lib/private/TemplateLayout.php b/lib/private/TemplateLayout.php index c21df495b5bdd..60c7526435e2c 100644 --- a/lib/private/TemplateLayout.php +++ b/lib/private/TemplateLayout.php @@ -193,13 +193,15 @@ public function __construct($renderAs, $appId = '') { } else { parent::__construct('core', 'layout.base'); } - // Send the language and the locale to our layouts + // Send the language, locale, and direction to our layouts $lang = \OC::$server->get(IFactory::class)->findLanguage(); $locale = \OC::$server->get(IFactory::class)->findLocale($lang); + $direction = \OC::$server->getL10NFactory()->getLanguageDirection($lang); $lang = str_replace('_', '-', $lang); $this->assign('language', $lang); $this->assign('locale', $locale); + $this->assign('direction', $direction); if (\OC::$server->getSystemConfig()->getValue('installed', false)) { if (empty(self::$versionHash)) { diff --git a/lib/public/L10N/IFactory.php b/lib/public/L10N/IFactory.php index 5c2f4dcbfa467..aebd318dfad2c 100644 --- a/lib/public/L10N/IFactory.php +++ b/lib/public/L10N/IFactory.php @@ -103,6 +103,15 @@ public function languageExists($app, $lang); */ public function localeExists($locale); + /** + * Return the language direction + * + * @param string $language + * @return 'ltr'|'rtl' + * @since 31.0.0 + */ + public function getLanguageDirection(string $language): string; + /** * iterate through language settings (if provided) in this order: * 1. returns the forced language or: diff --git a/tests/lib/L10N/FactoryTest.php b/tests/lib/L10N/FactoryTest.php index 0f243c124ca41..c29c31bf6503a 100644 --- a/tests/lib/L10N/FactoryTest.php +++ b/tests/lib/L10N/FactoryTest.php @@ -775,4 +775,22 @@ public function testGetLanguageIterator(bool $hasSession, ?IUser $iUserMock = nu $iterator = $factory->getLanguageIterator($iUserMock); self::assertInstanceOf(ILanguageIterator::class, $iterator); } + + public static function dataGetLanguageDirection(): array { + return [ + ['en', 'ltr'], + ['de', 'ltr'], + ['fa', 'rtl'], + ['ar', 'rtl'] + ]; + } + + /** + * @dataProvider dataGetLanguageDirection + */ + public function testGetLanguageDirection(string $language, string $expectedDirection) { + $factory = $this->getFactory(); + + self::assertEquals($expectedDirection, $factory->getLanguageDirection($language)); + } }