1
0
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:
Jonathan Harris 2015-09-01 13:41:35 +01:00
parent 0505db63c9
commit b2a0545771
7 changed files with 388 additions and 60 deletions

View File

@ -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
View 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!";

View File

@ -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
View 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()

View File

@ -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():

View File

@ -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

View File

@ -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: