diff --git a/.gitignore b/.gitignore index 186b0fd7..91f99136 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,8 @@ *.swp *.so* *.dylib +*.mo +*~ # Ignore tags created by etags, ctags, gtags (GNU global) and cscope TAGS @@ -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 diff --git a/TRANSLATION.md b/TRANSLATION.md new file mode 100644 index 00000000..e2d77ef8 --- /dev/null +++ b/TRANSLATION.md @@ -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 `.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. diff --git a/debian/control b/debian/control index 661e6c36..82b5583e 100644 --- a/debian/control +++ b/debian/control @@ -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 diff --git a/debian/kano-profile.install b/debian/kano-profile.install index 58b8cd92..00d4fbef 100644 --- a/debian/kano-profile.install +++ b/debian/kano-profile.install @@ -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/ + diff --git a/debian/rules b/debian/rules index e0e1f328..e7c7e369 100755 --- a/debian/rules +++ b/debian/rules @@ -1,6 +1,7 @@ #!/usr/bin/make -f %: + cd po && make dh $@ .PHONY: override_dh_strip diff --git a/kano_profile_gui/MenuBar.py b/kano_profile_gui/MenuBar.py index 9bfa1f23..c8b58a72 100644 --- a/kano_profile_gui/MenuBar.py +++ b/kano_profile_gui/MenuBar.py @@ -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) diff --git a/kano_profile_gui/QuestInfo.py b/kano_profile_gui/QuestInfo.py index 6ebfc7a8..4554bd65 100644 --- a/kano_profile_gui/QuestInfo.py +++ b/kano_profile_gui/QuestInfo.py @@ -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") diff --git a/kano_registration_gui/GetData.py b/kano_registration_gui/GetData.py index 06bf05f9..6a97f024 100644 --- a/kano_registration_gui/GetData.py +++ b/kano_registration_gui/GetData.py @@ -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") diff --git a/kano_world/functions.py b/kano_world/functions.py index 50b40646..e410de17 100644 --- a/kano_world/functions.py +++ b/kano_world/functions.py @@ -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): @@ -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): @@ -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(): @@ -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() @@ -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: @@ -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: @@ -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', diff --git a/kano_world/share.py b/kano_world/share.py index fc418b0d..b52e1789 100644 --- a/kano_world/share.py +++ b/kano_world/share.py @@ -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) @@ -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) @@ -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) @@ -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) @@ -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) @@ -100,7 +100,7 @@ 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) @@ -108,7 +108,7 @@ def 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) @@ -116,7 +116,7 @@ def 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) diff --git a/kano_world/share_helpers.py b/kano_world/share_helpers.py index 8d79b42b..23ad2a50 100644 --- a/kano_world/share_helpers.py +++ b/kano_world/share_helpers.py @@ -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) diff --git a/po/Makefile b/po/Makefile new file mode 100644 index 00000000..aecd0992 --- /dev/null +++ b/po/Makefile @@ -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 diff --git a/po/PYPOTFILES b/po/PYPOTFILES new file mode 100644 index 00000000..e6a5c490 --- /dev/null +++ b/po/PYPOTFILES @@ -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 diff --git a/po/ja.po b/po/ja.po new file mode 100644 index 00000000..bf756255 --- /dev/null +++ b/po/ja.po @@ -0,0 +1,352 @@ +# Japanese translations for kano-tutorial package. +# Copyright (C) 2016 Kano Computing Ltd. +# This file is distributed under the same license as the kano-tutorial package. +# Choppin Antoine , 2016. +# +msgid "" +msgstr "" +"Project-Id-Version: kano-tutorial\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-07-06 22:09+0900\n" +"PO-Revision-Date: 2016-05-19 20:29+0900\n" +"Last-Translator: Choppin Antoine \n" +"Language-Team: Japanese\n" +"Language: ja\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: ../kano_login/components/logged_in.py:24 ../kano_login/login.py:258 +msgid "OK" +msgstr "了解" + +#: ../kano_login/components/logged_in.py:28 +msgid "Logged in!" +msgstr "ログイン完了!" + +#: ../kano_login/components/logged_in.py:28 +msgid "You're already logged in" +msgstr "すでにログイン中" + +#: ../kano_login/components/logged_in.py:46 +msgid "Logged out!" +msgstr "ログアウト完了!" + +#: ../kano_login/first_screen.py:34 +msgid "Create" +msgstr "作成" + +#: ../kano_login/first_screen.py:35 +msgid "Skip" +msgstr "飛ばす" + +#: ../kano_login/first_screen.py:36 +msgid "I have a profile" +msgstr "すでにプロフィールを持っている" + +#: ../kano_login/first_screen.py:37 +msgid "Now let's make your online Profile" +msgstr "君のオンラインプロフィールを作ろう" + +#: ../kano_login/first_screen.py:38 +msgid "So you can save badges, games, and projects" +msgstr "バッジ、ゲーム、プロジェクトなどを保存できるようになる" + +#: ../kano_login/first_screen.py:121 +msgid "Oops! You need Internet to make a profile" +msgstr "おっと、プロフィールを作るにはインターネットが必要だよ" + +#: ../kano_login/first_screen.py:123 +msgid "But you can skip this if you have no connection right now" +msgstr "でもインターネット接続がなければ、これを飛ばして進められる" + +#: ../kano_login/first_screen.py:127 +msgid "Connect" +msgstr "接続する" + +#: ../kano_login/first_screen.py:128 +msgid "Register later" +msgstr "後で登録する" + +#: ../kano_login/login.py:52 ../kano_login/login.py:80 +msgid "Login" +msgstr "ログイン" + +#: ../kano_login/login.py:53 +msgid "Enter your username and password" +msgstr "ユーザー名とパスワードを入力して下さい" + +#: ../kano_login/login.py:64 +msgid "Username" +msgstr "ユーザー名" + +#: ../kano_login/login.py:65 ../kano_login/login.py:127 +#: ../kano_registration_gui/GetData.py:91 +msgid "Password" +msgstr "パスワード" + +#: ../kano_login/login.py:81 +msgid "Create New" +msgstr "新規作成" + +#: ../kano_login/login.py:82 +msgid "Forgotten password?" +msgstr "パスワード忘れた?" + +#: ../kano_login/login.py:108 +msgid " Username: " +msgstr " ユーザー名: " + +#: ../kano_login/login.py:176 +msgid "You don't have internet!" +msgstr "インターネット接続がない!" + +#: ../kano_login/login.py:178 +msgid "Connect with wifi or ethernet and try again" +msgstr "WiFiかイーサーネットで接続し、やり直して下さい" + +#: ../kano_login/login.py:197 +#: ../kano_registration_gui/RegistrationScreen.py:117 +msgid "Houston, we have a problem" +msgstr "あれあれ、問題発生!" + +#: ../kano_login/login.py:245 ../kano_login/login.py:339 +msgid "Success!" +msgstr "成功!" + +#: ../kano_login/login.py:246 +msgid "You're in - online features now enabled." +msgstr "入れた - オンライン機能が使用可能" + +#: ../kano_login/login.py:292 +msgid "Reset your password" +msgstr "パスワードリセット" + +#: ../kano_login/login.py:293 +msgid "We'll send a new password to your email" +msgstr "新しいパスワードをメールで送ります" + +#: ../kano_login/login.py:298 +msgid "Email" +msgstr "メール" + +#: ../kano_login/login.py:315 +msgid "Reset password" +msgstr "パスワードをリセットする" + +#: ../kano_login/login.py:340 +msgid "Sent new password to your email" +msgstr "新しいパスワードをメールで送りました" + +#: ../kano_login/login.py:342 +msgid "Go to login screen" +msgstr "ログイン画面へ" + +#: ../kano_login/login.py:343 ../kano_login/login.py:349 +msgid "Quit" +msgstr "終了" + +#: ../kano_login/login.py:346 +msgid "Something went wrong!" +msgstr "失敗しました!" + +#: ../kano_login/login.py:350 +msgid "Try again" +msgstr "やり直す" + +#: ../kano_login/swag_screen.py:28 +#: ../kano_registration_gui/RegistrationScreen.py:136 +msgid "Profile activated!" +msgstr "プロフィールが有効です!" + +#: ../kano_login/swag_screen.py:30 +msgid "" +"Now you can share stuff, build your character, and connect with friends! " +"You've earned some rewards…" +msgstr "" +"これで作品を共有したり、キャラクターを作ったり、仲間と連絡を取り合ったりする" +"ことができる!" + +#: ../kano_login/swag_screen.py:35 ../kano_login/swag_screen.py:44 +msgid "Let's go" +msgstr "レッツゴー" + +#: ../kano_login/swag_screen.py:37 +msgid "No online profile - for now." +msgstr "オンライン・プロフィール無し(今のところ)" + +#: ../kano_login/swag_screen.py:39 +msgid "" +"Your profile stores all your rewards, projects, and challenges. But fear not " +"- we'll save everything for when you have internet." +msgstr "" +"君の全ての賞やプロジェクトやチャレンジがプロフィールに保存される。\n" +"でも大丈夫。インターネットに繋がるまでに全て取っておくよ。" + +#: ../kano_profile_gui/badge_screen.py:45 +msgid "Next page" +msgstr "次のページへ" + +#: ../kano_profile_gui/badge_screen.py:49 +#: ../kano_profile_gui/badge_screen.py:245 +msgid "Previous" +msgstr "前のページへ" + +#: ../kano_profile_gui/badge_screen.py:249 +msgid "Back to grid" +msgstr "一覧へ戻る" + +#: ../kano_profile_gui/badge_screen.py:253 +#: ../kano_registration_gui/PageControl.py:27 +msgid "Next" +msgstr "次へ" + +#: ../kano_profile_gui/character_screens.py:34 +msgid "Edit" +msgstr "編集" + +#: ../kano_profile_gui/character_screens.py:109 +msgid "Save changes" +msgstr "修正を保存" + +#: ../kano_profile_gui/character_screens.py:118 +msgid "Discard" +msgstr "修正を破棄" + +#: ../kano_profile_gui/MenuBar.py:53 +msgid "BADGES" +msgstr "バッジ" + +#: ../kano_profile_gui/MenuBar.py:53 +msgid "CHARACTER" +msgstr "キャラクター" + +#: ../kano_profile_gui/MenuBar.py:164 +msgid "Level {}" +msgstr "レベル{}" + +#: ../kano_profile_gui/QuestInfo.py:115 +msgid "Rewards" +msgstr "賞" + +#: ../kano_profile_gui/projects.py:86 +msgid "Make" +msgstr "作成" + +#: ../kano_registration_gui/BirthdayWidget.py:65 +msgid "Birthday" +msgstr "誕生日" + +#: ../kano_registration_gui/BirthdayWidget.py:146 +msgid " date is in the future" +msgstr " 将来の日付" + +#: ../kano_registration_gui/BirthdayWidget.py:150 +msgid " year out of range" +msgstr " 年は範囲外" + +#: ../kano_registration_gui/BirthdayWidget.py:152 +msgid " is valid" +msgstr " 有効" + +#: ../kano_registration_gui/BirthdayWidget.py:235 +msgid "Oops!" +msgstr "おっと!" + +#: ../kano_registration_gui/BirthdayWidget.py:236 +msgid "You haven't entered a valid birthday" +msgstr "有効な誕生日を記入していません" + +#: ../kano_registration_gui/BirthdayWidget.py:279 +#, python-brace-format +msgid "There's a problem - {0}" +msgstr "問題が発生しました - {0}" + +#: ../kano_registration_gui/GetData.py:96 +msgid "Show password" +msgstr "パスワードを表示" + +#: ../kano_registration_gui/GetData.py:102 +msgid "Email Address" +msgstr "メールアドレス" + +#: ../kano_registration_gui/GetData.py:138 +msgid " looks good!" +msgstr " よさそう!" + +#: ../kano_registration_gui/GetData.py:140 +msgid " is not valid" +msgstr " 無効" + +#: ../kano_registration_gui/GetData.py:151 +msgid " is invalid" +msgstr " 無効" + +#: ../kano_registration_gui/GetData.py:226 +msgid "Terms and conditions" +msgstr "規約と条件" + +#: ../kano_registration_gui/PageControl.py:26 +msgid "Back" +msgstr "戻る" + +#: ../kano_registration_gui/RegistrationScreen.py:40 +msgid "Kano World" +msgstr "Kanoワールド" + +#: ../kano_registration_gui/RegistrationScreen.py:41 +msgid "Choose a cool name and secure password" +msgstr "かっこいい名前と難しいパスワードを選ぼう" + +#: ../kano_registration_gui/RegistrationScreen.py:50 +msgid "Join Kano World" +msgstr "Kanoワールドに参加する" + +#: ../kano_registration_gui/RegistrationScreen.py:137 +msgid "" +"Now you can share stuff, build your character, and connect with friends." +msgstr "" +"これで作品を共有したり、キャラクターを作ったり、仲間と連絡を取り合ったりする" +"ことができる。" + +#: ../kano_registration_gui/TermsAndConditions.py:25 +msgid "I agree to the terms and conditions" +msgstr "規約と条件に同意します" + +#: ../kano_world/functions.py:33 +msgid "Cannot log in, problem: {}" +msgstr "ログイン出来ません: {}" + +#: ../kano_world/functions.py:52 +msgid "Cannot register, problem: {}" +msgstr "登録出来ません: {}" + +#: ../kano_world/functions.py:77 +msgid "We cannot reach the Kano server. Try again in a few minutes. Error = {}" +msgstr "Kanoサーバーに接続できません。数分後に再度やってみてください。 {}" + +#: ../kano_world/functions.py:108 +msgid "Not registered!" +msgstr "登録されていません!" + +#: ../kano_world/functions.py:110 +msgid "No token!" +msgstr "トークンがありません!" + +#: ../kano_world/functions.py:122 ../kano_world/functions.py:149 +#: ../kano_world/functions.py:156 ../kano_world/functions.py:163 +#: ../kano_world/functions.py:233 ../kano_world/share.py:57 +#: ../kano_world/share.py:66 ../kano_world/share.py:75 +#: ../kano_world/share.py:84 ../kano_world/share.py:94 +#: ../kano_world/share.py:103 ../kano_world/share.py:111 +#: ../kano_world/share.py:119 +msgid "You are not logged in!" +msgstr "ログインしていません!" + +#: ../kano_world/share_helpers.py:33 +msgid "Cannot login" +msgstr "ログインできません" + +#~ msgid "QUESTS" +#~ msgstr "クエスト"