mirror of
https://github.com/EDCD/EDMarketConnector.git
synced 2025-04-13 07:47:14 +03:00
Add support for localization.
This commit is contained in:
parent
0505db63c9
commit
b2a0545771
@ -1,7 +1,6 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import sys
|
||||
from sys import platform
|
||||
import json
|
||||
from os import mkdir
|
||||
@ -17,6 +16,7 @@ import tkFont
|
||||
if __debug__:
|
||||
from traceback import print_exc
|
||||
|
||||
import l10n
|
||||
import companion
|
||||
import bpc
|
||||
import td
|
||||
@ -27,8 +27,9 @@ import flightlog
|
||||
import prefs
|
||||
from config import appname, applongname, config
|
||||
|
||||
l10n.Translations().install()
|
||||
|
||||
shipyard_retry = 5 # retry pause for shipyard data [s]
|
||||
SHIPYARD_RETRY = 5 # retry pause for shipyard data [s]
|
||||
|
||||
|
||||
class AppWindow:
|
||||
@ -67,14 +68,14 @@ class AppWindow:
|
||||
frame.columnconfigure(1, weight=1)
|
||||
frame.rowconfigure(4, weight=1)
|
||||
|
||||
ttk.Label(frame, text="Cmdr:").grid(row=0, column=0, sticky=tk.W)
|
||||
ttk.Label(frame, text="System:").grid(row=1, column=0, sticky=tk.W)
|
||||
ttk.Label(frame, text="Station:").grid(row=2, column=0, sticky=tk.W)
|
||||
ttk.Label(frame, text=_('Cmdr:')).grid(row=0, column=0, sticky=tk.W) # Main window
|
||||
ttk.Label(frame, text=_('System:')).grid(row=1, column=0, sticky=tk.W) # Main window
|
||||
ttk.Label(frame, text=_('Station:')).grid(row=2, column=0, sticky=tk.W) # Main window
|
||||
|
||||
self.cmdr = ttk.Label(frame, width=-20)
|
||||
self.system = ttk.Label(frame, width=-20)
|
||||
self.station = ttk.Label(frame, width=-20)
|
||||
self.button = ttk.Button(frame, text='Update', command=self.getandsend, default=tk.ACTIVE, state=tk.DISABLED)
|
||||
self.button = ttk.Button(frame, text=_('Update'), command=self.getandsend, default=tk.ACTIVE, state=tk.DISABLED) # Update button in main window
|
||||
self.status = ttk.Label(frame, width=-25)
|
||||
self.w.bind('<Return>', self.getandsend)
|
||||
self.w.bind('<KP_Enter>', self.getandsend)
|
||||
@ -93,12 +94,13 @@ class AppWindow:
|
||||
from Foundation import NSBundle
|
||||
# https://www.tcl.tk/man/tcl/TkCmd/menu.htm
|
||||
apple_menu = tk.Menu(menubar, name='apple')
|
||||
apple_menu.add_command(label="About %s" % applongname, command=lambda:self.w.call('tk::mac::standardAboutPanel'))
|
||||
apple_menu.add_command(label="Check for Update", command=lambda:self.updater.checkForUpdates())
|
||||
apple_menu.add_command(label=_("About {APP}").format(APP=applongname), command=lambda:self.w.call('tk::mac::standardAboutPanel')) # App menu entry on OSX
|
||||
apple_menu.add_command(label=_("Check for Updates..."), command=lambda:self.updater.checkForUpdates())
|
||||
menubar.add_cascade(menu=apple_menu)
|
||||
window_menu = tk.Menu(menubar, name='window')
|
||||
menubar.add_cascade(menu=window_menu)
|
||||
# https://www.tcl.tk/man/tcl/TkCmd/tk_mac.htm
|
||||
self.w.call('set', 'tk::mac::useCompatibilityMetrics', '0')
|
||||
self.w.createcommand('tkAboutDialog', lambda:self.w.call('tk::mac::standardAboutPanel'))
|
||||
self.w.createcommand("::tk::mac::Quit", self.onexit)
|
||||
self.w.createcommand("::tk::mac::ShowPreferences", lambda:prefs.PreferencesDialog(self.w, self.login))
|
||||
@ -106,11 +108,11 @@ class AppWindow:
|
||||
self.w.protocol("WM_DELETE_WINDOW", self.w.withdraw) # close button shouldn't quit app
|
||||
else:
|
||||
file_menu = tk.Menu(menubar, tearoff=tk.FALSE)
|
||||
file_menu.add_command(label="Check for Update", command=lambda:self.updater.checkForUpdates())
|
||||
file_menu.add_command(label="Settings", command=lambda:prefs.PreferencesDialog(self.w, self.login))
|
||||
file_menu.add_command(label=_("Check for Updates..."), command=lambda:self.updater.checkForUpdates())
|
||||
file_menu.add_command(label=_("Settings"), command=lambda:prefs.PreferencesDialog(self.w, self.login)) # Menu item
|
||||
file_menu.add_separator()
|
||||
file_menu.add_command(label="Exit", command=self.onexit)
|
||||
menubar.add_cascade(label="File", menu=file_menu)
|
||||
file_menu.add_command(label=_("Exit"), command=self.onexit) # Menu item
|
||||
menubar.add_cascade(label=_("File"), menu=file_menu) # Top-level menu on Windows
|
||||
self.w.protocol("WM_DELETE_WINDOW", self.onexit)
|
||||
if platform == 'linux2':
|
||||
# Fix up menu to use same styling as everything else
|
||||
@ -148,7 +150,7 @@ class AppWindow:
|
||||
|
||||
# call after credentials have changed
|
||||
def login(self):
|
||||
self.status['text'] = 'Logging in...'
|
||||
self.status['text'] = _('Logging in...')
|
||||
self.button['state'] = tk.DISABLED
|
||||
self.w.update_idletasks()
|
||||
try:
|
||||
@ -188,7 +190,7 @@ class AppWindow:
|
||||
if not retrying:
|
||||
if time() < self.holdofftime: return # Was invoked by Return key while in cooldown
|
||||
self.cmdr['text'] = self.system['text'] = self.station['text'] = ''
|
||||
self.status['text'] = 'Fetching station data...'
|
||||
self.status['text'] = _('Fetching station data...')
|
||||
self.button['state'] = tk.DISABLED
|
||||
self.w.update_idletasks()
|
||||
|
||||
@ -206,15 +208,15 @@ class AppWindow:
|
||||
|
||||
# Validation
|
||||
if not data.get('commander') or not data['commander'].get('name','').strip():
|
||||
self.status['text'] = "Who are you?!" # Shouldn't happen
|
||||
self.status['text'] = _("Who are you?!") # Shouldn't happen
|
||||
elif not data.get('lastSystem') or not data['lastSystem'].get('name','').strip() or not data.get('lastStarport') or not data['lastStarport'].get('name','').strip():
|
||||
self.status['text'] = "Where are you?!" # Shouldn't happen
|
||||
self.status['text'] = _("Where are you?!") # Shouldn't happen
|
||||
elif not data.get('ship') or not data['ship'].get('modules') or not data['ship'].get('name','').strip():
|
||||
self.status['text'] = "What are you flying?!" # Shouldn't happen
|
||||
self.status['text'] = _("What are you flying?!") # Shouldn't happen
|
||||
|
||||
elif (config.getint('output') & config.OUT_EDDN) and data['commander'].get('docked') and not data['lastStarport'].get('ships') and not retrying:
|
||||
# API is flakey about shipyard info - retry if missing (<1s is usually sufficient - 5s for margin).
|
||||
self.w.after(shipyard_retry * 1000, lambda:self.getandsend(retrying=True))
|
||||
self.w.after(SHIPYARD_RETRY * 1000, lambda:self.getandsend(retrying=True))
|
||||
|
||||
# Stuff we can do while waiting for retry
|
||||
if config.getint('output') & config.OUT_LOG:
|
||||
@ -243,10 +245,10 @@ class AppWindow:
|
||||
|
||||
if not (config.getint('output') & (config.OUT_CSV|config.OUT_TD|config.OUT_BPC|config.OUT_EDDN)):
|
||||
# no further output requested
|
||||
self.status['text'] = strftime('Last updated at %H:%M:%S', localtime(querytime))
|
||||
self.status['text'] = strftime(_('Last updated at {HH}:{MM}:{SS}').format(HH='%H', MM='%M', SS='%S').encode('utf-8'), localtime(querytime)).decode('utf-8')
|
||||
|
||||
elif not data['commander'].get('docked'):
|
||||
self.status['text'] = "You're not docked at a station!"
|
||||
self.status['text'] = _("You're not docked at a station!")
|
||||
else:
|
||||
if data['lastStarport'].get('commodities'):
|
||||
# Fixup anomalies in the commodity data
|
||||
@ -261,16 +263,16 @@ class AppWindow:
|
||||
|
||||
if config.getint('output') & config.OUT_EDDN:
|
||||
if data['lastStarport'].get('commodities') or data['lastStarport'].get('modules') or data['lastStarport'].get('ships'):
|
||||
self.status['text'] = 'Sending data to EDDN...'
|
||||
self.status['text'] = _('Sending data to EDDN...')
|
||||
self.w.update_idletasks()
|
||||
eddn.export(data)
|
||||
self.status['text'] = strftime('Last updated at %H:%M:%S', localtime(querytime))
|
||||
self.status['text'] = strftime(_('Last updated at {HH}:{MM}:{SS}').format(HH='%H', MM='%M', SS='%S').encode('utf-8'), localtime(querytime)).decode('utf-8')
|
||||
else:
|
||||
self.status['text'] = "Station doesn't have anything!"
|
||||
self.status['text'] = _("Station doesn't have anything!")
|
||||
elif not data['lastStarport'].get('commodities'):
|
||||
self.status['text'] = "Station doesn't have a market!"
|
||||
self.status['text'] = _("Station doesn't have a market!")
|
||||
else:
|
||||
self.status['text'] = strftime('Last updated at %H:%M:%S', localtime(querytime))
|
||||
self.status['text'] = strftime(_('Last updated at {HH}:{MM}:{SS}').format(HH='%H', MM='%M', SS='%S').encode('utf-8'), localtime(querytime)).decode('utf-8')
|
||||
|
||||
except companion.VerificationRequired:
|
||||
return prefs.AuthenticationDialog(self.w, self.verify)
|
||||
@ -281,11 +283,11 @@ class AppWindow:
|
||||
|
||||
except requests.exceptions.ConnectionError as e:
|
||||
if __debug__: print_exc()
|
||||
self.status['text'] = "Error: Can't connect to EDDN"
|
||||
self.status['text'] = _("Error: Can't connect to EDDN")
|
||||
|
||||
except requests.exceptions.Timeout as e:
|
||||
if __debug__: print_exc()
|
||||
self.status['text'] = "Error: Connection to EDDN timed out"
|
||||
self.status['text'] = _("Error: Connection to EDDN timed out")
|
||||
|
||||
except Exception as e:
|
||||
if __debug__: print_exc()
|
||||
@ -295,10 +297,10 @@ class AppWindow:
|
||||
|
||||
def cooldown(self):
|
||||
if time() < self.holdofftime:
|
||||
self.button['text'] = 'cool down %ds' % (self.holdofftime - time())
|
||||
self.button['text'] = _('cooldown {SS}s').format(SS = int(self.holdofftime - time())) # Update button in main window
|
||||
self.w.after(1000, self.cooldown)
|
||||
else:
|
||||
self.button['text'] = 'Update'
|
||||
self.button['text'] = _('Update') # Update button in main window
|
||||
self.button['state'] = tk.NORMAL
|
||||
|
||||
def onexit(self, event=None):
|
||||
|
150
L10n/en.template
Normal file
150
L10n/en.template
Normal file
@ -0,0 +1,150 @@
|
||||
/* Use same text as E:D Launcher's verification dialog. [prefs.py:208] */
|
||||
"A verification code has now been sent to the{CR}email address associated with your Elite account." = "A verification code has now been sent to the{CR}email address associated with your Elite account.";
|
||||
|
||||
/* App menu entry on OSX. [EDMarketConnector.py:97] */
|
||||
"About {APP}" = "About {APP}";
|
||||
|
||||
/* Folder selection button on Windows. [prefs.py:101] */
|
||||
"Browse..." = "Browse...";
|
||||
|
||||
/* Folder selection button on OSX. [prefs.py:100] */
|
||||
"Change..." = "Change...";
|
||||
|
||||
/* [EDMarketConnector.py:98] */
|
||||
"Check for Updates..." = "Check for Updates...";
|
||||
|
||||
/* Privacy setting. [prefs.py:114] */
|
||||
"Cmdr name" = "Cmdr name";
|
||||
|
||||
/* Main window. [EDMarketConnector.py:71] */
|
||||
"Cmdr:" = "Cmdr:";
|
||||
|
||||
/* Update button in main window. [EDMarketConnector.py:300] */
|
||||
"cooldown {SS}s" = "cooldown {SS}s";
|
||||
|
||||
/* Section heading in settings. [prefs.py:58] */
|
||||
"Credentials" = "Credentials";
|
||||
|
||||
/* [EDMarketConnector.py:286] */
|
||||
"Error: Can't connect to EDDN" = "Error: Can't connect to EDDN";
|
||||
|
||||
/* [EDMarketConnector.py:290] */
|
||||
"Error: Connection to EDDN timed out" = "Error: Connection to EDDN timed out";
|
||||
|
||||
/* [companion.py:107] */
|
||||
"Error: Invalid Credentials" = "Error: Invalid Credentials";
|
||||
|
||||
/* [companion.py:103] */
|
||||
"Error: Server is down" = "Error: Server is down";
|
||||
|
||||
/* Menu item. [EDMarketConnector.py:114] */
|
||||
"Exit" = "Exit";
|
||||
|
||||
/* [EDMarketConnector.py:193] */
|
||||
"Fetching station data..." = "Fetching station data...";
|
||||
|
||||
/* Top-level menu on Windows. [EDMarketConnector.py:115] */
|
||||
"File" = "File";
|
||||
|
||||
/* Output folder prompt on Windows. [prefs.py:99] */
|
||||
"File location:" = "File location:";
|
||||
|
||||
/* [prefs.py:96] */
|
||||
"Flight log" = "Flight log";
|
||||
|
||||
/* Shouldn't happen. [companion.py:176] */
|
||||
"General error" = "General error";
|
||||
|
||||
/* [prefs.py:113] */
|
||||
"How do you want to be identified in the saved data" = "How do you want to be identified in the saved data";
|
||||
|
||||
/* [EDMarketConnector.py:248] */
|
||||
"Last updated at {HH}:{MM}:{SS}" = "Last updated at {HH}:{MM}:{SS}";
|
||||
|
||||
/* [EDMarketConnector.py:153] */
|
||||
"Logging in..." = "Logging in...";
|
||||
|
||||
/* [prefs.py:90] */
|
||||
"Market data in CSV format" = "Market data in CSV format";
|
||||
|
||||
/* [prefs.py:86] */
|
||||
"Market data in Slopey's BPC format" = "Market data in Slopey's BPC format";
|
||||
|
||||
/* [prefs.py:88] */
|
||||
"Market data in Trade Dangerous format" = "Market data in Trade Dangerous format";
|
||||
|
||||
/* [prefs.py:124] */
|
||||
"OK" = "OK";
|
||||
|
||||
/* Section heading in settings. [prefs.py:77] */
|
||||
"Output" = "Output";
|
||||
|
||||
/* Use same text as E:D Launcher's login dialog. [prefs.py:64] */
|
||||
"Password" = "Password";
|
||||
|
||||
/* [prefs.py:82] */
|
||||
"Please choose what data to save" = "Please choose what data to save";
|
||||
|
||||
/* Use same text as E:D Launcher's verification dialog. [prefs.py:211] */
|
||||
"Please enter the code into the box below." = "Please enter the code into the box below.";
|
||||
|
||||
/* Use same text as E:D Launcher's login dialog. [prefs.py:62] */
|
||||
"Please log in with your Elite: Dangerous account details" = "Please log in with your Elite: Dangerous account details";
|
||||
|
||||
/* [prefs.py:37] */
|
||||
"Preferences" = "Preferences";
|
||||
|
||||
/* Section heading in settings. [prefs.py:108] */
|
||||
"Privacy" = "Privacy";
|
||||
|
||||
/* Privacy setting. [prefs.py:115] */
|
||||
"Pseudo-anonymized ID" = "Pseudo-anonymized ID";
|
||||
|
||||
/* [prefs.py:84] */
|
||||
"Send station data to the Elite Dangerous Data Network" = "Send station data to the Elite Dangerous Data Network";
|
||||
|
||||
/* [EDMarketConnector.py:266] */
|
||||
"Sending data to EDDN..." = "Sending data to EDDN...";
|
||||
|
||||
/* Menu item. [EDMarketConnector.py:112] */
|
||||
"Settings" = "Settings";
|
||||
|
||||
/* [prefs.py:94] */
|
||||
"Ship loadout in Coriolis format" = "Ship loadout in Coriolis format";
|
||||
|
||||
/* [prefs.py:92] */
|
||||
"Ship loadout in E:D Shipyard format" = "Ship loadout in E:D Shipyard format";
|
||||
|
||||
/* [EDMarketConnector.py:273] */
|
||||
"Station doesn't have a market!" = "Station doesn't have a market!";
|
||||
|
||||
/* [EDMarketConnector.py:271] */
|
||||
"Station doesn't have anything!" = "Station doesn't have anything!";
|
||||
|
||||
/* Main window. [EDMarketConnector.py:73] */
|
||||
"Station:" = "Station:";
|
||||
|
||||
/* Main window. [EDMarketConnector.py:72] */
|
||||
"System:" = "System:";
|
||||
|
||||
/* Update button in main window. [EDMarketConnector.py:78] */
|
||||
"Update" = "Update";
|
||||
|
||||
/* Use same text as E:D Launcher's login dialog. [prefs.py:63] */
|
||||
"Username (Email)" = "Username (Email)";
|
||||
|
||||
/* Shouldn't happen. [EDMarketConnector.py:215] */
|
||||
"What are you flying?!" = "What are you flying?!";
|
||||
|
||||
/* Shouldn't happen. [EDMarketConnector.py:213] */
|
||||
"Where are you?!" = "Where are you?!";
|
||||
|
||||
/* Output folder prompt on OSX. [prefs.py:98] */
|
||||
"Where:" = "Where:";
|
||||
|
||||
/* Shouldn't happen. [EDMarketConnector.py:211] */
|
||||
"Who are you?!" = "Who are you?!";
|
||||
|
||||
/* [EDMarketConnector.py:251] */
|
||||
"You're not docked at a station!" = "You're not docked at a station!";
|
||||
|
@ -100,15 +100,14 @@ def listify(thing):
|
||||
|
||||
class ServerError(Exception):
|
||||
def __str__(self):
|
||||
return 'Error: Server is down'
|
||||
return _('Error: Server is down')
|
||||
|
||||
class CredentialsError(Exception):
|
||||
def __str__(self):
|
||||
return 'Error: Invalid Credentials'
|
||||
return _('Error: Invalid Credentials')
|
||||
|
||||
class VerificationRequired(Exception):
|
||||
def __str__(self):
|
||||
return 'Authentication required'
|
||||
pass
|
||||
|
||||
# Server companion.orerve.net uses a session cookie ("CompanionApp") to tie together login, verification
|
||||
# and query. So route all requests through a single Session object which holds this state.
|
||||
@ -174,7 +173,7 @@ class Session:
|
||||
|
||||
def query(self):
|
||||
if self.state == Session.STATE_NONE:
|
||||
raise Exception('General error') # Shouldn't happen
|
||||
raise Exception(_('General error')) # Shouldn't happen
|
||||
elif self.state == Session.STATE_INIT:
|
||||
self.login()
|
||||
elif self.state == Session.STATE_AUTH:
|
||||
|
152
l10n.py
Executable file
152
l10n.py
Executable file
@ -0,0 +1,152 @@
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# Localization with gettext is a pain on non-Unix systems. Use OSX-style strings files instead.
|
||||
#
|
||||
|
||||
import codecs
|
||||
import os
|
||||
from os.path import dirname, isfile, join, normpath
|
||||
import re
|
||||
import sys
|
||||
from sys import platform
|
||||
import __builtin__
|
||||
|
||||
|
||||
class Translations:
|
||||
|
||||
FALLBACK = 'en' # strings in this code are in English
|
||||
|
||||
def __init__(self):
|
||||
self.translations = {}
|
||||
|
||||
def install(self):
|
||||
path = join(self.respath(), 'L10n')
|
||||
available = self.available()
|
||||
available.add(Translations.FALLBACK)
|
||||
|
||||
for preferred in self.preferred():
|
||||
if preferred in available:
|
||||
lang = preferred
|
||||
break
|
||||
else:
|
||||
for preferred in self.preferred():
|
||||
preferred = preferred.split('-',1)[0] # just base language
|
||||
if preferred in available:
|
||||
lang = preferred
|
||||
break
|
||||
else:
|
||||
lang = Translations.FALLBACK
|
||||
|
||||
if lang not in self.available():
|
||||
__builtin__.__dict__['_'] = lambda x: x
|
||||
else:
|
||||
regexp = re.compile(r'\s*"([^"]+)"\s*=\s*"([^"]+)"\s*;\s*$')
|
||||
comment= re.compile(r'\s*/\*.*\*/\s*$')
|
||||
with self.file(lang) as h:
|
||||
for line in h:
|
||||
if line.strip():
|
||||
match = regexp.match(line)
|
||||
if match:
|
||||
self.translations[match.group(1)] = match.group(2)
|
||||
elif not comment.match(line):
|
||||
assert match, 'Bad translation: %s' % line
|
||||
__builtin__.__dict__['_'] = self.translate
|
||||
|
||||
if __debug__:
|
||||
def translate(self, x):
|
||||
if x in self.translations:
|
||||
return self.translations[x]
|
||||
else:
|
||||
print 'Missing translation: "%s"' % x
|
||||
else:
|
||||
def translate(self, x):
|
||||
return self.translations.get(x, x)
|
||||
|
||||
# Returns list of available language codes
|
||||
def available(self):
|
||||
path = self.respath()
|
||||
if getattr(sys, 'frozen', False) and platform=='darwin':
|
||||
available = set([x[:-len('.lproj')] for x in os.listdir(path) if x.endswith('.lproj') and isfile(join(x, 'Localizable.strings'))])
|
||||
else:
|
||||
available = set([x[:-len('.strings')] for x in os.listdir(path) if x.endswith('.strings')])
|
||||
return available
|
||||
|
||||
# Returns list of preferred language codes in lowercase RFC4646 format.
|
||||
# Typically "lang[-script][-region]" where lang is a 2 alpha ISO 639-1 or 3 alpha ISO 639-2 code,
|
||||
# script is a 4 alpha ISO 15924 code and region is a 2 alpha ISO 3166 code
|
||||
def preferred(self):
|
||||
|
||||
if platform=='darwin':
|
||||
from Foundation import NSLocale
|
||||
return [x.lower() for x in NSLocale.preferredLanguages()] or None
|
||||
|
||||
elif platform=='win32':
|
||||
|
||||
def wszarray_to_list(array):
|
||||
offset = 0
|
||||
while offset < len(array):
|
||||
sz = ctypes.wstring_at(ctypes.addressof(array) + offset*2)
|
||||
if sz:
|
||||
yield sz
|
||||
offset += len(sz)+1
|
||||
else:
|
||||
break
|
||||
|
||||
# https://msdn.microsoft.com/en-us/library/windows/desktop/dd318124%28v=vs.85%29.aspx
|
||||
import ctypes
|
||||
MUI_LANGUAGE_ID = 4
|
||||
MUI_LANGUAGE_NAME = 8
|
||||
GetUserPreferredUILanguages = ctypes.windll.kernel32.GetUserPreferredUILanguages
|
||||
|
||||
num = ctypes.c_ulong()
|
||||
size = ctypes.c_ulong(0)
|
||||
if (GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, ctypes.byref(num), None, ctypes.byref(size)) and size.value):
|
||||
buf = ctypes.create_unicode_buffer(size.value)
|
||||
if GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, ctypes.byref(num), ctypes.byref(buf), ctypes.byref(size)):
|
||||
return [x.lower() for x in wszarray_to_list(buf)]
|
||||
return None
|
||||
|
||||
else: # POSIX
|
||||
import locale
|
||||
lang = locale.getdefaultlocale()[0]
|
||||
return lang and [lang.replace('_','-').lower()]
|
||||
|
||||
def respath(self):
|
||||
if getattr(sys, 'frozen', False):
|
||||
if platform=='darwin':
|
||||
return normpath(join(dirname(sys.executable), os.pardir, 'Resources'))
|
||||
else:
|
||||
return dirname(sys.executable)
|
||||
elif __file__:
|
||||
return join(dirname(__file__), 'L10n')
|
||||
else:
|
||||
return 'L10n'
|
||||
|
||||
def file(self, lang):
|
||||
if getattr(sys, 'frozen', False) and platform=='darwin':
|
||||
return codecs.open(join(self.respath(), '%s.lproj' % lang, 'Localizable.strings'), 'r', 'utf-16')
|
||||
else:
|
||||
return codecs.open(join(self.respath(), '%s.strings' % lang), 'r', 'utf-8')
|
||||
|
||||
|
||||
# generate template strings file - like xgettext
|
||||
# parsing is limited - only single ' or " delimited strings, and only one string per line
|
||||
if __name__ == "__main__":
|
||||
import re
|
||||
regexp = re.compile(r'''_\([ur]?(['"])(((?<!\\)\\\1|.)+?)\1\)[^#]*(#.+)?''') # match a single line python literal
|
||||
seen = {}
|
||||
for f in sorted([x for x in os.listdir('.') if x.endswith('.py')]):
|
||||
with codecs.open(f, 'r', 'utf-8') as h:
|
||||
lineno = 0
|
||||
for line in h:
|
||||
lineno += 1
|
||||
match = regexp.search(line)
|
||||
if match and not match.group(2) in seen: # only record first instance of a string
|
||||
seen[match.group(2)] = (match.group(4) and (match.group(4)[1:].strip()) + '. ' or '') + '[%s:%d]' % (f,lineno)
|
||||
if seen:
|
||||
template = codecs.open('L10n/en.template', 'w', 'utf-8')
|
||||
for thing in sorted(seen, key=unicode.lower):
|
||||
if seen[thing]:
|
||||
template.write('/* %s */\n' % (seen[thing]))
|
||||
template.write('"%s" = "%s";\n\n' % (thing, thing))
|
||||
template.close()
|
55
prefs.py
55
prefs.py
@ -34,7 +34,8 @@ class PreferencesDialog(tk.Toplevel):
|
||||
|
||||
self.parent = parent
|
||||
self.callback = callback
|
||||
self.title(platform=='darwin' and 'Preferences' or 'Settings')
|
||||
self.title(platform=='darwin' and _('Preferences') or
|
||||
_('Settings'))
|
||||
|
||||
if parent.winfo_viewable():
|
||||
self.transient(parent)
|
||||
@ -54,13 +55,13 @@ class PreferencesDialog(tk.Toplevel):
|
||||
frame = ttk.Frame(self)
|
||||
frame.grid(sticky=tk.NSEW)
|
||||
|
||||
credframe = ttk.LabelFrame(frame, text='Credentials')
|
||||
credframe = ttk.LabelFrame(frame, text=_('Credentials')) # Section heading in settings
|
||||
credframe.grid(padx=10, pady=10, sticky=tk.NSEW)
|
||||
credframe.columnconfigure(1, weight=1)
|
||||
|
||||
ttk.Label(credframe, text="Please log in with your Elite: Dangerous account details").grid(row=0, columnspan=2, sticky=tk.W)
|
||||
ttk.Label(credframe, text="Username (Email)").grid(row=1, sticky=tk.W)
|
||||
ttk.Label(credframe, text="Password").grid(row=2, sticky=tk.W)
|
||||
ttk.Label(credframe, text=_('Please log in with your Elite: Dangerous account details')).grid(row=0, columnspan=2, sticky=tk.W) # Use same text as E:D Launcher's login dialog
|
||||
ttk.Label(credframe, text=_('Username (Email)')).grid(row=1, sticky=tk.W) # Use same text as E:D Launcher's login dialog
|
||||
ttk.Label(credframe, text=_('Password')).grid(row=2, sticky=tk.W) # Use same text as E:D Launcher's login dialog
|
||||
|
||||
self.username = ttk.Entry(credframe)
|
||||
self.username.insert(0, config.get('username') or '')
|
||||
@ -73,43 +74,45 @@ class PreferencesDialog(tk.Toplevel):
|
||||
for child in credframe.winfo_children():
|
||||
child.grid_configure(padx=5, pady=3)
|
||||
|
||||
outframe = ttk.LabelFrame(frame, text='Output')
|
||||
outframe = ttk.LabelFrame(frame, text=_('Output')) # Section heading in settings
|
||||
outframe.grid(padx=10, pady=10, sticky=tk.NSEW)
|
||||
outframe.columnconfigure(0, weight=1)
|
||||
|
||||
output = config.getint('output') or (config.OUT_EDDN | config.OUT_SHIP_EDS)
|
||||
ttk.Label(outframe, text="Please choose what data to save").grid(row=0, columnspan=2, padx=5, pady=3, sticky=tk.W)
|
||||
ttk.Label(outframe, text=_('Please choose what data to save')).grid(row=0, columnspan=2, padx=5, pady=3, sticky=tk.W)
|
||||
self.out_eddn= tk.IntVar(value = (output & config.OUT_EDDN) and 1 or 0)
|
||||
ttk.Checkbutton(outframe, text="Send station data to the Elite Dangerous Data Network", variable=self.out_eddn).grid(row=1, columnspan=2, padx=5, sticky=tk.W)
|
||||
ttk.Checkbutton(outframe, text=_('Send station data to the Elite Dangerous Data Network'), variable=self.out_eddn).grid(row=1, columnspan=2, padx=5, sticky=tk.W)
|
||||
self.out_bpc = tk.IntVar(value = (output & config.OUT_BPC ) and 1 or 0)
|
||||
ttk.Checkbutton(outframe, text="Market data in Slopey's BPC format", variable=self.out_bpc, command=self.outvarchanged).grid(row=2, columnspan=2, padx=5, sticky=tk.W)
|
||||
ttk.Checkbutton(outframe, text=_("Market data in Slopey's BPC format"), variable=self.out_bpc, command=self.outvarchanged).grid(row=2, columnspan=2, padx=5, sticky=tk.W)
|
||||
self.out_td = tk.IntVar(value = (output & config.OUT_TD ) and 1 or 0)
|
||||
ttk.Checkbutton(outframe, text="Market data in Trade Dangerous format", variable=self.out_td, command=self.outvarchanged).grid(row=3, columnspan=2, padx=5, sticky=tk.W)
|
||||
ttk.Checkbutton(outframe, text=_('Market data in Trade Dangerous format'), variable=self.out_td, command=self.outvarchanged).grid(row=3, columnspan=2, padx=5, sticky=tk.W)
|
||||
self.out_csv = tk.IntVar(value = (output & config.OUT_CSV ) and 1 or 0)
|
||||
ttk.Checkbutton(outframe, text="Market data in CSV format", variable=self.out_csv, command=self.outvarchanged).grid(row=4, columnspan=2, padx=5, sticky=tk.W)
|
||||
ttk.Checkbutton(outframe, text=_('Market data in CSV format'), variable=self.out_csv, command=self.outvarchanged).grid(row=4, columnspan=2, padx=5, sticky=tk.W)
|
||||
self.out_ship_eds= tk.IntVar(value = (output & config.OUT_SHIP_EDS) and 1 or 0)
|
||||
ttk.Checkbutton(outframe, text="Ship loadout in E:D Shipyard format", variable=self.out_ship_eds, command=self.outvarchanged).grid(row=5, columnspan=2, padx=5, sticky=tk.W)
|
||||
ttk.Checkbutton(outframe, text=_('Ship loadout in E:D Shipyard format'), variable=self.out_ship_eds, command=self.outvarchanged).grid(row=5, columnspan=2, padx=5, sticky=tk.W)
|
||||
self.out_ship_coriolis= tk.IntVar(value = (output & config.OUT_SHIP_CORIOLIS) and 1 or 0)
|
||||
ttk.Checkbutton(outframe, text="Ship loadout in Coriolis format", variable=self.out_ship_coriolis, command=self.outvarchanged).grid(row=6, columnspan=2, padx=5, sticky=tk.W)
|
||||
ttk.Checkbutton(outframe, text=_('Ship loadout in Coriolis format'), variable=self.out_ship_coriolis, command=self.outvarchanged).grid(row=6, columnspan=2, padx=5, sticky=tk.W)
|
||||
self.out_log = tk.IntVar(value = (output & config.OUT_LOG ) and 1 or 0)
|
||||
ttk.Checkbutton(outframe, text="Flight log", variable=self.out_log, command=self.outvarchanged).grid(row=7, columnspan=2, padx=5, sticky=tk.W)
|
||||
ttk.Checkbutton(outframe, text=_('Flight log'), variable=self.out_log, command=self.outvarchanged).grid(row=7, columnspan=2, padx=5, sticky=tk.W)
|
||||
|
||||
ttk.Label(outframe, text=(platform=='darwin' and 'Where:' or 'File location:')).grid(row=8, padx=5, pady=(5,0), sticky=tk.NSEW)
|
||||
self.outbutton = ttk.Button(outframe, text=(platform=='darwin' and 'Change...' or 'Browse...'), command=self.outbrowse)
|
||||
ttk.Label(outframe, text=(platform=='darwin' and _('Where:') or # Output folder prompt on OSX
|
||||
_('File location:'))).grid(row=8, padx=5, pady=(5,0), sticky=tk.NSEW) # Output folder prompt on Windows
|
||||
self.outbutton = ttk.Button(outframe, text=(platform=='darwin' and _('Change...') or # Folder selection button on OSX
|
||||
_('Browse...')), command=self.outbrowse) # Folder selection button on Windows
|
||||
self.outbutton.grid(row=8, column=1, padx=5, pady=(5,0), sticky=tk.NSEW)
|
||||
self.outdir = ttk.Entry(outframe)
|
||||
self.outdir.insert(0, config.get('outdir'))
|
||||
self.outdir.grid(row=9, columnspan=2, padx=5, pady=5, sticky=tk.EW)
|
||||
self.outvarchanged()
|
||||
|
||||
privacyframe = ttk.LabelFrame(frame, text='Privacy')
|
||||
privacyframe = ttk.LabelFrame(frame, text=_('Privacy')) # Section heading in settings
|
||||
privacyframe.grid(padx=10, pady=10, sticky=tk.NSEW)
|
||||
privacyframe.columnconfigure(0, weight=1)
|
||||
|
||||
self.out_anon= tk.IntVar(value = config.getint('anonymous') and 1)
|
||||
ttk.Label(privacyframe, text="How do you want to be identified in the saved data").grid(row=0, columnspan=2, padx=5, pady=3, sticky=tk.W)
|
||||
ttk.Radiobutton(privacyframe, text="Cmdr name", variable=self.out_anon, value=0).grid(padx=5, sticky=tk.W)
|
||||
ttk.Radiobutton(privacyframe, text="Pseudo-anonymized ID", variable=self.out_anon, value=1).grid(padx=5, pady=3, sticky=tk.W)
|
||||
ttk.Label(privacyframe, text=_('How do you want to be identified in the saved data')).grid(row=0, columnspan=2, padx=5, pady=3, sticky=tk.W)
|
||||
ttk.Radiobutton(privacyframe, text=_('Cmdr name'), variable=self.out_anon, value=0).grid(padx=5, sticky=tk.W) # Privacy setting
|
||||
ttk.Radiobutton(privacyframe, text=_('Pseudo-anonymized ID'), variable=self.out_anon, value=1).grid(padx=5, pady=3, sticky=tk.W) # Privacy setting
|
||||
|
||||
if platform=='darwin':
|
||||
self.protocol("WM_DELETE_WINDOW", self.apply) # close button applies changes
|
||||
@ -118,7 +121,7 @@ class PreferencesDialog(tk.Toplevel):
|
||||
buttonframe.grid(padx=10, pady=10, sticky=tk.NSEW)
|
||||
buttonframe.columnconfigure(0, weight=1)
|
||||
ttk.Label(buttonframe).grid(row=0, column=0) # spacer
|
||||
ttk.Button(buttonframe, text='OK', command=self.apply).grid(row=0, column=1, sticky=tk.E)
|
||||
ttk.Button(buttonframe, text=_('OK'), command=self.apply).grid(row=0, column=1, sticky=tk.E)
|
||||
|
||||
# wait for window to appear on screen before calling grab_set
|
||||
self.wait_visibility()
|
||||
@ -202,14 +205,16 @@ class AuthenticationDialog(tk.Toplevel):
|
||||
frame.columnconfigure(0, weight=3)
|
||||
frame.columnconfigure(2, weight=1)
|
||||
|
||||
ttk.Label(frame, text='A verification code has now been sent to the\nemail address associated with your Elite account.\nPlease enter the code into the box below.', anchor=tk.W, justify=tk.LEFT).grid(columnspan=4, sticky=tk.NSEW)
|
||||
ttk.Label(frame, text=_('A verification code has now been sent to the{CR}email address associated with your Elite account.').format(CR='\n') + # Use same text as E:D Launcher's verification dialog
|
||||
|
||||
'\n' +
|
||||
_('Please enter the code into the box below.'), anchor=tk.W, justify=tk.LEFT).grid(columnspan=4, sticky=tk.NSEW) # Use same text as E:D Launcher's verification dialog
|
||||
ttk.Label(frame).grid(row=1, column=0) # spacer
|
||||
self.code = ttk.Entry(frame, width=8, validate='key', validatecommand=(self.register(self.validatecode),
|
||||
'%P'))
|
||||
self.code = ttk.Entry(frame, width=8, validate='key', validatecommand=(self.register(self.validatecode), '%P'))
|
||||
self.code.grid(row=1, column=1)
|
||||
self.code.focus_set()
|
||||
ttk.Label(frame).grid(row=1, column=2) # spacer
|
||||
self.button = ttk.Button(frame, text='OK', command=self.apply, state=tk.DISABLED)
|
||||
self.button = ttk.Button(frame, text=_('OK'), command=self.apply, state=tk.DISABLED)
|
||||
self.button.grid(row=1, column=3, sticky=tk.E)
|
||||
|
||||
for child in frame.winfo_children():
|
||||
|
13
setup.py
13
setup.py
@ -8,6 +8,7 @@ Usage:
|
||||
"""
|
||||
|
||||
from setuptools import setup
|
||||
import codecs
|
||||
import os
|
||||
from os.path import exists, isdir, join
|
||||
import platform
|
||||
@ -94,7 +95,8 @@ elif sys.platform=='win32':
|
||||
'WinSparkle.dll',
|
||||
'WinSparkle.pdb', # For debugging - don't include in package
|
||||
'%s.VisualElementsManifest.xml' % APPNAME,
|
||||
'%s.ico' % APPNAME ] ) ]
|
||||
'%s.ico' % APPNAME ] +
|
||||
[join('L10n',x) for x in os.listdir('L10n') if x.endswith('.strings')] ) ]
|
||||
|
||||
setup(
|
||||
name = APPLONGNAME,
|
||||
@ -117,6 +119,15 @@ setup(
|
||||
if sys.platform == 'darwin':
|
||||
if isdir('%s/%s.app' % (dist_dir, APPLONGNAME)): # from CFBundleName
|
||||
os.rename('%s/%s.app' % (dist_dir, APPLONGNAME), '%s/%s.app' % (dist_dir, APPNAME))
|
||||
|
||||
# Generate OSX-style localization files
|
||||
for x in os.listdir('L10n'):
|
||||
if x.endswith('.strings'):
|
||||
lang = x[:-len('.strings')]
|
||||
path = '%s/%s.app/Contents/Resources/%s.lproj' % (dist_dir, APPNAME, lang)
|
||||
os.mkdir(path)
|
||||
codecs.open('%s/Localizable.strings' % path, 'w', 'utf-16').write(codecs.open('L10n/%s' % x, 'r', 'utf-8').read())
|
||||
|
||||
if macdeveloperid:
|
||||
os.system('codesign --deep -v -s "Developer ID Application: %s" %s/%s.app' % (macdeveloperid, dist_dir, APPNAME))
|
||||
# Make zip for distribution, preserving signature
|
||||
|
@ -74,6 +74,15 @@ elif sys.platform=='win32':
|
||||
self.callback_fn = self.callback_t(shutdown_request)
|
||||
self.updater.win_sparkle_set_shutdown_request_callback(self.callback_fn)
|
||||
|
||||
# Translations require winsparkle 0.5
|
||||
try:
|
||||
import l10n
|
||||
langs = l10n.Translations().preferred()
|
||||
if langs:
|
||||
self.updater.win_sparkle_set_lang(langs[0])
|
||||
except:
|
||||
pass
|
||||
|
||||
self.updater.win_sparkle_init()
|
||||
|
||||
except:
|
||||
|
Loading…
x
Reference in New Issue
Block a user