This file is indexed.

/usr/lib/python3/dist-packages/plainbox/i18n.py is in python3-plainbox 0.25-1.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
# This file is part of Checkbox.
#
# Copyright 2014 Canonical Ltd.
# Written by:
#   Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
#
# Checkbox is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3,
# as published by the Free Software Foundation.
#
# Checkbox is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Checkbox.  If not, see <http://www.gnu.org/licenses/>.

"""
:mod:`plainbox.i18n` -- i18n support
====================================

This module provides public APIs for plainbox translation system.
"""

from abc import ABCMeta, abstractmethod
import collections
import gettext as gettext_module
import logging
import os
import random
import re

__all__ = [
    'bindtextdomain',
    'dgettext',
    'dngettext',
    'gettext',
    'ngettext',
    'pdgettext',
    'pdngettext',
    'pgettext',
    'pngettext',
    'textdomain',
]

_logger = logging.getLogger("plainbox.i18n")


class ITranslator(metaclass=ABCMeta):
    """
    Interface for all translators
    """

    @abstractmethod
    def gettext(self, msgid):
        """
        Translate a message

        :param msgid:
            Identifier of the message to translate
        :returns:
            Translated message or msgid if translation is not available
        """

    @abstractmethod
    def ngettext(self, msgid1, msgid2, n):
        """
        Translate a message involving plural form

        :param msgid1:
            Identifier of the singular form of the message to translate
        :param msgid2:
            Identifier of the plural form of message to translate
        :param n:
            Any integer number
        :returns:
            Translated message appropriate for the specified number, if
            available.  If the translated number is not available one of msgid1
            and msgid2 are returned, depending on the value of n.
        """

    # Context aware gettext + ngettext

    @abstractmethod
    def pgettext(self, msgctxt, msgid):
        """
        Translate a message within a context.

        :param msgctxt:
            Context that specifies which translation of msgid to pick
        :param msgid:
            Identifier of the message to translate
        :returns:
            Translated message or msgid if translation is not available
        """

    @abstractmethod
    def pngettext(self, msgctxt, msgid1, msgid2, n):
        """
        Translate a message involving plural form

        :param msgctxt:
            Context that specifies which translation of msgid1/msgid2 to pick
        :param msgid1:
            Identifier of the singular form of the message to translate
        :param msgid2:
            Identifier of the plural form of message to translate
        :param n:
            Any integer number
        :returns:
            Translated message appropriate for the specified number, if
            available. If the translated number is not available one of msgid1
            and msgid2 are returned, depending on the value of n.
        """

    # Explicit domain gettext + ngettext

    @abstractmethod
    def dgettext(self, domain, msgid):
        """
        Translate a message using a specific domain

        :param domain:
            Name of the domain from which translations are obtained
        :param msgid:
            Identifier of the message to translate
        :returns:
            Translated message or msgid if translation is not available
        """

    @abstractmethod
    def dngettext(self, domain, msgid1, msgid2, n):
        """
        Translate a message involving plural form using a specific domain

        :param domain:
            Name of the domain from which translations are obtained
        :param msgid1:
            Identifier of the singular form of the message to translate
        :param msgid2:
            Identifier of the plural form of message to translate
        :param n:
            Any integer number
        :returns:
            Translated message appropriate for the specified number, if
            available.  If the translated number is not available one of msgid1
            and msgid2 are returned, depending on the value of n.
        """

    # Explicit domain and context gettext + ngettext

    @abstractmethod
    def pdgettext(self, msgctxt, domain, msgid):
        """
        Translate a message using a specific context and domain

        :param msgctxt:
            Context that specifies which translation of msgid to pick
        :param domain:
            Name of the domain from which translations are obtained
        :param msgid:
            Identifier of the message to translate
        :returns:
            Translated message or msgid if translation is not available
        """

    @abstractmethod
    def pdngettext(self, msgctxt, domain, msgid1, msgid2, n):
        """
        Translate a message involving plural form using a specific context and
        domain

        :param msgctxt:
            Context that specifies which translation of msgid1/msgid2 to pick
        :param domain:
            Name of the domain from which translations are obtained
        :param msgid1:
            Identifier of the singular form of the message to translate
        :param msgid2:
            Identifier of the plural form of message to translate
        :param n:
            Any integer number
        :returns:
            Translated message appropriate for the specified number, if
            available.  If the translated number is not available one of msgid1
            and msgid2 are returned, depending on the value of n.
        """

    @abstractmethod
    def textdomain(self, domain):
        """
        Set global gettext domain

        :param domain:
            Name of the global gettext domain. This domain will be used to all
            unqualified calls to gettext() and ngettext().

        .. note::
            gettext and ngettext exposed from this module transparently use
            "plainbox" as the domain name. This call affects all *other*,
            typical gettext calls.
        """

    @abstractmethod
    def bindtextdomain(self, domain, localedir=None):
        """
        Set set directory for gettext messages for a specific domain

        :param domain:
            Name of the domain to configure
        :param localedir:
            Name of the directory with translation catalogs.
        """


