Merge branch 'translations' into 'master'

Translations

Closes #14

See merge request ynerant/squirrel-battle!31
This commit is contained in:
ynerant 2020-11-28 16:23:12 +01:00
commit e5886bbe44
24 changed files with 1010 additions and 88 deletions

3
.gitignore vendored
View File

@ -23,3 +23,6 @@ save.json
# Don't commit docs output
docs/_build
# Don't commit compiled messages
*.mo

View File

@ -7,6 +7,7 @@ py37:
stage: test
image: python:3.7-alpine
before_script:
- apk add --no-cache gettext
- pip install tox
script: tox -e py3
@ -14,6 +15,7 @@ py38:
stage: test
image: python:3.8-alpine
before_script:
- apk add --no-cache gettext
- pip install tox
script: tox -e py3
@ -22,6 +24,7 @@ py39:
stage: test
image: python:3.9-alpine
before_script:
- apk add --no-cache gettext
- pip install tox
script: tox -e py3
@ -37,7 +40,7 @@ build-deb:
image: debian:buster-slim
stage: build
before_script:
- apt-get update && apt-get -y --no-install-recommends install build-essential debmake dh-python debhelper python3-all python3-setuptools
- apt-get update && apt-get -y --no-install-recommends install build-essential debmake dh-python debhelper gettext python3-all python3-setuptools
script:
- dpkg-buildpackage
- mkdir build && cp ../*.deb build/

2
debian/changelog vendored
View File

@ -1,4 +1,4 @@
python3-squirrel-battle (3.14) beta; urgency=low
python3-squirrel-battle (3.14.1) beta; urgency=low
* Some graphical improvements.

2
debian/control vendored
View File

@ -2,7 +2,7 @@ Source: python3-squirrel-battle
Section: devel
Priority: optional
Maintainer: ynerant <squirrel-battle@crans.org>
Build-Depends: debhelper (>=10~), dh-python, python3-all, python3-setuptools
Build-Depends: debhelper (>=10~), dh-python, gettext, python3-all, python3-setuptools
Depends: fonts-noto-color-emoji
Standards-Version: 4.1.4
Homepage: https://gitlab.crans.org/ynerant/squirrel-battle

View File

@ -34,6 +34,16 @@ paquet ainsi que des détails à fournir à PyPI :
with open("README.md", "r") as f:
long_description = f.read()
# Compile messages
for language in ["de", "en", "fr"]:
args = ["msgfmt", "--check-format",
"-o", f"squirrelbattle/locale/{language}/LC_MESSAGES"
"/squirrelbattle.mo",
f"squirrelbattle/locale/{language}/LC_MESSAGES"
"/squirrelbattle.po"]
print(f"Compiling {language} messages...")
subprocess.Popen(args)
setup(
name="squirrel-battle",
version="3.14.1",
@ -60,7 +70,7 @@ paquet ainsi que des détails à fournir à PyPI :
],
python_requires='>=3.6',
include_package_data=True,
package_data={"squirrelbattle": ["assets/*"]},
package_data={"squirrelbattle": ["assets/*", "locale/*/*/*.mo"]},
entry_points={
"console_scripts": [
"squirrel-battle = squirrelbattle.bootstrap:Bootstrap.run_game",
@ -72,6 +82,8 @@ Ce fichier contient le nom du paquet, sa version, l'auteur et son contact,
sa description en une ligne et sa description longue, le lien d'accueil du projet,
sa licence, ses classificateurs et son exécutable.
Il commence tout d'abord par compiler les fichiers de `traduction <translation.html>`_.
Le paramètre ``entry_points`` définit un exécutable nommé ``squirrel-battle``,
qui permet de lancer le jeu.
@ -167,7 +179,7 @@ du dépôt Git. Le fichier ``PKGBUILD`` dispose de cette structure :
url="https://gitlab.crans.org/ynerant/squirrel-battle"
license=('GPLv3')
depends=('python')
makedepends=('python-setuptools')
makedepends=('gettext' 'python-setuptools')
depends=('noto-fonts-emoji')
checkdepends=('python-tox')
ssource=("git+https://gitlab.crans.org/ynerant/squirrel-battle.git")
@ -217,7 +229,7 @@ les releases, est plus ou moins similaire :
url="https://gitlab.crans.org/ynerant/squirrel-battle"
license=('GPLv3')
depends=('python')
makedepends=('python-setuptools')
makedepends=('gettext' 'python-setuptools')
depends=('noto-fonts-emoji')
checkdepends=('python-tox')
source=("https://gitlab.crans.org/ynerant/squirrel-battle/-/archive/v3.14.1/$pkgbase-v$pkgver.tar.gz")
@ -296,7 +308,7 @@ D'abord on installe les paquets nécessaires :
.. code::
apt update
apt --no-install-recommends install build-essential debmake dh-python debhelper python3-all python3-setuptools
apt --no-install-recommends install build-essential debmake dh-python debhelper gettext python3-all python3-setuptools
On peut ensuite construire le paquet :

View File

@ -37,6 +37,7 @@ Bienvenue dans la documentation de Squirrel Battle !
install-dev
tests
display/index
translation
deployment
documentation

View File

@ -1,16 +1,19 @@
Installation d'un environnement de développement
================================================
Il est toujours préférable de travailler dans un environnement Python isolé du reste de son instalation.
Il est toujours préférable de travailler dans un environnement Python isolé du
reste de son instalation.
1. **Installation des dépendances de la distribution.**
Vous devez déjà installer Python et le module qui permet de créer des environnements virtuels.
On donne ci-dessous l'exemple pour une distribution basée sur Debian, mais vous pouvez facilement adapter pour ArchLinux ou autre.
Vous devez déjà installer Python et le module qui permet de créer des
environnements virtuels.
On donne ci-dessous l'exemple pour une distribution basée sur Debian,
mais vous pouvez facilement adapter pour ArchLinux ou autre.
.. code:: bash
$ sudo apt update
$ sudo apt install --no-install-recommends -y python3-setuptools python3-venv python3-dev git
$ sudo apt install --no-install-recommends -y python3-setuptools python3-venv python3-dev gettext git
2. **Clonage du dépot** là où vous voulez :
@ -25,7 +28,13 @@ Il est toujours préférable de travailler dans un environnement Python isolé d
$ python3 -m venv env
$ source env/bin/activate # entrer dans l'environnement
(env)$ pip3 install -r requirements.txt
(env)$ deactivate # sortir de l'environnement
(env) $ pip3 install -r requirements.txt
(env) $ deactivate # sortir de l'environnement
4. **Compilation des messages de traduction.**
.. code:: bash
(env) $ python3 main.py --compilemessages
Le lancement du jeu se fait en lançant la commande ``python3 main.py``.

120
docs/translation.rst Normal file
View File

@ -0,0 +1,120 @@
Traduction
==========
Le jeu Squirrel Battle est entièrement traduit en anglais, en français et en allement.
La langue se choisit dans les `paramètres <settings.html>`_.
Utitisation
-----------
Les traductions sont gérées grâce au module natif ``gettext``. Le module
``squirrelbattle.translations`` s'occupe d'installer les traductions, et de
donner les chaînes traduites.
Pour choisir la langue, il faut appeler ``Translator.setlocale(language: str)``,
``language`` correspond au code à 2 lettres de la langue.
Enfin, le module expose une fonction ``gettext(str) -> str`` qui permet de
traduire les chaînes.
Il est courant et recommandé d'importer cette fonction sous l'alias ``_``,
afin de limiter la verbositer et de permettre de rendre facilement une chaîne
traduisible.
.. code:: python
from squirrelbattle.translations import gettext as _, Translator
Translator.setlocale("fr")
print(_("I am a translatable string"))
print("I am not translatable")
Si les traductions sont bien faites (voir ci-dessous), cela donnera :
.. code::
Je suis une chaîne traduisible
I am not translatable
À noter que si la chaîne n'est pas traduite, alors par défaut on renvoie la
chaîne elle-même.
Extraction des chaînes à traduire
---------------------------------
L'appel à ``gettext`` ne fait pas que traduire les chaînes : il est possible
également d'extraire toutes les chaînes à traduire.
Il est nécessaire d'installer le paquet Linux ``gettext`` pour cela.
L'utilitaire ``xgettext`` s'occupe de cette extraction. Il s'utilise de la façon
suivante :
.. code:: bash
xgettext --from-code utf-8 -o output_file.po source_1.py ... source_n.py
Afin de ne pas avoir à sélectionner manuellement chaque fichier, il est possible
d'appeler directement ``python3 main.py --makemessages``. Cela a pour effet
d'exécuter pour chaque langue ``<LANG>`` :
.. code:: bash
find squirrelbattle -iname '*.py' | xargs xgettext --from-code utf-8
--add-comments
--package-name=squirrelbattle
--package-version=3.14.1
"--copyright-holder=ÿnérant, eichhornchen, nicomarg, charlse"
--msgid-bugs-address=squirrel-battle@crans.org
-o squirrelbattle/locale/<LANG>/LC_MESSAGES/squirrelbattle.po
Les fichiers de traductions se trouvent alors dans
``squirrelbattle/locale/<LANG>/LC_MESSAGES/squirrelbattle.po``.
Traduire les chaînes
--------------------
Après extraction des chaînes, les chaînes à traduire se trouvent dans
``squirrelbattle/locale/<LANG>/LC_MESSAGES/squirrelbattle.po``, comme indiqué
ci-dessus.
Ce fichier peut-être édité avec un utilitaire tel que ``poedit``, sur
l'interface Web sur `<https://translate.ynerant.fr/squirrel-battle/squirrel-battle>`_,
mais surtout manuellement avec un éditeur de texte.
Dans ce fichier, on obtient pour chaque chaîne à traduire un paragraphe de la
forme :
.. code:: po
#: main.py:4
msgid "I am a translatable string"
msgstr "Je suis une chaîne traduisible"
Il sufift de remplir les champs ``msgstr``.
Compilation des chaînes
-----------------------
Pour gagner en efficacité, les chaînes sont compilées dans un fichier avec
l'extension ``.mo``. Ce sont ces fichiers qui sont lus par le module de traduction.
Pour compiler les traductions, c'est l'utilitaire ``msgfmt`` fourni toujours par
le paquet Linux ``gettext`` que nous utilisons. Il s'utilise assez simplement :
.. code:: bash
msgfmt po_file.po -o mo_file.mo
À nouveau, il est possible de compiler automatiquement les messages en exécutant
``python3 main.py --compilemessages``.
.. warning::
On ne partagera pas dans le dépôt Git les fichiers compilé. En développement,
on compilera soi-même les messages, et en production, la construction des
paquets se charge de compiler automatiquement les traductions.

18
main.py
View File

@ -2,8 +2,24 @@
# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse
# SPDX-License-Identifier: GPL-3.0-or-later
import argparse
import sys
from squirrelbattle.bootstrap import Bootstrap
from squirrelbattle.translations import Translator
if __name__ == "__main__":
Bootstrap.run_game()
parser = argparse.ArgumentParser()
parser.add_argument("--makemessages", "-mm", action="store_true",
help="Extract translatable strings")
parser.add_argument("--compilemessages", "-cm", action="store_true",
help="Compile translatable strings")
args = parser.parse_args(sys.argv[1:])
if args.makemessages:
Translator.makemessages()
elif args.compilemessages:
Translator.compilemessages()
else:
Bootstrap.run_game()

View File

@ -3,13 +3,23 @@
# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse
# SPDX-License-Identifier: GPL-3.0-or-later
import os
import subprocess
from setuptools import find_packages, setup
with open("README.md", "r") as f:
long_description = f.read()
# Compile messages
for language in ["de", "en", "fr"]:
args = ["msgfmt", "--check-format",
"-o", f"squirrelbattle/locale/{language}/LC_MESSAGES"
"/squirrelbattle.mo",
f"squirrelbattle/locale/{language}/LC_MESSAGES"
"/squirrelbattle.po"]
print(f"Compiling {language} messages...")
subprocess.Popen(args)
setup(
name="squirrel-battle",
version="3.14.1",
@ -36,7 +46,7 @@ setup(
],
python_requires='>=3.6',
include_package_data=True,
package_data={"squirrelbattle": ["assets/*"]},
package_data={"squirrelbattle": ["assets/*", "locale/*/*/*.mo"]},
entry_points={
"console_scripts": [
"squirrel-battle = squirrelbattle.bootstrap:Bootstrap.run_game",

View File

@ -6,6 +6,7 @@ from typing import List
from squirrelbattle.menus import Menu, MainMenu
from .display import Display, Box
from ..resources import ResourceManager
from ..translations import gettext as _
class MenuDisplay(Display):
@ -17,8 +18,6 @@ class MenuDisplay(Display):
def update_menu(self, menu: Menu) -> None:
self.menu = menu
self.trueheight = len(self.values)
self.truewidth = max([len(a) for a in self.values])
# Menu values are printed in pad
self.pad = self.newpad(self.trueheight, self.truewidth + 2)
@ -44,6 +43,14 @@ class MenuDisplay(Display):
self.height - 2 + self.y,
self.width - 2 + self.x)
@property
def truewidth(self) -> int:
return max([len(str(a)) for a in self.values])
@property
def trueheight(self) -> int:
return len(self.values)
@property
def preferred_width(self) -> int:
return self.truewidth + 6
@ -60,9 +67,10 @@ class MenuDisplay(Display):
class SettingsMenuDisplay(MenuDisplay):
@property
def values(self) -> List[str]:
return [a[1][1] + (" : "
return [_(a[1][1]) + (" : "
+ ("?" if self.menu.waiting_for_key
and a == self.menu.validate() else a[1][0])
and a == self.menu.validate() else a[1][0]
.replace("\n", "\\n"))
if a[1][0] else "") for a in self.menu.values]

View File

@ -3,10 +3,10 @@
import curses
from ..entities.player import Player
from ..translations import gettext as _
from .display import Display
from squirrelbattle.entities.player import Player
class StatsDisplay(Display):
player: Player
@ -31,12 +31,12 @@ class StatsDisplay(Display):
self.player.dexterity, self.player.constitution)
self.addstr(self.pad, 3, 0, string3)
inventory_str = "Inventaire : " + "".join(
inventory_str = _("Inventory:") + " " + "".join(
self.pack[item.name.upper()] for item in self.player.inventory)
self.addstr(self.pad, 8, 0, inventory_str)
if self.player.dead:
self.addstr(self.pad, 10, 0, "VOUS ÊTES MORT",
self.addstr(self.pad, 10, 0, _("YOU ARE DEAD"),
curses.A_BOLD | curses.A_BLINK | curses.A_STANDOUT
| self.color_pair(3))

View File

@ -1,5 +1,6 @@
# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse
# SPDX-License-Identifier: GPL-3.0-or-later
from json import JSONDecodeError
from random import randint
from typing import Any, Optional
@ -13,6 +14,7 @@ from .interfaces import Map, Logs
from .resources import ResourceManager
from .settings import Settings
from . import menus
from .translations import gettext as _, Translator
from typing import Callable
@ -30,11 +32,12 @@ class Game:
Init the game.
"""
self.state = GameMode.MAINMENU
self.main_menu = menus.MainMenu()
self.settings_menu = menus.SettingsMenu()
self.settings = Settings()
self.settings.load_settings()
self.settings.write_settings()
Translator.setlocale(self.settings.LOCALE)
self.main_menu = menus.MainMenu()
self.settings_menu = menus.SettingsMenu()
self.settings_menu.update_values(self.settings)
self.logs = Logs()
self.message = None
@ -142,16 +145,16 @@ class Game:
try:
self.map.load_state(d)
except KeyError:
self.message = "Some keys are missing in your save file.\n" \
"Your save seems to be corrupt. It got deleted."
self.message = _("Some keys are missing in your save file.\n"
"Your save seems to be corrupt. It got deleted.")
os.unlink(ResourceManager.get_config_path("save.json"))
self.display_actions(DisplayActions.UPDATE)
return
players = self.map.find_entities(Player)
if not players:
self.message = "No player was found on this map!\n" \
"Maybe you died?"
self.message = _("No player was found on this map!\n"
"Maybe you died?")
self.player.health = 0
self.display_actions(DisplayActions.UPDATE)
return
@ -170,8 +173,9 @@ class Game:
state = json.loads(f.read())
self.load_state(state)
except JSONDecodeError:
self.message = "The JSON file is not correct.\n" \
"Your save seems corrupted. It got deleted."
self.message = _("The JSON file is not correct.\n"
"Your save seems corrupted. "
"It got deleted.")
os.unlink(file_path)
self.display_actions(DisplayActions.UPDATE)

View File

@ -6,7 +6,8 @@ from math import sqrt
from random import choice, randint
from typing import List, Optional
from squirrelbattle.display.texturepack import TexturePack
from .display.texturepack import TexturePack
from .translations import gettext as _
class Logs:
@ -128,7 +129,7 @@ class Map:
"""
Put randomly {count} hedgehogs on the map, where it is available.
"""
for _ in range(count):
for ignored in range(count):
y, x = 0, 0
while True:
y, x = randint(0, self.height - 1), randint(0, self.width - 1)
@ -314,6 +315,10 @@ class Entity:
from squirrelbattle.entities.items import Item
return isinstance(self, Item)
@property
def translated_name(self) -> str:
return _(self.name.replace("_", " "))
@staticmethod
def get_all_entity_classes():
"""
@ -390,8 +395,10 @@ class FightingEntity(Entity):
"""
Deals damage to the opponent, based on the stats
"""
return f"{self.name} hits {opponent.name}. "\
+ opponent.take_damage(self, self.strength)
return _("{name} hits {opponent}.")\
.format(name=_(self.translated_name.capitalize()),
opponent=_(opponent.translated_name)) + " " + \
opponent.take_damage(self, self.strength)
def take_damage(self, attacker: "Entity", amount: int) -> str:
"""
@ -400,8 +407,11 @@ class FightingEntity(Entity):
self.health -= amount
if self.health <= 0:
self.die()
return f"{self.name} takes {amount} damage."\
+ (f" {self.name} dies." if self.health <= 0 else "")
return _("{name} takes {amount} damage.")\
.format(name=self.translated_name.capitalize(), amount=str(amount))\
+ (" " + _("{name} dies.")
.format(name=self.translated_name.capitalize())
if self.health <= 0 else "")
def die(self) -> None:
"""

View File

@ -0,0 +1,166 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ÿnérant, eichhornchen, nicomarg, charlse
# This file is distributed under the same license as the squirrelbattle package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: squirrelbattle 3.14.1\n"
"Report-Msgid-Bugs-To: squirrel-battle@crans.org\n"
"POT-Creation-Date: 2020-11-28 16:03+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: squirrelbattle/tests/game_test.py:284 squirrelbattle/tests/game_test.py:287
#: squirrelbattle/tests/translations_test.py:16
msgid "New game"
msgstr "Neu Spiel"
#: squirrelbattle/tests/translations_test.py:17
msgid "Resume"
msgstr "Weitergehen"
#: squirrelbattle/tests/translations_test.py:18
msgid "Load"
msgstr "Laden"
#: squirrelbattle/tests/translations_test.py:19
msgid "Save"
msgstr "Speichern"
#: squirrelbattle/tests/translations_test.py:20
msgid "Settings"
msgstr "Einstellungen"
#: squirrelbattle/tests/translations_test.py:21
msgid "Exit"
msgstr "Verlassen"
#: squirrelbattle/tests/translations_test.py:27
msgid "Main key to move up"
msgstr "Haupttaste zum Obengehen"
#: squirrelbattle/tests/translations_test.py:29
msgid "Secondary key to move up"
msgstr "Sekundärtaste zum Obengehen"
#: squirrelbattle/tests/translations_test.py:31
msgid "Main key to move down"
msgstr "Haupttaste zum Untergehen"
#: squirrelbattle/tests/translations_test.py:33
msgid "Secondary key to move down"
msgstr "Sekundärtaste zum Untergehen"
#: squirrelbattle/tests/translations_test.py:35
msgid "Main key to move left"
msgstr "Haupttaste zum Linksgehen"
#: squirrelbattle/tests/translations_test.py:37
msgid "Secondary key to move left"
msgstr "Sekundärtaste zum Linksgehen"
#: squirrelbattle/tests/translations_test.py:39
msgid "Main key to move right"
msgstr "Haupttaste zum Rechtsgehen"
#: squirrelbattle/tests/translations_test.py:41
msgid "Secondary key to move right"
msgstr "Sekundärtaste zum Rechtsgehen"
#: squirrelbattle/tests/translations_test.py:43
msgid "Key to validate a menu"
msgstr "Menütaste"
#: squirrelbattle/tests/translations_test.py:45
msgid "Texture pack"
msgstr "Textur-Packung"
#: squirrelbattle/tests/translations_test.py:46
msgid "Language"
msgstr "Sprache"
#: squirrelbattle/tests/translations_test.py:49
msgid "player"
msgstr "Spieler"
#: squirrelbattle/tests/translations_test.py:51
msgid "tiger"
msgstr "Tiger"
#: squirrelbattle/tests/translations_test.py:52
msgid "hedgehog"
msgstr "Igel"
#: squirrelbattle/tests/translations_test.py:53
msgid "rabbit"
msgstr "Kanninchen"
#: squirrelbattle/tests/translations_test.py:54
msgid "teddy bear"
msgstr "Teddybär"
#: squirrelbattle/tests/translations_test.py:56
msgid "bomb"
msgstr "Bombe"
#: squirrelbattle/tests/translations_test.py:57
msgid "heart"
msgstr "Herz"
#: squirrelbattle/display/statsdisplay.py:34
msgid "Inventory:"
msgstr "Bestand:"
#: squirrelbattle/display/statsdisplay.py:39
msgid "YOU ARE DEAD"
msgstr "SIE WURDEN GESTORBEN"
#: squirrelbattle/interfaces.py:398
#, python-brace-format
msgid "{name} hits {opponent}."
msgstr "{name} schlägt {opponent}."
#: squirrelbattle/interfaces.py:410
#, python-brace-format
msgid "{name} takes {amount} damage."
msgstr "{name} nimmt {amount} Schadenspunkte."
#: squirrelbattle/interfaces.py:412
#, python-brace-format
msgid "{name} dies."
msgstr "{name} stirbt."
#: squirrelbattle/menus.py:71
msgid "Back"
msgstr "Zurück"
#: squirrelbattle/game.py:148
msgid ""
"Some keys are missing in your save file.\n"
"Your save seems to be corrupt. It got deleted."
msgstr ""
"In Ihrer Speicherdatei fehlen einige Schlüssel.\n"
"Ihre Speicherung scheint korrupt zu sein. Es wird gelöscht."
#: squirrelbattle/game.py:156
msgid ""
"No player was found on this map!\n"
"Maybe you died?"
msgstr ""
"Auf dieser Karte wurde kein Spieler gefunden!\n"
"Vielleicht sind Sie gestorben?"
#: squirrelbattle/game.py:176
msgid ""
"The JSON file is not correct.\n"
"Your save seems corrupted. It got deleted."
msgstr ""
"Die JSON-Datei ist nicht korrekt.\n"
"Ihre Speicherung scheint korrumpiert. Sie wurde gelöscht."

View File

@ -0,0 +1,195 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ÿnérant, eichhornchen, nicomarg, charlse
# This file is distributed under the same license as the squirrelbattle package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: squirrelbattle 3.14.1\n"
"Report-Msgid-Bugs-To: squirrel-battle@crans.org\n"
"POT-Creation-Date: 2020-11-28 16:03+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: squirrelbattle/display/statsdisplay.py:34
msgid "Inventory:"
msgstr ""
#: squirrelbattle/display/statsdisplay.py:39
msgid "YOU ARE DEAD"
msgstr ""
#: squirrelbattle/interfaces.py:394 squirrelbattle/interfaces.py:398
#, python-brace-format
msgid "{name} hits {opponent}."
msgstr ""
#: squirrelbattle/interfaces.py:405 squirrelbattle/interfaces.py:410
#, python-brace-format
msgid "{name} takes {amount} damage."
msgstr ""
#: squirrelbattle/menus.py:45 squirrelbattle/tests/translations_test.py:14
#: squirrelbattle/tests/game_test.py:284 squirrelbattle/tests/game_test.py:287
#: squirrelbattle/tests/translations_test.py:16
msgid "New game"
msgstr ""
#: squirrelbattle/menus.py:46 squirrelbattle/tests/translations_test.py:15
#: squirrelbattle/tests/translations_test.py:17
msgid "Resume"
msgstr ""
#: squirrelbattle/menus.py:47 squirrelbattle/tests/translations_test.py:17
#: squirrelbattle/tests/translations_test.py:19
msgid "Save"
msgstr ""
#: squirrelbattle/menus.py:48 squirrelbattle/tests/translations_test.py:16
#: squirrelbattle/tests/translations_test.py:18
msgid "Load"
msgstr ""
#: squirrelbattle/menus.py:49 squirrelbattle/tests/translations_test.py:18
#: squirrelbattle/tests/translations_test.py:20
msgid "Settings"
msgstr ""
#: squirrelbattle/menus.py:50 squirrelbattle/tests/translations_test.py:19
#: squirrelbattle/tests/translations_test.py:21
msgid "Exit"
msgstr ""
#: squirrelbattle/menus.py:71
msgid "Back"
msgstr ""
#: squirrelbattle/game.py:147 squirrelbattle/game.py:148
msgid ""
"Some keys are missing in your save file.\n"
"Your save seems to be corrupt. It got deleted."
msgstr ""
#: squirrelbattle/game.py:155 squirrelbattle/game.py:156
msgid ""
"No player was found on this map!\n"
"Maybe you died?"
msgstr ""
#: squirrelbattle/game.py:175 squirrelbattle/game.py:176
msgid ""
"The JSON file is not correct.\n"
"Your save seems corrupted. It got deleted."
msgstr ""
#: squirrelbattle/settings.py:21 squirrelbattle/tests/translations_test.py:21
#: squirrelbattle/tests/translations_test.py:25
#: squirrelbattle/tests/translations_test.py:27
msgid "Main key to move up"
msgstr ""
#: squirrelbattle/settings.py:22 squirrelbattle/tests/translations_test.py:23
#: squirrelbattle/tests/translations_test.py:27
#: squirrelbattle/tests/translations_test.py:29
msgid "Secondary key to move up"
msgstr ""
#: squirrelbattle/settings.py:23 squirrelbattle/tests/translations_test.py:25
#: squirrelbattle/tests/translations_test.py:29
#: squirrelbattle/tests/translations_test.py:31
msgid "Main key to move down"
msgstr ""
#: squirrelbattle/settings.py:24 squirrelbattle/tests/translations_test.py:27
#: squirrelbattle/tests/translations_test.py:31
#: squirrelbattle/tests/translations_test.py:33
msgid "Secondary key to move down"
msgstr ""
#: squirrelbattle/settings.py:25 squirrelbattle/tests/translations_test.py:29
#: squirrelbattle/tests/translations_test.py:33
#: squirrelbattle/tests/translations_test.py:35
msgid "Main key to move left"
msgstr ""
#: squirrelbattle/settings.py:26 squirrelbattle/tests/translations_test.py:31
#: squirrelbattle/tests/translations_test.py:35
#: squirrelbattle/tests/translations_test.py:37
msgid "Secondary key to move left"
msgstr ""
#: squirrelbattle/settings.py:27 squirrelbattle/tests/translations_test.py:33
#: squirrelbattle/tests/translations_test.py:37
#: squirrelbattle/tests/translations_test.py:39
msgid "Main key to move right"
msgstr ""
#: squirrelbattle/settings.py:29 squirrelbattle/tests/translations_test.py:35
#: squirrelbattle/tests/translations_test.py:39
#: squirrelbattle/tests/translations_test.py:41
msgid "Secondary key to move right"
msgstr ""
#: squirrelbattle/settings.py:30 squirrelbattle/tests/translations_test.py:37
#: squirrelbattle/tests/translations_test.py:41
#: squirrelbattle/tests/translations_test.py:43
msgid "Key to validate a menu"
msgstr ""
#: squirrelbattle/settings.py:31 squirrelbattle/tests/translations_test.py:39
#: squirrelbattle/tests/translations_test.py:43
#: squirrelbattle/tests/translations_test.py:45
msgid "Texture pack"
msgstr ""
#: squirrelbattle/settings.py:32 squirrelbattle/tests/translations_test.py:40
#: squirrelbattle/tests/translations_test.py:44
#: squirrelbattle/tests/translations_test.py:46
msgid "Language"
msgstr ""
#: squirrelbattle/interfaces.py:407 squirrelbattle/interfaces.py:412
#, python-brace-format
msgid "{name} dies."
msgstr ""
#: squirrelbattle/tests/translations_test.py:47
#: squirrelbattle/tests/translations_test.py:49
msgid "player"
msgstr ""
#: squirrelbattle/tests/translations_test.py:49
#: squirrelbattle/tests/translations_test.py:51
msgid "tiger"
msgstr ""
#: squirrelbattle/tests/translations_test.py:50
#: squirrelbattle/tests/translations_test.py:52
msgid "hedgehog"
msgstr ""
#: squirrelbattle/tests/translations_test.py:51
#: squirrelbattle/tests/translations_test.py:53
msgid "rabbit"
msgstr ""
#: squirrelbattle/tests/translations_test.py:52
#: squirrelbattle/tests/translations_test.py:54
msgid "teddy bear"
msgstr ""
#: squirrelbattle/tests/translations_test.py:54
#: squirrelbattle/tests/translations_test.py:56
msgid "bomb"
msgstr ""
#: squirrelbattle/tests/translations_test.py:55
#: squirrelbattle/tests/translations_test.py:57
msgid "heart"
msgstr ""

View File

@ -0,0 +1,201 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ÿnérant, eichhornchen, nicomarg, charlse
# This file is distributed under the same license as the squirrelbattle package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: squirrelbattle 3.14.1\n"
"Report-Msgid-Bugs-To: squirrel-battle@crans.org\n"
"POT-Creation-Date: 2020-11-28 16:03+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: squirrelbattle/display/statsdisplay.py:34
msgid "Inventory:"
msgstr "Inventaire :"
#: squirrelbattle/display/statsdisplay.py:39
msgid "YOU ARE DEAD"
msgstr "VOUS ÊTES MORT"
#: squirrelbattle/interfaces.py:394 squirrelbattle/interfaces.py:398
#, python-brace-format
msgid "{name} hits {opponent}."
msgstr "{name} frappe {opponent}."
#: squirrelbattle/interfaces.py:405 squirrelbattle/interfaces.py:410
#, python-brace-format
msgid "{name} takes {amount} damage."
msgstr "{name} prend {amount} points de dégât."
#: squirrelbattle/menus.py:45 squirrelbattle/tests/translations_test.py:14
#: squirrelbattle/tests/game_test.py:284 squirrelbattle/tests/game_test.py:287
#: squirrelbattle/tests/translations_test.py:16
msgid "New game"
msgstr "Nouvelle partie"
#: squirrelbattle/menus.py:46 squirrelbattle/tests/translations_test.py:15
#: squirrelbattle/tests/translations_test.py:17
msgid "Resume"
msgstr "Continuer"
#: squirrelbattle/menus.py:47 squirrelbattle/tests/translations_test.py:17
#: squirrelbattle/tests/translations_test.py:19
msgid "Save"
msgstr "Sauvegarder"
#: squirrelbattle/menus.py:48 squirrelbattle/tests/translations_test.py:16
#: squirrelbattle/tests/translations_test.py:18
msgid "Load"
msgstr "Charger"
#: squirrelbattle/menus.py:49 squirrelbattle/tests/translations_test.py:18
#: squirrelbattle/tests/translations_test.py:20
msgid "Settings"
msgstr "Paramètres"
#: squirrelbattle/menus.py:50 squirrelbattle/tests/translations_test.py:19
#: squirrelbattle/tests/translations_test.py:21
msgid "Exit"
msgstr "Quitter"
#: squirrelbattle/menus.py:71
msgid "Back"
msgstr "Retour"
#: squirrelbattle/game.py:147 squirrelbattle/game.py:148
msgid ""
"Some keys are missing in your save file.\n"
"Your save seems to be corrupt. It got deleted."
msgstr ""
"Certaines clés de votre ficher de sauvegarde sont manquantes.\n"
"Votre sauvegarde semble corrompue. Elle a été supprimée."
#: squirrelbattle/game.py:155 squirrelbattle/game.py:156
msgid ""
"No player was found on this map!\n"
"Maybe you died?"
msgstr ""
"Aucun joueur n'a été trouvé sur la carte !\n"
"Peut-être êtes-vous mort ?"
#: squirrelbattle/game.py:175 squirrelbattle/game.py:176
msgid ""
"The JSON file is not correct.\n"
"Your save seems corrupted. It got deleted."
msgstr ""
"Le fichier JSON de sauvegarde est incorrect.\n"
"Votre sauvegarde semble corrompue. Elle a été supprimée."
#: squirrelbattle/settings.py:21 squirrelbattle/tests/translations_test.py:21
#: squirrelbattle/tests/translations_test.py:25
#: squirrelbattle/tests/translations_test.py:27
msgid "Main key to move up"
msgstr "Touche principale pour aller vers le haut"
#: squirrelbattle/settings.py:22 squirrelbattle/tests/translations_test.py:23
#: squirrelbattle/tests/translations_test.py:27
#: squirrelbattle/tests/translations_test.py:29
msgid "Secondary key to move up"
msgstr "Touche secondaire pour aller vers le haut"
#: squirrelbattle/settings.py:23 squirrelbattle/tests/translations_test.py:25
#: squirrelbattle/tests/translations_test.py:29
#: squirrelbattle/tests/translations_test.py:31
msgid "Main key to move down"
msgstr "Touche principale pour aller vers le bas"
#: squirrelbattle/settings.py:24 squirrelbattle/tests/translations_test.py:27
#: squirrelbattle/tests/translations_test.py:31
#: squirrelbattle/tests/translations_test.py:33
msgid "Secondary key to move down"
msgstr "Touche secondaire pour aller vers le bas"
#: squirrelbattle/settings.py:25 squirrelbattle/tests/translations_test.py:29
#: squirrelbattle/tests/translations_test.py:33
#: squirrelbattle/tests/translations_test.py:35
msgid "Main key to move left"
msgstr "Touche principale pour aller vers la gauche"
#: squirrelbattle/settings.py:26 squirrelbattle/tests/translations_test.py:31
#: squirrelbattle/tests/translations_test.py:35
#: squirrelbattle/tests/translations_test.py:37
msgid "Secondary key to move left"
msgstr "Touche secondaire pour aller vers la gauche"
#: squirrelbattle/settings.py:27 squirrelbattle/tests/translations_test.py:33
#: squirrelbattle/tests/translations_test.py:37
#: squirrelbattle/tests/translations_test.py:39
msgid "Main key to move right"
msgstr "Touche principale pour aller vers la droite"
#: squirrelbattle/settings.py:29 squirrelbattle/tests/translations_test.py:35
#: squirrelbattle/tests/translations_test.py:39
#: squirrelbattle/tests/translations_test.py:41
msgid "Secondary key to move right"
msgstr "Touche secondaire pour aller vers la droite"
#: squirrelbattle/settings.py:30 squirrelbattle/tests/translations_test.py:37
#: squirrelbattle/tests/translations_test.py:41
#: squirrelbattle/tests/translations_test.py:43
msgid "Key to validate a menu"
msgstr "Touche pour valider un menu"
#: squirrelbattle/settings.py:31 squirrelbattle/tests/translations_test.py:39
#: squirrelbattle/tests/translations_test.py:43
#: squirrelbattle/tests/translations_test.py:45
msgid "Texture pack"
msgstr "Pack de textures"
#: squirrelbattle/settings.py:32 squirrelbattle/tests/translations_test.py:40
#: squirrelbattle/tests/translations_test.py:44
#: squirrelbattle/tests/translations_test.py:46
msgid "Language"
msgstr "Langue"
#: squirrelbattle/interfaces.py:407 squirrelbattle/interfaces.py:412
#, python-brace-format
msgid "{name} dies."
msgstr "{name} meurt."
#: squirrelbattle/tests/translations_test.py:47
#: squirrelbattle/tests/translations_test.py:49
msgid "player"
msgstr "joueur"
#: squirrelbattle/tests/translations_test.py:49
#: squirrelbattle/tests/translations_test.py:51
msgid "tiger"
msgstr "tigre"
#: squirrelbattle/tests/translations_test.py:50
#: squirrelbattle/tests/translations_test.py:52
msgid "hedgehog"
msgstr "hérisson"
#: squirrelbattle/tests/translations_test.py:51
#: squirrelbattle/tests/translations_test.py:53
msgid "rabbit"
msgstr "lapin"
#: squirrelbattle/tests/translations_test.py:52
#: squirrelbattle/tests/translations_test.py:54
msgid "teddy bear"
msgstr "nounours"
#: squirrelbattle/tests/translations_test.py:54
#: squirrelbattle/tests/translations_test.py:56
msgid "bomb"
msgstr "bombe"
#: squirrelbattle/tests/translations_test.py:55
#: squirrelbattle/tests/translations_test.py:57
msgid "heart"
msgstr "cœur"

View File

@ -7,6 +7,7 @@ from typing import Any, Optional
from .display.texturepack import TexturePack
from .enums import GameMode, KeyValues, DisplayActions
from .settings import Settings
from .translations import gettext as _, Translator
class Menu:
@ -41,15 +42,15 @@ class MainMenuValues(Enum):
"""
Values of the main menu
"""
START = 'Nouvelle partie'
RESUME = 'Continuer'
SAVE = 'Sauvegarder'
LOAD = 'Charger'
SETTINGS = 'Paramètres'
EXIT = 'Quitter'
START = "New game"
RESUME = "Resume"
SAVE = "Save"
LOAD = "Load"
SETTINGS = "Settings"
EXIT = "Exit"
def __str__(self):
return self.value
return _(self.value)
class MainMenu(Menu):
@ -67,7 +68,7 @@ class SettingsMenu(Menu):
def update_values(self, settings: Settings) -> None:
self.values = list(settings.__dict__.items())
self.values.append(("RETURN", ["", "Retour"]))
self.values.append(("RETURN", ["", _("Back")]))
def handle_key_pressed(self, key: Optional[KeyValues], raw_key: str,
game: Any) -> None:
@ -95,6 +96,12 @@ class SettingsMenu(Menu):
game.settings.TEXTURE_PACK)
game.settings.write_settings()
self.update_values(game.settings)
elif option == "LOCALE":
game.settings.LOCALE = 'fr' if game.settings.LOCALE == 'en'\
else 'de' if game.settings.LOCALE == 'fr' else 'en'
Translator.setlocale(game.settings.LOCALE)
game.settings.write_settings()
self.update_values(game.settings)
else:
self.waiting_for_key = True
self.update_values(game.settings)

View File

@ -2,10 +2,12 @@
# SPDX-License-Identifier: GPL-3.0-or-later
import json
import locale
import os
from typing import Any, Generator
from .resources import ResourceManager
from .translations import gettext as _
class Settings:
@ -16,25 +18,17 @@ class Settings:
We can define the setting by simply use settings.TEXTURE_PACK = 'new_key'
"""
def __init__(self):
self.KEY_UP_PRIMARY = \
['z', 'Touche principale pour aller vers le haut']
self.KEY_UP_SECONDARY = \
['KEY_UP', 'Touche secondaire pour aller vers le haut']
self.KEY_DOWN_PRIMARY = \
['s', 'Touche principale pour aller vers le bas']
self.KEY_DOWN_SECONDARY = \
['KEY_DOWN', 'Touche secondaire pour aller vers le bas']
self.KEY_LEFT_PRIMARY = \
['q', 'Touche principale pour aller vers la gauche']
self.KEY_LEFT_SECONDARY = \
['KEY_LEFT', 'Touche secondaire pour aller vers la gauche']
self.KEY_RIGHT_PRIMARY = \
['d', 'Touche principale pour aller vers la droite']
self.KEY_RIGHT_SECONDARY = \
['KEY_RIGHT', 'Touche secondaire pour aller vers la droite']
self.KEY_ENTER = \
['\n', 'Touche pour valider un menu']
self.TEXTURE_PACK = ['ascii', 'Pack de textures utilisé']
self.KEY_UP_PRIMARY = ['z', 'Main key to move up']
self.KEY_UP_SECONDARY = ['KEY_UP', 'Secondary key to move up']
self.KEY_DOWN_PRIMARY = ['s', 'Main key to move down']
self.KEY_DOWN_SECONDARY = ['KEY_DOWN', 'Secondary key to move down']
self.KEY_LEFT_PRIMARY = ['q', 'Main key to move left']
self.KEY_LEFT_SECONDARY = ['KEY_LEFT', 'Secondary key to move left']
self.KEY_RIGHT_PRIMARY = ['d', 'Main key to move right']
self.KEY_RIGHT_SECONDARY = ['KEY_RIGHT', 'Secondary key to move right']
self.KEY_ENTER = ['\n', 'Key to validate a menu']
self.TEXTURE_PACK = ['ascii', 'Texture pack']
self.LOCALE = [locale.getlocale()[0][:2], 'Language']
def __getattribute__(self, item: str) -> Any:
superattribute = super().__getattribute__(item)
@ -53,10 +47,10 @@ class Settings:
Retrieve the comment of a setting.
"""
if item in self.settings_keys:
return object.__getattribute__(self, item)[1]
return _(object.__getattribute__(self, item)[1])
for key in self.settings_keys:
if getattr(self, key) == item:
return object.__getattribute__(self, key)[1]
return _(object.__getattribute__(self, key)[1])
@property
def settings_keys(self) -> Generator[str, Any, None]:

View File

@ -46,10 +46,10 @@ class TestEntities(unittest.TestCase):
self.assertEqual(entity.strength, 2)
for _ in range(9):
self.assertEqual(entity.hit(entity),
"tiger hits tiger. tiger takes 2 damage.")
"Tiger hits tiger. Tiger takes 2 damage.")
self.assertFalse(entity.dead)
self.assertEqual(entity.hit(entity), "tiger hits tiger. "
+ "tiger takes 2 damage. tiger dies.")
self.assertEqual(entity.hit(entity), "Tiger hits tiger. "
+ "Tiger takes 2 damage. Tiger dies.")
self.assertTrue(entity.dead)
entity = Rabbit()
@ -70,8 +70,8 @@ class TestEntities(unittest.TestCase):
self.assertTrue(entity.y == 2 and entity.x == 6)
self.assertEqual(old_health - entity.strength, self.player.health)
self.assertEqual(self.map.logs.messages[-1],
f"{entity.name} hits {self.player.name}. \
{self.player.name} takes {entity.strength} damage.")
f"{entity.name.capitalize()} hits {self.player.name}. \
{self.player.name.capitalize()} takes {entity.strength} damage.")
# Fight the rabbit
old_health = entity.health

View File

@ -4,17 +4,16 @@
import os
import unittest
from squirrelbattle.resources import ResourceManager
from squirrelbattle.enums import DisplayActions
from squirrelbattle.bootstrap import Bootstrap
from squirrelbattle.display.display import Display
from squirrelbattle.display.display_manager import DisplayManager
from squirrelbattle.entities.player import Player
from squirrelbattle.game import Game, KeyValues, GameMode
from squirrelbattle.menus import MainMenuValues
from squirrelbattle.settings import Settings
from ..bootstrap import Bootstrap
from ..display.display import Display
from ..display.display_manager import DisplayManager
from ..entities.player import Player
from ..enums import DisplayActions
from ..game import Game, KeyValues, GameMode
from ..menus import MainMenuValues
from ..resources import ResourceManager
from ..settings import Settings
from ..translations import gettext as _, Translator
class TestGame(unittest.TestCase):
@ -275,12 +274,23 @@ class TestGame(unittest.TestCase):
self.game.handle_key_pressed(KeyValues.ENTER)
self.assertEqual(self.game.settings.TEXTURE_PACK, "ascii")
# Change language
Translator.compilemessages()
Translator.refresh_translations()
self.game.settings.LOCALE = "en"
self.game.handle_key_pressed(KeyValues.DOWN)
self.game.handle_key_pressed(KeyValues.ENTER)
self.assertEqual(self.game.settings.LOCALE, "fr")
self.assertEqual(_("New game"), "Nouvelle partie")
self.game.handle_key_pressed(KeyValues.ENTER)
self.assertEqual(self.game.settings.LOCALE, "de")
self.assertEqual(_("New game"), "Neu Spiel")
self.game.handle_key_pressed(KeyValues.ENTER)
self.assertEqual(self.game.settings.LOCALE, "en")
self.assertEqual(_("New game"), "New game")
# Navigate to "back" button
self.game.handle_key_pressed(KeyValues.DOWN)
self.game.handle_key_pressed(KeyValues.DOWN)
self.game.handle_key_pressed(KeyValues.DOWN)
self.game.handle_key_pressed(KeyValues.DOWN)
self.game.handle_key_pressed(KeyValues.DOWN)
self.game.handle_key_pressed(KeyValues.ENTER)
self.assertEqual(self.game.state, GameMode.MAINMENU)

View File

@ -24,7 +24,7 @@ class TestSettings(unittest.TestCase):
self.assertEqual(settings.get_comment(settings.TEXTURE_PACK),
settings.get_comment('TEXTURE_PACK'))
self.assertEqual(settings.get_comment(settings.TEXTURE_PACK),
'Pack de textures utilisé')
'Texture pack')
settings.TEXTURE_PACK = 'squirrel'
self.assertEqual(settings.TEXTURE_PACK, 'squirrel')

View File

@ -0,0 +1,57 @@
import unittest
from squirrelbattle.translations import gettext as _, Translator
class TestTranslations(unittest.TestCase):
def setUp(self) -> None:
Translator.compilemessages()
Translator.refresh_translations()
Translator.setlocale("fr")
def test_main_menu_translation(self) -> None:
"""
Ensure that the main menu is translated.
"""
self.assertEqual(_("New game"), "Nouvelle partie")
self.assertEqual(_("Resume"), "Continuer")
self.assertEqual(_("Load"), "Charger")
self.assertEqual(_("Save"), "Sauvegarder")
self.assertEqual(_("Settings"), "Paramètres")
self.assertEqual(_("Exit"), "Quitter")
def test_settings_menu_translation(self) -> None:
"""
Ensure that the settings menu is translated.
"""
self.assertEqual(_("Main key to move up"),
"Touche principale pour aller vers le haut")
self.assertEqual(_("Secondary key to move up"),
"Touche secondaire pour aller vers le haut")
self.assertEqual(_("Main key to move down"),
"Touche principale pour aller vers le bas")
self.assertEqual(_("Secondary key to move down"),
"Touche secondaire pour aller vers le bas")
self.assertEqual(_("Main key to move left"),
"Touche principale pour aller vers la gauche")
self.assertEqual(_("Secondary key to move left"),
"Touche secondaire pour aller vers la gauche")
self.assertEqual(_("Main key to move right"),
"Touche principale pour aller vers la droite")
self.assertEqual(_("Secondary key to move right"),
"Touche secondaire pour aller vers la droite")
self.assertEqual(_("Key to validate a menu"),
"Touche pour valider un menu")
self.assertEqual(_("Texture pack"), "Pack de textures")
self.assertEqual(_("Language"), "Langue")
def test_entities_translation(self) -> None:
self.assertEqual(_("player"), "joueur")
self.assertEqual(_("tiger"), "tigre")
self.assertEqual(_("hedgehog"), "hérisson")
self.assertEqual(_("rabbit"), "lapin")
self.assertEqual(_("teddy bear"), "nounours")
self.assertEqual(_("bomb"), "bombe")
self.assertEqual(_("heart"), "cœur")

View File

@ -0,0 +1,96 @@
# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse
# SPDX-License-Identifier: GPL-3.0-or-later
import gettext as gt
import os
import subprocess
from pathlib import Path
from typing import Any, List
class Translator:
"""
This module uses gettext to translate strings.
Translator.setlocale defines the language of the strings,
then gettext() translates the message.
"""
SUPPORTED_LOCALES: List[str] = ["de", "en", "fr"]
locale: str = "en"
translators: dict = {}
@classmethod
def refresh_translations(cls) -> None:
"""
Load compiled translations.
"""
for language in cls.SUPPORTED_LOCALES:
rep = Path(__file__).parent / "locale" / language / "LC_MESSAGES"
rep.mkdir(parents=True) if not rep.is_dir() else None
if os.path.isfile(rep / "squirrelbattle.mo"):
cls.translators[language] = gt.translation(
"squirrelbattle",
localedir=Path(__file__).parent / "locale",
languages=[language],
)
@classmethod
def setlocale(cls, lang: str) -> None:
"""
Define the language used to translate the game.
The language must be supported, otherwise nothing is done.
"""
lang = lang[:2]
if lang in cls.SUPPORTED_LOCALES:
cls.locale = lang
@classmethod
def get_translator(cls) -> Any:
return cls.translators.get(cls.locale, gt.NullTranslations())
@classmethod
def makemessages(cls) -> None: # pragma: no cover
"""
Analyse all strings in the project and extract them.
"""
for language in cls.SUPPORTED_LOCALES:
file_name = Path(__file__).parent / "locale" / language \
/ "LC_MESSAGES" / "squirrelbattle.po"
args = ["find", "squirrelbattle", "-iname", "*.py"]
find = subprocess.Popen(args, cwd=Path(__file__).parent.parent,
stdout=subprocess.PIPE)
args = ["xargs", "xgettext", "--from-code", "utf-8",
"--add-comments",
"--package-name=squirrelbattle",
"--package-version=3.14.1",
"--copyright-holder=ÿnérant, eichhornchen, "
"nicomarg, charlse",
"--msgid-bugs-address=squirrel-battle@crans.org",
"-o", file_name]
if file_name.is_file():
args.append("--join-existing")
print(f"Make {language} messages...")
subprocess.Popen(args, stdin=find.stdout).wait()
@classmethod
def compilemessages(cls) -> None:
"""
Compile translation messages from source files.
"""
for language in cls.SUPPORTED_LOCALES:
args = ["msgfmt", "--check-format",
"-o", Path(__file__).parent / "locale" / language
/ "LC_MESSAGES" / "squirrelbattle.mo",
Path(__file__).parent / "locale" / language
/ "LC_MESSAGES" / "squirrelbattle.po"]
print(f"Compiling {language} messages...")
subprocess.Popen(args).wait()
def gettext(message: str) -> str:
"""
Translate a message.
"""
return Translator.get_translator().gettext(message)
Translator.refresh_translations()