Skip to content
Open
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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
*.swp
*.so*
*.dylib
*.mo
*~

# Ignore tags created by etags, ctags, gtags (GNU global) and cscope
TAGS
Expand All @@ -24,3 +26,5 @@ cscope.po.out
# Ignore locked versions of original files
media/images/environments/originals/**/*_locked.png
media/images/badges/originals/**/*_locked.png

po/messages.pot
56 changes: 56 additions & 0 deletions TRANSLATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# 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

## 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.
2 changes: 1 addition & 1 deletion debian/control
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Priority: optional
Standards-Version: 3.9.4
Build-Depends: debhelper (>= 9), build-essential, pkg-config, libgtk2.0-dev,
lxpanel (>= 0.7.0), libfm-dev, libkdesk-dev, libparson-dev,
python-dev, libkano-python-dev
python-dev, libkano-python-dev, gettext

Package: kano-profile
Architecture: all
Expand Down
3 changes: 3 additions & 0 deletions debian/kano-profile.install
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,6 @@ icon/kano-world.png usr/share/icons/Kano/66x66/apps
lxpanel-plugin/kano_profile.so usr/lib/arm-linux-gnueabihf/lxpanel/plugins/

xdg/* usr/share/applications

locale/* usr/share/locale/

1 change: 1 addition & 0 deletions debian/rules
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/usr/bin/make -f

%:
cd po && make
dh $@

.PHONY: override_dh_strip
Expand Down
4 changes: 2 additions & 2 deletions kano_profile_gui/MenuBar.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,9 @@ def __init__(self, win_width, quests):
hbox.pack_end(close_button, False, False, 0)
close_button.connect("clicked", self.close_window)

name_array = ['BADGES', 'CHARACTER'] # TODO: add 'QUESTS' to enable
name_array = [N_('BADGES'), N_('CHARACTER')] # TODO: add 'QUESTS' to enable
for name in name_array:
button = MenuButton(name, self._quests)
button = MenuButton(_(name), self._quests)
button.connect("clicked", self.emit_menu_signal, name)
button.connect("clicked", self.set_selected_wrapper, name)
hbox.pack_end(button, False, False, 0)
Expand Down
2 changes: 1 addition & 1 deletion kano_profile_gui/QuestInfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ def create_reward_section(self):
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
background.add(vbox)

title = Gtk.Label("Rewards")
title = Gtk.Label(_("Rewards"))
title.set_alignment(xalign=0.5, yalign=0.5)
title.set_padding(xpad=10, ypad=10)
title.get_style_context().add_class("reward_title")
Expand Down
2 changes: 1 addition & 1 deletion kano_registration_gui/GetData.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ def validate_username(self, widget=None, event=None):
if len(username) == 0:
self._username.label_success("")
elif check_username(username):
self._username.label_success(_(""), "success")
self._username.label_success("", "success")
else:
self._username.label_success(_(" is invalid"), "fail")

Expand Down
20 changes: 10 additions & 10 deletions kano_world/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def login(login_name, password):
if success:
return login_register_data(data)
else:
return False, 'Cannot log in, problem: {}'.format(text)
return False, _('Cannot log in, problem: {}').format(text)


def register(email, username, password, marketing_enabled=False):
Expand All @@ -49,7 +49,7 @@ def register(email, username, password, marketing_enabled=False):
if success:
return login_register_data(data)
else:
return False, 'Cannot register, problem: {}'.format(text)
return False, _('Cannot register, problem: {}').format(text)


def login_register_data(data):
Expand All @@ -74,7 +74,7 @@ def login_register_data(data):
glob_session = KanoWorldSession(profile['token'])
return True, None
except Exception as e:
return False, 'We cannot reach the Kano server. Try again in a few minutes. Error = {}'.format(str(e))
return False, _('We cannot reach the Kano server. Try again in a few minutes. Error = {}').format(str(e))


def is_registered():
Expand Down Expand Up @@ -105,9 +105,9 @@ def login_using_token():
global glob_session

if not is_registered():
return False, 'Not registered!'
return False, _('Not registered!')
if not has_token():
return False, 'No token!'
return False, _('No token!')

try:
profile = load_profile()
Expand All @@ -119,7 +119,7 @@ def login_using_token():

def sync():
if not glob_session:
return False, 'You are not logged in!'
return False, _('You are not logged in!')

success, value = glob_session.upload_profile_stats()
if not success:
Expand All @@ -146,21 +146,21 @@ def sync():

def backup_content(file_path):
if not glob_session:
return False, 'You are not logged in!'
return False, _('You are not logged in!')

return glob_session.backup_content(file_path)


def restore_content(file_path):
if not glob_session:
return False, 'You are not logged in!'
return False, _('You are not logged in!')

return glob_session.restore_content(file_path)


def upload_tracking_data():
if not glob_session:
return False, 'You are not logged in!'
return False, _('You are not logged in!')

success, value = glob_session.upload_tracking_data()
if not success:
Expand Down Expand Up @@ -230,7 +230,7 @@ def reset_password(email):

def mark_notification_read(n_id):
if not glob_session:
return False, 'You are not logged in!'
return False, _('You are not logged in!')

success, text, data = request_wrapper(
'post',
Expand Down
16 changes: 8 additions & 8 deletions kano_world/share.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def post_comment(share_id, comment):
glob_session = get_glob_session()

if not glob_session:
return False, 'You are not logged in!'
return False, _('You are not logged in!')

return glob_session.post_comment(share_id, comment)

Expand All @@ -63,7 +63,7 @@ def like_share(share_id):
glob_session = get_glob_session()

if not glob_session:
return False, 'You are not logged in!'
return False, _('You are not logged in!')

return glob_session.like_share(share_id)

Expand All @@ -72,7 +72,7 @@ def unlike_share(share_id):
glob_session = get_glob_session()

if not glob_session:
return False, 'You are not logged in!'
return False, _('You are not logged in!')

return glob_session.unlike_share(share_id)

Expand All @@ -81,7 +81,7 @@ def get_following():
glob_session = get_glob_session()

if not glob_session:
return False, 'You are not logged in!'
return False, _('You are not logged in!')

user_id = get_kano_world_id()
return glob_session.get_users_following(user_id)
Expand All @@ -91,7 +91,7 @@ def follow_user(user_id):
glob_session = get_glob_session()

if not glob_session:
return False, 'You are not logged in!'
return False, _('You are not logged in!')

return glob_session.follow_user(user_id)

Expand All @@ -100,23 +100,23 @@ def unfollow_user(user_id):
glob_session = get_glob_session()

if not glob_session:
return False, 'You are not logged in!'
return False, _('You are not logged in!')

return glob_session.unfollow_user(user_id)


def upload_share(file_path, title, app_name):
glob_session = get_glob_session()
if not glob_session:
return False, 'You are not logged in!'
return False, _('You are not logged in!')

return glob_session.upload_share(file_path, title, app_name)


def delete_share(share_id):
glob_session = get_glob_session()
if not glob_session:
return False, 'You are not logged in!'
return False, _('You are not logged in!')

return glob_session.delete_share(share_id)

Expand Down
2 changes: 1 addition & 1 deletion kano_world/share_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,6 @@ def login_and_share(file_path, title, app_name):
run_cmd('kano-login -r')
success, unused = login_using_token()
if not success:
return False, 'Cannot login'
return False, _('Cannot login')

return upload_share(file_path, title, app_name)
36 changes: 36 additions & 0 deletions po/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
APPNAME=kano-tutorial
ORG="Kano Computing Ltd."

MSGLANGS=$(notdir $(wildcard *po))
MSGOBJS=$(addprefix ../locale/,$(MSGLANGS:.po=/LC_MESSAGES/$(APPNAME).mo))

.PHONY: clean_locales messages

build: $(MSGOBJS)

update: $(MSGLANGS)

clean_locales:
rm -rf ../locale

clean: clean_locales
rm -f messages.pot

define run-xgettext
xgettext -f PYPOTFILES -L Python --force-po -o messages.pot \
--keyword=N_ --package-name=$(APPNAME) --copyright-holder=$(ORG)
endef

messages:
$(run-xgettext)

messages.pot:
$(run-xgettext)

%.po: messages.pot
msgmerge -N -U $*.po messages.pot
touch $*.po

../locale/%/LC_MESSAGES/$(APPNAME).mo: clean_locales
mkdir -p $(dir $@)
msgfmt -c -o $@ $*.po
18 changes: 18 additions & 0 deletions po/PYPOTFILES
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
../kano_login/components/logged_in.py
../kano_login/first_screen.py
../kano_login/login.py
../kano_login/swag_screen.py
../kano_profile_gui/badge_screen.py
../kano_profile_gui/character_screens.py
../kano_profile_gui/MenuBar.py
../kano_profile_gui/QuestInfo.py
../kano_profile_gui/projects.py
../kano_registration_gui/BirthdayWidget.py
../kano_registration_gui/GetData.py
../kano_registration_gui/PageControl.py
../kano_registration_gui/RegistrationScreen.py
../kano_registration_gui/TermsAndConditions.py
../kano_world/functions.py
../kano_world/session.py
../kano_world/share.py
../kano_world/share_helpers.py
Loading