【问题标题】:python gettext: specify locale in _()python gettext:在_()中指定语言环境
【发布时间】:2016-06-23 17:34:41
【问题描述】:

在请求翻译 gettext 中的字符串时,我正在寻找一种即时设置语言的方法。我会解释为什么:

我有一个多线程机器人,它通过多个服务器上的文本响应用户,因此需要以不同的语言回复。 gettext 的documentation 指出,要在运行时更改语言环境,您应该执行以下操作:

import gettext # first, import gettext

lang1 = gettext.translation('myapplication', languages=['en']) # Load every translations
lang2 = gettext.translation('myapplication', languages=['fr'])
lang3 = gettext.translation('myapplication', languages=['de'])

# start by using language1
lang1.install()

# ... time goes by, user selects language 2
lang2.install()

# ... more time goes by, user selects language 3
lang3.install()

但是,这不适用于我的情况,因为机器人是多线程的:

想象以下 2 个 sn-ps 同时运行:

import time
import gettext 
lang1 = gettext.translation('myapplication', languages=['fr'])
lang1.install()
message(_("Loading a dummy task")) # This should be in french, and it will
time.sleep(10)
message(_("Finished loading")) # This should be in french too, but it wont :'(

和

import time
import gettext 
lang = gettext.translation('myapplication', languages=['en'])
time.sleep(3) # Not requested on the same time
lang.install()
message(_("Loading a dummy task")) # This should be in english, and it will
time.sleep(10)
message(_("Finished loading")) # This should be in english too, and it will

您可以看到有时会在错误的语言环境中翻译消息。 但是,如果我能做类似_("string", lang="FR") 的事情,问题就会消失!

我是否遗漏了什么,或者我使用了错误的模块来完成任务... 我正在使用python3

【问题讨论】:

  • 也许我的问题措辞不好?我该如何改进它?
  • 您没有提供足够的信息让他人轻松重现您的问题。我必须弄清楚如何使用gettext 并创建一个最小的翻译设置。理想情况下,您应该提供minimal reproducible example。
  • 显然不要使用install ...直接在实例上调用gettext。 (无论如何,与builtins 混在一起很讨厌)。

标签: python multithreading python-3.x internationalization gettext


【解决方案1】:

虽然上述解决方案似乎有效,但它们不能很好地与别名为 gettext() 的传统 _() 函数配合使用。但我想保留该功能,因为它用于从源中提取翻译字符串(参见docs 或例如this blog)。

因为我的模块在多进程和多线程环境中运行,using the application’s built-in namespace 或 a module’s global namespace 将不起作用,因为如果多个线程安装不同的翻译,_() 将是共享资源并且会受到竞争条件的影响。

所以,首先我写了一个简短的帮助函数,它返回一个翻译闭包:

import gettext

def get_translator(lang: str = "en"):
    trans = gettext.translation("foo", localedir="/path/to/locale", languages=(lang,))
    return trans.gettext

然后,在使用翻译字符串的函数中,我将该翻译闭包分配给_,从而使其成为我的函数本地范围内所需的函数_(),而不会污染全局共享命名空间:

def some_function(...):
    _ = get_translator()  # Pass whatever language is needed.

    log.info(_("A translated log message!"))

(将get_translator() 函数包装成memoizing cache 以避免创建相同的闭包太多次。)

