在 Python 3.x 中使用 PyInstaller 编译 gettext 语言环境

Compiling gettext locales with PyInstaller in Python 3.x

我正在冻结 gettext 本地化(英语和法语,但将来可能更多)Python 脚本 pyinstaller --onefile palc.py 并且它编译完美,但是当我尝试 运行 它尝试使用存储在 locales 目录中的语言环境(这意味着如果我不使用 locales 目录分发包,它就找不到它们)。正如您可以想象的那样,这是一个主要缺点并且几乎破坏了 PyInstaller 的意义——为了分发它,我必须提供一个目录和包以使其工作——尽管如此,正如我将要告诉你,即使那样它也不起作用。

这里是主要问题:

是否有可能(最好不要太难或需要重写的东西)让 PyInstaller 编译 Python 脚本 gettext语言环境?

编辑:我尝试编辑我的 palc.spec,这是新版本:

# -*- 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')

这里是编译包的输出:

>>> ./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

这与 没有 编辑 palc.spec 时的输出完全相同。另外,它使编译后的包成为一个 目录(我 运行 ./palc 在 dist 的 palc 目录中),所以我仍然需要分发一个目录。我需要的是像 here.

中找到的那样的单个文件

有人可以帮忙吗?谢谢! :D

首先,生成规格文件后,将您的规格文件提供给 pysintaller 而不是 Python 文件:运行 pyinstaller palc.spec 而不是 pyinstaller palc.py。否则,pyinstaller 每次都会重置 spec 文件。

然后,为了为 onefile 应用程序生成正确的规范文件,请使用 pyi-makespec --onefile palc.py。它生成一个没有 COLLECT 步骤和不同的 EXE 步骤的规范文件。

然后您可以在规范文件中使用自定义 python 函数为您的语言环境构建 datas(请记住,规范文件只是一个带有自定义文件的 Python 文件扩展名):

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

然后使用这个函数的return值作为Analysis步骤中datas参数的值:

a = Analysis(['palc.py'],
             ...
             datas=get_locales_data(),
             ...)

然后您将不得不调整您的代码以在正确的位置查找语言环境文件(根据 运行时间环境:是否打包),但我没有更多时间开发这部分答案如此 here is a thread discussing this。 ;)


为方便起见,下面是使用 pyi-makespec 生成并更改为包含语言环境的正确规范文件的示例:

# -*- 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 )

事情是这样的:
你的新 .spec 文件是正确的,你在说明你想要在你的包中的文件以及你想把它们放在包中的什么地方。

正是您访问它们的方式导致了您的痛苦。
错误代码中的行 l_translations = gettext.translation('base', localedir='locales', languages=["en"]) 建议您列出文件所在的目录 locales 这实际上是有道理的,因为您说它们应该在那里......但是 PyInstaller 的工作方式是有点不同。由于您将其捆绑为一个文件,因此 bootloader.
实际上会在其他地方打开它 如何解决:
而不是指定目录 locales 将其更改为:

from os import path

bundle_dir = getattr(sys, '_MEIPASS', path.abspath(path.dirname(__file__))) # get the bundle dir if bundled or simply the __file__ dir if not bundled
locales_dir = path.abspath(path.join(bundle_dir, 'locales'))

现在 locales_dir 指向您捆绑的目录 PyInstaller

P.S.
如果这会导致错误(不应该),请将 Analasis 数据部分编辑为 datas=[('~/python-text-calculator/locales', 'locales')],