Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

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

# Authors: 

#   Jason Gerard DeRose <jderose@redhat.com> 

# 

# Copyright (C) 2009  Red Hat 

# see file 'COPYING' for use and warranty contextrmation 

# 

# This program is free software; you can redistribute it and/or modify 

# it under the terms of the GNU General Public License as published by 

# the Free Software Foundation, either version 3 of the License, or 

# (at your option) any later version. 

# 

# This program 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 this program.  If not, see <http://www.gnu.org/licenses/>. 

 

""" 

Defers gettext translation till request time. 

 

IPA presents some tricky gettext challenges.  On the one hand, most translatable 

message are defined as class attributes on the plugins, which means these get 

evaluated at module-load time.  But on the other hand, each request to the 

server can be in a different locale, so the actual translation must not occur 

till request time. 

 

The `text` module provides a mechanism for for deferred gettext translation.  It 

was designed to: 

 

    1. Allow translatable strings to be marked with the usual ``_()`` and 

       ``ngettext()`` functions so that standard tools like xgettext can still 

       be used 

 

    2. Allow programmers to mark strings in a natural way without burdening them 

       with details of the deferred translation mechanism 

 

A typical plugin will use the deferred translation like this: 

 

>>> from ipalib import Command, _, ngettext 

>>> class my_plugin(Command): 

...     my_string = _('Hello, %(name)s.') 

...     my_plural = ngettext('%(count)d goose', '%(count)d geese', 0) 

... 

 

With normal gettext usage, the *my_string* and *my_plural* message would be 

translated at module-load-time when your ``my_plugin`` class is defined.  This 

would mean that all message are translated in the locale of the server rather 

than the locale of the request. 

 

However, the ``_()`` function above is actually a `GettextFactory` instance, 

which when called returns a `Gettext` instance.  A `Gettext` instance stores the 

message to be translated, and the gettext domain and localedir, but it doesn't 

perform the translation till `Gettext.__unicode__()` is called.  For example: 

 

>>> my_plugin.my_string 

Gettext('Hello, %(name)s.', domain='ipa', localedir=None) 

>>> unicode(my_plugin.my_string) 

u'Hello, %(name)s.' 

 

Translation can also be performed via the `Gettext.__mod__()` convenience 

method.  For example, these two are equivalent: 

 

>>> my_plugin.my_string % dict(name='Joe') 

u'Hello, Joe.' 

>>> unicode(my_plugin.my_string) % dict(name='Joe')  # Long form 

u'Hello, Joe.' 

 

Similar to ``_()``, the ``ngettext()`` function above is actually an 

`NGettextFactory` instance, which when called returns an `NGettext` instance. 

An `NGettext` instance stores the singular and plural messages, and the gettext 

domain and localedir, but it doesn't perform the translation till 

`NGettext.__call__()` is called.  For example: 

 

>>> my_plugin.my_plural 

NGettext('%(count)d goose', '%(count)d geese', domain='ipa', localedir=None) 

>>> my_plugin.my_plural(1) 

u'%(count)d goose' 

>>> my_plugin.my_plural(2) 

u'%(count)d geese' 

 

Translation can also be performed via the `NGettext.__mod__()` convenience 

method.  For example, these two are equivalent: 

 

>>> my_plugin.my_plural % dict(count=1) 

u'1 goose' 

>>> my_plugin.my_plural(1) % dict(count=1)  # Long form 

u'1 goose' 

 

Lastly, 3rd-party plugins can create factories bound to a different gettext 

domain.  The default domain is ``'ipa'``, which is also the domain of the 

standard ``ipalib._()`` and ``ipalib.ngettext()`` factories.  But 3rd-party 

plugins can create their own factories like this: 

 

>>> from ipalib import GettextFactory, NGettextFactory 

>>> _ = GettextFactory(domain='ipa_foo') 

>>> ngettext = NGettextFactory(domain='ipa_foo') 

>>> class foo(Command): 

...     msg1 = _('Foo!') 

...     msg2 = ngettext('%(count)d bar', '%(count)d bars', 0) 

... 

 

Notice that these messages are bound to the ``'ipa_foo'`` domain: 

 

>>> foo.msg1 

Gettext('Foo!', domain='ipa_foo', localedir=None) 

>>> foo.msg2 

NGettext('%(count)d bar', '%(count)d bars', domain='ipa_foo', localedir=None) 

 

For additional details, see `GettextFactory` and `Gettext`, and for plural 

forms, see `NGettextFactory` and `NGettext`. 

""" 

 

