Skip to content

Commit 1cef155

Browse files
authored
feat: Add i18n support. Closes #19 (#362)
1 parent 5b2b511 commit 1cef155

12 files changed

Lines changed: 198 additions & 14 deletions

File tree

docs/contributing.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,39 @@ Python State Machine could always use more documentation, whether as part of the
3737
official Python State Machine docs, in docstrings, or even on the web in blog posts,
3838
articles, and such.
3939

40+
### Add a translation
41+
42+
43+
Extract a `Portable Object Template` (`POT`) file:
44+
45+
```shell
46+
pybabel extract statemachine -o statemachine/locale/statemachine.pot
47+
```
48+
49+
Then, copy the template as a `.po` file into the target locale folder. For example, if you're adding support for Brazilian Portuguese language, the code is `pt_BR`, and the file path should be `statemachine/locale/pt_BR/LC_MESSAGES/statemachine.po`:
50+
51+
```shell
52+
cp statemachine/locale/statemachine.pot statemachine/locale/pt_BR/LC_MESSAGES/statemachine.po
53+
```
54+
55+
Then open the `statemachine.po` and translate.
56+
57+
After translation, to get the new language working locally, you need to compile the `.po` files into `.mo` (binary format). Run:
58+
59+
```shell
60+
pybabel compile -d statemachine/locale/ -f statemachine.po
61+
```
62+
63+
64+
On Linux (Debian based), you can test changing the `LANGUAGE` environment variable.
65+
66+
```shell
67+
# If the last line is `Can't guess when in Won.` something went wrong.
68+
LANGUAGE=pt_BR python tests/examples/guess_the_number_machine.py
69+
```
70+
71+
Then open a [pull request](https://github.com/fgmacedo/python-statemachine/pulls) with your translation file.
72+
4073
### Submit Feedback
4174

4275
The best way to send feedback is to file an issue at https://github.com/fgmacedo/python-statemachine/issues.

docs/releases/2.0.0.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ Even if you trigger an event inside an action.
3232
See {ref}`processing model` for more details.
3333
```
3434

35+
### Added support for translations (i18n)
36+
37+
Now the library messages can be translated into any language.
38+
39+
See {ref}`Add a translation` on how to contribute with translations.
40+
3541
### State names are now optional
3642

3743
{ref}`State` names are now by default derived from the class variable that they are assigned to.

statemachine/callbacks.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from .exceptions import AttrNotFound
22
from .exceptions import InvalidDefinition
3+
from .i18n import _
34
from .utils import ensure_iterable
4-
from .utils import ugettext as _
55

66

77
class CallbackWrapper:

statemachine/dispatcher.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
from operator import attrgetter
44

55
from .exceptions import AttrNotFound
6+
from .i18n import _
67
from .signature import SignatureAdapter
7-
from .utils import ugettext as _
88

99

1010
class ObjectConfig(namedtuple("ObjectConfig", "obj skip_attrs")):

statemachine/exceptions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from .utils import ugettext as _
1+
from .i18n import _
22

33

44
class StateMachineError(Exception):

statemachine/factory.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
from .event import trigger_event_factory
66
from .exceptions import InvalidDefinition
77
from .graph import visit_connected_states
8+
from .i18n import _
89
from .state import State
910
from .transition import Transition
1011
from .transition_list import TransitionList
11-
from .utils import ugettext as _
1212

1313

1414
class StateMachineMetaclass(type):

