Skip to content

Commit

Permalink
Merge pull request #316 from Bristol-Braille/198-translation-packs
Browse files Browse the repository at this point in the history
Allow translations to be provided on external media
  • Loading branch information
woodcoder authored Jan 5, 2025
2 parents 2c70dd1 + 9feaa8d commit a464640
Show file tree
Hide file tree
Showing 20 changed files with 165 additions and 834 deletions.
8 changes: 4 additions & 4 deletions INSTALL.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,19 @@ For MacPorts:

For Homebrew:

brew install python@3.12
brew install python@3.13
brew install qt5
export PATH="$(brew --prefix qt5)/bin:$PATH"
brew install pyqt@5
export PYTHONPATH="$(brew --prefix pyqt@5)/lib/python3.12/site-packages:$PYTHONPATH"
python3.12 -m venv ve
export PYTHONPATH="$(brew --prefix pyqt@5)/lib/python3.13/site-packages:$PYTHONPATH"
python3.13 -m venv ve
. ve/bin/activate
pip install -r requirements.txt

brew install liblouis
cd ui/locale
pip install -r requirements-translate.txt
export PYTHONPATH="$(brew --prefix liblouis)/lib/python3.12/site-packages:$PYTHONPATH"
export PYTHONPATH="$(brew --prefix liblouis)/lib/python3.13/site-packages:$PYTHONPATH"

## Deploying to Raspberry Pi

Expand Down
113 changes: 75 additions & 38 deletions ui/i18n.py
Original file line number Diff line number Diff line change
@@ -1,61 +1,98 @@
import os.path
import gettext
import logging
from collections import namedtuple, OrderedDict
from collections import OrderedDict
from . import config_loader
from .braille import brailleify

log = logging.getLogger(__name__)


def install(locale_code):
try:
translations = gettext.translation(
'canute', localedir='ui/locale', languages=[locale_code],
fallback=False
)
except OSError as e:
log.warning(e)
translations = gettext.NullTranslations()
# search external media (sd/usb1/usb2) then built-in ui dir
# for locale translation directories
def locale_dirs():
config = config_loader.load()
files = config.get('files', {})
media_dir = files.get('media_dir')
library = files.get('library', [])
dirs = [os.path.join(media_dir, lib.get('path')) for lib in library]
# add the default relative path for the ui installation
dirs.append('ui')
return dirs


def load_translations(locale_code):
for dir in locale_dirs():
localedir = os.path.join(dir, 'locale')
if os.path.exists(localedir):
try:
return gettext.translation('canute', localedir, languages=[locale_code])
except OSError as e:
pass
return None


def install(locale_code, fallback=False):
translations = load_translations(locale_code)

if translations is None:
if fallback:
log.warning(f"Unable to install language {locale_code}, using null translation")
translations = gettext.NullTranslations()
else:
log.warning(f"Unable to install language {locale_code}, language left unchanged")
return
translations.install()
log.info(f"installed translation language {locale_code}")
return translations


# Before having installed _() we need extractors to see language titles.
# It's convenient to have it act as the identity function, too.
# we don't actually want to translate the generic key below, so
# just mark it using this identity function
def _(x): return x

# TRANSLATORS: This is the title/label used for this language in the
# language menu. It should always appear in the language it denotes so
# that it remains readable to those who speak only that language.
# Addition of a Braille grade marker is appropriate, if possible.
TRANSLATION_LANGUAGE_TITLE = _('Language Name, UEB grade')

Builtin = namedtuple('BuiltinLang', ['code', 'title'])

# Would prefer "British English, UEB grade N" for the following but
# (1) it's too long to be included in the languages menu title, (2) it
# might be irrelevant if there are no British-isms in this small
# collection of text, (3) US users might object on principle.

builtin = [
# TRANSLATORS: This is a language name menu item, so should always appear
# in the language it denotes so that it remains readable to those who
# speak only that language, just as "Deutsch" should always be left as
# "Deutsch" in a language menu. Addition of a Braille grade marker seems
# appropriate, if possible.
Builtin(code='en_GB.UTF-8@ueb1', title=_('English, UEB grade 1')),
# TRANSLATORS: This is a language name menu item.
Builtin(code='en_GB.UTF-8@ueb2', title=_('English, UEB grade 2')),
# TRANSLATORS: This is a language name menu item.
Builtin(code='de_DE.UTF-8@ueb1', title=_('Deutsch, UEB grade 1')),
# TRANSLATORS: This is a language name menu item.
Builtin(code='de_DE.UTF-8@ueb2', title=_('Deutsch, UEB grade 2'))
]