import threading 

import locale 

import gettext 

from request import context 

 

 

def create_translation(key): 

    assert key not in context.__dict__ 

    (domain, localedir) = key 

    translation = gettext.translation(domain, 

        localedir=localedir, 

        languages=getattr(context, 'languages', None), 

        fallback=True, 

    ) 

    context.__dict__[key] = translation 

    return translation 

 

 

class LazyText(object): 

    """ 

    Base class for deferred translation. 

 

    This class is not used directly.  See the `Gettext` and `NGettext` 

    subclasses. 

    """ 

 

    __slots__ = ('domain', 'localedir', 'key', 'args') 

 

    def __init__(self, domain=None, localedir=None): 

        """ 

        Initialize. 

 

        :param domain: The gettext domain in which this message will be 

            translated, e.g. ``'ipa'`` or ``'ipa_3rd_party'``; default is 

            ``None`` 

        :param localedir: The directory containing the gettext translations, 

            e.g. ``'/usr/share/locale/'``; default is ``None``, in which case 

            gettext will use the default system locale directory. 

        """ 

        self.domain = domain 

        self.localedir = localedir 

        self.key = (domain, localedir) 

        self.args = None 

 

    def __eq__(self, other): 

        """ 

        Return ``True`` if this instances is equal to *other*. 

 

        Note that this method cannot be used on the `LazyText` base class itself 

        as subclasses must define an *args* instance attribute. 

        """ 

        if type(other) is not self.__class__: 

            return False 

        return self.args == other.args 

 

    def __ne__(self, other): 

        """ 

        Return ``True`` if this instances is not equal to *other*. 

 

        Note that this method cannot be used on the `LazyText` base class itself 

        as subclasses must define an *args* instance attribute. 

        """ 

        return not self.__eq__(other) 

 

 

class Gettext(LazyText): 

    """ 

    Deferred translation using ``gettext.ugettext()``. 

 

    Normally the `Gettext` class isn't used directly and instead is created via 

    a `GettextFactory` instance.  However, for illustration, we can create one 

    like this: 

 

    >>> msg = Gettext('Hello, %(name)s.') 

 

    When you create a `Gettext` instance, the message is stored on the *msg* 

    attribute: 

 

    >>> msg.msg 

    'Hello, %(name)s.' 

 

    No translation is performed till `Gettext.__unicode__()` is called.  This 

    will translate *msg* using ``gettext.ugettext()``, which will return the 

    translated string as a Python ``unicode`` instance.  For example: 

 

    >>> unicode(msg) 

    u'Hello, %(name)s.' 

 

    `Gettext.__unicode__()` should be called at request time, which in a 

    nutshell means it should be called from within your plugin's 

    ``Command.execute()`` method.  `Gettext.__unicode__()` will perform the 

    translation based on the locale of the current request. 

 

    `Gettext.__mod__()` is a convenience method for Python "percent" string 

    formatting.  It will translate your message using `Gettext.__unicode__()` 

    and then perform the string substitution on the translated message.  For 

    example, these two are equivalent: 

 

    >>> msg % dict(name='Joe') 

    u'Hello, Joe.' 

    >>> unicode(msg) % dict(name='Joe')  # Long form 

    u'Hello, Joe.' 

 

    See `GettextFactory` for additional details.  If you need to pick between 

    singular and plural form, use `NGettext` instances via the 

    `NGettextFactory`. 

    """ 

 

    __slots__ = ('msg') 

 

    def __init__(self, msg, domain=None, localedir=None): 

        super(Gettext, self).__init__(domain, localedir) 

        self.msg = msg 

        self.args = (msg, domain, localedir) 

 

    def __repr__(self): 

        return '%s(%r, domain=%r, localedir=%r)' % (self.__class__.__name__, 

            self.msg, self.domain, self.localedir) 

 

    def __unicode__(self): 

        """ 

        Translate this message and return as a ``unicode`` instance. 

        """ 

        if self.key in context.__dict__: 

            g = context.__dict__[self.key].ugettext 

        else: 

            g = create_translation(self.key).ugettext 

        return g(self.msg) 

 

    def __json__(self): 

        return self.__unicode__() 

 

    def __mod__(self, kw): 

        return self.__unicode__() % kw 

 

 

