i18n.py 7.48 KB
Newer Older
1 2 3
"""
Internationalization tasks
"""
4 5

import re
6
import subprocess
7
import sys
8

9
from path import Path as path
10
from paver.easy import cmdopts, needs, sh, task
11

12
from .utils.cmd import django_cmd
13
from .utils.envs import Env
14
from .utils.timer import timed
15 16 17 18

try:
    from pygments.console import colorize
except ImportError:
19
    colorize = lambda color, text: text
20

21
DEFAULT_SETTINGS = Env.DEVSTACK_SETTINGS
22

23 24 25

@task
@needs(
26
    "pavelib.prereqs.install_prereqs",
27 28 29 30 31 32
    "pavelib.i18n.i18n_validate_gettext",
    "pavelib.assets.compile_coffeescript",
)
@cmdopts([
    ("verbose", "v", "Sets 'verbose' to True"),
])
33
@timed
34 35 36 37 38
def i18n_extract(options):
    """
    Extract localizable strings from sources
    """
    verbose = getattr(options, "verbose", None)
39
    cmd = "i18n_tool extract"
40 41 42 43 44 45 46 47

    if verbose:
        cmd += " -vv"

    sh(cmd)


@task
48
@timed
louyihua committed
49 50 51 52
def i18n_fastgenerate():
    """
    Compile localizable strings from sources without re-extracting strings first.
    """
53
    sh("i18n_tool generate")
louyihua committed
54 55 56


@task
57
@needs("pavelib.i18n.i18n_extract")
58
@timed
59 60 61 62
def i18n_generate():
    """
    Compile localizable strings from sources, extracting strings first.
    """
63
    sh("i18n_tool generate")
64 65 66 67


@task
@needs("pavelib.i18n.i18n_extract")
68
@timed
69 70 71 72 73
def i18n_generate_strict():
    """
    Compile localizable strings from sources, extracting strings first.
    Complains if files are missing.
    """
74
    sh("i18n_tool generate --strict")
75 76 77 78


@task
@needs("pavelib.i18n.i18n_extract")
79 80 81
@cmdopts([
    ("settings=", "s", "The settings to use (defaults to devstack)"),
])
82
@timed
83
def i18n_dummy(options):
84 85 86 87
    """
    Simulate international translation by generating dummy strings
    corresponding to source strings.
    """
88 89
    settings = options.get('settings', DEFAULT_SETTINGS)

90
    sh("i18n_tool dummy")
91
    # Need to then compile the new dummy strings
92
    sh("i18n_tool generate")
93

94 95
    # Generate static i18n JS files.
    for system in ['lms', 'cms']:
96
        sh(django_cmd(system, settings, 'compilejsi18n'))
97

98 99

@task
100
@timed
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
def i18n_validate_gettext():
    """
    Make sure GNU gettext utilities are available
    """

    returncode = subprocess.call(['which', 'xgettext'])

    if returncode != 0:
        msg = colorize(
            'red',
            "Cannot locate GNU gettext utilities, which are "
            "required by django for internationalization.\n (see "
            "https://docs.djangoproject.com/en/dev/topics/i18n/"
            "translation/#message-files)\nTry downloading them from "
            "http://www.gnu.org/software/gettext/ \n"
        )

        sys.stderr.write(msg)
        sys.exit(1)


@task
123
@timed
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
def i18n_validate_transifex_config():
    """
    Make sure config file with username/password exists
    """
    home = path('~').expanduser()
    config = home / '.transifexrc'

    if not config.isfile or config.getsize == 0:
        msg = colorize(
            'red',
            "Cannot connect to Transifex, config file is missing"
            " or empty: {config} \nSee "
            "http://help.transifex.com/features/client/#transifexrc \n".format(
                config=config,
            )
        )

        sys.stderr.write(msg)
        sys.exit(1)


@task
@needs("pavelib.i18n.i18n_validate_transifex_config")
147
@timed
148 149 150 151
def i18n_transifex_push():
    """
    Push source strings to Transifex for translation
    """
152
    sh("i18n_tool transifex push")
153 154 155 156


@task
@needs("pavelib.i18n.i18n_validate_transifex_config")
157
@timed
158 159 160 161
def i18n_transifex_pull():
    """
    Pull translated strings from Transifex
    """
162
    sh("i18n_tool transifex pull")
163 164 165


@task
166
@timed
Sarina Canelake committed
167
def i18n_rtl():
Sarina Canelake committed
168 169 170
    """
    Pull all RTL translations (reviewed AND unreviewed) from Transifex
    """
171
    sh("i18n_tool transifex rtl")
Sarina Canelake committed
172

173
    print "Now generating langugage files..."
Sarina Canelake committed
174

175
    sh("i18n_tool generate --rtl")
Sarina Canelake committed
176