class NoOpTranslator(ITranslator):
    """
    A translator that doesn't translate anything
    """

    def gettext(self, msgid):
        return msgid

    def ngettext(self, msgid1, msgid2, n):
        return msgid1 if n == 1 else msgid2

    def pgettext(self, msgctxt, msgid):
        return self.gettext(msgid)

    def pngettext(self, msgctxt, msgid1, msgid2, n):
        return self.ngettext(msgid1, msgid2, n)

    def dgettext(self, domain, msgid):
        return self.gettext(msgid)

    def dngettext(self, domain, msgid1, msgid2, n):
        return self.ngettext(msgid1, msgid2, n)

    def pdgettext(self, msgctxt, domain, msgid):
        return self.gettext(msgid)

    def pdngettext(self, msgctxt, domain, msgid1, msgid2, n):
        return self.ngettext(msgid1, msgid2, n)

    def textdomain(self, domain):
        pass

    def bindtextdomain(self, domain, localedir=None):
        pass


class LoremIpsumTranslator(NoOpTranslator):

    LOREM_IPSUM = {
        "ch": ('', """小經 施消 了稱 能文 安種 之用 無心 友市 景內 語格。坡對
               轉醫 題苦 們會員! 我親就 藝了參 間通。 有發 轉前 藥想
               亞沒,通須 應管、打者 小成 公出? 般記 中成化 他四華 分國越
               分位離,更為者 文難 我如 我布?經動 著為 安經, 們天然 我親 唱顯
               不;得當 出一來得金 著作 到到 操弟 人望!去指 在格據!"""),
        "kr": (' ' """말을 하고 곁에서 일 말려가고 그걸로 하다 같은 없네
               앉은 뿌리치더니 동소문 일 보지 재우쳤다 분량 말을 가지고
               김첨지의 시작하였다 내리는 나를 김첨지는 좁쌀 준 반가운지
               김첨지는 놓치겠구먼 늦추잡았다 인력거 속 생각하게 돈을 시체를
               한 정거장까지 느끼었다 귀에 넘어 왜목 것을 싶어 설레는 맞붙들고
               하네 오늘 배가 하늘은 하자마자 맞물고 일이었다 운수가 못쓸
               돈의 라고 어이 없지만 받아야 아내의 시작하였다 차도 왜
               사용자로부터 추어탕을 처음 보라 출판사 차원 따라서 펴서 풀이
               사람은 근심과 초조해온다 트고 제 창을 내리었다 인력거하고
               같으면 큰 이놈아 어린애 그 넘어 울었다V"""),
        "he": (' ', """תורת קרימינולוגיה אל אתה הטבע לחיבור אם אחר מדע חינוך
               ממונרכיה גם פנאי אחרים המקובל את אתה תנך אחרים לטיפול של את
               תיאטרון ואלקטרוניקה מתן דת והנדסה שימושיים סדר בה סרבול
               אינטרנט שתי ב אנא תוכל לערך רוסית כדי את תוכל כניסה המלחמה
               עוד מה מיזמי אודות ומהימנה"""),
        "ar": (' ', """ دار أن منتصف أوراقهم الرئيسية هو الا الحرب الجبهة لان
               مع تنفّس للصين لإنعدام نتيجة الثقيلة أي شيء عقبت وأزيز لألمانيا
               وفي كل حدى إختار المنتصرة أي به، بغزو بالسيطرة أن  جدول
               بالفشل إيطاليا قام كل هنا؟ فرنسا الهجوم هذه مع حقول
               الإمبراطورية لها أي قدما اليابانية عام مع جنود أراضي السوفييتي،
               هو بلا لم وجهان الساحة الإمبراطورية لان ما بحق ألمانيا الياباني،
               فعل فاتّبع الشّعبين المعركة، ما  الى ما يطول المشتّتون وكسبت
               وإيطالي ذات أم تلك ثم القصف قبضتهم قد وأزيز إستمات ونستون غزو
               الأرض الأولية عن بين بـ دفّة كانت النفط لمّ تلك فهرست الأرض
               الإتفاقية مع"""),
        "ru": (' ', """Магна азжюывырит мэль ут нам ыт видырэр такематыш кибо
               ыррор ут квюо Вяш аппарэат пондэрюм интылльэгэбат эи про ед
               еллум дикунт Квюо экз льаборэж нужквюам анкилльаы мэль омйттам
               мэнандря ед Мэль эи рэктэквуэ консэквюат контынтёонэж ты ёужто
               фэугяат вивэндюм шэа Атквюе трётанё эю квуй омнеж латины экз
               вимi"""),
        "jp": ('', """戸ぶだ の意 化巡奇 供 クソリヤ 無断 ヨサリヲ 念休ばイ
               例会 コトヤ 耕智う ばっゃ 佐告決う で打表 ぞ ぼび情記ト レ表関銀
               ロモア ニ次川 よ全子 コロフ ソ政象 住岳ぴ 読ワ 一針 ヘ断
               首画リ のぽ せ足 決属 術こ てラ 領 技 けリぴ 分率ぴ きぜっ
               物味ドン おぎ一田ぴ ぶの謙 調ヲ星度 レぼむ囲 舗双脈 鶴挑げ
               ほぶ。無無 ツ縄第が 本公作 ゅゃふ く質失フ 米上議 ア記治 えれ本
               意つん ぎレ局 総ケ盛 載テ コ部止 メツ輪 帰歴 就些ル っき"""),
        "pl": (' ', """
               litwo ojczyzno moja ty jesteś jak zdrowie ile cię stracił
               dziś piękność widziana więc wszyscy dokoła brali stronę kusego
               albo sam wewnątrz siebie czuł się położył co by stary
               dąbrowskiego usłyszeć mazurek biegał po stole i krwi tonęła
               gdy sędziego służono niedbale słudzy nie na utrzymanie lecz
               mniej piękne niż myśliwi młodzi tak nie zmruża jako swe
               osadzał dziwna rzecz miejsca wkoło pali nawet stary który
               teraz za nim psów gromada gracz szarak skoro poczuł wszystkie
               charty w drobne strączki białe dziwnie ozdabiał głowę bo tak
               przekradł się uparta coraz głośniejsza kłótnia o wiejskiego
               pożycia nudach i długie paznokcie przedstawiając dwa tysiące
               jako jenerał dąbrowski z wysogierdem radziwiłł z drzewa lecz
               lekki odgadniesz że pewnie na jutro solwuję i na kształt
               ogrodowych grządek że ją bardzo szybko suwała się na
               przeciwnej zajadłość dowiodę że dziś z lasu wracało towarzystwo
               całe wesoło lecz go grzecznie na złość rejentowi że u
               wieczerzy będzie jego upadkiem domy i bagnami skradał się tłocz
               i jak bawić się nie było bo tak na jutro solwuję i przepraszał
               sędziego sędzia sam na początek dać małą kiedy"""),
    }

    def __init__(self, kind):
        self.kind = kind
        self.space = self.LOREM_IPSUM[self.kind][0]
        self.words = self.LOREM_IPSUM[self.kind][1].split()
        self.n_words = collections.defaultdict(list)
        for word in self.words:
            self.n_words[len(word)].append(word)

    def _get_ipsum(self, text):
        return re.sub(
            '(%[sdr]|{[^}]*}|[a-zA-Z]+)',
            lambda match: self._tr_word(match.group(1)),
            text)

    def _tr_word(self, word):
        if re.search("(%[sdr])|({[^}]*})", word):
            return word
        elif word.startswith("--"):
            return "--{}".format(self._tr_word(word[2:]))
        elif word.startswith("-"):
            return "-{}".format(self._tr_word(word[1:]))
        elif word.startswith("[") and word.endswith("]"):
            return "[{}]".format(self._tr_word(word[1:-1]))
        elif word.startswith("<") and word.endswith(">"):
            return "<{}>".format(self._tr_word(word[1:-1]))
        else:
            tr_word = self._tr_approx(len(word))
            if word.isupper():
                return tr_word.upper()
            if word[0].isupper():
                return tr_word.capitalize()
            else:
                return tr_word

    def _tr_approx(self, desired_length):
        for avail_length in sorted(self.n_words):
            if desired_length <= avail_length:
                break
        return random.choice(self.n_words[avail_length])

    def gettext(self, msgid):
        return self.dgettext("plainbox", msgid)

    def ngettext(self, msgid1, msgid2, n):
        if n == 1:
            return self._get_ipsum(msgid1)
        else:
            return self._get_ipsum(msgid2)

    def dgettext(self, domain, msgid):
        return "<{}: {}>".format(domain, self._get_ipsum(msgid))