statemachine/i18n.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import gettext
2+
from pathlib import Path
3+
4+
script_dir = Path(__file__).resolve().parent
5+
locale_dir = script_dir / "locale"
6+
7+
8+
def setup_i18n():
9+
translate = gettext.translation("statemachine", locale_dir, fallback=True)
10+
gettext.bindtextdomain("statemachine", locale_dir)
11+
gettext.textdomain("statemachine")
12+
return translate.gettext
13+
14+
15+
_ = setup_i18n()
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# This file is distributed under the same license as the PROJECT project.
2+
# Fernando Macedo <fgmacedo@gmail.com>, 2023.
3+
#
4+
#, fuzzy
5+
msgid ""
6+
msgstr ""
7+
"Project-Id-Version: 2.0.0\n"
8+
"Report-Msgid-Bugs-To: fgmacedo@gmail.com\n"
9+
"POT-Creation-Date: 2023-03-04 16:10-0300\n"
10+
"PO-Revision-Date: 2023-03-04 16:10-0300\n"
11+
"Last-Translator: Fernando Macedo <fgmacedo@gmail.com>\n"
12+
"MIME-Version: 1.0\n"
13+
"Content-Type: text/plain; charset=utf-8\n"
14+
"Content-Transfer-Encoding: 8bit\n"
15+
"Generated-By: Babel 2.12.1\n"
16+
17+
#: statemachine/callbacks.py:56
18+
msgid "Callback {!r} not property configured."
19+
msgstr ""
20+
21+
#: statemachine/dispatcher.py:35
22+
msgid "Did not found name '{}' from model or statemachine"
23+
msgstr ""
24+
25+
#: statemachine/exceptions.py:17
26+
msgid "{!r} is not a valid state value."
27+
msgstr ""
28+
29+
#: statemachine/exceptions.py:31
30+
msgid "Can't {} when in {}."
31+
msgstr ""
32+
33+
#: statemachine/factory.py:35
34+
msgid ""
35+
"There should be one and only one initial state. Your currently have "
36+
"these: {!r}"
37+
msgstr ""
38+
39+
#: statemachine/factory.py:51
40+
msgid ""
41+
"There are unreachable states. The statemachine graph should have a single"
42+
" component. Disconnected states: {}"
43+
msgstr ""
44+
45+
#: statemachine/factory.py:69
46+
msgid "There are no states."
47+
msgstr ""
48+
49+
#: statemachine/factory.py:72
50+
msgid "There are no events."
51+
msgstr ""
52+
53+
#: statemachine/factory.py:82
54+
msgid "Cannot declare transitions from final state. Invalid state(s): {}"
55+
msgstr ""
56+
57+
#: statemachine/mixins.py:30
58+
msgid "{!r} is not a valid state machine name."
59+
msgstr ""
60+
61+
#: statemachine/state.py:148
62+
msgid "State overriding is not allowed. Trying to add '{}' to {}"
63+
msgstr ""
64+
65+
#: statemachine/statemachine.py:48
66+
msgid "There are no states or transitions."
67+
msgstr ""
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# This file is distributed under the same license as the PROJECT project.
2+
# Fernando Macedo <fgmacedo@gmail.com>, 2023.
3+
#
4+
#, fuzzy
5+
msgid ""
6+
msgstr ""
7+
"Project-Id-Version: 2.0.0\n"
8+
"Report-Msgid-Bugs-To: fgmacedo@gmail.com\n"
9+
"POT-Creation-Date: 2023-03-04 16:10-0300\n"
10+
"PO-Revision-Date: 2023-03-04 16:10-0300\n"
11+
"Last-Translator: Fernando Macedo <fgmacedo@gmail.com>\n"
12+
"MIME-Version: 1.0\n"
13+
"Content-Type: text/plain; charset=utf-8\n"
14+
"Content-Transfer-Encoding: 8bit\n"
15+
"Generated-By: Babel 2.12.1\n"
16+
17+
#: statemachine/callbacks.py:56
18+
msgid "Callback {!r} not property configured."
19+
msgstr "Callback {!r} não está configurado corretamente."
20+
21+
#: statemachine/dispatcher.py:35
22+
msgid "Did not found name '{}' from model or statemachine"
23+
msgstr "Não foi encontrado o nome '{}' no modelo ou na máquina de estados"
24+
25+
#: statemachine/exceptions.py:17
26+
msgid "{!r} is not a valid state value."
27+
msgstr "{!r} não é um valor de estado válido."
28+
29+
#: statemachine/exceptions.py:31
30+
msgid "Can't {} when in {}."
31+
msgstr "Não é possível {} quando em {}."
32+
33+
#: statemachine/factory.py:35
34+
msgid ""
35+
"There should be one and only one initial state. Your currently have "
36+
"these: {!r}"
37+
msgstr ""
38+
"Deve haver um e apenas um estado inicial. Atualmente, você tem "
39+
"os seguintes: {!r}"
40+
41+
#: statemachine/factory.py:51
42+
msgid ""
43+
"There are unreachable states. The statemachine graph should have a single"
44+
" component. Disconnected states: {}"
45+
msgstr ""
46+
"Há estados inalcançáveis. O grafo da máquina de estados deve ter apenas um"
47+
" componente. Estados desconectados: {}"
48+
49+
#: statemachine/factory.py:69
50+
msgid "There are no states."
51+
msgstr "Não há estados."
52+
53+
#: statemachine/factory.py:72
54+
msgid "There are no events."
55+
msgstr "Não há eventos."
56+
57+
#: statemachine/factory.py:82
58+
msgid "Cannot declare transitions from final state. Invalid state(s): {}"
59+
msgstr "Não é possível declarar transições a partir do estado final. Estado(s) inválido(s): {}"
60+
61+
#: statemachine/mixins.py:30
62+
msgid "{!r} is not a valid state machine name."
63+
msgstr "{!r} não é um nome válido para a máquina de estados."
64+
65+
#: statemachine/state.py:148
66+
msgid "State overriding is not allowed. Trying to add '{}' to {}"
67+
msgstr "A substituição de estado não é permitida. Tentando adicionar '{}' a {}"
68+
69+
#: statemachine/statemachine.py:48
70+
msgid "There are no states or transitions."
71+
msgstr "Não há estados ou transições."

statemachine/state.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from .exceptions import StateMachineError
66
from .transition import Transition
77
from .transition_list import TransitionList
8-
from .utils import ugettext as _
8+
from .i18n import _
99

1010

1111
class State:

0 commit comments

Comments
 (0)