Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Internationalization/Localization support #236

Merged
merged 22 commits into from Nov 19, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 2 additions & 7 deletions .azure/msys2-install.sh
Expand Up @@ -14,13 +14,8 @@ pacman --noconfirm -S --needed \
mingw-w64-$MSYS2_ARCH-cairo \
mingw-w64-$MSYS2_ARCH-gobject-introspection \
mingw-w64-$MSYS2_ARCH-python3 \
mingw-w64-$MSYS2_ARCH-python3-importlib-metadata \
mingw-w64-$MSYS2_ARCH-python3-gobject \
mingw-w64-$MSYS2_ARCH-python3-cairo \
mingw-w64-$MSYS2_ARCH-python3-pip \
mingw-w64-$MSYS2_ARCH-python3-setuptools \
mingw-w64-$MSYS2_ARCH-python3-zope.interface \
mingw-w64-$MSYS2_ARCH-python3-coverage \
mingw-w64-$MSYS2_ARCH-python3-pytest
mingw-w64-$MSYS2_ARCH-python3-pip

source venv
./venv -S
2 changes: 1 addition & 1 deletion .gitignore
@@ -1,6 +1,6 @@
build
dist
po/gaphor.pot
gaphor/locale/*/LC_MESSAGES
html
*.pyc
*.pyo
Expand Down
16 changes: 16 additions & 0 deletions Makefile
@@ -0,0 +1,16 @@

help: ## Show this help
@echo "make <target>, where <target> is one of:"
@grep -hP "\t##" $(MAKEFILE_LIST) | sed -e 's/:.*##/ /' | expand -t20


docs: ## Generate documentation (requirss Sphinx)
$(MAKE) -C docs html

translate: ## Translate and update .po and .mo files (requires PyBabel)
$(MAKE) -C po

icons: ## Generate icons from stensil (requires Inkscape)
$(MAKE) -C gaphor/ui/icons

.PHONY: help docs translate icons
8 changes: 7 additions & 1 deletion README.md
Expand Up @@ -113,11 +113,17 @@ mingw-w64-x86-64-python3-setuptools mingw-w64-x86-64-python3-zope.interface \
mingw-w64-x86-64-python3-coverage mingw-w64-x86-64-python3-pytest
```

Ensure `/mingw64/bin` is added to your `PATH`:
```bash
$ export PATH=/mingw64/bin:$PATH
```

[Clone the
repository](https://help.github.com/en/github/creating-cloning-and-archiving-repositories/cloning-a-repository).

```bash
$ cd gaphor
$ source ./venv
$ source ./venv -S
$ poetry run gaphor
```

Expand Down
5 changes: 1 addition & 4 deletions azure-pipelines.yml
Expand Up @@ -41,13 +41,10 @@ jobs:
steps:
- script: |
choco install msys2 --params="/InstallDir:%CD:~0,2%\msys64 /NoUpdate /NoPath"
displayName: Install MSYS2

- script: |
set PATH=%CD:~0,2%\msys64\usr\bin;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem
%CD:~0,2%\msys64\usr\bin\pacman --noconfirm -Syyuu
%CD:~0,2%\msys64\usr\bin\pacman --noconfirm -Syuu
displayName: Update MSYS2
displayName: Install MSYS2

- script: |
set PATH=C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem
Expand Down
14 changes: 8 additions & 6 deletions gaphor/i18n.py
Expand Up @@ -6,20 +6,22 @@
__all__ = ["gettext"]

import gettext as _gettext
import importlib.resources
import logging
import os

import importlib_metadata

localedir = os.path.join(
importlib_metadata.distribution("gaphor").locate_file("gaphor/data/locale")
)
log = logging.getLogger(__name__)

try:

catalog = _gettext.Catalog("gaphor", localedir=localedir)
gettext = catalog.gettext
with importlib.resources.path("gaphor", "locale") as path:
translate = _gettext.translation("gaphor", localedir=str(path), fallback=True)
gettext = translate.gettext

except OSError:
except OSError as e:
log.info(f"No translations were found: {e}")

def gettext(s):
return s
8 changes: 8 additions & 0 deletions gaphor/locale/README
@@ -0,0 +1,8 @@
This folder contains the localized text for Gaphor.

Sources are in the source code repository in the po/ folder.

If you want to add or update a translation, please get in contact.

https://github.com/gaphor/gaphor

10 changes: 7 additions & 3 deletions gaphor/misc/errorhandler.py
Expand Up @@ -23,12 +23,16 @@ def error_handler(message=None, exc_info=None):
message = gettext("An error occurred.")

buttons = Gtk.ButtonsType.OK
message = f"{message}\n\nTechnical details:\n\t{exc_type}\n\t{exc_value}"
message = (
f"{message}\n\n"
+ gettext("Technical details:")
+ f"\n\t{exc_type}\n\t{exc_value}"
)

if __debug__ and sys.stdin.isatty():
buttons = Gtk.ButtonsType.YES_NO
message += gettext(
"\n\nDo you want to debug?\n(Gaphor should have been started from the command line)"
message += "\n\n" + gettext(
"Do you want to debug?\n(Gaphor should have been started from the command line)"
)

dialog = Gtk.MessageDialog(
Expand Down
17 changes: 6 additions & 11 deletions gaphor/plugins/diagramexport/__init__.py
Expand Up @@ -43,14 +43,9 @@ def save_dialog(self, diagram, title, ext):
while True:
filename = file_dialog.selection
if os.path.exists(filename):
question = (
gettext(
"The file %s already exists. Do you want to "
"replace it with the file you are exporting "
"to?"
)
% filename
)
question = gettext(
"The file {filename} already exists. Do you want to replace it?"
).format(filename=filename)
question_dialog = QuestionDialog(question)
answer = question_dialog.answer
question_dialog.destroy()
Expand Down Expand Up @@ -120,7 +115,7 @@ def save_pdf(self, filename, canvas):
@action(
name="file-export-svg",
label=gettext("Export to SVG"),
tooltip=gettext("Export the diagram to SVG"),
tooltip=gettext("Export diagram to SVG"),
)
def save_svg_action(self):
title = gettext("Export diagram to SVG")
Expand All @@ -133,7 +128,7 @@ def save_svg_action(self):
@action(
name="file-export-png",
label=gettext("Export to PNG"),
tooltip=gettext("Export the diagram to PNG"),
tooltip=gettext("Export diagram to PNG"),
)
def save_png_action(self):
title = gettext("Export diagram to PNG")
Expand All @@ -146,7 +141,7 @@ def save_png_action(self):
@action(
name="file-export-pdf",
label=gettext("Export to PDF"),
tooltip=gettext("Export the diagram to PDF"),
tooltip=gettext("Export diagram to PDF"),
)
def save_pdf_action(self):
title = gettext("Export diagram to PDF")
Expand Down
12 changes: 2 additions & 10 deletions gaphor/services/undomanager.py
Expand Up @@ -187,12 +187,7 @@ def discard_transaction(self):

self._action_executed()

@action(
name="edit-undo",
label=gettext("_Undo"),
icon_name="edit-undo",
shortcut="<Primary>z",
)
@action(name="edit-undo", shortcut="<Primary>z")
def undo_transaction(self):
if not self._undo_stack:
return
Expand Down Expand Up @@ -223,10 +218,7 @@ def undo_transaction(self):
self._action_executed()

@action(
name="edit-redo",
label=gettext("_Redo"),
icon_name="edit-redo",
shortcut="<Primary><Shift>z",
name="edit-redo", shortcut="<Primary><Shift>z",
)
def redo_transaction(self):
if not self._redo_stack:
Expand Down
2 changes: 1 addition & 1 deletion gaphor/storage/storage.py
Expand Up @@ -176,7 +176,7 @@ def load_elements_generator(elements, factory, gaphor_version): # noqa: C901
Load a file and create a model if possible.
Exceptions: IOError, ValueError.
"""
log.debug(gettext("Loading %d elements...") % len(elements))
log.debug("Loading %d elements..." % len(elements))

# The elements are iterated three times:
size = len(elements) * 3
Expand Down
26 changes: 6 additions & 20 deletions gaphor/ui/diagrampage.py
Expand Up @@ -152,54 +152,40 @@ def close(self):
self.view = None

@action(
name="diagram.zoom-in",
label=gettext("Zoom _In"),
icon_name="zoom-in",
shortcut="<Primary>plus",
name="diagram.zoom-in", shortcut="<Primary>plus",
)
def zoom_in(self):
assert self.view
self.view.zoom(1.2)

@action(
name="diagram.zoom-out",
label=gettext("Zoom _Out"),
icon_name="zoom-out",
shortcut="<Primary>minus",
name="diagram.zoom-out", shortcut="<Primary>minus",
)
def zoom_out(self):
assert self.view
self.view.zoom(1 / 1.2)

@action(
name="diagram.zoom-100",
label=gettext("_Normal Size"),
icon_name="zoom-original",
shortcut="<Primary>0",
name="diagram.zoom-100", shortcut="<Primary>0",
)
def zoom_100(self):
assert self.view
zx = self.view.matrix[0]
self.view.zoom(1 / zx)

@action(
name="diagram.select-all",
label="_Select all",
icon_name="edit-select-all",
shortcut="<Primary>a",
name="diagram.select-all", shortcut="<Primary>a",
)
def select_all(self):
assert self.view
self.view.select_all()

@action(
name="diagram.unselect-all", label="Des_elect all", shortcut="<Primary><Shift>a"
)
@action(name="diagram.unselect-all", shortcut="<Primary><Shift>a")
def unselect_all(self):
assert self.view
self.view.unselect_all()

@action(name="diagram.delete", label=gettext("_Delete"), icon_name="edit-delete")
@action(name="diagram.delete")
@transactional
def delete_selected_items(self):
assert self.view
Expand Down
17 changes: 9 additions & 8 deletions gaphor/ui/filemanager.py
Expand Up @@ -72,7 +72,7 @@ def load(self, filename):
main_window = self.main_window
status_window = StatusWindow(
gettext("Loading..."),
gettext(f"Loading model from {filename}"),
gettext("Loading model from {filename}").format(filename=filename),
parent=main_window.window,
queue=queue,
)
Expand All @@ -93,7 +93,9 @@ def load(self, filename):
self.event_manager.handle(FileLoaded(self, filename))
except (QueueEmpty, QueueFull):
error_handler(
message=gettext("Error while loading model from file %s") % filename
message=gettext(
"Error while loading model from file {filename}"
).format(filename=filename)
)
raise
finally:
Expand All @@ -113,10 +115,7 @@ def verify_orphans(self):

dialog = QuestionDialog(
gettext(
"The model contains some references"
" to items that are not maintained."
" Do you want to clean this before"
" saving the model?"
"The model contains some references to items that are not maintained. Do you want to clean the model before saving?"
),
parent=main_window.window,
)
Expand Down Expand Up @@ -155,7 +154,7 @@ def save(self, filename):
queue = Queue()
status_window = StatusWindow(
gettext("Saving..."),
gettext("Saving model to %s") % filename,
gettext("Saving model to {filename}").format(filename=filename),
parent=main_window.window,
queue=queue,
)
Expand All @@ -173,7 +172,9 @@ def save(self, filename):
self.event_manager.handle(FileSaved(self, filename))
except (OSError, QueueEmpty, QueueFull):
error_handler(
message=gettext("Error while saving model to file %s") % filename
message=gettext("Error while saving model to file {filename}").format(
filename=filename
)
)
raise
finally:
Expand Down
2 changes: 1 addition & 1 deletion gaphor/ui/mainwindow.py
Expand Up @@ -278,7 +278,7 @@ def set_title(self):
title = self.title
subtitle = ""
if self.model_changed:
title += gettext(" [edited]")
title += " [" + gettext("edited") + "]"
self.window.set_title(title)
self.window.get_titlebar().set_subtitle(subtitle)

Expand Down
3 changes: 2 additions & 1 deletion gaphor/ui/namespace.py
Expand Up @@ -382,7 +382,8 @@ def namespace_popup_model(self):
for presentation in element.presentation:
diagram = presentation.canvas.diagram
menu_item = Gio.MenuItem.new(
gettext(f'Show in "{diagram.name}"'), "tree-view.show-in-diagram"
gettext('Show in "{diagram}"').format(diagram=diagram.name),
"tree-view.show-in-diagram",
)
menu_item.set_attribute_value("target", GLib.Variant.new_string(diagram.id))
part.append_item(menu_item)
Expand Down
18 changes: 18 additions & 0 deletions po/Makefile
@@ -0,0 +1,18 @@
LANGS=ca es fr nl pt_BR ru sv

SRCS=$(shell find ../gaphor -path ../gaphor/locale -prune -o -print)
MO_FILES=$(patsubst %,../gaphor/locale/%/LC_MESSAGES/gaphor.mo,$(LANGS))

all: $(MO_FILES)

gaphor.pot: $(SRCS)
pybabel extract -o gaphor.pot -F babel.ini ../gaphor

%.po: gaphor.pot
pybabel update -i $< -o $@ -l $* -D gaphor

../gaphor/locale/%/LC_MESSAGES/gaphor.mo: %.po
mkdir -p $$(dirname $@)
pybabel compile -i $< -o $@ -l $* -D gaphor

.PHONY: all
3 changes: 3 additions & 0 deletions po/babel.ini
@@ -0,0 +1,3 @@
[python: **.py]

[glade: **.glade]