~ubuntu-langpack/langpack-o-matic/main

« back to all changes in this revision

Viewing changes to import

  • Committer: Sebastien Bacher
  • Date: 2021-11-22 16:04:05 UTC
  • mfrom: (594.2.1 langpack-o-matic)
  • Revision ID: seb128@ubuntu.com-20211122160405-17hx41i98tvg459q
The repository has been moved to git

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/env python
2
 
 
3
 
# this is part of langpack-o-matic, by Martin Pitt <martin.pitt@canonical.com>
4
 
#
5
 
# (C) 2005, 2011 Canonical Ltd.
6
 
#
7
 
# Update all language-pack-%PKGNAME% source packages (they must be in the state of
8
 
# the previous upload). If a locale does not yet have a -base package, it gets
9
 
# created.
10
 
#
11
 
# After this script finishes successfully, there will be a file
12
 
# 'updated-packages' which contains the source package directory names of all
13
 
# packages that were updated.
14
 
#
15
 
# Usage: import <archive> <distro-release> <target dir>
16
 
 
17
 
import os
18
 
import sys
19
 
import subprocess
20
 
import tempfile
21
 
import shutil
22
 
import optparse
23
 
import logging
24
 
import urllib
25
 
import json
26
 
 
27
 
DEFAULT_MIRROR = 'http://archive.ubuntu.com/ubuntu'
28
 
 
29
 
# change working directory to the directory of this script
30
 
os.chdir(os.path.dirname(sys.argv[0]))
31
 
 
32
 
# import our own libraries
33
 
sys.path.append('lib')
34
 
import pkg_classify
35
 
import localeinfo
36
 
import macros
37
 
import makepkg
38
 
import static_translations
39
 
 
40
 
# global variables
41
 
distribution = None
42
 
locinfo = localeinfo.SupportedLocales()
43
 
macros_map = {}  # class -> locale -> LangpackMacros
44
 
 
45
 
locale_count = {}  # locale -> #translated strings
46
 
pot_count = {}  # domain -> #translatable strings
47
 
pot_priority = {}  # domain -> priority
48
 
 
49
 
static_tar_dir = None
50
 
 
51
 
 
52
 
def parse_argv():
53
 
    '''Parse command line options.
54
 
 
55
 
    Return (options, args) pair.
56
 
    '''
57
 
    optparser = optparse.OptionParser('%prog <translation tarball> <distro release> <target dir>')
58
 
    optparser.add_option('--mirror', default=DEFAULT_MIRROR,
59
 
                         metavar='URL', help='Archive mirror URL')
60
 
    optparser.add_option('-s', '--no-static', action='store_false',
61
 
                         dest='static', default=True,
62
 
                         help='Disable inclusion of static translations (GNOME help)')
63
 
    optparser.add_option('-u', '--update', action='store_true',
64
 
                         help='tarball only has updated translations, update '
65
 
                         'existing packages (will not create new packages)')
66
 
    optparser.add_option('--no-classes', action='store_false',
67
 
                         dest='classes', default=True,
68
 
                         help='Disable splitting by classes (GNOME/KDE/common)')
69
 
    optparser.add_option('--class', dest='custom_class',
70
 
                         help='build custom class from a package list (needs --pkglist too)')
71
 
    optparser.add_option('-t', '--treshold', type='int', metavar='PERCENT',
72
 
                         help='Only build language packs that cover at least '
73
 
                         'the given percentage of all translatable strings '
74
 
                         '(ignored when updating already existing packages)')
75
 
    optparser.add_option('-p', '--min-priority', type='int', metavar='PRIORITY',
76
 
                         help='Only include domains with at least given priority')
77
 
    optparser.add_option('--pkglist',
78
 
                         help='file with source package names for --class')
79
 
    optparser.add_option('--distribution', default='ubuntu',
80
 
                         metavar='NAME', help='Distribution name (default: ubuntu)')
81
 
    optparser.add_option('-v', '--verbose', action='store_true', default=False,
82
 
                         help='Verbose logging')
83
 
 
84
 
    (opts, args) = optparser.parse_args()
85
 
 
86
 
    if len(args) != 3:
87
 
        optparser.error('incorrect number of arguments; use --help for a short help')
88
 
 
89
 
    if opts.custom_class and not opts.pkglist:
