[TikiWiki-commits] [Git][tikiwiki/tiki][master] [FIX] Resolve Summernote locale file path loading errors

Benoit Grégoire (@benoitg) via TikiWiki-cvs <[email protected]>
Newsgroups gmane.comp.cms.tiki.cvs
Message-ID <6a8f3cc53cc6e_3818dad0-357@gitlab-sidekiq-low-urgency-cpu-bound-v2-64bbd65c45-4hkck.mail>

Benoit Grégoire pushed to branch master at Tiki Wiki CMS Groupware / Tiki


Commits:
4012173d by Moïse Nturubika at 2026-08-26T19:05:25+00:00
[FIX] Resolve Summernote locale file path loading errors
---
* [FIX] Address review: fix locale map intent, remove dead 'pt' entry

* [FIX] Address review: clarify TIKI_EDITOR_LOCALE_MAP usage and fix getEditorLang() dispatch logic

* [FIX] Refactor editor language mapping and resolve Summernote locale file path loading errors

See merge request tikiwiki/tiki!10559

- - - - -


1 changed file:

- lib/wysiwyg/wysiwyglib.php


Changes:

=====================================
lib/wysiwyg/wysiwyglib.php
=====================================
@@ -10,6 +10,74 @@
 
 class WYSIWYGLib
 {
+    /**
+     * Editorial disambiguation map from Tiki language codes to editor locale codes.
+     * Used by resolveLocaleCode() (Summernote) and languageMapISO() (Toast UI).
+     *
+     * TikiLib::get_language() returns bare codes (e.g. 'es', 'fr'), which can match
+     * more than one locale file on disk. This map makes the explicit editorial choice
+     * (e.g. 'de' → 'de-DE' rather than 'de-CH'). Languages absent from the map fall
+     * back to disk-based discovery in resolveSummernoteLocale().
+     *
+     * Special cases:
+     *   - Empty string (''): English — Summernote has no locale file; Toast uses its default.
+     *   - 'ar': bare code kept because Toast UI ships ar.js; Summernote resolves ar-AR from disk.
+     */
+    private const TIKI_EDITOR_LOCALE_MAP = [
+        'ar'      => 'ar',           // Toast UI: ar.js exists; Summernote: disk resolves ar-AR regardless
+        //'bg'    => 'bg',           // Bulgarian — no editor file yet
+        //'ca'    => 'ca',           // Catalan — no editor file yet
+        'cn'      => 'zh-CN',        // Simplified Chinese: Tiki='cn', editors='zh-CN'
+        'cs'      => 'cs-CZ',        // Czech
+        //'cy'    => 'cy',           // Welsh — no editor file yet
+        //'da'    => 'da',           // Danish — no editor file yet
+        'de'      => 'de-DE',        // German
+        'en'      => '',             // English — Toast default (no file); Summernote uses 'en-US' (built-in, no locale file loaded)
+        'en-uk'   => 'en-US',        // British English → en-US for both editors
+        'es'      => 'es-ES',        // Spanish
+        //'el'    => 'el',           // Greek — no editor file yet
+        //'fa'    => 'fa',           // Farsi — no editor file yet
+        'fi'      => 'fi-FI',        // Finnish
+        //'fj'    => 'fj',           // Fijian — no editor file yet
+        'fr'      => 'fr-FR',        // French
+        'fy-NL'   => 'nl-NL',        // West Frisian → Dutch (no dedicated file in either editor)
+        'gl'      => 'gl-ES',        // Galician
+        //'he'    => 'he',           // Hebrew — no editor file yet
+        'hr'      => 'hr-HR',        // Croatian
+        //'id'    => 'id',           // Indonesian — no editor file yet
+        //'is'    => 'is',           // Icelandic — no editor file yet
+        'it'      => 'it-IT',        // Italian
+        //'iu'    => 'iu',           // Inuktitut — no editor file yet
+        //'iu-ro' => 'iu-ro',        // Inuktitut (Roman) — no editor file yet
+        //'iu-iq' => 'iu-iq',        // Iniunnaqtun — no editor file yet
+        'ja'      => 'ja-JP',        // Japanese
+        'ko'      => 'ko-KR',        // Korean
+        //'hu'    => 'hu',           // Hungarian — no editor file yet
+        //'lt'    => 'lt',           // Lithuanian — no editor file yet
+        'nds'     => 'de-DE',        // Low German → German locale
+        'nl'      => 'nl-NL',        // Dutch
+        'no'      => 'nb-NO',        // Norwegian: Tiki='no', editors use Bokmål 'nb'
+        'pl'      => 'pl-PL',        // Polish
+        // 'pt' is intentionally absent: Toast UI has no pt.js (only pt-br.js, covered below),
+        // and Summernote resolves pt-PT from disk without a map entry.
+        'pt-br'   => 'pt-BR',        // Brazilian Portuguese
+        //'ro'    => 'ro',           // Romanian — no editor file yet
+        //'rm'    => 'rm',           // Romansh — no editor file yet
+        'ru'      => 'ru-RU',        // Russian
+        //'sb'    => 'sb',           // Pijin Solomon — no editor file yet
+        //'si'    => 'si',           // Sinhala — no editor file yet
+        //'sk'    => 'sk',           // Slovak — no editor file yet
+        //'sl'    => 'sl',           // Slovene — no editor file yet
+        //'sq'    => 'sq',           // Albanian — no editor file yet
+        'sr-latn' => 'sr-RS-Latin',  // Latin Serbian: suffix naming mismatch across editors
+        'sv'      => 'sv-SE',        // Swedish
+        //'tv'    => 'tv',           // Tuvaluan — no editor file yet
+        'tr'      => 'tr-TR',        // Turkish
+        'tw'      => 'zh-TW',        // Traditional Chinese: Tiki='tw', editors='zh-TW'
+        'uk'      => 'uk-UA',        // Ukrainian
+        //'vi'    => 'vi',           // Vietnamese — no editor file yet
+    ];
+
     public function setupInlineEditor($pageName)
     {
         global $prefs, $user;
@@ -64,17 +132,98 @@ class WYSIWYGLib
 
     public function getEditorLang()
     {
-        $lang = TikiLib::lib('tiki')->get_language();
-        if ($lang === 'en' || $lang === 'en-uk') { // Summernote only has en-US
-            $lang = 'en-US';
+        $tikLang = TikiLib::lib('tiki')->get_language();
+        $locale = $this->resolveLocaleCode($tikLang);
+        $langFilePath = NODE_PUBLIC_DIST_PATH . '/summernote/dist/lang/summernote-' . $locale . '.min.js';
+
+        return ['lang' => $locale, 'filePath' => $langFilePath];
+    }
+
+    /**
+     * Resolve the Summernote locale code for a given Tiki language code.
+     *
+     * TikiLib::get_language() returns bare codes (e.g. 'es', 'fr'). TIKI_EDITOR_LOCALE_MAP
+     * makes the editorial choice when a bare code would match multiple files on disk
+     * (e.g. 'de' → 'de-DE' rather than 'de-CH'). Languages absent from the map fall back
+     * to disk-based discovery in resolveSummernoteLocale().
+     *
+     * @see TikiLib::get_language()
+     */
+    protected function resolveLocaleCode(string $tikLang): string
+    {
+        $mapped = self::TIKI_EDITOR_LOCALE_MAP[$tikLang] ?? null;
+
+        if ($mapped === null) {
+            // Language not in map: auto-discover locale from disk.
+            return $this->resolveSummernoteLocale($tikLang);
+        } elseif ($mapped === '') {
+            // English: Summernote renders in English by default; no locale file is loaded.
+            return 'en-US';
+        } elseif (strpos($mapped, '-') !== false) {
+            // Full hyphenated locale (e.g. 'fr-FR', 'zh-CN'): use directly.
+            return $mapped;
         } else {
-            $parts = explode('-', $lang);
-            $lang = $parts[0] . '-' . strtoupper($parts[1] ?? $parts[0]);
+            // Bare language hint (e.g. 'ar'): let disk resolution find the best variant.
+            return $this->resolveSummernoteLocale($mapped);
         }
+    }
 
-        $langFilePath = NODE_PUBLIC_DIST_PATH . '/summernote/dist/lang/summernote-' . $lang . '.min.js';
+    /**
+     * Auto-discover and resolve the correct Summernote locale code from disk.
+     * Matches exact case-insensitive code, then falls back to prefix matching
+     * (with country-code tiebreaker), and finally defaults to 'en-US'.
+     */
+    protected function resolveSummernoteLocale(string $tikLang): string
+    {
+        $langDir = NODE_PUBLIC_DIST_PATH . '/summernote/dist/lang/';
+        $files = glob($langDir . 'summernote-*.min.js');
+
+        if (empty($files)) {
+            return 'en-US';
+        }
 
-        return ['lang' => $lang, 'filePath' => $langFilePath];
+        // Extract locale codes from file names (e.g. 'sv-SE', 'de-DE', 'de-CH')
+        $availableLocales = [];
+        foreach ($files as $file) {
+            if (preg_match('/summernote-(.+)\.min\.js$/', basename($file), $matches)) {
+                $availableLocales[] = $matches[1];
+            }
+        }
+
+        // 1. Exact match (case-insensitive) — e.g. Tiki 'pt-br' → Summernote 'pt-BR'
+        foreach ($availableLocales as $locale) {
+            if (strcasecmp($locale, $tikLang) === 0) {
+                return $locale;
+            }
+        }
+
+        // 2. Prefix match — e.g. Tiki 'sv' matches Summernote 'sv-SE'
+        $langPrefix = strtolower(explode('-', $tikLang)[0]);
+        $candidates = array_values(array_filter($availableLocales, function ($locale) use ($langPrefix) {
+            return strtolower(explode('-', $locale)[0]) === $langPrefix;
+        }));
+
+        if (count($candidates) === 1) {
+            return $candidates[0];
+        }
+
+        if (count($candidates) > 1) {
+            // Tiebreak: prefer the locale whose country suffix (uppercased)
+            // mirrors the language prefix — e.g. 'de' prefers 'de-DE' over 'de-CH'.
+            $preferredCountry = strtoupper($langPrefix);
+            foreach ($candidates as $locale) {
+                $parts = explode('-', $locale);
+                if (isset($parts[1]) && $parts[1] === $preferredCountry) {
+                    return $locale;
+                }
+            }
+            // No preferred match: return the first one alphabetically
+            sort($candidates);
+            return $candidates[0];
+        }
+
+        // 3. Fallback
+        return 'en-US';
     }
 
     public function setUpEditor($dom_id, $params = [])
@@ -284,71 +433,17 @@ class WYSIWYGLib
         return [];
     }
 
-    /** Map between tiki lang codes and Toast (uses ISO codes)
+    /**
+     * Map a Tiki language code to the locale code used by Toast UI editor.
+     * Reads from the shared TIKI_EDITOR_LOCALE_MAP constant so both editors
+     * are always in sync — update the constant, not this method.
      *
      * @param string $lang  Tiki language code
-     *
-     * @return string       mapped language code
-     *                      defaults empty if not found so not supported
+     * @return string       Toast UI locale code, or '' if Toast has no translation
      */
     private function languageMapISO($lang)
     {
-
-        $langMap = [
-            'ar' => 'ar',           // Arabic = United Arab Emirates
-            //'bg' => 'bg',         // Bulgarian
-            //'ca' => 'ca',         // Catalan
-            'cn' => 'zh-CN',        // China - Simplified Chinese
-            'cs' => 'cs-CZ',        // Czech
-            //'cy' => 'cy',         // Welsh
-            //'da' => 'da',         // Danish
-            'de' => 'de-DE',        // Germany - German
-            //'en-uk' => 'en-GB',   // United Kingdom - English
-            'en' => '',        // United States - English
-            'es' => 'es-ES',        // Spain - Spanish
-            //'el' => 'el',         // Greek
-            //'fa' => 'fa',         // Farsi
-            'fi' => 'fi-FI',         // Finnish
-            //'fj' => 'fj',         // Fijian
-            'fr' => 'fr-FR',        // France - French
-            'fy-NL' => 'nl',        // Netherlands - Dutch
-            'gl' => 'gl-ES',        // Galician
-            //'he' => 'he',         // Israel - Hebrew
-            'hr' => 'hr-HR',        // Croatian
-            //'id' => 'id',         // Indonesian
-            //'is' => 'is',         // Icelandic
-            'it' => 'it-IT',        // Italy - Italian
-            //'iu' => 'iu',         // Inuktitut
-            //'iu-ro' => 'iu-ro',   // Inuktitut (Roman)
-            //'iu-iq' => 'iu-iq',   // Iniunnaqtun
-            'ja' => 'ja-JP',        // Japan - Japanese
-            'ko' => 'ko-KR',        // Korean
-            //'hu' => 'hu',         // Hungarian
-            //'lt' => 'lt',         // Lithuanian
-            'nds' => 'de-DE',       // Low German
-            'nl' => 'nl-NL',        // Netherlands - Dutch
-            'no' => 'nb-NO',        // Norway - Norwegian
-            'pl' => 'pl-PL',        // Poland - Polish
-            'pt' => 'pt',           // Portuguese
-            'pt-br' => 'pt-BR',     // Brazil - Portuguese
-            //'ro' => 'ro',         // Romanian
-            //'rm' => 'rm',         // Romansh
-            'ru' => 'ru-RU',        // Russia - Russian
-            //'sb' => 'sb',           // Pijin Solomon
-            //'si' => 'si',         // Sinhala
-            //'sk' => 'sk',         // Slovak
-            //'sl' => 'sl',         // Slovene
-            //'sq' => 'sq',         // Albanian
-            //'sr-latn' => 'sr-latn',   // Serbian Latin
-            'sv' => 'sv-SE',        // Sweden - Swedish
-            //'tv' => 'tv',           // Tuvaluansr-latn
-            'tr' => 'tr-TR',        // Turkey - Turkish
-            'tw' => 'zh-TW',        // Taiwan - Traditional Chinese
-            'uk' => 'uk-UA',        // Ukrainian
-            //'vi' => 'vi',         // Vietnamese
-        ];
-
-        return $langMap[$lang] ?? '';
+        return self::TIKI_EDITOR_LOCALE_MAP[$lang] ?? '';
     }
 
     private function processSpecialHeadings($content)



View it on GitLab: https://gitlab.com/tikiwiki/tiki/-/commit/4012173d9d650434f3fa7d9aba367f0538180c0b

-- 
View it on GitLab: https://gitlab.com/tikiwiki/tiki/-/commit/4012173d9d650434f3fa7d9aba367f0538180c0b
You're receiving this email because of your account on gitlab.com. Manage all notifications: https://gitlab.com/-/profile/notifications | Help: https://gitlab.com/help

_______________________________________________
TikiWiki-cvs mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/tikiwiki-cvs
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.