2
2
from __future__ import unicode_literals
3
3
4
4
import os
5
+ import sys
5
6
import codecs
6
7
import logging
7
8
@@ -18,7 +19,7 @@ def getParser(path):
18
19
from .cldr import get_plural_categories
19
20
from .transforms import Source
20
21
from .merge import merge_resource
21
- from .util import get_message
22
+ from .errors import NotSupportedError , UnreadableReferenceError
22
23
23
24
24
25
class MergeContext (object ):
@@ -79,6 +80,10 @@ def read_ftl_resource(self, path):
79
80
f = codecs .open (path , 'r' , 'utf8' )
80
81
try :
81
82
contents = f .read ()
83
+ except UnicodeDecodeError as err :
84
+ logger = logging .getLogger ('migrate' )
85
+ logger .warn ('Unable to read file {}: {}' .format (path , err ))
86
+ raise err
82
87
finally :
83
88
f .close ()
84
89
@@ -94,7 +99,7 @@ def read_ftl_resource(self, path):
94
99
logger = logging .getLogger ('migrate' )
95
100
for annot in annots :
96
101
msg = annot .message
97
- logger .warn (u 'Syntax error in {}: {}' .format (path , msg ))
102
+ logger .warn ('Syntax error in {}: {}' .format (path , msg ))
98
103
99
104
return ast
100
105
@@ -105,52 +110,33 @@ def read_legacy_resource(self, path):
105
110
# Transform the parsed result which is an iterator into a dict.
106
111
return {entity .key : entity .val for entity in parser }
107
112
108
- def add_reference (self , path , realpath = None ):
109
- """Add an FTL AST to this context's reference resources."""
110
- fullpath = os .path .join (self .reference_dir , realpath or path )
111
- try :
112
- ast = self .read_ftl_resource (fullpath )
113
- except IOError as err :
114
- logger = logging .getLogger ('migrate' )
115
- logger .error (u'Missing reference file: {}' .format (path ))
116
- raise err
117
- except UnicodeDecodeError as err :
118
- logger = logging .getLogger ('migrate' )
119
- logger .error (u'Error reading file {}: {}' .format (path , err ))
120
- raise err
121
- else :
122
- self .reference_resources [path ] = ast
113
+ def maybe_add_localization (self , path ):
114
+ """Add a localization resource to migrate translations from.
123
115
124
- def add_localization ( self , path ):
125
- """Add an existing localization resource .
116
+ Only legacy resources can be added as migration sources. The resource
117
+ may be missing on disk .
126
118
127
- If it's an FTL resource, add an FTL AST. Otherwise, it's a legacy
128
- resource. Use a compare-locales parser to create a dict of (key,
129
- string value) tuples.
119
+ Uses a compare-locales parser to create a dict of (key, string value)
120
+ tuples.
130
121
"""
131
- fullpath = os .path .join (self .localization_dir , path )
132
- if fullpath .endswith ('.ftl' ):
133
- try :
134
- ast = self .read_ftl_resource (fullpath )
135
- except IOError :
136
- logger = logging .getLogger ('migrate' )
137
- logger .warn (u'Missing localization file: {}' .format (path ))
138
- except UnicodeDecodeError as err :
139
- logger = logging .getLogger ('migrate' )
140
- logger .warn (u'Error reading file {}: {}' .format (path , err ))
141
- else :
142
- self .localization_resources [path ] = ast
122
+ if path .endswith ('.ftl' ):
123
+ error_message = (
124
+ 'Migrating translations from Fluent files is not supported '
125
+ '({})' .format (path ))
126
+ logging .getLogger ('migrate' ).error (error_message )
127
+ raise NotSupportedError (error_message )
128
+
129
+ try :
130
+ fullpath = os .path .join (self .localization_dir , path )
131
+ collection = self .read_legacy_resource (fullpath )
132
+ except IOError :
133
+ logger = logging .getLogger ('migrate' )
134
+ logger .warn ('Missing localization file: {}' .format (path ))
143
135
else :
144
- try :
145
- collection = self .read_legacy_resource (fullpath )
146
- except IOError :
147
- logger = logging .getLogger ('migrate' )
148
- logger .warn (u'Missing localization file: {}' .format (path ))
149
- else :
150
- self .localization_resources [path ] = collection
136
+ self .localization_resources [path ] = collection
151
137
152
- def add_transforms (self , path , transforms ):
153
- """Define transforms for path.
138
+ def add_transforms (self , path , reference , transforms ):
139
+ """Define transforms for path using reference as template .
154
140
155
141
Each transform is an extended FTL node with `Transform` nodes as some
156
142
values. Transforms are stored in their lazy AST form until
@@ -165,6 +151,22 @@ def get_sources(acc, cur):
165
151
acc .add ((cur .path , cur .key ))
166
152
return acc
167
153
154
+ refpath = os .path .join (self .reference_dir , reference )
155
+ try :
156
+ ast = self .read_ftl_resource (refpath )
157
+ except IOError as err :
158
+ error_message = 'Missing reference file: {}' .format (refpath )
159
+ logging .getLogger ('migrate' ).error (error_message )
160
+ raise UnreadableReferenceError (error_message )
161
+ except UnicodeDecodeError as err :
162
+ error_message = 'Error reading file {}: {}' .format (refpath , err )
163
+ logging .getLogger ('migrate' ).error (error_message )
164
+ raise UnreadableReferenceError (error_message )
165
+ else :
166
+ # The reference file will be used by the merge function as
167
+ # a template for serializing the merge results.
168
+ self .reference_resources [path ] = ast
169
+
168
170
for node in transforms :
169
171
# Scan `node` for `Source` nodes and collect the information they
170
172
# store into a set of dependencies.
@@ -175,17 +177,30 @@ def get_sources(acc, cur):
175
177
path_transforms = self .transforms .setdefault (path , [])
176
178
path_transforms += transforms
177
179
180
+ if path not in self .localization_resources :
181
+ fullpath = os .path .join (self .localization_dir , path )
182
+ try :
183
+ ast = self .read_ftl_resource (fullpath )
184
+ except IOError :
185
+ logger = logging .getLogger ('migrate' )
186
+ logger .info (
187
+ 'Localization file {} does not exist and '
188
+ 'it will be created' .format (path ))
189
+ except UnicodeDecodeError :
190
+ logger = logging .getLogger ('migrate' )
191
+ logger .warn (
192
+ 'Localization file {} will be re-created and some '
193
+ 'translations might be lost' .format (path ))
194
+ else :
195
+ self .localization_resources [path ] = ast
196
+
178
197
def get_source (self , path , key ):
179
- """Get an entity value from the localized source.
198
+ """Get an entity value from a localized legacy source.
180
199
181
200
Used by the `Source` transform.
182
201
"""
183
- if path .endswith ('.ftl' ):
184
- resource = self .localization_resources [path ]
185
- return get_message (resource .body , key )
186
- else :
187
- resource = self .localization_resources [path ]
188
- return resource .get (key , None )
202
+ resource = self .localization_resources [path ]
203
+ return resource .get (key , None )
189
204
190
205
def merge_changeset (self , changeset = None ):
191
206
"""Return a generator of FTL ASTs for the changeset.
@@ -200,10 +215,11 @@ def merge_changeset(self, changeset=None):
200
215
"""
201
216
202
217
if changeset is None :
203
- # Merge all known legacy translations.
218
+ # Merge all known legacy translations. Used in tests.
204
219
changeset = {
205
220
(path , key )
206
221
for path , strings in self .localization_resources .iteritems ()
222
+ if not path .endswith ('.ftl' )
207
223
for key in strings .iterkeys ()
208
224
}
209
225
@@ -240,10 +256,15 @@ def in_changeset(ident):
240
256
self , reference , current , transforms , in_changeset
241
257
)
242
258
243
- # If none of the transforms is in the given changeset, the merged
244
- # snapshot is identical to the current translation. We compare
245
- # JSON trees rather then use filtering by `in_changeset` to account
246
- # for translations removed from `reference`.
259
+ # Skip this path if the merged snapshot is identical to the current
260
+ # state of the localization file. This may happen when:
261
+ #
262
+ # - none of the transforms is in the changset, or
263
+ # - all messages which would be migrated by the context's
264
+ # transforms already exist in the current state.
265
+ #
266
+ # We compare JSON trees rather then use filtering by `in_changeset`
267
+ # to account for translations removed from `reference`.
247
268
if snapshot .to_json () == current .to_json ():
248
269
continue
249
270
0 commit comments