diff --git a/Dockerfile b/Dockerfile index 71f0b39..03359da 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,5 +23,21 @@ COPY resources/keyman-site.conf /etc/apache2/conf-available/ RUN cp /usr/local/etc/php/php.ini-production /usr/local/etc/php/php.ini RUN chown -R www-data:www-data /var/www/html/ +# Because the base Docker image doesn't include locales, install these to generate locale files. +# gettext needed to compile .po files to .mo with msgfmt +RUN apt-get update && apt-get install -y \ + locales \ + gettext + +# Install PHP-extension gettext for localization at runtime +RUN docker-php-ext-install gettext +RUN docker-php-ext-enable gettext + +# Only enable en_US locale in /etc/locale.gen +# PHP will use textdomain() to specify "localization" .mo files +RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen \ + && dpkg-reconfigure --frontend=noninteractive locales \ + && update-locale + COPY --from=composer-builder /composer/vendor /var/www/vendor RUN a2enmod rewrite headers; a2enconf keyman-site diff --git a/_includes/locale/README.md b/_includes/locale/README.md new file mode 100644 index 0000000..bfa88e0 --- /dev/null +++ b/_includes/locale/README.md @@ -0,0 +1,42 @@ +### Setup for Localization + +[init-container.sh](../../resources/init-container.sh) contains steps for the Docker container to compile .po files to .mo files which PHP uses for `gettext()`. + +If you want to compile the files on your host machine, install `gettext`. + +```bash +sudo apt-get install gettext +``` + +### Adding locales + +The Docker image has the "en_US.UTF-8" locale enabled in `/etc/locale.gen` +We'll use `textdomain` to specify filenames for "switching" localization. +The filenames will include the `%locale%` as defined in the [crowdin.com project](https://crowdin.com/project/keymancom). +In the example below, the English file `keyboards-en.po` is copied to `keyboards-fr-FR.po` for French. + +1. In `/_includes/locale/en/LC_MESSAGES/` + * Copy `keyboards-en.po` file and rename to the `keyboards-fr-FR.po`. + * Translate/upload the new .po file to crowdin + * Convert .po file to .mo with the following + +```bash +msgfmt keyboards-fr-FR.po --output-file=keyboards-fr-FR.mo +``` + +(The container handles the msgfmt step in init-container.sh) + +2. Add the file to the PHP (path is relative the PHP file) + +```php +bindtextdomain("keyboards-fr-FR", "../_includes/locale"); +``` + +3. To use French, +```php +textdomain('keyboards-fr-FR'); +``` + +---- + +For formatted string, use the PHP wrapper [`_s(msgstr, $args)`](./locale.php). \ No newline at end of file diff --git a/_includes/locale/en/LC_MESSAGES/.gitignore b/_includes/locale/en/LC_MESSAGES/.gitignore new file mode 100644 index 0000000..e1b0b2c --- /dev/null +++ b/_includes/locale/en/LC_MESSAGES/.gitignore @@ -0,0 +1,2 @@ +# Ignore generated files from msgfmt +*.mo diff --git a/_includes/locale/en/LC_MESSAGES/keyboards-en.po b/_includes/locale/en/LC_MESSAGES/keyboards-en.po new file mode 100644 index 0000000..21ea45b --- /dev/null +++ b/_includes/locale/en/LC_MESSAGES/keyboards-en.po @@ -0,0 +1,85 @@ +msgid "" +msgstr "" +"Content-Type: text/plain; charset=UTF-8\n" + +# Default English strings for keyboards/index.php + +# Page Title +msgid "Keyboard Search" +msgstr "Keyboard Search" + +# Page Description +msgid "Keyman Keyboard Search" +msgstr "Keyman Keyboard Search" + +# Keyboard search bar +msgid "Keyboard search%s" +msgstr "Keyboard search%s" + +# Search bar placeholder +msgid "Enter language or keyboard" +msgstr "Enter language or keyboard" + +# Search Button Value +msgid "Search" +msgstr "Search" + +# Link to start a new keyboard search +msgid "New search" +msgstr "New search" + +# Search box prompt +msgid "Enter the name of a keyboard or language to search for%s" +msgstr "Enter the name of a keyboard or language to search for%s" + +# Search box link for popular keyboards +msgid "Popular keyboards" +msgstr "Popular keyboards" + +# Search box link for all Keyman keyboards +msgid "All keyboards" +msgstr "All keyboards" + +# Search box hint: List header +msgid "Hints" +msgstr "Hints" + +# Search box hint: Description +msgid "The search always returns a list of keyboards. It searches for keyboard names and details, language names, country names and script names." +msgstr "The search always returns a list of keyboards. It searches for keyboard names and details, language names, country names and script names." + +# Search box hint: available prefixes to use in the search +msgid "You can apply prefixes" +msgstr "You can apply prefixes" + +# (keyboards) +msgid "%skeyboards%s" +msgstr "%skeyboards%s" + +# (languages) +msgid "%slanguages%s" +msgstr "%slanguages%s" + +# (scripts, writing systems) or... +msgid "%sscripts, writing systems%s or" +msgstr "%sscripts, writing systems%s or" + +# (countries) to filter your search results... +msgid "%scountries%s to filter your search results. For example" +msgstr "%scountries%s to filter your search results. For example" + +# Search box hint: example of country search +msgid "searches for keyboards for languages used in Thailand." +msgstr "searches for keyboards for languages used in Thailand." + +# Search box hint: BCP 47 prefix +msgid "Use prefix" +msgstr "Use prefix" + +# Seach box hint: BCP 47 language example +msgid "to search for a BCP 47 language tag, for example" +msgstr "to search for a BCP 47 language tag, for example" + +# Search box hint: BCP 47 language example +msgid "searches for Tigrigna %sEthiopia%s" +msgstr "searches for Tigrigna %sEthiopia%s" diff --git a/_includes/locale/en/LC_MESSAGES/keyboards-es-ES.po b/_includes/locale/en/LC_MESSAGES/keyboards-es-ES.po new file mode 100644 index 0000000..4f18593 --- /dev/null +++ b/_includes/locale/en/LC_MESSAGES/keyboards-es-ES.po @@ -0,0 +1,94 @@ +msgid "" +msgstr "" +"Content-Type: text/plain; charset=UTF-8\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Crowdin-Project: keymancom\n" +"X-Crowdin-Project-ID: 740839\n" +"X-Crowdin-Language: es-ES\n" +"X-Crowdin-File: /master/keyboards/keyboards.po\n" +"X-Crowdin-File-ID: 2\n" +"Project-Id-Version: keymancom\n" +"Language-Team: Spanish\n" +"Language: es_ES\n" +"PO-Revision-Date: 2024-11-11 08:20\n" + +# Page Title +msgid "Keyboard Search" +msgstr "Búsqueda por Teclado" + +# Page Description +msgid "Keyman Keyboard Search" +msgstr "Keyman Búsqueda por Teclado" + +# Keyboard search bar +msgid "Keyboard search%s" +msgstr "Búsqueda por teclado%s" + +# Search bar placeholder +msgid "Enter language or keyboard" +msgstr "Ingresar idioma o teclado" + +# Search Button Value +msgid "Search" +msgstr "Buscar" + +# Link to start a new keyboard search +msgid "New search" +msgstr "Nueva buscar" + +# Search box prompt +msgid "Enter the name of a keyboard or language to search for%s" +msgstr "Introduzca el nombre de un teclado o idioma para buscar%s" + +# Search box link for popular keyboards +msgid "Popular keyboards" +msgstr "Teclados populares" + +# Search box link for all Keyman keyboards +msgid "All keyboards" +msgstr "Todos los teclados" + +# Search box hint: List header +msgid "Hints" +msgstr "Consejos" + +# Search box hint: Description +msgid "The search always returns a list of keyboards. It searches for keyboard names and details, language names, country names and script names." +msgstr "La búsqueda siempre devuelve una lista de teclados. Busca nombres de teclados y detalles, nombres de idiomas, nombres de países y nombres de alfabetos." + +# Search box hint: available prefixes to use in the search +msgid "You can apply prefixes" +msgstr "Puedes aplicar prefijos" + +# (keyboards) +msgid "%skeyboards%s" +msgstr "%stescados%s" + +# (languages) +msgid "%slanguages%s" +msgstr "%sidiomas%s" + +# (scripts, writing systems) or... +msgid "%sscripts, writing systems%s or" +msgstr "%sguiones, sistemas de escritura%s o" + +# (countries) to filter your search results... +msgid "%scountries%s to filter your search results. For example" +msgstr "%spaíses%s para filtrar los resultados de búsqueda. Por ejemplo" + +# Search box hint: example of country search +msgid "searches for keyboards for languages used in Thailand." +msgstr "busca teclados para los idiomas utilizados en Tailandia." + +# Search box hint: BCP 47 prefix +msgid "Use prefix" +msgstr "Utilice prefijo" + +# Seach box hint: BCP 47 language example +msgid "to search for a BCP 47 language tag, for example" +msgstr "para buscar una etiqueta de idioma BCP 47, por ejemplo" + +# Search box hint: BCP 47 language example +msgid "searches for Tigrigna %sEthiopia%s" +msgstr "busca Tigrigna %sEtiopía%s" + diff --git a/_includes/locale/en/LC_MESSAGES/keyboards-fr-FR.po b/_includes/locale/en/LC_MESSAGES/keyboards-fr-FR.po new file mode 100644 index 0000000..dcc57b1 --- /dev/null +++ b/_includes/locale/en/LC_MESSAGES/keyboards-fr-FR.po @@ -0,0 +1,94 @@ +msgid "" +msgstr "" +"Content-Type: text/plain; charset=UTF-8\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" +"X-Crowdin-Project: keymancom\n" +"X-Crowdin-Project-ID: 740839\n" +"X-Crowdin-Language: fr\n" +"X-Crowdin-File: /master/keyboards/keyboards.po\n" +"X-Crowdin-File-ID: 2\n" +"Project-Id-Version: keymancom\n" +"Language-Team: French\n" +"Language: fr_FR\n" +"PO-Revision-Date: 2024-11-11 08:15\n" + +# Page Title +msgid "Keyboard Search" +msgstr "Recherche au clavier" + +# Page Description +msgid "Keyman Keyboard Search" +msgstr "Recherche de clavier Keyman" + +# Keyboard search bar +msgid "Keyboard search%s" +msgstr "Recherche au clavier%s" + +# Search bar placeholder +msgid "Enter language or keyboard" +msgstr "Entrez la langue ou le clavier" + +# Search Button Value +msgid "Search" +msgstr "Recherche" + +# Link to start a new keyboard search +msgid "New search" +msgstr "Nouvelle recherche" + +# Search box prompt +msgid "Enter the name of a keyboard or language to search for%s" +msgstr "Saisissez le nom d'un clavier ou d'une langue à rechercher%s" + +# Search box link for popular keyboards +msgid "Popular keyboards" +msgstr "Claviers populaires" + +# Search box link for all Keyman keyboards +msgid "All keyboards" +msgstr "Tous les claviers" + +# Search box hint: List header +msgid "Hints" +msgstr "Conseils" + +# Search box hint: Description +msgid "The search always returns a list of keyboards. It searches for keyboard names and details, language names, country names and script names." +msgstr "La recherche renvoie toujours une liste de claviers. Elle recherche les noms et les détails des claviers, les noms de langues, les noms de pays et les noms d'écritures." + +# Search box hint: available prefixes to use in the search +msgid "You can apply prefixes" +msgstr "Vous pouvez appliquer des préfixes" + +# (keyboards) +msgid "%skeyboards%s" +msgstr "%sclaviers%s" + +# (languages) +msgid "%slanguages%s" +msgstr "%slangues%s" + +# (scripts, writing systems) or... +msgid "%sscripts, writing systems%s or" +msgstr "%sscripts, systèmes d'écriture%s ou" + +# (countries) to filter your search results... +msgid "%scountries%s to filter your search results. For example" +msgstr "%spays%s pour filtrer vos résultats de recherche. Par exemple" + +# Search box hint: example of country search +msgid "searches for keyboards for languages used in Thailand." +msgstr "recherche des claviers pour les langues utilisées en Thaïlande." + +# Search box hint: BCP 47 prefix +msgid "Use prefix" +msgstr "Utiliser le préfixe" + +# Seach box hint: BCP 47 language example +msgid "to search for a BCP 47 language tag, for example" +msgstr "pour rechercher une balise de langue BCP 47, par exemple" + +# Search box hint: BCP 47 language example +msgid "searches for Tigrigna %sEthiopia%s" +msgstr "busca Tigrigna %sEtiopía%s" + diff --git a/_includes/locale/locale.php b/_includes/locale/locale.php new file mode 100644 index 0000000..b39bb66 --- /dev/null +++ b/_includes/locale/locale.php @@ -0,0 +1,44 @@ + 'English', // Default + 'es-ES' => 'Española', + 'fr-FR' => 'Français' + ]; + + /** + * Use textdomain to specify the localization file for "localization". + * Ignore if locale is "en" or the filename doesn't exist + * Filename expected to be "$basename-$locale.mo" + * locale - xx-YY locale as specified in crowdin %locale% + * basename - base name of the .mo file to use + */ + function setTextDomain($locale, $basename) { + $filename = sprintf("%s-%s", $basename, $locale); + $fullPath = __DIR__ . "/en/LC_MESSAGES/" . $filename . ".mo"; + if(file_exists($fullPath)) { + textdomain($filename); + } else { + # Log or warn? + return; + } + } + + /** + * Wrapper to format string with gettext '_(' alias and variable args + * s - the format string + * args - optional remaining args to the format string + */ + function _s($s, ...$args) { + return vsprintf(_($s), $args); + } + + /** + * Wrapper of echo and _s + */ + function echo_s($s, ...$args) { + $tempString = _s($s, $args); + echo $tempString; + } + + diff --git a/crowdin.yml b/crowdin.yml new file mode 100644 index 0000000..1fca8ca --- /dev/null +++ b/crowdin.yml @@ -0,0 +1,151 @@ +# +# Shared Crowdin control for https://keyman.com +# + +# +# Your Crowdin credentials +# +project_id_env: "KEYMAN_COM_CROWDIN_PROJECT_ID" +api_token_env: "CROWDIN_PERSONAL_TOKEN" +base_path: "." # local base path +base_url: "https://api.crowdin.com" + +# +# Choose file structure in Crowdin +# e.g. true or false +# +preserve_hierarchy: true + +# +# Files configuration +# +files: + # source: local path of file that gets uploaded to crowdin + # dest: crowdin path in https://crowdin.com/project/keymancom + # translation: local path where downloaded translations from crowdin go + + + # Keyboard search files + + - source: /_includes/locale/en/LC_MESSAGES/keyboards-en.po + dest: /keyboards/keyboards.po + translation: /_includes/locale/en/LC_MESSAGES/keyboards-%locale%.po + languages_mapping: + # Remap locales as needed + + # crowdin parameters descriptions: + + # + # Source files filter + # e.g. "/resources/en/*.json" + # + + # + # Where translations will be placed + # e.g. "/resources/%two_letters_code%/%original_file_name%" + # + #"translation" : "", + + # + # Files or directories for ignore + # e.g. ["/**/?.txt", "/**/[0-9].txt", "/**/*\?*.txt"] + # + #"ignore" : [], + + # + # The dest allows you to specify a file name in Crowdin + # e.g. "/messages.json" + # + #"dest" : "", + + # + # File type + # e.g. "json" + # + #"type" : "", + + # + # The parameter "update_option" is optional. If it is not set, after the files update the translations for changed strings will be removed. Use to fix typos and for minor changes in the source strings + # e.g. "update_as_unapproved" or "update_without_changes" + # + #"update_option" : "", + + # + # Start block (for XML only) + # + + # + # Defines whether to translate tags attributes. + # e.g. 0 or 1 (Default is 1) + # + # "translate_attributes" : 1, + + # + # Defines whether to translate texts placed inside the tags. + # e.g. 0 or 1 (Default is 1) + # + # "translate_content" : 1, + + # + # This is an array of strings, where each item is the XPaths to DOM element that should be imported + # e.g. ["/content/text", "/content/text[@value]"] + # + # "translatable_elements" : [], + + # + # Defines whether to split long texts into smaller text segments + # e.g. 0 or 1 (Default is 1) + # + # "content_segmentation" : 1, + + # + # End block (for XML only) + # + + # + # Start .properties block + # + + # + # Defines whether single quote should be escaped by another single quote or backslash in exported translations + # e.g. 0 or 1 or 2 or 3 (Default is 3) + # 0 - do not escape single quote; + # 1 - escape single quote by another single quote; + # 2 - escape single quote by backslash; + # 3 - escape single quote by another single quote only in strings containing variables ( {0} ). + # + # "escape_quotes" : 3, + + # + # Defines whether any special characters (=, :, ! and #) should be escaped by backslash in exported translations. + # e.g. 0 or 1 (Default is 0) + # 0 - do not escape special characters + # 1 - escape special characters by a backslash + # + # "escape_special_characters": 0 + # + + # + # End .properties block + # + + # + # Often software projects have custom names for the directories where translations are placed. crowdin-cli allows you to map your own languages to be understandable by Crowdin. + # + #"languages_mapping" : { + # "two_letters_code" : { + # "crowdin_language_code" : "local_name" + # } + #}, + + # + # Does the first line contain header? + # e.g. true or false + # + #"first_line_contains_header" : true, + + # + # for spreadsheets + # e.g. "identifier,source_phrase,context,uk,ru,fr" + # + # "scheme" : "", diff --git a/keyboards/index.php b/keyboards/index.php index c188fa8..3d7ecf5 100644 --- a/keyboards/index.php +++ b/keyboards/index.php @@ -2,15 +2,25 @@ require_once('includes/template.php'); require_once('./session.php'); require_once __DIR__ . '/../_includes/autoload.php'; + require_once __DIR__ . '/../_includes/locale/locale.php'; use Keyman\Site\com\keyman\Util; use Keyman\Site\com\keyman\templates\Head; use Keyman\Site\com\keyman\templates\Menu; use Keyman\Site\com\keyman\templates\Body; use Keyman\Site\com\keyman\templates\Foot; + // Container uses English locale, but use setTextDomain to change localization as needed + setLocale(LC_ALL, 'en_US.UTF-8'); + bindtextdomain("keyboards-fr-FR", __DIR__ . "/../_includes/locale"); + bindtextdomain("keyboards-es-ES", __DIR__ . "/../_includes/locale"); + + // $embed_locale set by session.php + setTextDomain($embed_locale, "keyboards"); + $head_options = [ - 'title' =>'Keyboard Search', - 'description' => 'Keyman Keyboard Search', + 'title' => _("Keyboard Search"), + 'description' => _("Keyman Keyboard Search"), + 'language' => $embed_locale, 'css' => [Util::cdn('css/template.css'), Util::cdn('keyboard-search/search.css')], 'js' => [Util::cdn('keyboard-search/jquery.mark.js'), Util::cdn('keyboard-search/dedicated-landing-pages.js'), Util::cdn('keyboard-search/search.js')] @@ -46,14 +56,14 @@