class GettextTranslator(ITranslator):
    """
    A translator using native stdlib gettext

    # NOTE: The gettext API is a bit wrong as it doesn't respect the
    # textdomain/bindtextdomain calls.
    """

    def __init__(self, domain, locale_dir=None):
        self._domain = domain
        self._translations = {}
        self._locale_dir_map = {
            domain: locale_dir
        }

    def _get_translation(self, domain):
        try:
            return self._translations[domain]
        except KeyError:
            try:
                translation = gettext_module.translation(
                    domain, self._locale_dir_map.get(domain))
            except IOError:
                translation = gettext_module.NullTranslations()
            self._translations[domain] = translation
            return translation

    def _contextualize(self, ctx, msg):
        """
        Contextualize message identifier

        This method combines the context string with the message identifier
        using the character used by gettext (END OF TRANSMISSION, U+0004)
        """
        GETTEXT_CONTEXT_GLUE = "\004"
        return ctx + GETTEXT_CONTEXT_GLUE + msg

    def gettext(self, msgid):
        return self._get_translation(self._domain).gettext(msgid)

    def ngettext(self, msgid1, msgid2, n):
        return self._get_translation(self._domain).ngettext(msgid1, msgid2, n)

    def pgettext(self, msgctxt, msgid):
        effective_msgid = self._contextualize(msgctxt, msgid)
        msgstr = self.gettext(effective_msgid)
        # If we got the untranslated version then we want to just return msgid
        # back, without msgctxt prepended in front.
        if msgstr == effective_msgid:
            return msgid
        else:
            return msgstr

    def pngettext(self, msgctxt, msgid1, msgid2, n):
        effective_msgid1 = self._contextualize(msgctxt, msgid1)
        effective_msgid2 = self._contextualize(msgctxt, msgid2)
        msgstr = self.ngettext(effective_msgid1, effective_msgid2, n)
        # If we got the untranslated version then we want to just return msgid1
        # or msgid2 back, without msgctxt prepended in front.
        if msgstr == effective_msgid1:
            return msgid1
        elif msgstr == effective_msgid2:
            return msgid2
        else:
            return msgstr

    def dgettext(self, domain, msgid):
        return self._get_translation(domain).gettext(msgid)

    def dngettext(self, domain, msgid1, msgid2, n):
        return self._get_translation(domain).ngettext(msgid1, msgid2, n)

    def pdgettext(self, msgctxt, domain, msgid):
        effective_msgid = self._contextualize(msgctxt, msgid)
        msgstr = self._get_translation(domain).gettext(effective_msgid)
        # If we got the untranslated version then we want to just return msgid
        # back, without msgctxt prepended in front.
        if msgstr == effective_msgid:
            return msgid
        else:
            return msgstr

    def pdngettext(self, msgctxt, domain, msgid1, msgid2, n):
        effective_msgid1 = self._contextualize(msgctxt, msgid1)
        effective_msgid2 = self._contextualize(msgctxt, msgid2)
        msgstr = self._get_translation(domain).ngettext(
            effective_msgid1, effective_msgid2, n)
        # If we got the untranslated version then we want to just return msgid1
        # or msgid2 back, without msgctxt prepended in front.
        if msgstr == effective_msgid1:
            return msgid1
        elif msgstr == effective_msgid2:
            return msgid2
        else:
            return msgstr

    def textdomain(self, domain):
        """
        Set global gettext domain

        :param domain:
            Name of the global gettext domain. This domain will be used to all
            unqualified calls to gettext() and ngettext().

        .. note::
            gettext and ngettext exposed from this module transparently use
            "plainbox" as the domain name. This call affects all *other*,
            typical gettext calls.
        """
        _logger.debug("textdomain(%r)", domain)
        self._domain = domain
        gettext_module.textdomain(domain)

    def bindtextdomain(self, domain, localedir=None):
        """
        Set set directory for gettext messages for a specific domain

        :param domain:
            Name of the domain to configure
        :param localedir:
            Name of the directory with translation catalogs.
        """
        _logger.debug("bindtextdomain(%r, %r)", domain, localedir)
        self._locale_dir_map[domain] = localedir
        gettext_module.bindtextdomain(domain, localedir)


