Merge pull request #2203 from zhaoweny/i18n
I18n: more zh_CN translations and a better locale/README.md
This commit is contained in:
@@ -41,6 +41,7 @@ cipher = None
|
||||
clientHash = None
|
||||
experimentalFeatures = None
|
||||
version = None
|
||||
language = None
|
||||
|
||||
ESI_CACHE = 'esi_cache'
|
||||
|
||||
|
||||
@@ -14,8 +14,8 @@ from service.fit import Fit
|
||||
from utils.cjk import isStringCjk
|
||||
from .events import FitSelected, SearchSelected, ImportSelected, Stage1Selected, Stage2Selected, Stage3Selected
|
||||
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
_ = wx.GetTranslation
|
||||
|
||||
|
||||
class NavigationPanel(SFItem.SFBrowserItem):
|
||||
@@ -45,20 +45,20 @@ class NavigationPanel(SFItem.SFBrowserItem):
|
||||
self.recentBmp = self.AdjustChannels(self.recentBmpH)
|
||||
self.newBmp = self.AdjustChannels(self.newBmpH)
|
||||
|
||||
self.toolbar.AddButton(self.resetBmp, "Ship groups", clickCallback=self.OnHistoryReset,
|
||||
self.toolbar.AddButton(self.resetBmp, _("Ship groups"), clickCallback=self.OnHistoryReset,
|
||||
hoverBitmap=self.resetBmpH)
|
||||
self.toolbar.AddButton(self.rewBmp, "Back", clickCallback=self.OnHistoryBack, hoverBitmap=self.rewBmpH)
|
||||
self.btnNew = self.toolbar.AddButton(self.newBmp, "New fitting", clickCallback=self.OnNewFitting,
|
||||
self.toolbar.AddButton(self.rewBmp, _("Back"), clickCallback=self.OnHistoryBack, hoverBitmap=self.rewBmpH)
|
||||
self.btnNew = self.toolbar.AddButton(self.newBmp, _("New fitting"), clickCallback=self.OnNewFitting,
|
||||
hoverBitmap=self.newBmpH, show=False)
|
||||
self.btnSwitch = self.toolbar.AddButton(self.switchBmpD, "Hide empty ship groups",
|
||||
self.btnSwitch = self.toolbar.AddButton(self.switchBmpD, _("Hide empty ship groups"),
|
||||
clickCallback=self.ToggleEmptyGroupsView, hoverBitmap=self.switchBmpH,
|
||||
show=False)
|
||||
self.btnRecent = self.toolbar.AddButton(self.recentBmpD, "Recent Fits",
|
||||
self.btnRecent = self.toolbar.AddButton(self.recentBmpD, _("Recent Fits"),
|
||||
clickCallback=self.ToggleRecentShips, hoverBitmap=self.recentBmpH,
|
||||
show=True)
|
||||
|
||||
modifier = "CTRL" if 'wxMac' not in wx.PlatformInfo else "CMD"
|
||||
self.toolbar.AddButton(self.searchBmp, "Search fittings ({}+F)".format(modifier), clickCallback=self.ToggleSearchBox,
|
||||
self.toolbar.AddButton(self.searchBmp, _("Search fittings") + " ({}+F)".format(modifier), clickCallback=self.ToggleSearchBox,
|
||||
hoverBitmap=self.searchBmpH)
|
||||
|
||||
self.padding = 4
|
||||
@@ -70,7 +70,7 @@ class NavigationPanel(SFItem.SFBrowserItem):
|
||||
w, h = size
|
||||
self.BrowserSearchBox = wx.TextCtrl(self, wx.ID_ANY, "", wx.DefaultPosition,
|
||||
(-1, h - 2 if 'wxGTK' in wx.PlatformInfo else -1),
|
||||
(wx.BORDER_NONE if 'wxGTK' in wx.PlatformInfo else 0))
|
||||
(wx.BORDER_NONE if 'wxGTK' in wx.PlatformInfo else 0))
|
||||
self.BrowserSearchBox.Show(False)
|
||||
|
||||
# self.BrowserSearchBox.Bind(wx.EVT_TEXT_ENTER, self.OnBrowserSearchBoxEnter)
|
||||
@@ -144,11 +144,11 @@ class NavigationPanel(SFItem.SFBrowserItem):
|
||||
def ToggleEmptyGroupsView(self):
|
||||
if self.shipBrowser.filterShipsWithNoFits:
|
||||
self.shipBrowser.filterShipsWithNoFits = False
|
||||
self.btnSwitch.label = "Hide empty ship groups"
|
||||
self.btnSwitch.label = _("Hide empty ship groups")
|
||||
self.btnSwitch.normalBmp = self.switchBmpD
|
||||
else:
|
||||
self.shipBrowser.filterShipsWithNoFits = True
|
||||
self.btnSwitch.label = "Show empty ship groups"
|
||||
self.btnSwitch.label = _("Show empty ship groups")
|
||||
self.btnSwitch.normalBmp = self.switchBmp
|
||||
|
||||
stage = self.shipBrowser.GetActiveStage()
|
||||
|
||||
@@ -58,7 +58,7 @@ class ResistancesViewFull(StatsView):
|
||||
# Custom header EHP
|
||||
headerContentSizer = self.headerPanel.Parent.GetHeaderContentSizer()
|
||||
|
||||
self.stEff = wx.StaticText(headerPanel, wx.ID_ANY, "( Effective HP: ")
|
||||
self.stEff = wx.StaticText(headerPanel, wx.ID_ANY, "(" + _("Effective HP") + ": ")
|
||||
headerContentSizer.Add(self.stEff)
|
||||
headerPanel.GetParent().AddToggleItem(self.stEff)
|
||||
|
||||
@@ -66,7 +66,7 @@ class ResistancesViewFull(StatsView):
|
||||
headerContentSizer.Add(self.labelEhp, 0)
|
||||
headerPanel.GetParent().AddToggleItem(self.labelEhp)
|
||||
|
||||
stCls = wx.StaticText(headerPanel, wx.ID_ANY, " )")
|
||||
stCls = wx.StaticText(headerPanel, wx.ID_ANY, ")")
|
||||
|
||||
headerPanel.GetParent().AddToggleItem(stCls)
|
||||
headerContentSizer.Add(stCls)
|
||||
|
||||
@@ -340,7 +340,7 @@ class SkillTreeView(wx.Panel):
|
||||
self.skillBookDirtyImageId = self.imageList.Add(wx.Icon(BitmapLoader.getBitmap("skill_small_red", "gui")))
|
||||
|
||||
tree.AppendColumn(_("Skill"))
|
||||
tree.AppendColumn(_("Level"), align=wx.ALIGN_CENTER)
|
||||
tree.AppendColumn(_("Level"))
|
||||
# tree.SetMainColumn(0)
|
||||
|
||||
self.root = tree.GetRootItem()
|
||||
|
||||
@@ -24,7 +24,7 @@ from gui.bitmap_loader import BitmapLoader
|
||||
from gui.utils import color as color_utils, draw, fonts
|
||||
from service.fit import Fit
|
||||
|
||||
|
||||
_ = wx.GetTranslation
|
||||
_PageChanging, EVT_NOTEBOOK_PAGE_CHANGING = wx.lib.newevent.NewEvent()
|
||||
_PageChanged, EVT_NOTEBOOK_PAGE_CHANGED = wx.lib.newevent.NewEvent()
|
||||
_PageAdding, EVT_NOTEBOOK_PAGE_ADDING = wx.lib.newevent.NewEvent()
|
||||
@@ -215,7 +215,8 @@ class ChromeNotebook(wx.Panel):
|
||||
|
||||
wx.PostEvent(self, PageChanged(current_page, new_page))
|
||||
|
||||
def AddPage(self, win=None, title="Empty Tab", image: wx.Image=None, closeable=True):
|
||||
def AddPage(self, win=None, title=None, image: wx.Image=None, closeable=True):
|
||||
title = title or "Empty Tab"
|
||||
if self._active_page:
|
||||
self._active_page.Hide()
|
||||
|
||||
|
||||
@@ -37,7 +37,8 @@ class MultiSwitch(ChromeNotebook):
|
||||
if h:
|
||||
h(type, info)
|
||||
|
||||
def AddPage(self, tabWnd=None, tabTitle=_("Empty Tab"), tabImage=None):
|
||||
def AddPage(self, tabWnd=None, tabTitle=None, tabImage=None):
|
||||
tabTitle = tabTitle or _("Empty Tab")
|
||||
if tabWnd is None:
|
||||
tabWnd = gui.builtinViews.emptyView.BlankPage(self)
|
||||
tabWnd.handleDrag = lambda type, info: self.handleDrag(type, info)
|
||||
|
||||
@@ -19,7 +19,9 @@ from service.market import Market
|
||||
|
||||
pyfalog = Logger(__name__)
|
||||
|
||||
_ = wx.GetTranslation
|
||||
_t = wx.GetTranslation
|
||||
|
||||
|
||||
class AttributeEditor(AuxiliaryFrame):
|
||||
|
||||
def __init__(self, parent):
|
||||
@@ -142,8 +144,8 @@ class AttributeEditor(AuxiliaryFrame):
|
||||
def OnClear(self, event):
|
||||
with wx.MessageDialog(
|
||||
self,
|
||||
_("Are you sure you want to delete all overrides?"),
|
||||
_("Confirm Delete"),
|
||||
_t("Are you sure you want to delete all overrides?"),
|
||||
_t("Confirm Delete"),
|
||||
wx.YES | wx.NO | wx.ICON_EXCLAMATION
|
||||
) as dlg:
|
||||
if dlg.ShowModal() == wx.ID_YES:
|
||||
|
||||
@@ -3,6 +3,8 @@ import gui.mainFrame
|
||||
import webbrowser
|
||||
import gui.globalEvents as GE
|
||||
|
||||
_t = wx.GetTranslation
|
||||
|
||||
|
||||
class SsoLogin(wx.Dialog):
|
||||
|
||||
@@ -10,12 +12,12 @@ class SsoLogin(wx.Dialog):
|
||||
mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
|
||||
super().__init__(
|
||||
mainFrame, id=wx.ID_ANY, title=_("SSO Login"), style=wx.DEFAULT_DIALOG_STYLE,
|
||||
size=wx.Size(450, 240) if "wxGTK" in wx.PlatformInfo else wx.Size(400, 240))
|
||||
mainFrame, id=wx.ID_ANY, title=_t("SSO Login"), style=wx.DEFAULT_DIALOG_STYLE,
|
||||
size=wx.Size(450, 240) if "wxGTK" in wx.PlatformInfo else wx.Size(400, 240))
|
||||
|
||||
bSizer1 = wx.BoxSizer(wx.VERTICAL)
|
||||
|
||||
text = wx.StaticText(self, wx.ID_ANY, _("Copy and paste the block of text provided by pyfa.io"))
|
||||
text = wx.StaticText(self, wx.ID_ANY, _t("Copy and paste the block of text provided by pyfa.io"))
|
||||
bSizer1.Add(text, 0, wx.ALL | wx.EXPAND, 10)
|
||||
|
||||
self.ssoInfoCtrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, (-1, -1), style=wx.TE_MULTILINE)
|
||||
@@ -44,7 +46,7 @@ class SsoLoginServer(wx.Dialog):
|
||||
|
||||
def __init__(self, port):
|
||||
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
|
||||
super().__init__(self.mainFrame, id=wx.ID_ANY, title=_("SSO Login"), size=(-1, -1), style=wx.DEFAULT_DIALOG_STYLE)
|
||||
super().__init__(self.mainFrame, id=wx.ID_ANY, title=_t("SSO Login"), size=(-1, -1), style=wx.DEFAULT_DIALOG_STYLE)
|
||||
|
||||
from service.esi import Esi
|
||||
|
||||
@@ -57,7 +59,7 @@ class SsoLoginServer(wx.Dialog):
|
||||
self.mainFrame.Bind(GE.EVT_SSO_LOGIN, self.OnLogin)
|
||||
self.Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroy)
|
||||
|
||||
text = wx.StaticText(self, wx.ID_ANY, _("Waiting for character login through EVE Single Sign-On."))
|
||||
text = wx.StaticText(self, wx.ID_ANY, _t("Waiting for character login through EVE Single Sign-On."))
|
||||
bSizer1.Add(text, 0, wx.ALL | wx.EXPAND, 10)
|
||||
|
||||
bSizer3 = wx.BoxSizer(wx.VERTICAL)
|
||||
|
||||
@@ -1,12 +1,81 @@
|
||||
On my Windows wsl2 (more generic explaination is needed):
|
||||
# Pyfa Internationalization(i18n) and Localization(l10n)
|
||||
|
||||
`find ./gui -iname "*.py" | xargs pygettext3 -d lang -o locale/lang.pot`
|
||||
Below is a summary of [GNU gettext](https://www.gnu.org/software/gettext/) manual, adapted for Pyfa i18n workflow.
|
||||
|
||||
This will generate the pot that should be carried over to the various language directories and renamed .po
|
||||
[Poedit](https://poedit.net/) offers a nice GUI for same GNU gettext translation workflow.
|
||||
|
||||
`msgfmt -o lang.mo lang`
|
||||
## i18n with command line
|
||||
|
||||
Run in each language directory, will compile the .po files to .mo files
|
||||
Windows users can get these tools via Git for windows, Msys2 or Cygwin; or just use WSL / WSL2.
|
||||
For Linux and macOS users these tools might be avaliable out-of-box.
|
||||
|
||||
## Issues
|
||||
`zh_CH` doesn't seem to work. AddCatalog is not functioning. See https://discuss.wxpython.org/t/localization-not-working-with-zh-ch-addcatalog-returns-false/34628
|
||||
### To generate new template for translation:
|
||||
|
||||
```console
|
||||
$ find */ *.py -name "*.py" | xgettext -o locale/lang.pot -d lang -k_t -k_r -
|
||||
```
|
||||
|
||||
explaination:
|
||||
|
||||
* `find */ *.py -name "*.py"`: collect all `.py` file path in root dir and all sub-folder, write it to stdout
|
||||
* `xgettext`: a utility looking for keyword and put string literals in a specific format for human translation
|
||||
* `-o locale/lang.pot`: let `xgettext` write to `locale/lang.pot`
|
||||
* `-d lang`: default language domain is `lang`
|
||||
* `-k_t -k_r`: besides default keyword (including `_`, see `info xgettext` for detail), also look for `_t` and `_r`
|
||||
* `-`: let `xgettext` to read from stdin, which is connected to `find` stdout
|
||||
|
||||
this `locale/lang.pot` is called PO template, which is throwed away once actual `ll_CC/LC_MESSAGES/lang.po` is ready for use.
|
||||
|
||||
### To initalize PO file for new language
|
||||
|
||||
```console
|
||||
$ msginit -i locale/lang.pot -l ll_CC -o locale/ll_CC/LC_MESSAGES/lang.po
|
||||
```
|
||||
|
||||
explaination:
|
||||
|
||||
* `-i locale/lang.pot`: input file location
|
||||
* `-l ll_CC`: target locale. `ll` should be a language code, and `CC` should be a country code
|
||||
* `-o locale/ll_CC/LC_MESSAGES/lang.po`: output file
|
||||
* `ll_CC`: same as above
|
||||
* `LC_MESSAGES`: GNU gettext conventional path to search for localized messages
|
||||
* `lang.po`: language domain and file format
|
||||
|
||||
this `locale/ll_CC/LC_MESSAGES/lang.po` should be checked into VCS, later it will be converted into mechine readable format (MO).
|
||||
|
||||
### To update PO file for existing translation
|
||||
|
||||
```console
|
||||
$ msgmerge locale/ll_CC/LC_MESSAGES/lang.po locale/lang.pot
|
||||
```
|
||||
|
||||
### To do actual translation
|
||||
|
||||
just edit the `lang.po` file :)
|
||||
|
||||
### To generate mechine readable MO file
|
||||
|
||||
For a single locale:
|
||||
|
||||
```console
|
||||
$ msgfmt locale/ll_CC/LC_MESSAGES/lang.po -o locale/ll_CC/LC_MESSAGES/lang.mo
|
||||
```
|
||||
|
||||
For all avaliable locale:
|
||||
```bash
|
||||
for $f in locale/*/; do
|
||||
msgfmt $f/LC_MESSAGES/lang.po -o $f/LC_MESSAGES/lang.mo
|
||||
done
|
||||
```
|
||||
|
||||
## i18n with Poedit
|
||||
|
||||
### To update PO file for existing translation
|
||||
|
||||
1. open a existing `locale/ll_CC/LC_MESSAGES/lang.po`
|
||||
2. *Catalog* -> *Update form POT file*
|
||||
3. select pre-prepared `lang.pot` file
|
||||
|
||||
### To translate and generate MO file
|
||||
|
||||
edit the translation and hit Save :)
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user