class FixMe(Gettext): 

    """ 

    Non-translated place-holder for UI labels. 

 

    `FixMe` is a subclass of `Gettext` and is used for automatically created 

    place-holder labels.  It generally behaves exactly like `Gettext` except no 

    translation is ever performed. 

 

    `FixMe` allows programmers to get plugins working without first filling in 

    all the labels that will ultimately be required, while at the same time it 

    creates conspicuous looking UI labels that remind the programmer to 

    "fix-me!".  For example, the typical usage would be something like this: 

 

    >>> class Plugin(object): 

    ...     label = None 

    ...     def __init__(self): 

    ...         self.name = self.__class__.__name__ 

    ...         if self.label is None: 

    ...             self.label = FixMe(self.name + '.label') 

    ...         assert isinstance(self.label, Gettext) 

    ... 

    >>> class user(Plugin): 

    ...     pass # Oops, we didn't set user.label yet 

    ... 

    >>> u = user() 

    >>> u.label 

    FixMe('user.label') 

 

    Note that as `FixMe` is a subclass of `Gettext`, is passes the above type 

    check using ``isinstance()``. 

 

    Calling `FixMe.__unicode__()` performs no translation, but instead returns 

    said conspicuous looking label: 

 

    >>> unicode(u.label) 

    u'<user.label>' 

 

    For more examples of how `FixMe` is used, see `ipalib.parameters`. 

    """ 

 

    __slots__ = tuple() 

 

    def __repr__(self): 

        return '%s(%r)' % (self.__class__.__name__, self.msg) 

 

    def __unicode__(self): 

        return u'<%s>' % self.msg 

 

 

class NGettext(LazyText): 

    """ 

    Deferred translation for plural forms using ``gettext.ungettext()``. 

 

    Normally the `NGettext` class isn't used directly and instead is created via 

    a `NGettextFactory` instance.  However, for illustration, we can create one 

    like this: 

 

    >>> msg = NGettext('%(count)d goose', '%(count)d geese') 

 

    When you create an `NGettext` instance, the singular and plural forms of 

    your message are stored on the *singular* and *plural* instance attributes: 

 

    >>> msg.singular 

    '%(count)d goose' 

    >>> msg.plural 

    '%(count)d geese' 

 

    The translation and number selection isn't performed till 

    `NGettext.__call__()` is called.  This will translate and pick the correct 

    number using ``gettext.ungettext()``.  As a callable, an `NGettext` instance 

    takes a single argument, an integer specifying the count.  For example: 

 

    >>> msg(0) 

    u'%(count)d geese' 

    >>> msg(1) 

    u'%(count)d goose' 

    >>> msg(2) 

    u'%(count)d geese' 

 

    `NGettext.__mod__()` is a convenience method for Python "percent" string 

    formatting.  It can only be used if your substitution ``dict`` contains the 

    count in a ``'count'`` item.  For example: 

 

    >>> msg % dict(count=0) 

    u'0 geese' 

    >>> msg % dict(count=1) 

    u'1 goose' 

    >>> msg % dict(count=2) 

    u'2 geese' 

 

    Alternatively, these longer forms have the same effect as the three examples 

    above: 

 

    >>> msg(0) % dict(count=0) 

    u'0 geese' 

    >>> msg(1) % dict(count=1) 

    u'1 goose' 

    >>> msg(2) % dict(count=2) 

    u'2 geese' 

 

    A ``KeyError`` is raised if your substitution ``dict`` doesn't have a 

    ``'count'`` item.  For example: 

 

    >>> msg2 = NGettext('%(num)d goose', '%(num)d geese') 

    >>> msg2 % dict(num=0) 

    Traceback (most recent call last): 

      ... 

    KeyError: 'count' 

 

    However, in this case you can still use the longer, explicit form for string 

    substitution: 

 

    >>> msg2(0) % dict(num=0) 

    u'0 geese' 

 

    See `NGettextFactory` for additional details. 

    """ 

 

    __slots__ = ('singular', 'plural') 

 

    def __init__(self, singular, plural, domain=None, localedir=None): 

        super(NGettext, self).__init__(domain, localedir) 

        self.singular = singular 

        self.plural = plural 

        self.args = (singular, plural, domain, localedir) 

 

    def __repr__(self): 

        return '%s(%r, %r, domain=%r, localedir=%r)' % (self.__class__.__name__, 

            self.singular, self.plural, self.domain, self.localedir) 

 

    def __mod__(self, kw): 

        count = kw['count'] 

        return self(count) % kw 

 

    def __call__(self, count): 

        if self.key in context.__dict__: 

            ng = context.__dict__[self.key].ungettext 

        else: 

            ng = create_translation(self.key).ungettext 

        return ng(self.singular, self.plural, count) 

 

 