def docstring(docstring):
    """
    Decorator factory for assigning docstrings to functions.

    This decorator is intended for functions that reuse their docstring
    as translatable text that needs to be tagged with gettext_noop.

    Example:

        @docstring("the foo function")
        def foo():
            pass


        @docstring("the Foo class")
        class Foo:
            pass
    """
    def decorator(cls_or_func):
        try:
            cls_or_func.__doc__ = docstring
            return cls_or_func
        except AttributeError:
            assert isinstance(cls_or_func, type)
            return type(
                cls_or_func.__name__,
                (cls_or_func,),
                {'__doc__': docstring})
    return decorator


def gettext_noop(msgid):
    """
    No-operation gettext implementation.

    :param msgid:
        The message not to translate
    :returns:
        msgid itself

    This function should be used (typically aliased as ``N_`` to mark strings
    that don't require translation at the place where they are defined but will
    be translated later on. This is just a hint to the message extraction
    system.
    """
    return msgid


# This is the global plainbox-specific translator.
try:
    _translator = {
        "gettext": GettextTranslator(
            "plainbox", os.getenv("PLAINBOX_LOCALE_DIR", None)),
        "no-op": NoOpTranslator(),
        "lorem-ipsum-ar": LoremIpsumTranslator("ar"),
        "lorem-ipsum-ch": LoremIpsumTranslator("ch"),
        "lorem-ipsum-he": LoremIpsumTranslator("he"),
        "lorem-ipsum-jp": LoremIpsumTranslator("jp"),
        "lorem-ipsum-kr": LoremIpsumTranslator("kr"),
        "lorem-ipsum-pl": LoremIpsumTranslator("pl"),
        "lorem-ipsum-ru": LoremIpsumTranslator("ru"),
    }[os.getenv("PLAINBOX_I18N_MODE", "gettext")]
except KeyError as exc:
    raise RuntimeError(
        "Unsupported PLAINBOX_I18N_MODE: {!r}".format(exc.args[0]))


# This is the public API of this module
gettext = _translator.gettext
ngettext = _translator.ngettext
pgettext = _translator.pgettext
pngettext = _translator.pngettext
dgettext = _translator.dgettext
dngettext = _translator.dngettext
pdgettext = _translator.pdgettext
pdngettext = _translator.pdngettext
bindtextdomain = _translator.bindtextdomain
textdomain = _translator.textdomain