Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@
*.swp
*.swo
.*
*.mo
*~
po/messages.pot
60 changes: 60 additions & 0 deletions TRANSLATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Translation

Translation files are found in the `po/` directory.

## i18n not released yet

Kano OS is not fully i18n-aware and locales are not installed for end users, yet. You can translate this application, but as of now, users will still see the default English message strings.

## How to add a new translation

In this example, we're going to add a French translation:

# install your target locale `fr_FR.utf8` with:
sudo dpkg-reconfigure locales

cd po/
# create messages.pot
make messages

# create fr.po from messages.pot:
msginit -l fr_FR.utf8

# now use your favourite editor to translate fr.po

# build locale files:
make

cd ..

# run test

## How to make sure your code is i18n-aware

Add the gettext `_()` macro to all the user-visible message strings in your Python. List the Python source files that contain message strings in `PYPOTFILES`.

If you added new message strings or made changes to existing ones, do `make messages` to keep the template file up-to-date.

After that, merge the existing translations with `make update` and ask your translators to update their translations.

## gettext explained (in 20 seconds)

* User-visible strings in the source are marked with a macro of your choice. Usually, it's `_()`.
* `xgettext` extracts these message strings from your sources and puts them into a template file.
* This template file, usually named `messages.pot`, contains all user-visible strings of the project, but no translations.
* Translators use `msginit` to copy the template file into a new *portable object* file for their language (explained above).
* The translations are put into `<lang>.po`. It's a plain-text file format, you can use any text editor.
* More convenient, specialized `.po`-editors and web-based tools such as Pootle exist, as well.
* If your template file changes, use `msgmerge` to merge your existing translations with the new template, then re-translate the updated messages. Beware of `msgmerge`'s "fuzzy" matches.
* `msgfmt` converts a `.po` file into a binary *message object* file.
* You don't link these `.mo` files with your application binary.
* The `.mo` files are bundled alongside with your software as part of the distribution package.
* During installation, the `.mo` files are copied into the system's locale directory, usually `/usr/share/locale`.
* On startup, your application will look for the message object file that it needs for the current system locale.
* The locale even allows you to provide region-specific translations, e.g. "colour" for en_UK vs "color" for en_US.
* At runtime, all user-visible strings are being replaced with the translations.
* If no message object was found for the system locale, the original strings will be shown.

## To-Do

Pootle or Transifex integration.
36 changes: 21 additions & 15 deletions bin/kano-apps
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# kano-apps
#
# Copyright (C) 2014, 2015 Kano Computing Ltd.
# Copyright (C) 2014-2016 Kano Computing Ltd.
# License: http://www.gnu.org/licenses/gpl-2.0.txt GNU GPL v2
#

Expand All @@ -15,6 +15,12 @@ if __name__ == '__main__' and __package__ is None:
dir_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
if dir_path != '/usr':
sys.path = [dir_path] + sys.path
locale_path = os.path.join(dir_path, 'locale')
else:
locale_path = None

import kano_i18n.init
kano_i18n.init.install('kano-apps', locale_path)

