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
|
# Copyright 2003, 2004, 2005, 2006 Vladimir Prus
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt)
# This module support GNU gettext internationalization utilities.
#
# It provides two main target rules: 'gettext.catalog', used for
# creating machine-readable catalogs from translations files, and
# 'gettext.update', used for update translation files from modified
# sources.
#
# To add i18n support to your application you should follow these
# steps.
#
# - Decide on a file name which will contain translations and
# what main target name will be used to update it. For example::
#
# gettext.update update-russian : russian.po a.cpp my_app ;
#
# - Create the initial translation file by running::
#
# bjam update-russian
#
# - Edit russian.po. For example, you might change fields like LastTranslator.
#
# - Create a main target for final message catalog::
#
# gettext.catalog russian : russian.po ;
#
# The machine-readable catalog will be updated whenever you update
# "russian.po". The "russian.po" file will be updated only on explicit
# request. When you're ready to update translations, you should
#
# - Run::
#
# bjam update-russian
#
# - Edit "russian.po" in appropriate editor.
#
# The next bjam run will convert "russian.po" into machine-readable form.
#
# By default, translations are marked by 'i18n' call. The 'gettext.keyword'
# feature can be used to alter this.
import targets ;
import property-set ;
import virtual-target ;
import "class" : new ;
import project ;
import type ;
import generators ;
import errors ;
import feature : feature ;
import toolset : flags ;
import regex ;
.path = "" ;
# Initializes the gettext module.
rule init ( path ? # Path where all tools are located. If not specified,
# they should be in PATH.
)
{
if $(.initialized) && $(.path) != $(path)
{
errors.error "Attempt to reconfigure with different path" ;
}
.initialized = true ;
if $(path)
{
.path = $(path)/ ;
}
}
# Creates a main target 'name', which, when updated, will cause
# file 'existing-translation' to be updated with translations
# extracted from 'sources'. It's possible to specify main target
# in sources --- it which case all target from dependency graph
# of those main targets will be scanned, provided they are of
# appropricate type. The 'gettext.types' feature can be used to
# control the types.
#
# The target will be updated only if explicitly requested on the
# command line.
rule update ( name : existing-translation sources + : requirements * )
{
local project = [ project.current ] ;
targets.main-target-alternative
[ new typed-target $(name) : $(project) : gettext.UPDATE :
$(existing-translation) $(sources)
: [ targets.main-target-requirements $(requirements) : $(project) ]
] ;
$(project).mark-target-as-explicit $(name) ;
}
# The human editable source, containing translation.
type.register gettext.PO : po ;
# The machine readable message catalog.
type.register gettext.catalog : mo ;
# Intermediate type produce by extracting translations from
# sources.
type.register gettext.POT : pot ;
# Pseudo type used to invoke update-translations generator
type.register gettext.UPDATE ;
# Identifies the keyword that should be used when scanning sources.
# Default: i18n
feature gettext.keyword : : free ;
# Contains space-separated list of sources types which should be scanned.
# Default: "C CPP"
feature gettext.types : : free ;
generators.register-standard gettext.compile : gettext.PO : gettext.catalog ;
class update-translations-generator : generator
{
import regex : split ;
import property-set ;
rule __init__ ( * : * )
{
generator.__init__ $(1) : $(2) : $(3) : $(4) : $(5) : $(6) : $(7) : $(8) : $(9) ;
}
# The rule should be called with at least two sources. The first source
# is the translation (.po) file to update. The remaining sources are targets
# which should be scanned for new messages. All sources files for those targets
# will be found and passed to the 'xgettext' utility, which extracts the
# messages for localization. Those messages will be merged to the .po file.
rule run ( project name ? : property-set : sources * : multiple ? )
{
local types = [ $(property-set).get <gettext.types> ] ;
types ?= "C CPP" ;
types = [ regex.split $(types) " " ] ;
local keywords = [ $(property-set).get <gettext.keyword> ] ;
property-set = [ property-set.create $(keywords:G=<gettext.keyword>) ] ;
# First deterime the list of sources that must be scanned for
# messages.
local all-sources ;
# CONSIDER: I'm not sure if the logic should be the same as for 'stage':
# i.e. following dependency properties as well.
for local s in $(sources[2-])
{
all-sources += [ virtual-target.traverse $(s) : : include-sources ] ;
}
local right-sources ;
for local s in $(all-sources)
{
if [ $(s).type ] in $(types)
{
right-sources += $(s) ;
}
}
local .constructed ;
if $(right-sources)
{
# Create the POT file, which will contain list of messages extracted
# from the sources.
local extract =
[ new action $(right-sources) : gettext.extract : $(property-set) ] ;
local new-messages = [ new file-target $(name) : gettext.POT
: $(project) : $(extract) ] ;
# Create a notfile target which will update the existing translation file
# with new messages.
local a = [ new action $(sources[1]) $(new-messages)
: gettext.update-po-dispatch ] ;
local r = [ new notfile-target $(name) : $(project) : $(a) ] ;
.constructed = [ virtual-target.register $(r) ] ;
}
else
{
errors.error "No source could be scanned by gettext tools" ;
}
return $(.constructed) ;
}
}
generators.register [ new update-translations-generator gettext.update : : gettext.UPDATE ] ;
flags gettext.extract KEYWORD <gettext.keyword> ;
actions extract
{
$(.path)xgettext -k$(KEYWORD:E=i18n) -o $(<) $(>)
}
# Does realy updating of po file. The tricky part is that
# we're actually updating one of the sources:
# $(<) is the NOTFILE target we're updating
# $(>[1]) is the PO file to be really updated.
# $(>[2]) is the PO file created from sources.
#
# When file to be updated does not exist (during the
# first run), we need to copy the file created from sources.
# In all other cases, we need to update the file.
rule update-po-dispatch
{
NOCARE $(>[1]) ;
gettext.create-po $(<) : $(>) ;
gettext.update-po $(<) : $(>) ;
_ on $(<) = " " ;
ok on $(<) = "" ;
EXISTING_PO on $(<) = $(>[1]) ;
}
# Due to fancy interaction of existing and updated, this rule can be called with
# one source, in which case we copy the lonely source into EXISTING_PO, or with
# two sources, in which case the action body expands to nothing. I'd really like
# to have "missing" action modifier.
actions quietly existing updated create-po bind EXISTING_PO
{
cp$(_)"$(>[1])"$(_)"$(EXISTING_PO)"$($(>[2]:E=ok))
}
actions updated update-po bind EXISTING_PO
{
$(.path)msgmerge$(_)-U$(_)"$(EXISTING_PO)"$(_)"$(>[1])"
}
actions gettext.compile
{
$(.path)msgfmt -o $(<) $(>)
}
IMPORT $(__name__) : update : : gettext.update ;
|