177
    print "Committing translations..."
Sarina Canelake committed
178 179 180
    sh('git clean -fdX conf/locale')
    sh('git add conf/locale')
    sh('git commit --amend')
Sarina Canelake committed
181 182 183


@task
184
@timed
Sarina Canelake committed
185 186 187 188
def i18n_ltr():
    """
    Pull all LTR translations (reviewed AND unreviewed) from Transifex
    """
189
    sh("i18n_tool transifex ltr")
Sarina Canelake committed
190

191
    print "Now generating langugage files..."
Sarina Canelake committed
192

193
    sh("i18n_tool generate --ltr")
Sarina Canelake committed
194

195
    print "Committing translations..."
Sarina Canelake committed
196 197 198
    sh('git clean -fdX conf/locale')
    sh('git add conf/locale')
    sh('git commit --amend')
Sarina Canelake committed
199

200

Sarina Canelake committed
201
@task
202
@needs(
203
    "pavelib.i18n.i18n_clean",
204 205 206 207 208
    "pavelib.i18n.i18n_transifex_pull",
    "pavelib.i18n.i18n_extract",
    "pavelib.i18n.i18n_dummy",
    "pavelib.i18n.i18n_generate_strict",
)
209
@timed
210 211 212 213
def i18n_robot_pull():
    """
    Pull source strings, generate po and mo files, and validate
    """
214

215 216 217
    # sh('paver test_i18n')
    # Tests were removed from repo, but there should still be tests covering the translations
    # TODO: Validate the recently pulled translations, and give a bail option
218 219
    sh('git clean -fdX conf/locale/rtl')
    sh('git clean -fdX conf/locale/eo')
220
    print "\n\nValidating translations with `i18n_tool validate`..."
221
    sh("i18n_tool validate")
222 223 224 225 226

    con = raw_input("Continue with committing these translations (y/n)? ")

    if con.lower() == 'y':
        sh('git add conf/locale')
227 228
        sh('git add cms/static/js/i18n')
        sh('git add lms/static/js/i18n')
229 230

        sh(
231 232
            'git commit --message='
            '"Update translations (autogenerated message)" --edit'
233
        )
234 235 236


@task
237
@timed
238 239 240 241 242 243 244 245
def i18n_clean():
    """
    Clean the i18n directory of artifacts
    """
    sh('git clean -fdX conf/locale')


@task
246 247 248 249
@needs(
    "pavelib.i18n.i18n_extract",
    "pavelib.i18n.i18n_transifex_push",
)
250
@timed
251 252 253 254 255
def i18n_robot_push():
    """
    Extract new strings, and push to transifex
    """
    pass
256 257 258 259 260 261 262


@task
@needs(
    "pavelib.i18n.i18n_validate_transifex_config",
    "pavelib.i18n.i18n_generate",
)
263
@timed
264 265 266 267 268 269 270 271 272 273 274 275
def i18n_release_push():
    """
    Push release-specific resources to Transifex.
    """
    resources = find_release_resources()
    sh("i18n_tool transifex push " + " ".join(resources))


@task
@needs(
    "pavelib.i18n.i18n_validate_transifex_config",
)
276
@timed
277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292
def i18n_release_pull():
    """
    Pull release-specific translations from Transifex.
    """
    resources = find_release_resources()
    sh("i18n_tool transifex pull " + " ".join(resources))


def find_release_resources():
    """
    Validate the .tx/config file for release files, returning the resource names.

    For working with release files, the .tx/config file should have exactly
    two resources defined named "release-*".  Check that this is true.  If
    there's a problem, print messages about it.

293 294
    Returns a list of resource names, or raises ValueError if .tx/config
    doesn't have two resources.
295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318

    """
    # An entry in .tx/config for a release will look like this:
    #
    #    [edx-platform.release-dogwood]
    #    file_filter = conf/locale/<lang>/LC_MESSAGES/django.po
    #    source_file = conf/locale/en/LC_MESSAGES/django.po
    #    source_lang = en
    #    type = PO
    #
    #    [edx-platform.release-dogwood-js]
    #    file_filter = conf/locale/<lang>/LC_MESSAGES/djangojs.po
    #    source_file = conf/locale/en/LC_MESSAGES/djangojs.po
    #    source_lang = en
    #    type = PO

    rx_release = r"^\[([\w-]+\.release-[\w-]+)\]$"
    with open(".tx/config") as tx_config:
        resources = re.findall(rx_release, tx_config.read(), re.MULTILINE)

    if len(resources) == 2:
        return resources

    if len(resources) == 0:
319
        raise ValueError("You need two release-* resources defined to use this command.")
320
    else:
321 322
        msg = "Strange Transifex config! Found these release-* resources:\n" + "\n".join(resources)
        raise ValueError(msg)