90
 
        optparser.error('need to specify --pkglist with --class')
91
 
 
92
 
    if opts.verbose:
93
 
        logging.basicConfig(level=logging.DEBUG, format='%(levelname)s: %(message)s')
94
 
    else:
95
 
        logging.basicConfig(level=logging.WARNING, format='%(levelname)s: %(message)s')
96
 
 
97
 
    return opts, args
98
 
 
99
 
 
100
 
def get_custom_class_domains(map_file, packages):
101
 
    '''Return set of domains corresponding to packages list'''
102
 
 
103
 
    # read domain map.txt
104
 
    pkg_domains = {}
105
 
    with open(map_file) as f:
106
 
        for line in f:
107
 
            f = line.split()
108
 
            if len(f) != 2:
109
 
                continue
110
 
            pkg_domains.setdefault(f[0], []).append(f[1])
111
 
 
112
 
    domains = set()
113
 
    with open(packages) as f:
114
 
        for line in f:
115
 
            pkg = line.strip()
116
 
            if not pkg:
117
 
                continue
118
 
            if pkg in pkg_domains:
119
 
                domains.update(pkg_domains[pkg])
120
 
            else:
121
 
                logging.debug('package %s is in --pkglist, but not in mapping.txt', pkg)
122
 
    return domains
123
 
 
124
 
 
125
 
def get_current_macros(cls, locale, version):
126
 
    '''Return a LangpackMacros object for the given class and locale.
127
 
 
128
 
    The LangpackMacros objects are cached for performace reasons.'''
129
 
 
130
 
    loc_map = macros_map.setdefault(cls, {})
131
 
    if locale not in loc_map:
132
 
        loc_map[locale] = macros.LangpackMacros(distribution, locale, cls, release, version)
133
 
    return loc_map[locale]
134
 
 
135
 
 
136
 
def package_updated(pkg):
137
 
    '''Check if the given package has already been updated (i. e. it appears in
138
 
    updated-packages).'''
139
 
 
140
 
    if not os.path.isfile('updated-packages'):
141
 
        return False
142
 
    for p in open('updated-packages'):
143
 
        if p.strip() == pkg.strip():
144
 
            return True
145
 
    return False
146
 
 
147
 
 
148
 
def write_po(locale, domain, pkgdir, contents):
149
 
    '''Write file contents to pkgdir/data/locale/LC_MESSAGES/domain.po.'''
150
 
 
151
 
    logging.debug('Copying %s/%s into package %s', locale, domain, pkgdir)
152
 
    try:
153
 
        os.makedirs(pkgdir + '/data/' + locale + '/LC_MESSAGES')
154
 
    except:
155
 
        pass
156
 
    dest = '%s/data/%s/LC_MESSAGES/%s.po' % (pkgdir, locale, domain)
157
 
    if locale.startswith('en_'):
158
 
        # many languages legitimagely have identical strings, such as fr
159
 
        # or pt_BR using the same string as English, but pt does not. So only
160
 
        # used msgequal for English.
161
 
        msgequal = subprocess.Popen(['bin/msgequal', '-', dest],
162
 
                                    stdin=subprocess.PIPE)
163
 
        msgequal.communicate(contents)
164
 
        assert msgequal.returncode == 0
165
 
    else:
166
 
        f = open(dest, 'w')
167
 
        f.write(contents)
168
 
        f.close()
169
 
 
170
 
 
171
 
def read_po(locale, domain, pkgdir):
172
 
    '''Read file contents from pkgdir/data/locale/domain.po.
173
 
 
174
 
    Return None if the file does not exist. Strips off surrounding white
175
 
    space.'''
176
 
 
177
 
    try:
178
 
        return open('%s/data/%s/LC_MESSAGES/%s.po' % (pkgdir, locale, domain)).read().strip()
179
 
    except:
180
 
        return None
181
 
 
182
 
 
183
 
def normalize_po(contents):
184
 
    '''Return PO contents in a canonical format suitable for comparison.
185
 
 
186
 
    Return (normalized, num_strings).
187
 
    '''
188
 
    if contents is None:
189
 
        return (None, 0)
190
 
 
191
 
    msgfmt = subprocess.Popen(['/usr/bin/msgfmt', '--statistics', '-o', '-', '-'],
192
 
                              stdin=subprocess.PIPE, stdout=subprocess.PIPE,
193
 
                              stderr=subprocess.PIPE)
