Skip to content

Commit 5fe65e1

Browse files
committed
Never migrate partial translations
Partial translations may break the AST because they produce `TextElements` with `None` values. For now, explicitly skip any transforms which depend on at least one missing legacy string. In the future we might be able to allow partial migrations in some situations, like migrating a subset of attributes of a message. See https://bugzilla.mozilla.org/show_bug.cgi?id=1321271.
1 parent 01be516 commit 5fe65e1

File tree

4 files changed

+314
-18
lines changed

4 files changed

+314
-18
lines changed

fluent/migrate/context.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -324,9 +324,11 @@ def in_changeset(ident):
324324
if len(message_deps) == 0:
325325
return True
326326

327-
# If the intersection of the dependencies and the current
328-
# changeset is non-empty, merge this message.
329-
return message_deps & changeset
327+
# Make sure all the dependencies are present in the current
328+
# changeset. Partial migrations are not currently supported.
329+
# See https://bugzilla.mozilla.org/show_bug.cgi?id=1321271
330+
available_deps = message_deps & changeset
331+
return message_deps == available_deps
330332

331333
# Merge legacy translations with the existing ones using the
332334
# reference as a template.

tests/migrate/test_context.py

Lines changed: 288 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ def test_missing_reference_file(self):
251251

252252

253253
@unittest.skipUnless(compare_locales, 'compare-locales requried')
254-
class TestEmptyLocalization(unittest.TestCase):
254+
class TestMissingLocalizationFiles(unittest.TestCase):
255255
def setUp(self):
256256
# Silence all logging.
257257
logging.disable(logging.CRITICAL)
@@ -266,7 +266,40 @@ def tearDown(self):
266266
# Resume logging.
267267
logging.disable(logging.NOTSET)
268268

