I was freezing a gettext
localized (English and French, but probably more in the future) Python script with pyinstaller --onefile palc.py
and it compiles perfectly, but when I try to run it it attempts to use the locales stored in the locales
directory (meaning it can't find them if I don't distribute the package with the locales
directory). As you can imagine, this is a major drawback and pretty much ruins the point of PyInstaller — in order to distribute it, I have to give a directory along with the package in order for it to work — though, as I'm going to show you, it doesn't work even with that.
Is it possible (preferably not too difficult or something that would require heavy rewriting) to make PyInstaller compile the Python script WITH the gettext
locales?
palc.spec
, here is the new version:# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis(['palc.py'],
pathex=['~/python-text-calculator'],
binaries=[],
datas=[('~/python-text-calculator/locales/*', 'locales')],
hiddenimports=[],
hookspath=[],
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
[],
exclude_binaries=True,
name='palc',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=True )
coll = COLLECT(exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='palc')
And here is the output of the compiled package:
>>> ./palc
--------------------------------------------------------------------------
Language Selection
--------------------------------------------------------------------------
1 - English // Anglais
2 - Francais // French
Type: 1
Traceback (most recent call last):
File "/Users/computer/python-text-calculator/palc.py", line 30, in <module>
l_translations = gettext.translation('base', localedir='locales', languages=["en"])
File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/gettext.py", line 514, in translation
raise OSError(ENOENT, 'No translation file found for domain', domain)
FileNotFoundError: [Errno 2] No translation file found for domain: 'base'
[19393] Failed to execute script palc
This is the exact same output as it was without editing the palc.spec
. Plus, it made the compiled package a directory (I ran ./palc
inside the palc
directory in dist), so I would still have to distribute a directory. What I need is a SINGLE FILE like the ones found here.
Can anyone help? Thanks! :D
First, once the spec file has been generated, provide your spec file to pysintaller instead of a Python file: run pyinstaller palc.spec
instead of pyinstaller palc.py
. Otherwise, pyinstaller will reset the spec file each time.
Then, in order to generate a correct spec file for a onefile application, use pyi-makespec --onefile palc.py
. It generates a spec file with no COLLECT
step, and a different EXE
step.
Then you can use a custom python function in your spec file to build datas
for your locales (remember that a spec file is just a Python file with a custom file extension):
def get_locales_data():
locales_data = []
for locale in os.listdir(os.path.join('./locales')):
locales_data.append((
os.path.join('./locales', locale, 'LC_MESSAGES/*.mo'),
os.path.join('locales', locale, 'LC_MESSAGES')
))
return locales_data
Then use the return value of this function as the value of the datas parameter in the Analysis
step:
a = Analysis(['palc.py'],
...
datas=get_locales_data(),
...)
Then you will have to adapt your code to look for the locales files at the correct place (according to the runtime envrionment: packaged or not), but I have no more time to develop this part of the answer so here is a thread discussing this. ;)
For convenience, below is an example of correct specfile generated with pyi-makespec and altered to include locales:
# -*- mode: python ; coding: utf-8 -*-
import os
block_cipher = None
def get_locales_data():
locales_data = []
for locale in os.listdir(os.path.join('./locales')):
locales_data.append((
os.path.join('./locales', locale, 'LC_MESSAGES/*.mo'),
os.path.join('locales', locale, 'LC_MESSAGES')
))
return locales_data
a = Analysis(['palc.py'],
pathex=['.'],
binaries=[],
datas=get_locales_data(),
hiddenimports=[],
hookspath=[],
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name='palc',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=True )