194
 
    try:
195
 
        (out, err) = msgfmt.communicate(contents)
196
 
    except OSError as e:
197
 
        logging.warning('msgfmt failed with OSError: %s, not normalizing', str(e))
198
 
        return (contents, 0)
199
 
    if msgfmt.returncode:
200
 
        logging.warning('msgfmt failed with code %i, not normalizing', msgfmt.returncode)
201
 
        return (contents, 0)
202
 
 
203
 
    num_strings = int(err.split()[0])
204
 
 
205
 
    msgunfmt = subprocess.Popen(['/usr/bin/msgunfmt', '-'],
206
 
                                stdin=subprocess.PIPE, stdout=subprocess.PIPE,
207
 
                                stderr=subprocess.PIPE)
208
 
    if msgunfmt.returncode:
209
 
        raise Exception('msgunfmt returned with exit code ' + str(msgunfmt.returncode))
210
 
    (out, err) = msgunfmt.communicate(out)
211
 
 
212
 
    # remove X-Launchpad-Export-Date:
213
 
    export_date = out.find('\n"X-Launchpad-Export-Date: ')
214
 
    if export_date >= 0:
215
 
        out = out[:export_date] + out[out.index('\n', export_date + 1):]
216
 
    return (out, num_strings)
217
 
 
218
 
 
219
 
def install_po_auto_class(locale, domain, contents, data_version,
220
 
                          include_static, update_mode):
221
 
    '''Install translation file for automatically classified packages (default).
222
 
 
223
 
    locale: Target locale
224
 
    domain: Translation domain
225
 
    data_version: version number of the PO export
226
 
    contents: The actual translation data (PO file contents)
227
 
    update_mode: delta tarball mode, do not build new packages
228
 
 
229
 
    There is a magic domain None which just causes the class-less base package
230
 
    to be created, but nothing installed into it. This is important since the
231
 
    common package must always exist. If e. g. just -gnome exist, its
232
 
    dependencies are unsatisfyable, and it's missing the extra tarball and
233
 
    locales. (LP #422760, #335307)
234
 
    '''
235
 
    try:
236
 
        if domain and classifier:
237
 
            cls = classifier.classify_domain(domain)
238
 
        else:
239
 
            cls = ''
240
 
    except KeyError:
241
 
        logging.warning('unknown translation domain: %s', domain)
242
 
        return
243
 
    logging.debug('The domain is classified as %s', cls)
244
 
 
245
 
    if cls == 'kde' and release_version >= '12.10':
246
 
        logging.debug('Skipping KDE language pack for release %s', release)
247
 
        return
248
 
 
249
 
    macr = get_current_macros(cls, locale, data_version)
250
 
 
251
 
    # workaround for Rosetta exported files without translations
252
 
    if contents:
253
 
        (ncontents, num_strings) = normalize_po(contents)
254
 
        if not ncontents or not num_strings:
255
 
            return
256
 
        # update #translated strings
257
 
        locale_count[locale] = locale_count.get(locale, 0) + num_strings
258
 
 
259
 
    update_pkg = macr.subst_string(target_dir + '/sources-update/language-pack-%PKGNAME%')
260
 
    base_pkg = macr.subst_string(target_dir + '/sources-base/language-pack-%PKGNAME%-base')
261
 
 
262
 
    # if base package does not exist, create it
263
 
    if not os.path.isdir(base_pkg):
264
 
        if update_mode:
265
 
            logging.debug('Skipping %s/%s in update mode, %s does not already exist',
266
 
                          locale, domain, base_pkg)
267
 
            return
268
 
 
269
 
        # determine name of tarball with extra files
270
 
        extra_tar = 'extra-files/%s-%s.tar' % (cls, macr['PKGCODE'])
271
 
        if not os.path.isfile(extra_tar):
272
 
            extra_tar = extra_tar + '.gz'
273
 
            if not os.path.isfile(extra_tar):
274
 
                extra_tar = None
275
 
 
276
 
        # add locales to extra tarball of base package
277
 
        locale_tar = None
278
 
        if cls == '':
279
 
            logging.debug('Creating locale tarball')
280
 
            locale_tar = locinfo.create_locale_tar(macr['PKGCODE'])