269-
def test_all_localization_missing(self):
269+
def test_missing_file(self):
270+
self.ctx.add_transforms('aboutDownloads.ftl', 'aboutDownloads.ftl', [
271+
FTL.Message(
272+
id=FTL.Identifier('title'),
273+
value=COPY(
274+
'aboutDownloads.dtd',
275+
'aboutDownloads.title'
276+
)
277+
),
278+
FTL.Message(
279+
id=FTL.Identifier('header'),
280+
value=COPY(
281+
'missing.dtd',
282+
'missing'
283+
)
284+
),
285+
])
286+
287+
expected = {
288+
'aboutDownloads.ftl': ftl_resource_to_json('''
289+
# This Source Code Form is subject to the terms of the Mozilla Public
290+
# License, v. 2.0. If a copy of the MPL was not distributed with this
291+
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
292+
293+
title = Pobrane pliki
294+
''')
295+
}
296+
297+
self.assertDictEqual(
298+
to_json(self.ctx.merge_changeset()),
299+
expected
300+
)
301+
302+
def test_all_files_missing(self):
270303
pattern = ('No localization files were found')
271304
with self.assertRaisesRegexp(EmptyLocalizationError, pattern):
272305
self.ctx.add_transforms('existing.ftl', 'existing.ftl', [
@@ -281,7 +314,9 @@ def test_all_localization_missing(self):
281314

282315

283316
@unittest.skipUnless(compare_locales, 'compare-locales requried')
284-
class TestIncompleteLocalization(unittest.TestCase):
317+
class TestMissingLocalizationStrings(unittest.TestCase):
318+
maxDiff = None
319+
285320
def setUp(self):
286321
# Silence all logging.
287322
logging.disable(logging.CRITICAL)
@@ -296,20 +331,128 @@ def tearDown(self):
296331
# Resume logging.
297332
logging.disable(logging.NOTSET)
298333

299-
def test_missing_localization_file(self):
300-
self.ctx.add_transforms('existing.ftl', 'existing.ftl', [
334+
def test_missing_string_in_simple_value(self):
335+
self.ctx.add_transforms('aboutDownloads.ftl', 'aboutDownloads.ftl', [
301336
FTL.Message(
302-
id=FTL.Identifier('foo'),
337+
id=FTL.Identifier('title'),
303338
value=COPY(
304-
'existing.dtd',
305-
'foo'
339+
'aboutDownloads.dtd',
340+
'missing'
306341
)
307342
),
343+
])
344+
345+
self.assertDictEqual(
346+
to_json(self.ctx.merge_changeset()),
347+
{}
348+
)
349+
350+
def test_missing_string_in_only_variant(self):
351+
self.ctx.add_transforms('aboutDownloads.ftl', 'aboutDownloads.ftl', [
308352
FTL.Message(
309-
id=FTL.Identifier('bar'),
310-
value=COPY(
311-
'missing.dtd',
312-
'bar'
353+
id=FTL.Identifier('title'),
354+
value=FTL.Pattern(
355+
elements=[
356+
FTL.Placeable(
357+
expression=FTL.SelectExpression(
358+
expression=FTL.CallExpression(
359+
callee=FTL.Identifier('PLATFORM')
360+
),
361+
variants=[
362+
FTL.Variant(
363+
key=FTL.VariantName('other'),
364+
default=True,
365+
value=COPY(
366+
'aboutDownloads.dtd',
367+
'missing'
368+
)
369+
),
370+
]
371+
)
372+
),
373+
]
374+
)
375+
),
376+
])
377+
378+
self.assertDictEqual(
379+
to_json(self.ctx.merge_changeset()),
380+
{}
381+
)
382+
383+
def test_missing_string_in_all_variants(self):
384+
self.ctx.add_transforms('aboutDownloads.ftl', 'aboutDownloads.ftl', [
385+
FTL.Message(
386+
id=FTL.Identifier('title'),
387+
value=FTL.Pattern(
388+
elements=[
389+
FTL.Placeable(
390+
expression=FTL.SelectExpression(
391+
expression=FTL.CallExpression(
392+
callee=FTL.Identifier('PLATFORM')
393+
),
394+
variants=[
395+
FTL.Variant(
396+
key=FTL.VariantName('windows'),
397+
default=False,
398+
value=COPY(
399+
'aboutDownloads.dtd',
400+
'missing.windows'
401+
)
402+
),
403+
FTL.Variant(
404+
key=FTL.VariantName('other'),
405+
default=True,
406+
value=COPY(
407+
'aboutDownloads.dtd',
408+
'missing.other'
409+
)
410+
),
411+
]
412+
)
413+
),
414+
]
415+
)
416+
),
417+
])
418+
419+
self.assertDictEqual(
420+
to_json(self.ctx.merge_changeset()),
421+
{}
422+
)
423+
424+
def test_missing_string_in_one_of_variants(self):
425+
self.ctx.add_transforms('aboutDownloads.ftl', 'aboutDownloads.ftl', [
426+
FTL.Message(
427+
id=FTL.Identifier('title'),
428+
value=FTL.Pattern(
429+
elements=[
430+
FTL.Placeable(
431+
expression=FTL.SelectExpression(
432+
expression=FTL.CallExpression(
433+
callee=FTL.Identifier('PLATFORM')
434+
),
435+
variants=[
436+
FTL.Variant(
437+
key=FTL.VariantName('windows'),
438+
default=False,
439+
value=COPY(
440+
'aboutDownloads.dtd',
441+
'aboutDownloads.title'
442+
)
443+
),
444+
FTL.Variant(
445+
key=FTL.VariantName('other'),
446+
default=True,
447+
value=COPY(
448+
'aboutDownloads.dtd',
449+
'missing.other'
450+
)
451+
),
452+
]
453+
)
454+
),
455+
]
313456
)
314457
),
315458
])
@@ -319,6 +462,139 @@ def test_missing_localization_file(self):
319462
{}
320463
)
321464

465+
def test_missing_string_in_all_attributes(self):
466+
self.ctx.add_transforms('aboutDownloads.ftl', 'aboutDownloads.ftl', [
467+
FTL.Message(
468+
id=FTL.Identifier('title'),
469+
attributes=[
470+
FTL.Attribute(
471+
FTL.Identifier('one'),
472+
COPY(
473+
'aboutDownloads.dtd',
474+
'missing.one'
475+
)
476+
),
477+
FTL.Attribute(
478+
FTL.Identifier('two'),
479+
COPY(
480+
'aboutDownloads.dtd',
481+
'missing.two'
482+
)
483+
),
484+
]
485+
),
486+
])
487+
488+
self.assertDictEqual(
489+
to_json(self.ctx.merge_changeset()),
490+
{}
491+
)
492+
493+
def test_missing_string_in_one_of_attributes(self):
494+
self.ctx.add_transforms('aboutDownloads.ftl', 'aboutDownloads.ftl', [
495+
FTL.Message(
496+
id=FTL.Identifier('title'),
497+
attributes=[
498+
FTL.Attribute(
499+
FTL.Identifier('title'),
500+
COPY(
501+
'aboutDownloads.dtd',
502+
'aboutDownloads.title'
503+
)
504+
),
505+
FTL.Attribute(
506+
FTL.Identifier('missing'),
507+
COPY(
508+
'aboutDownloads.dtd',
509+
'missing'
510+
)
511+
),
512+
]
513+
),
514+
])
515+
516+
self.assertDictEqual(
517+
to_json(self.ctx.merge_changeset()),
518+
{}
519+
)
520+
521+
def test_missing_string_in_only_attribute(self):
522+
self.ctx.add_transforms('aboutDownloads.ftl', 'aboutDownloads.ftl', [
523+
FTL.Message(
524+
id=FTL.Identifier('title'),
525+
attributes=[
526+
FTL.Attribute(
527+
FTL.Identifier('one'),
528+
COPY(
529+
'aboutDownloads.dtd',
530+
'missing'
531+
)
532+
),
533+
]
534+
),
535+
])
536+
537+
self.assertDictEqual(
538+
to_json(self.ctx.merge_changeset()),
539+
{}
540+
)
541+
542+
def test_missing_string_in_all_attributes(self):
543+
self.ctx.add_transforms('aboutDownloads.ftl', 'aboutDownloads.ftl', [
544+
FTL.Message(
545+
id=FTL.Identifier('title'),
546+
attributes=[
547+
FTL.Attribute(
548+
FTL.Identifier('one'),
549+
COPY(
550+
'aboutDownloads.dtd',
551+
'missing.one'
552+
)
553+
),
554+
FTL.Attribute(
555+
FTL.Identifier('two'),
556+
COPY(
557+
'aboutDownloads.dtd',
558+
'missing.two'
559+
)
560+
),
561+
]
562+
),
563+
])
564+
565+
self.assertDictEqual(
566+
to_json(self.ctx.merge_changeset()),
567+
{}
568+
)
569+
570+
def test_missing_string_in_one_of_attributes(self):
571+
self.ctx.add_transforms('aboutDownloads.ftl', 'aboutDownloads.ftl', [
572+
FTL.Message(
573+
id=FTL.Identifier('title'),
574+
attributes=[
575+
FTL.Attribute(
576+
FTL.Identifier('title'),
577+
COPY(
578+
'aboutDownloads.dtd',
579+
'aboutDownloads.title'
580+
)
581+
),
582+
FTL.Attribute(
583+
FTL.Identifier('missing'),
584+
COPY(
585+
'aboutDownloads.dtd',
586+
'missing'
587+
)
588+
),
589+
]
590+
),
591+
])
592+
593+
self.assertDictEqual(
594+
to_json(self.ctx.merge_changeset()),
595+
{}
596+
)
597+
322598

323599
@unittest.skipUnless(compare_locales, 'compare-locales requried')
324600
class TestExistingTarget(unittest.TestCase):
@@ -360,7 +636,6 @@ def test_existing_target_ftl_missing_string(self):
360636
''')
361637
}
362638

363-
self.maxDiff = None
364639
self.assertDictEqual(
365640
to_json(self.ctx.merge_changeset()),
366641
expected
@@ -399,7 +674,6 @@ def test_existing_target_ftl_existing_string(self):
399674
''')
400675
}
401676

402-
self.maxDiff = None
403677
self.assertDictEqual(
404678
to_json(self.ctx.merge_changeset()),
405679
expected

0 commit comments

Comments
 (0)