from kano.gtk3.kano_dialog import KanoDialog
from kano.logging import logger
Expand Down Expand Up @@ -50,18 +56,18 @@ def check_for_expand_partition():
# Check if partition has been expanded
if not is_partition_expanded(device):
# Show dialogue
head = "Do you want to reboot now?"
msg = "A reboot is required to complete the installation of Kano" \
" OS so it can access all the space on the SD card."
head = _("Do you want to reboot now?")
msg = _("A reboot is required to complete the installation of Kano" +\
" OS so it can access all the space on the SD card.")
kdialog = KanoDialog(
head,
msg,
{
"LATER": {
_("LATER"): {
"return_value": False,
"color": "red"
},
"REBOOT": {
_("REBOOT"): {
"return_value": True,
"color": "green"
},
Expand Down Expand Up @@ -107,29 +113,29 @@ def batch_install(apps, icon_only=False):

def args_parse():
if len(sys.argv) >= 2:
desc = "An application hub for Kano OS."
desc = _("An application hub for Kano OS.")
parser = argparse.ArgumentParser(description=desc)
parser.set_defaults(cmd=None)
subparsers = parser.add_subparsers(dest="cmd")

install_help = "Use this to install applications"
install_help = _("Use this to install applications")
install = subparsers.add_parser("install", help=install_help)

install.add_argument("--no-gui", dest="gui", action="store_const",
const=False, default=True,
help="Run without the GUI")
help=_("Run without the GUI"))
install.add_argument("--icon-only", dest="icon_only",
action="store_const", const=True, default=False,
help="Install just the app icon")
help=_("Install just the app icon"))
install.add_argument("apps", metavar="APP", type=str, nargs="+",
help="A list of applications to install")
help=_("A list of applications to install"))

update_help = "Check for possible updates of all installed " \
"applications"
update_help = _("Check for possible updates of all installed " +\
"applications")
subparsers.add_parser("check-for-updates", help=update_help)

tutorial_help = "Shows a tutorial that explains how to add icons to" \
"the desktop"
tutorial_help = _("Shows a tutorial that explains how to add icons to " +\
"the desktop")
subparsers.add_parser("icon-tutorial", help=tutorial_help)

return parser.parse_args()
Expand Down
4 changes: 2 additions & 2 deletions debian/control
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ Maintainer: Team Kano <dev@kano.me>
Section: misc
Priority: optional
Standards-Version: 3.9.4
Build-Depends: debhelper (>=9.0.0), python
Build-Depends: debhelper (>=9.0.0), python, gettext

Package: kano-apps
Architecture: all
Depends: ${misc:Depends}, python, python-gi, kano-toolset (>= 1.2-5),
kano-themes, kano-profile (>=1.3-6)
kano-themes, kano-profile (>=1.3-6), kano-i18n
Replaces: kano-desktop (<< 1.0-60)
Breaks: kano-desktop (<< 1.0-60)
Conflicts: kano-extras
Expand Down
1 change: 1 addition & 0 deletions debian/rules
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#!/usr/bin/make -f

%:
cd po && make
dh $@ --with python2
52 changes: 26 additions & 26 deletions kano_apps/AppGrid.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# AppGrid.py
#
# Copyright (C) 2014 Kano Computing Ltd.
# Copyright (C) 2014-2016 Kano Computing Ltd.
# License: http://www.gnu.org/licenses/gpl-2.0.txt GNU General Public License v2
#

Expand Down Expand Up @@ -29,8 +29,8 @@ def __init__(self, apps, main_win):

want_more_app = {
"type": "app",
"title": "Want more apps?",
"tagline": "Go to Kano World to install more",
"title": _("Want more apps?"),
"tagline": _("Go to Kano World to install more"),
"slug": "want-more",

"origin": "-",
Expand All @@ -51,8 +51,8 @@ def __init__(self, apps, main_win):

last_page = 0

self._cat_names = ["latest", "code", "games", "media", "tools",
"others", "experimental"]
self._cat_names = [N_("latest"), N_("code"), N_("games"), N_("media"), N_("tools"),
N_("others"), N_("experimental")]
self._categories = {}

self._apps = {}
Expand All @@ -73,7 +73,7 @@ def __init__(self, apps, main_win):
# Prepare tabs for the apps
for cat in sorted_categories:
self._categories[cat] = AppGrid(main_win, self)
label = Gtk.Label(cat.upper())
label = Gtk.Label(_(cat).upper())
ebox = Gtk.EventBox()
ebox.add(label)
ebox.connect("realize", self._set_cursor_to_hand_cb)
Expand Down Expand Up @@ -244,10 +244,10 @@ def refresh(self):

if self._is_on_desktop():
self.set_image(Gtk.Image.new_from_file(self._RM_IMG_PATH))
self.set_tooltip_text("Remove from desktop")
self.set_tooltip_text(_("Remove from desktop"))
else:
self.set_image(Gtk.Image.new_from_file(self._ADD_IMG_PATH))
self.set_tooltip_text("Add to desktop")
self.set_tooltip_text(_("Add to desktop"))

self.show_all()

Expand Down Expand Up @@ -415,10 +415,10 @@ def _launch_app(self, cmd, args):
# The execvp should not return, so if we reach this point,
# there was an error.
message = KanoDialog(
"Error",
"Unable to start the application.",
_("Error"),
_("Unable to start the application."),
{
"OK": {
_("OK"): {
"return_value": 0,
"color": "red"
}
Expand All @@ -437,7 +437,7 @@ def _setup_desc_button(self):
more_btn.props.margin_right = 21
more_btn.get_style_context().add_class('more-button')
more_btn.connect("clicked", self._show_more_cb)
more_btn.set_tooltip_text("More information")
more_btn.set_tooltip_text(_("More information"))
more_btn.connect("realize", self._set_cursor_to_hand_cb)
self._entry.pack_start(more_btn, False, False, 0)

Expand All @@ -459,7 +459,7 @@ def _setup_remove_button(self):
remove_btn.props.margin_right = 21
remove_btn.get_style_context().add_class('more-button')
remove_btn.connect("clicked", self._uninstall_cb)
remove_btn.set_tooltip_text("Remove")
remove_btn.set_tooltip_text(_("Remove"))
remove_btn.connect("realize", self._set_cursor_to_hand_cb)
remove_btn.connect("enter-notify-event", self._open_bin_cb)
remove_btn.connect("leave-notify-event", self._close_bin_cb)
Expand Down Expand Up @@ -490,7 +490,7 @@ def _setup_update_button(self):
update_btn.props.margin_right = 21
update_btn.get_style_context().add_class('more-button')
update_btn.connect("clicked", self._update_cb)
update_btn.set_tooltip_text("Update app")
update_btn.set_tooltip_text(_("Update app"))
update_btn.connect("realize", self._set_cursor_to_hand_cb)
self._entry.pack_start(update_btn, False, False, 0)
self._entry.reorder_child(update_btn, 2)
Expand All @@ -509,7 +509,7 @@ def _show_more_cb(self, widget):
self._app["title"],
self._app['description'] if "description" in self._app else self._app['tagline'],
{
"OK, GOT IT": {
_("OK, GOT IT"): {
"return_value": 0,
"color": "green"
}
Expand All @@ -532,14 +532,14 @@ def _entry_click_cb(self, ebox, event):

def _uninstall_cb(self, event):
confirmation = KanoDialog(
title_text="Removing {}".format(self._app["title"]),
description_text="This application will be uninstalled and " +
"removed from apps. Do you wish to proceed?",
title_text=_("Removing {}").format(self._app["title"]),
description_text=_("This application will be uninstalled and " +
"removed from apps. Do you wish to proceed?"),
button_dict={
"YES": {
_("YES"): {
"return_value": 0
},
"NO": {
_("NO"): {
"return_value": -1,
"color": "red"
}
Expand All @@ -553,7 +553,7 @@ def _uninstall_cb(self, event):
if rv < 0:
return

prompt = "Uninstalling {}".format(self._app["title"])
prompt = _("Uninstalling {}").format(self._app["title"])
pw = get_sudo_password(prompt, self._window)
if pw is None:
return
Expand All @@ -578,14 +578,14 @@ def _install_cb(self):

def _update_cb(self, widget):
confirmation = KanoDialog(
title_text="Updating {}".format(self._app["title"]),
description_text="This application will be updated " +
"Do you wish to proceed?",
title_text=_("Updating {}").format(self._app["title"]),
description_text=_("This application will be updated " +
"Do you wish to proceed?"),
button_dict={
"YES": {
_("YES"): {
"return_value": 0
},
"NO": {
_("NO"): {
"return_value": -1,
"color": "red"
}
Expand Down
28 changes: 14 additions & 14 deletions kano_apps/AppInstaller.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,11 @@ def _download_app(self):
self._tmp_data_file = app_data_file
self._tmp_icon_file = app_icon_file
except AppDownloadError as err:
head = "Unable to download the application"
head = _("Unable to download the application")
dialog = KanoDialog(
head, str(err),
{
"OK": {
_("OK"): {
"return_value": 0
},
},
Expand All @@ -111,15 +111,15 @@ def _download_app(self):

def _installed_check(self):
if is_app_installed(self._app):
head = "{} is already installed".format(self._app["title"])
desc = "Would you like to update it?"
head = _("{} is already installed").format(self._app["title"])
desc = _("Would you like to update it?")
dialog = KanoDialog(
head, desc,
{
"YES": {
_("YES"): {
"return_value": 0
},
"NO": {
_("NO"): {
"return_value": -1
}
},
Expand All @@ -137,7 +137,7 @@ def _installed_check(self):

def _get_sudo_pw(self):
if self._pw is None:
self._pw = get_sudo_password("Installing {}".format(self._app["title"]))
self._pw = get_sudo_password(_("Installing {}").format(self._app["title"]))
return self._pw is not None

return True
Expand All @@ -160,21 +160,21 @@ def _install(self):
if not self._icon_only and self._add_to_desktop:
add_to_desktop(self._app)

head = "Done!"
message = self._app["title"] + " installed successfully! " + \
"Look for it in the Apps launcher."
head = _("Done!")
message = _("%s installed successfully! " + \
"Look for it in the Apps launcher.") % self._app["title"]
else:
head = "Installation failed"
message = self._app["title"] + " cannot be installed at " + \
head = _("Installation failed")
message = _("%s cannot be installed at " + \
"the moment. Please make sure your kit is connected " + \
"to the internet and there is enough space left on " + \
"your card."
"your card.") % self._app["title"]

dialog = KanoDialog(
head,
message,
{
"OK": {
_("OK"): {
"return_value": 0
},
},
Expand Down
Loading