281
 
            if extra_tar is not None:
282
 
                raise Exception('Not yet implemented: tarball merging (locale+extra.tar)')
283
 
            else:
284
 
                extra_tar = locale_tar
285
 
 
286
 
        makepkg.make_pkg('skel-base', base_pkg, macr, extra_tar)
287
 
 
288
 
        if locale_tar is not None:
289
 
            os.unlink(locale_tar)
290
 
 
291
 
        # add static translations
292
 
        if include_static:
293
 
            if not os.path.isdir(static_tar_dir):
294
 
                logging.debug('Downloading and preparing static translations...')
295
 
                # lazily download static tars, so that we don't have to do it when
296
 
                # building updates only
297
 
                os.mkdir(static_tar_dir)
298
 
                if distribution == 'ubuntu':
299
 
                    # we don't always rebuild everything in a release, so
300
 
                    # search for tarballs in previous releases too
301
 
                    tarballs = static_translations.get_static_translation_tarballs('ubuntu', 'trusty', release)
302
 
                else:
303
 
                    tarballs = static_translations.get_static_translation_tarballs(distribution, release, release)
304
 
                static_translations.create_static_tarballs(tarballs, static_tar_dir)
305
 
 
306
 
            static_tar = os.path.join(static_tar_dir, macr['PKGNAME'] + '.tar')
307
 
            if os.path.exists(static_tar):
308
 
                logging.debug('Adding static tarball %s', static_tar)
309
 
                shutil.move(static_tar, os.path.join(base_pkg, 'data', 'static.tar'))
310
 
 
311
 
        # sanity check: we just created -base, so the update package should not
312
 
        # be present
313
 
        if os.path.isdir(update_pkg):
314
 
            raise Exception('Inconsistency: just created fresh base, but update package already exists')
315
 
 
316
 
        # determine %BASEVERDEP% macro (needs to be postponed until here, since we
317
 
        # know where the -base package is, and which version it has, and it is
318
 
        # not created yet)
319
 
        if 'BASEVERDEP' not in macr:
320
 
            macr['BASEVERDEP'] = ' (>= %s)' % makepkg.get_pkg_version(base_pkg)
321
 
 
322
 
        # Create an empty update package
323
 
        makepkg.make_pkg('skel-update', update_pkg, macr)
324
 
    else:
325
 
        # determine %BASEVERDEP% macro (needs to be postponed until here, since we
326
 
        # know where the -base package is, and which version it has, and is not
327
 
        # updated yet)
328
 
        if 'BASEVERDEP' not in macr:
329
 
            macr['BASEVERDEP'] = ' (>= %s)' % makepkg.get_pkg_version(base_pkg)
330
 
 
331
 
    if domain is None:
332
 
        return
333
 
 
334
 
    # ensure that we always have the common package
335
 
    if macr['CLASS']:
336
 
        common_base_pkg = macr.subst_string(target_dir + '/sources-base/language-pack-%PKGCODE%-base')
337
 
        if not os.path.isdir(common_base_pkg) and not update_mode:
338
 
            logging.debug('Creating common package for %s', macr['PKGCODE'])
339
 
            install_po_auto_class(locale, None, None, data_version, options.static, False)
340
 
 
341
 
    # prefer to change the base package if we already changed it
342
 
    if package_updated(base_pkg):
343
 
        write_po(locale, domain, base_pkg, contents)
344
 
    else:
345
 
        if ncontents != normalize_po(read_po(locale, domain, base_pkg))[0] and \
346
 
                ncontents != normalize_po(read_po(locale, domain, update_pkg))[0]:
347
 
            if not package_updated(update_pkg):
348
 
                # if we have an extra tarball, do not install it if the same
349
 
                # version is already in the base package
350
 
                # XXX: deactivated for now, it costs lots of time, does not
351
 
                # respect locales, and is useless ATM
352
 
                # if extra_tar:
353
 
                #     base_extra_tar = os.path.join(base_pkg, 'data',
354
 
                #         os.path.basename(extra_tar))
355
 
                #     if os.path.isfile(base_extra_tar) and \
356
 
                #         filecmp(extra_tar, base_extra_tar):
357
 
                #         extra_tar = None
358
 
 
359
 
                makepkg.make_pkg('skel-update', update_pkg, macr)