【讨论】:

    【解决方案2】:

    下面的例子直接使用translation,如图o11c'sanswer允许使用线程:

    import gettext
    import threading
    import time
    
    def translation_function(quit_flag, language):
        lang = gettext.translation('simple', localedir='locale', languages=[language])
        while not quit_flag.is_set():
            print(lang.gettext("Running translator"), ": %s" % language)
            time.sleep(1.0)
    
    if __name__ == '__main__':
        thread_list = list()
        quit_flag = threading.Event()
        try:
            for lang in ['en', 'fr', 'de']:
                t = threading.Thread(target=translation_function, args=(quit_flag, lang,))
                t.daemon = True
                t.start()
                thread_list.append(t)
            while True:
                time.sleep(1.0)
        except KeyboardInterrupt:
            quit_flag.set()
            for t in thread_list:
                t.join()
    

    输出:

    正在运行的翻译器:en Traducteur en cours d'execution : fr 劳芬登 Übersetzer : de 正在运行的翻译器:en Traducteur en cours d'execution : fr 劳芬登 Übersetzer : de

    如果我对gettext 有更多了解,我会发布这个答案。我将之前的答案留给真正想继续使用_() 的人。

    【讨论】:

      【解决方案3】:

      您可以直接从.mo 文件为每种语言创建翻译对象:

      from babel.support import Translations
      
      def gettext(msg, lang):
          return get_translator(lang).gettext(msg)
      
      def get_translator(lang):
          with open(f"path_to_{lang}_mo_file", "rb") as fp:
              return Translations(fp=fp, domain="name_of_your_domain")
      

      他们的字典缓存也可以很容易地投入其中。

      【讨论】:

        【解决方案4】:

        以下简单示例展示了如何为每个翻译器使用单独的进程:

        import gettext
        import multiprocessing
        import time
        
        def translation_function(language):
            try:
                lang = gettext.translation('simple', localedir='locale', languages=[language])
                lang.install()
                while True:
                    print(_("Running translator"), ": %s" % language)
                    time.sleep(1.0)
            except KeyboardInterrupt:
                pass
        
        if __name__ == '__main__':
            thread_list = list()
            try:
                for lang in ['en', 'fr', 'de']:
                    t = multiprocessing.Process(target=translation_function, args=(lang,))
                    t.daemon = True
                    t.start()
                    thread_list.append(t)
                while True:
                    time.sleep(1.0)
            except KeyboardInterrupt:
                for t in thread_list:
                    t.join()
        

        输出如下:

        正在运行的翻译器:en Traducteur en cours d'execution : fr 劳芬登 Übersetzer : de 正在运行的翻译器:en Traducteur en cours d'execution : fr 劳芬登 Übersetzer : de

        当我尝试使用线程时,我只得到了英文翻译。您可以在每个进程中创建单独的线程来处理连接。您可能不想为每个连接创建一个新进程。

        【讨论】:

        • 谢谢,这很有用:我现在更好地理解了 gettext 模块!有我的赞成票。我会继续使用@o11c 解决方案,因为它可以更好地处理多线程!
        【解决方案5】:

        我花了一点时间编写了一个脚本,该脚本使用所有系统上可用的语言环境,并尝试在其中打印一条众所周知的消息。请注意,“所有语言环境”仅包括编码更改,无论如何都会被 Python 否定,并且大量翻译不完整,因此请使用后备。

        显然,您还必须对xgettext(或等效项)的使用进行适当的更改,以便您的真实代码识别翻译功能。

        #!/usr/bin/env python3
        
        import gettext
        import os
        
        def all_languages():
            rv = []
            for lang in os.listdir(gettext._default_localedir):
                base = lang.split('_')[0].split('.')[0].split('@')[0]
                if 2 <= len(base) <= 3 and all(c.islower() for c in base):
                    if base != 'all':
                        rv.append(lang)
            rv.sort()
            rv.append('C.UTF-8')
            rv.append('C')
            return rv
        
        class Domain:
            def __init__(self, domain):
                self._domain = domain
                self._translations = {}
        
            def _get_translation(self, lang):
                try:
                    return self._translations[lang]
                except KeyError:
                    # The fact that `fallback=True` is not the default is a serious design flaw.
                    rv = self._translations[lang] = gettext.translation(self._domain, languages=[lang], fallback=True)
                    return rv
        
            def get(self, lang, msg):
                return self._get_translation(lang).gettext(msg)
        
        def print_messages(domain, msg):
            domain = Domain(domain)
            for lang in all_languages():
                print(lang, ':', domain.get(lang, msg))
        
        def main():
            print_messages('libc', 'No such file or directory')
        
        if __name__ == '__main__':
            main()
        

        【讨论】:

        • 谢谢!这很有用。我使用了你的域类,它就像一个魅力。通过一些修改,我已经能够在 _ 函数中设置语言!
        猜你喜欢
        • 1970-01-01
        • 2013-06-14
        • 2023-03-26
        • 2019-01-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多