# remove the dummy _ definition ready to install the real translator
del _


DEFAULT_LOCALE = 'en_GB.UTF-8@ueb2'

translations = install(DEFAULT_LOCALE)
translations = install(DEFAULT_LOCALE, True)
# this will've already been installed globally, but this keeps flake8 happy
_ = translations.gettext

BUILTIN_LANGUAGES = OrderedDict([
(lang.code, _(lang.title)) for lang in builtin
])

def available_languages():
languages = []
for dir in locale_dirs():
localedir = os.path.join(dir, 'locale')
if os.path.exists(localedir):
subfolders = [f.name for f in os.scandir(localedir) if f.is_dir()]
for lang in subfolders:
mofile = os.path.join(localedir, lang, 'LC_MESSAGES', 'canute.mo')
if os.path.exists(mofile):
languages.append(lang)

menu = OrderedDict()
for lang in sorted(set(languages)):
translation = load_translations(lang)
if translation:
title = translation.gettext(TRANSLATION_LANGUAGE_TITLE)
if title == TRANSLATION_LANGUAGE_TITLE:
title = brailleify(lang)
log.warning(f"language file for {lang} missing '{TRANSLATION_LANGUAGE_TITLE}' title string")
else:
log.info(f"found language {lang} entitled {title}")
menu[lang] = title

return menu


# For detecting the default language of older installations, which
# didn't really have switchable language but did add a default
Expand Down
4 changes: 2 additions & 2 deletions ui/language/state.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
from ..i18n import BUILTIN_LANGUAGES, install
from ..i18n import available_languages, install
from ..manual import Manual, manual_filename
from .. import state


class LanguageState:
def __init__(self, root: 'state.RootState'):
self.root = root
self.available = BUILTIN_LANGUAGES
self.available = available_languages()

def select_language(self, value):
lang = abs(int(value))
Expand Down
2 changes: 1 addition & 1 deletion ui/language/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ async def render(width, height, state):

lang = state.app.user.current_language
languages = state.app.languages.available
current_lang = languages.get(lang, 'English Grade 1')
current_lang = languages.get(lang, 'English, UEB Grade 2')

try:
cur_index = list(languages.keys()).index(lang)
Expand Down
2 changes: 1 addition & 1 deletion ui/locale/README.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,5 @@ ___
If you get the error `ModuleNotFoundError: No module named 'louis'` you may
need to run something like the following first:

export PYTHONPATH="$(brew --prefix liblouis)/lib/python3.12/site-packages:$PYTHONPATH"
export PYTHONPATH="$(brew --prefix liblouis)/lib/python3.13/site-packages:$PYTHONPATH"

34 changes: 9 additions & 25 deletions ui/locale/canute.pot
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# Translations template for PROJECT.
# Copyright (C) 2024 ORGANIZATION
# Copyright (C) 2025 ORGANIZATION
# This file is distributed under the same license as the PROJECT project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2024.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2025.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2024-05-24 17:37+0100\n"
"POT-Creation-Date: 2025-01-05 14:10+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <[email protected]>\n"
Expand All @@ -25,28 +25,12 @@ msgstr ""
msgid "Eurobraille"
msgstr ""

#. TRANSLATORS: This is a language name menu item, so should always appear
#. in the language it denotes so that it remains readable to those who
#. speak only that language, just as "Deutsch" should always be left as
#. "Deutsch" in a language menu. Addition of a Braille grade marker seems
#. appropriate, if possible.
#: ui/i18n.py:39
msgid "English, UEB grade 1"
msgstr ""

#. TRANSLATORS: This is a language name menu item.
#: ui/i18n.py:41
msgid "English, UEB grade 2"
msgstr ""

#. TRANSLATORS: This is a language name menu item.
#: ui/i18n.py:43
msgid "Deutsch, UEB grade 1"
msgstr ""

#. TRANSLATORS: This is a language name menu item.
#: ui/i18n.py:45
msgid "Deutsch, UEB grade 2"
#. TRANSLATORS: This is the title/label used for this language in the
#. language menu. It should always appear in the language it denotes so
#. that it remains readable to those who speak only that language.
#. Addition of a Braille grade marker is appropriate, if possible.
#: ui/i18n.py:52
msgid "Language Name, UEB grade"
msgstr ""

#: ui/manual.py:34
Expand Down
Loading

0 comments on commit a464640

Please sign in to comment.