360
 
 
361
 
            write_po(locale, domain, update_pkg, contents)
362
 
 
363
 
 
364
 
def install_po_custom_class(locale, domain, contents, data_version, update_mode):
365
 
    '''Install translation file for custom class
366
 
 
367
 
    locale: Target locale
368
 
    domain: Translation domain
369
 
    contents: The actual translation data (PO file contents)
370
 
    data_version: version number of the PO export
371
 
    update_mode: delta tarball mode, do not build new packages
372
 
    '''
373
 
    macr = get_current_macros(options.custom_class, locale, data_version)
374
 
    pkg = macr.subst_string(target_dir + '/sources-%CLASS%/language-pack-%PKGNAME%')
375
 
    if not os.path.isdir(pkg):
376
 
        if update_mode:
377
 
            logging.debug('Skipping %s/%s in update mode, %s does not already exist',
378
 
                          locale, domain, pkg)
379
 
            return
380
 
        else:
381
 
            makepkg.make_pkg('skel-customclass', pkg, macr)
382
 
    else:
383
 
        if not package_updated(pkg):
384
 
            makepkg.make_pkg('skel-customclass', pkg, macr)
385
 
 
386
 
    write_po(locale, domain, pkg, contents)
387
 
 
388
 
    # update #translated strings
389
 
    msgfmt = subprocess.Popen(['msgfmt', '--statistics', '-o', '/dev/null', '-'],
390
 
                              stdin=subprocess.PIPE, stderr=subprocess.PIPE,
391
 
                              universal_newlines=True, env={})
392
 
    out = msgfmt.communicate(contents)[1]
393
 
    locale_count[locale] = locale_count.get(locale, 0) + int(out.split()[0])
394
 
 
395
 
 
396
 
def get_translatable_counts(release):
397
 
    '''Get number of translatable strings per language'''
398
 
 
399
 
    global pot_count, pot_priority
400
 
 
401
 
    url = os.path.join(
402
 
        os.environ.get('TRANSLATION_STATS_URL', 'http://people.canonical.com/~people-l10n/data/ubuntu-l10n/'),
403
 
        '%s_%s_potemplate-stats.json' % (distribution, release.split('-')[0]))
404
 
 
405
 
    f = urllib.urlopen(url)
406
 
    data = json.load(f)
407
 
    f.close()
408
 
 
409
 
    for i in data:
410
 
        if i['enabled'] and i['languagepack']:
411
 
            pot_count[i['translation_domain']] = i['total']
412
 
            pot_priority[i['translation_domain']] = i['priority']
413
 
 
414
 
 
415
 
def discard_languages(langs, custom_class=None):
416
 
    '''Remove built packages for given languages
417
 
 
418
 
    When custom_class is given, only remove packages for that.
419
 
    '''
420
 
    if not langs:
421
 
        return
422
 
 
423
 
    packages = []
424
 
    with open('updated-packages') as f:
425
 
        for pkg in f:
426
 
            packages.append(pkg.strip())
427
 
    for pkg in list(packages):
428
 
        if custom_class and 'sources-%s/' % custom_class not in pkg:
429
 
            continue
430
 
        for l in langs:
431
 
            if pkg.endswith('-' + l) or '-%s-' % l in pkg:
432
 
                logging.debug('Discarding %s', pkg)
433
 
                shutil.rmtree(pkg)
434
 
                packages.remove(pkg)
435
 
 
436
 
    with open('updated-packages.cleaned', 'w') as f:
437
 
        for pkg in packages:
438
 
            f.write(pkg + '\n')
439
 
    os.rename('updated-packages.cleaned', 'updated-packages')
440
 
 
441
 
 
442
 
#
443
 
# main
444
 
#
445
 
 
446
 
options, args = parse_argv()
447
 
(archive_fname, release, target_dir) = args
448
 
distribution = options.distribution
449
 
 
450
 
if not os.path.isdir(target_dir):
451
 
    sys.stderr.write('Target directory does not exist\n')
452
 
    sys.exit(1)
453
 
 
454
 
release_version = get_current_macros('', 'en_GB.UTF-8', 'invalid').subst_string('%RELEASEVERSION%')
455
 
assert release_version, 'no release version for ' + release
456
 
 
457
 
# unpack translation tarball
458
 