class GettextFactory(object): 

    """ 

    Factory for creating ``_()`` functions. 

 

    A `GettextFactory` allows you to mark translatable messages that are 

    evaluated at initialization time, but deferred their actual translation till 

    request time. 

 

    When you create a `GettextFactory` you can provide a specific gettext 

    *domain* and *localedir*.  By default the *domain* will be ``'ipa'`` and 

    the *localedir* will be ``None``.  Both are available via instance 

    attributes of the same name.  For example: 

 

    >>> _ = GettextFactory() 

    >>> _.domain 

    'ipa' 

    >>> _.localedir is None 

    True 

 

    When the *localedir* is ``None``, gettext will use the default system 

    localedir (typically ``'/usr/share/locale/'``).  In general, you should 

    **not** provide a *localedir*... it is intended only to support in-tree 

    testing. 

 

    Third party plugins will most likely want to use a different gettext 

    *domain*.  For example: 

 

    >>> _ = GettextFactory(domain='ipa_3rd_party') 

    >>> _.domain 

    'ipa_3rd_party' 

 

    When you call your `GettextFactory` instance, it will return a `Gettext` 

    instance associated with the same *domain* and *localedir*.  For example: 

 

    >>> my_msg = _('Hello world') 

    >>> my_msg.domain 

    'ipa_3rd_party' 

    >>> my_msg.localedir is None 

    True 

 

    The message isn't translated till `Gettext.__unicode__()` is called, which 

    should be done during each request.  See the `Gettext` class for additional 

    details. 

    """ 

 

    def __init__(self, domain='ipa', localedir=None): 

        """ 

        Initialize. 

 

        :param domain: The gettext domain in which this message will be 

            translated, e.g. ``'ipa'`` or ``'ipa_3rd_party'``; default is 

            ``'ipa'`` 

        :param localedir: The directory containing the gettext translations, 

            e.g. ``'/usr/share/locale/'``; default is ``None``, in which case 

            gettext will use the default system locale directory. 

        """ 

        self.domain = domain 

        self.localedir = localedir 

 

    def __repr__(self): 

        return '%s(domain=%r, localedir=%r)' % (self.__class__.__name__, 

            self.domain, self.localedir) 

 

    def __call__(self, msg): 

        return Gettext(msg, self.domain, self.localedir) 

 

 

class NGettextFactory(GettextFactory): 

    """ 

    Factory for creating ``ngettext()`` functions. 

 

    `NGettextFactory` is similar to `GettextFactory`, except `NGettextFactory` 

    is for plural forms. 

 

    So that standard tools like xgettext can find your plural forms, you should 

    reference your `NGettextFactory` instance using a variable named 

    *ngettext*.  For example: 

 

    >>> ngettext = NGettextFactory() 

    >>> ngettext 

    NGettextFactory(domain='ipa', localedir=None) 

 

    When you call your `NGettextFactory` instance to create a deferred 

    translation, you provide the *singular* message, the *plural* message, and 

    a dummy *count*.  An `NGettext` instance will be returned.  For example: 

 

    >>> my_msg = ngettext('%(count)d goose', '%(count)d geese', 0) 

    >>> my_msg 

    NGettext('%(count)d goose', '%(count)d geese', domain='ipa', localedir=None) 

 

    The *count* is ignored (because the translation is deferred), but you should 

    still provide it so parsing tools aren't confused.  For consistency, it is 

    recommended to always provide ``0`` for the *count*. 

 

    See `NGettext` for details on how the deferred translation is later 

    performed.  See `GettextFactory` for details on setting a different gettext 

    *domain* (likely needed for 3rd-party plugins). 

    """ 

 

    def __call__(self, singular, plural, count): 

        return NGettext(singular, plural, self.domain, self.localedir) 

 

 

# Process wide factories: 

_ = GettextFactory() 

ngettext = NGettextFactory() 

ugettext = _