contentdirbase = tempfile.mkdtemp()
459
 
try:
460
 
    # extract tarball to a temporary dir
461
 
    if archive_fname[-4:] == '.tar':
462
 
        result = os.spawnlp(os.P_WAIT, 'tar', 'tar', '-C', contentdirbase, '-xf',
463
 
                            os.path.abspath(archive_fname))
464
 
    elif archive_fname[-7:] == '.tar.gz':
465
 
        result = os.spawnlp(os.P_WAIT, 'tar', 'tar', '-C', contentdirbase, '-xzf',
466
 
                            os.path.abspath(archive_fname))
467
 
    elif archive_fname[-8:] == '.tar.bz2':
468
 
        result = os.spawnlp(os.P_WAIT, 'tar', 'tar', '-C', contentdirbase, '--bzip2 -xf',
469
 
                            os.path.abspath(archive_fname))
470
 
    elif archive_fname[-9:] == '.tar.lzma':
471
 
        result = os.spawnlp(os.P_WAIT, 'tar', 'tar', '-C', contentdirbase, '--lzma -xf',
472
 
                            os.path.abspath(archive_fname))
473
 
    else:
474
 
        sys.stderr.write('Unknown tar format')
475
 
        result = 1
476
 
    if result != 0:
477
 
        sys.stderr.write('Error executing tar, aborting')
478
 
        sys.exit(1)
479
 
 
480
 
    toplevel_dirs = os.listdir(contentdirbase)
481
 
    if len(toplevel_dirs) != 1:
482
 
        raise Exception('Archive does not contain a single top level directory')
483
 
 
484
 
    content_dir = os.path.join(contentdirbase, toplevel_dirs[0])
485
 
    static_tar_dir = os.path.join(contentdirbase, 'static-tars')
486
 
 
487
 
    # read time stamp
488
 
    timestamp_file = os.path.join(content_dir, 'timestamp.txt')
489
 
    if not os.path.exists(timestamp_file):
490
 
        raise Exception('Archive does not contain a timestamp')
491
 
    data_version = open(timestamp_file).read().strip()
492
 
    os.unlink(timestamp_file)
493
 
 
494
 
    # initialize domain map
495
 
    map_file = os.path.join(content_dir, 'mapping.txt')
496
 
    if not os.path.exists(map_file):
497
 
        raise Exception('Archive does not contain a domain map file (mapping.txt)')
498
 
    classifier = None
499
 
    if options.custom_class:
500
 
        custom_class_domains = get_custom_class_domains(map_file, options.pkglist)
501
 
    elif options.classes:
502
 
        classifier = pkg_classify.PackageClassificator(release, map_file,
503
 
                                                       options.mirror)
504
 
    os.unlink(map_file)
505
 
 
506
 
    try:
507
 
        get_translatable_counts(release)  # need pot_priority for updates, too!
508
 
    except ValueError:
509
 
        logging.warning('Translations stats missing for %s', release)
510
 
        # hack until http://people.canonical.com/~people-l10n/data/ubuntu-l10n/ exists
511
 
        # for RTM 15.04
512
 
        orig_distribution = distribution
513
 
        distribution = 'ubuntu'
514
 
        get_translatable_counts(release.replace('15.04', 'xenial'))
515
 
        distribution = orig_distribution
516
 
 
517
 
    if not options.update:
518
 
        total_translatable = 0
519
 
        counted_domains = set()
520
 
 
521
 
        def count_domain(domain):
522
 
            global total_translatable, counted_domains
523
 
            if domain in counted_domains:
524
 
                return
525
 
            counted_domains.add(domain)
526
 
            try:
527
 
                total_translatable += pot_count[domain]
528
 
            except KeyError:
529
 
                logging.warning('translation stats are missing domain %s', domain)
530
 
    else:
531
 
        def count_domain(domain):
532
 
            # not necessary in update mode
533
 
            pass
534
 
 
535
 
    # Process every .po file
536
 
    lastlocale = ''
537
 
    valid_locale = {}
538
 
    for root, dirs, files in os.walk(content_dir):
539
 
        for f in files:
540
 
            logging.debug('Considering %s', f)
541
 
            if not f.endswith('.po'):
542
 
                continue
543
 
 
544
 
            file = os.path.join(root, f)
545
 
            comp = file.split(os.sep)[-3:]
546
 
 
547
 
            # Verify that we have locale/LC_MESSAGES/domain.po
548
 
            if len(comp) < 3 or comp[2] == '':
549
 
                continue
550
 
            if comp[1] != 'LC_MESSAGES':
551
 
                raise IOError('Invalid file: %s' % file)
552
 
            (domain, ext) = os.path.splitext(comp[2])
553
 
            if ext != '.po':
554
 
                raise IOError('Unknown file type: %s' % file)
555
 
 
556
 
            # Ignore ISO files
557
 
            if domain.startswith('iso_'):
558
 
                continue
559
 
 
560
 
            # Verify that we have a known locale
561
 
            locale = comp[0]
562
 
            if locale != lastlocale:
563
 
                logging.debug('--------------------------------')
564
 
                logging.debug('Processing locale %s...', locale)
565
 
            lastlocale = locale
566
 
 
567
 
            # check language
568
 
            lang = (locale.split('_')[0]).split('@')[0]
569
 
            if not (locinfo.known_language(lang) and locinfo.language_locales(lang)):
570
 
                logging.warning('Skipping unknown language: %s', locale)
571
 
                continue
572
 
 
573
 
            # check country, if present
574
 
            noat = locale.split('@')[0]
575
 
            if noat.find('_') >= 0:
576
 
                (lang, country) = noat.split('_')
577
 
                # XXX: hack: ignore invalid/obsolete per-country locales from LP
578
 
                if lang not in ('zh', 'en') and noat != 'pt_BR':
579
 
                    logging.warning('Skipping obsolete locale: %s', locale)
580
 
                    continue
581
 
                if not locinfo.known_country(country):
582
 
                    logging.warning('Skipping unknown country: %s', locale)
583
 
                    continue
584
 
            if noat == 'zh':
585
 
                # split into zh_* now
586
 
                logging.warning('Skipping obsolete locale: %s', locale)
587
 
                continue
588
 
 
589
 
            # check minimal priority
590
 
            if options.min_priority is not None:
591
 
                try:
592
 
                    if pot_priority[domain] < options.min_priority and pot_priority[domain] > 0:
593
 
                        logging.debug('Skipping domain %s with too low priority %i' %
594
 
                                      (domain, pot_priority[domain]))
595
 
                        continue
596
 
                except KeyError:
597
 
                    logging.warning('domain %s has no priority' % domain)
598
 
 
599
 
            # Everything is fine, install it
600
 
            with open(file) as f:
601
 
                po_data = f.read()
602
 
            if options.custom_class:
603
 
                if domain in custom_class_domains:
604
 
                    install_po_custom_class(locale, domain, po_data,
605
 
                                            data_version, options.update)
606
 
                    count_domain(domain)
607
 
            else:
608
 
                install_po_auto_class(locale, domain, po_data, data_version,
609
 
                                      options.static, options.update)
610
 
                count_domain(domain)
611
 
 
612
 
    if not options.update:
613
 
        # translate locale_count into per-language counts; take the dominant locale
614
 
        # as determining the coverage
615
 
        lang_count = {}
616
 
        for locale, count in locale_count.items():
617
 
            lang = (locale.split('_')[0]).split('@')[0]
618
 
            if lang_count.setdefault(lang, 0) < count:
619
 
                lang_count[lang] = count
620
 
 
621
 
        # show stats and discard packages which don't reach the treshold
622
 
        print('Translated strings per language (%i translatable in total):' % total_translatable)
623
 
        discarded = set()
624
 
        for l in sorted(lang_count):
625
 
            if total_translatable:
626
 
                pct = lang_count[l] * 100 // total_translatable
627
 
                if options.treshold is not None and pct < options.treshold:
628
 
                    if l == 'ckb':
629
 
                        status = ' special-cased'
630
 
                    else:
631
 
                        discarded.add(l)
632
 
                        status = ' discarded'
633
 
                else:
634
 
                    status = ''
635
 
                print('  %s\t%i (%i%%)%s' % (l, lang_count[l], pct, status))
636
 
            else:
637
 
                print('  %s\t%i' % (l, lang_count[l]))
638
 
 
639
 
        discard_languages(discarded, options.custom_class)
640
 
finally:
641
 
    shutil.rmtree(contentdirbase, True)