From 07c412424b16b41d385f66b45e30cc03b793ae79 Mon Sep 17 00:00:00 2001 From: Lee Rowlands <lee.rowlands@previousnext.com.au> Date: Wed, 7 Aug 2019 08:26:49 +1000 Subject: [PATCH] Issue #2979964 by quietone, PieterDC: Migrate translations for D7 i18n taxonomy 'localized' terms --- ...d7_taxonomy_term_localized_translation.yml | 45 +++ .../content_translation.migrate_drupal.yml | 3 +- .../migrate_drupal/tests/fixtures/drupal7.php | 356 ++++++++++++++---- .../tests/src/Functional/d7/Upgrade7Test.php | 2 + .../source/d7/TermLocalizedTranslation.php | 109 ++++++ .../MigrateTermLocalizedTranslationTest.php | 135 +++++++ 6 files changed, 569 insertions(+), 81 deletions(-) create mode 100644 core/modules/content_translation/migrations/d7_taxonomy_term_localized_translation.yml create mode 100644 core/modules/taxonomy/src/Plugin/migrate/source/d7/TermLocalizedTranslation.php create mode 100644 core/modules/taxonomy/tests/src/Kernel/Migrate/d7/MigrateTermLocalizedTranslationTest.php diff --git a/core/modules/content_translation/migrations/d7_taxonomy_term_localized_translation.yml b/core/modules/content_translation/migrations/d7_taxonomy_term_localized_translation.yml new file mode 100644 index 000000000000..d0b238567e84 --- /dev/null +++ b/core/modules/content_translation/migrations/d7_taxonomy_term_localized_translation.yml @@ -0,0 +1,45 @@ +id: d7_taxonomy_term_localized_translation +label: Taxonomy localized term translations +migration_tags: + - Drupal 7 + - Content + - Multilingual +source: + plugin: d7_term_localized_translation + translations: true +process: + # If you are using this file to build a custom migration consider removing + # the tid field to allow incremental migrations. + tid: tid + # Use the language from the locales_target table. + langcode: ltlanguage + vid: + plugin: migration + migration: d7_taxonomy_vocabulary + source: vid + name: + - + plugin: callback + source: + - name_translated + - name + callable: array_filter + - + plugin: callback + callable: current + description: + - + plugin: callback + source: + - description_translated + - description + callable: array_filter + - + plugin: callback + callable: current +destination: + plugin: entity:taxonomy_term + translations: true +migration_dependencies: + required: + - d7_taxonomy_term diff --git a/core/modules/content_translation/migrations/state/content_translation.migrate_drupal.yml b/core/modules/content_translation/migrations/state/content_translation.migrate_drupal.yml index f560df5be763..13705b949c43 100644 --- a/core/modules/content_translation/migrations/state/content_translation.migrate_drupal.yml +++ b/core/modules/content_translation/migrations/state/content_translation.migrate_drupal.yml @@ -35,5 +35,6 @@ not_finished: i18n: content_translation # menu links. i18n_menu: content_translation - # localized. + # Migrate taxonomy term references + # https://www.drupal.org/project/drupal/issues/3035392 i18n_taxonomy: content_translation diff --git a/core/modules/migrate_drupal/tests/fixtures/drupal7.php b/core/modules/migrate_drupal/tests/fixtures/drupal7.php index dcdb0897d31d..87f234fc261e 100644 --- a/core/modules/migrate_drupal/tests/fixtures/drupal7.php +++ b/core/modules/migrate_drupal/tests/fixtures/drupal7.php @@ -4201,7 +4201,7 @@ 'storage_module' => 'field_sql_storage', 'storage_active' => '1', 'locked' => '0', - 'data' => 'a:7:{s:12:"translatable";i:0;s:12:"entity_types";a:0:{}s:8:"settings";a:3:{s:14:"allowed_values";a:1:{i:0;a:2:{s:10:"vocabulary";s:14:"vocablocalized";s:6:"parent";s:1:"0";}}s:21:"options_list_callback";s:29:"title_taxonomy_allowed_values";s:23:"entity_translation_sync";b:0;}s:7:"storage";a:5:{s:4:"type";s:17:"field_sql_storage";s:8:"settings";a:0:{}s:6:"module";s:17:"field_sql_storage";s:6:"active";s:1:"1";s:7:"details";a:1:{s:3:"sql";a:2:{s:18:"FIELD_LOAD_CURRENT";a:1:{s:31:"field_data_field_vocab_localize";a:1:{s:3:"tid";s:24:"field_vocab_localize_tid";}}s:19:"FIELD_LOAD_REVISION";a:1:{s:35:"field_revision_field_vocab_localize";a:1:{s:3:"tid";s:24:"field_vocab_localize_tid";}}}}}s:12:"foreign keys";a:1:{s:3:"tid";a:2:{s:5:"table";s:18:"taxonomy_term_data";s:7:"columns";a:1:{s:3:"tid";s:3:"tid";}}}s:7:"indexes";a:1:{s:3:"tid";a:1:{i:0;s:3:"tid";}}s:2:"id";s:2:"44";}', + 'data' => 'a:7:{s:12:"translatable";s:1:"0";s:12:"entity_types";a:0:{}s:8:"settings";a:3:{s:14:"allowed_values";a:1:{i:0;a:2:{s:10:"vocabulary";s:14:"vocablocalized";s:6:"parent";s:1:"0";}}s:21:"options_list_callback";s:28:"i18n_taxonomy_allowed_values";s:23:"entity_translation_sync";b:0;}s:7:"storage";a:5:{s:4:"type";s:17:"field_sql_storage";s:8:"settings";a:0:{}s:6:"module";s:17:"field_sql_storage";s:6:"active";s:1:"1";s:7:"details";a:1:{s:3:"sql";a:2:{s:18:"FIELD_LOAD_CURRENT";a:1:{s:31:"field_data_field_vocab_localize";a:1:{s:3:"tid";s:24:"field_vocab_localize_tid";}}s:19:"FIELD_LOAD_REVISION";a:1:{s:35:"field_revision_field_vocab_localize";a:1:{s:3:"tid";s:24:"field_vocab_localize_tid";}}}}}s:12:"foreign keys";a:1:{s:3:"tid";a:2:{s:5:"table";s:18:"taxonomy_term_data";s:7:"columns";a:1:{s:3:"tid";s:3:"tid";}}}s:7:"indexes";a:1:{s:3:"tid";a:1:{i:0;s:3:"tid";}}s:2:"id";s:2:"44";}', 'cardinality' => '1', 'translatable' => '0', 'deleted' => '0', @@ -4216,7 +4216,7 @@ 'storage_module' => 'field_sql_storage', 'storage_active' => '1', 'locked' => '0', - 'data' => 'a:7:{s:12:"translatable";i:0;s:12:"entity_types";a:0:{}s:8:"settings";a:3:{s:14:"allowed_values";a:1:{i:0;a:2:{s:10:"vocabulary";s:14:"vocabtranslate";s:6:"parent";s:1:"0";}}s:21:"options_list_callback";s:29:"title_taxonomy_allowed_values";s:23:"entity_translation_sync";b:0;}s:7:"storage";a:5:{s:4:"type";s:17:"field_sql_storage";s:8:"settings";a:0:{}s:6:"module";s:17:"field_sql_storage";s:6:"active";s:1:"1";s:7:"details";a:1:{s:3:"sql";a:2:{s:18:"FIELD_LOAD_CURRENT";a:1:{s:32:"field_data_field_vocab_translate";a:1:{s:3:"tid";s:25:"field_vocab_translate_tid";}}s:19:"FIELD_LOAD_REVISION";a:1:{s:36:"field_revision_field_vocab_translate";a:1:{s:3:"tid";s:25:"field_vocab_translate_tid";}}}}}s:12:"foreign keys";a:1:{s:3:"tid";a:2:{s:5:"table";s:18:"taxonomy_term_data";s:7:"columns";a:1:{s:3:"tid";s:3:"tid";}}}s:7:"indexes";a:1:{s:3:"tid";a:1:{i:0;s:3:"tid";}}s:2:"id";s:2:"45";}', + 'data' => 'a:7:{s:12:"translatable";s:1:"0";s:12:"entity_types";a:0:{}s:8:"settings";a:3:{s:14:"allowed_values";a:1:{i:0;a:2:{s:10:"vocabulary";s:14:"vocabtranslate";s:6:"parent";s:1:"0";}}s:21:"options_list_callback";s:28:"i18n_taxonomy_allowed_values";s:23:"entity_translation_sync";b:0;}s:7:"storage";a:5:{s:4:"type";s:17:"field_sql_storage";s:8:"settings";a:0:{}s:6:"module";s:17:"field_sql_storage";s:6:"active";s:1:"1";s:7:"details";a:1:{s:3:"sql";a:2:{s:18:"FIELD_LOAD_CURRENT";a:1:{s:32:"field_data_field_vocab_translate";a:1:{s:3:"tid";s:25:"field_vocab_translate_tid";}}s:19:"FIELD_LOAD_REVISION";a:1:{s:36:"field_revision_field_vocab_translate";a:1:{s:3:"tid";s:25:"field_vocab_translate_tid";}}}}}s:12:"foreign keys";a:1:{s:3:"tid";a:2:{s:5:"table";s:18:"taxonomy_term_data";s:7:"columns";a:1:{s:3:"tid";s:3:"tid";}}}s:7:"indexes";a:1:{s:3:"tid";a:1:{i:0;s:3:"tid";}}s:2:"id";s:2:"45";}', 'cardinality' => '1', 'translatable' => '0', 'deleted' => '0', @@ -4231,7 +4231,7 @@ 'storage_module' => 'field_sql_storage', 'storage_active' => '1', 'locked' => '0', - 'data' => 'a:7:{s:12:"translatable";i:0;s:12:"entity_types";a:0:{}s:8:"settings";a:3:{s:14:"allowed_values";a:1:{i:0;a:2:{s:10:"vocabulary";s:10:"vocabfixed";s:6:"parent";s:1:"0";}}s:21:"options_list_callback";s:29:"title_taxonomy_allowed_values";s:23:"entity_translation_sync";b:0;}s:7:"storage";a:5:{s:4:"type";s:17:"field_sql_storage";s:8:"settings";a:0:{}s:6:"module";s:17:"field_sql_storage";s:6:"active";s:1:"1";s:7:"details";a:1:{s:3:"sql";a:2:{s:18:"FIELD_LOAD_CURRENT";a:1:{s:28:"field_data_field_vocab_fixed";a:1:{s:3:"tid";s:21:"field_vocab_fixed_tid";}}s:19:"FIELD_LOAD_REVISION";a:1:{s:32:"field_revision_field_vocab_fixed";a:1:{s:3:"tid";s:21:"field_vocab_fixed_tid";}}}}}s:12:"foreign keys";a:1:{s:3:"tid";a:2:{s:5:"table";s:18:"taxonomy_term_data";s:7:"columns";a:1:{s:3:"tid";s:3:"tid";}}}s:7:"indexes";a:1:{s:3:"tid";a:1:{i:0;s:3:"tid";}}s:2:"id";s:2:"46";}', + 'data' => 'a:7:{s:12:"translatable";s:1:"0";s:12:"entity_types";a:0:{}s:8:"settings";a:3:{s:14:"allowed_values";a:1:{i:0;a:2:{s:10:"vocabulary";s:10:"vocabfixed";s:6:"parent";s:1:"0";}}s:21:"options_list_callback";s:28:"i18n_taxonomy_allowed_values";s:23:"entity_translation_sync";b:0;}s:7:"storage";a:5:{s:4:"type";s:17:"field_sql_storage";s:8:"settings";a:0:{}s:6:"module";s:17:"field_sql_storage";s:6:"active";s:1:"1";s:7:"details";a:1:{s:3:"sql";a:2:{s:18:"FIELD_LOAD_CURRENT";a:1:{s:28:"field_data_field_vocab_fixed";a:1:{s:3:"tid";s:21:"field_vocab_fixed_tid";}}s:19:"FIELD_LOAD_REVISION";a:1:{s:32:"field_revision_field_vocab_fixed";a:1:{s:3:"tid";s:21:"field_vocab_fixed_tid";}}}}}s:12:"foreign keys";a:1:{s:3:"tid";a:2:{s:5:"table";s:18:"taxonomy_term_data";s:7:"columns";a:1:{s:3:"tid";s:3:"tid";}}}s:7:"indexes";a:1:{s:3:"tid";a:1:{i:0;s:3:"tid";}}s:2:"id";s:2:"46";}', 'cardinality' => '1', 'translatable' => '0', 'deleted' => '0', @@ -16199,183 +16199,183 @@ )) ->values(array( 'lid' => '77', - 'textgroup' => 'field', - 'context' => 'comment_body:comment_node_article:label', - 'objectid' => 'comment_node_article', - 'type' => 'comment_body', - 'property' => 'label', - 'objectindex' => '0', + 'textgroup' => 'taxonomy', + 'context' => 'term:20:name', + 'objectid' => '20', + 'type' => 'term', + 'property' => 'name', + 'objectindex' => '20', 'format' => '', )) ->values(array( 'lid' => '78', - 'textgroup' => 'field', - 'context' => 'comment_body:comment_node_blog:label', - 'objectid' => 'comment_node_blog', - 'type' => 'comment_body', - 'property' => 'label', - 'objectindex' => '0', - 'format' => '', + 'textgroup' => 'taxonomy', + 'context' => 'term:20:description', + 'objectid' => '20', + 'type' => 'term', + 'property' => 'description', + 'objectindex' => '20', + 'format' => 'filtered_html', )) ->values(array( 'lid' => '79', 'textgroup' => 'taxonomy', - 'context' => 'vocabulary:1:name', - 'objectid' => '1', - 'type' => 'vocabulary', + 'context' => 'term:19:name', + 'objectid' => '19', + 'type' => 'term', 'property' => 'name', - 'objectindex' => '1', + 'objectindex' => '19', 'format' => '', )) ->values(array( 'lid' => '80', 'textgroup' => 'taxonomy', - 'context' => 'vocabulary:1:description', - 'objectid' => '1', - 'type' => 'vocabulary', + 'context' => 'term:19:description', + 'objectid' => '19', + 'type' => 'term', 'property' => 'description', - 'objectindex' => '1', - 'format' => '', + 'objectindex' => '19', + 'format' => 'filtered_html', )) ->values(array( 'lid' => '81', 'textgroup' => 'taxonomy', - 'context' => 'vocabulary:3:name', - 'objectid' => '3', + 'context' => 'vocabulary:2:name', + 'objectid' => '2', 'type' => 'vocabulary', 'property' => 'name', - 'objectindex' => '3', + 'objectindex' => '2', 'format' => '', )) ->values(array( 'lid' => '82', 'textgroup' => 'taxonomy', - 'context' => 'vocabulary:3:description', - 'objectid' => '3', + 'context' => 'vocabulary:2:description', + 'objectid' => '2', 'type' => 'vocabulary', 'property' => 'description', - 'objectindex' => '3', + 'objectindex' => '2', 'format' => '', )) ->values(array( 'lid' => '83', 'textgroup' => 'taxonomy', - 'context' => 'vocabulary:4:name', - 'objectid' => '4', + 'context' => 'vocabulary:1:name', + 'objectid' => '1', 'type' => 'vocabulary', 'property' => 'name', - 'objectindex' => '4', + 'objectindex' => '1', 'format' => '', )) ->values(array( 'lid' => '84', 'textgroup' => 'taxonomy', - 'context' => 'vocabulary:4:description', - 'objectid' => '4', + 'context' => 'vocabulary:1:description', + 'objectid' => '1', 'type' => 'vocabulary', 'property' => 'description', - 'objectindex' => '4', + 'objectindex' => '1', 'format' => '', )) ->values(array( 'lid' => '85', 'textgroup' => 'taxonomy', - 'context' => 'vocabulary:5:name', - 'objectid' => '5', + 'context' => 'vocabulary:3:name', + 'objectid' => '3', 'type' => 'vocabulary', 'property' => 'name', - 'objectindex' => '5', + 'objectindex' => '3', 'format' => '', )) ->values(array( 'lid' => '86', 'textgroup' => 'taxonomy', - 'context' => 'vocabulary:5:description', - 'objectid' => '5', + 'context' => 'vocabulary:3:description', + 'objectid' => '3', 'type' => 'vocabulary', 'property' => 'description', - 'objectindex' => '5', + 'objectindex' => '3', 'format' => '', )) ->values(array( 'lid' => '87', 'textgroup' => 'taxonomy', - 'context' => 'vocabulary:6:name', - 'objectid' => '6', + 'context' => 'vocabulary:7:name', + 'objectid' => '7', 'type' => 'vocabulary', 'property' => 'name', - 'objectindex' => '6', + 'objectindex' => '7', 'format' => '', )) ->values(array( 'lid' => '88', 'textgroup' => 'taxonomy', - 'context' => 'vocabulary:6:description', - 'objectid' => '6', + 'context' => 'vocabulary:7:description', + 'objectid' => '7', 'type' => 'vocabulary', 'property' => 'description', - 'objectindex' => '6', + 'objectindex' => '7', 'format' => '', )) ->values(array( 'lid' => '89', 'textgroup' => 'taxonomy', - 'context' => 'vocabulary:7:name', - 'objectid' => '7', + 'context' => 'vocabulary:5:name', + 'objectid' => '5', 'type' => 'vocabulary', 'property' => 'name', - 'objectindex' => '7', + 'objectindex' => '5', 'format' => '', )) ->values(array( 'lid' => '90', 'textgroup' => 'taxonomy', - 'context' => 'vocabulary:7:description', - 'objectid' => '7', + 'context' => 'vocabulary:5:description', + 'objectid' => '5', 'type' => 'vocabulary', 'property' => 'description', - 'objectindex' => '7', + 'objectindex' => '5', 'format' => '', )) ->values(array( 'lid' => '91', 'textgroup' => 'taxonomy', - 'context' => 'term:19:name', - 'objectid' => '19', - 'type' => 'term', + 'context' => 'vocabulary:6:name', + 'objectid' => '6', + 'type' => 'vocabulary', 'property' => 'name', - 'objectindex' => '19', + 'objectindex' => '6', 'format' => '', )) ->values(array( 'lid' => '92', 'textgroup' => 'taxonomy', - 'context' => 'term:19:description', - 'objectid' => '19', - 'type' => 'term', + 'context' => 'vocabulary:6:description', + 'objectid' => '6', + 'type' => 'vocabulary', 'property' => 'description', - 'objectindex' => '19', - 'format' => 'filtered_html', + 'objectindex' => '6', + 'format' => '', )) ->values(array( 'lid' => '93', 'textgroup' => 'taxonomy', - 'context' => 'term:20:name', - 'objectid' => '20', - 'type' => 'term', + 'context' => 'vocabulary:4:name', + 'objectid' => '4', + 'type' => 'vocabulary', 'property' => 'name', - 'objectindex' => '20', + 'objectindex' => '4', 'format' => '', )) ->values(array( 'lid' => '94', 'textgroup' => 'taxonomy', - 'context' => 'term:20:description', - 'objectid' => '20', - 'type' => 'term', + 'context' => 'vocabulary:4:description', + 'objectid' => '4', + 'type' => 'vocabulary', 'property' => 'description', - 'objectindex' => '20', - 'format' => 'filtered_html', + 'objectindex' => '4', + 'format' => '', )) ->values(array( 'lid' => '95', @@ -17397,6 +17397,26 @@ 'objectindex' => '0', 'format' => '', )) +->values(array( + 'lid' => '795', + 'textgroup' => 'field', + 'context' => 'comment_body:comment_node_article:label', + 'objectid' => 'comment_node_article', + 'type' => 'comment_body', + 'property' => 'label', + 'objectindex' => '0', + 'format' => '', +)) +->values(array( + 'lid' => '796', + 'textgroup' => 'field', + 'context' => 'comment_body:comment_node_blog:label', + 'objectid' => 'comment_node_blog', + 'type' => 'comment_body', + 'property' => 'label', + 'objectindex' => '0', + 'format' => '', +)) ->execute(); $connection->schema()->createTable('i18n_translation_set', array( 'fields' => array( @@ -18421,6 +18441,150 @@ 'context' => 'user:login:title', 'version' => '1', )) +->values(array( + 'lid' => '77', + 'location' => 'taxonomy:term:20:name', + 'textgroup' => 'taxonomy', + 'source' => 'DS9', + 'context' => 'term:20:name', + 'version' => '1', +)) +->values(array( + 'lid' => '78', + 'location' => 'taxonomy:term:20:description', + 'textgroup' => 'taxonomy', + 'source' => 'Terok Nor', + 'context' => 'term:20:description', + 'version' => '1', +)) +->values(array( + 'lid' => '79', + 'location' => 'taxonomy:term:19:name', + 'textgroup' => 'taxonomy', + 'source' => 'Jupiter Station', + 'context' => 'term:19:name', + 'version' => '1', +)) +->values(array( + 'lid' => '80', + 'location' => 'taxonomy:term:19:description', + 'textgroup' => 'taxonomy', + 'source' => 'Holographic research.', + 'context' => 'term:19:description', + 'version' => '1', +)) +->values(array( + 'lid' => '81', + 'location' => 'taxonomy:vocabulary:2:name', + 'textgroup' => 'taxonomy', + 'source' => 'Sujet de discussion', + 'context' => 'vocabulary:2:name', + 'version' => '1', +)) +->values(array( + 'lid' => '82', + 'location' => 'taxonomy:vocabulary:2:description', + 'textgroup' => 'taxonomy', + 'source' => 'Forum navigation vocabulary', + 'context' => 'vocabulary:2:description', + 'version' => '1', +)) +->values(array( + 'lid' => '83', + 'location' => 'taxonomy:vocabulary:1:name', + 'textgroup' => 'taxonomy', + 'source' => 'Tags', + 'context' => 'vocabulary:1:name', + 'version' => '1', +)) +->values(array( + 'lid' => '84', + 'location' => 'taxonomy:vocabulary:1:description', + 'textgroup' => 'taxonomy', + 'source' => 'Use tags to group articles on similar topics into categories.', + 'context' => 'vocabulary:1:description', + 'version' => '1', +)) +->values(array( + 'lid' => '85', + 'location' => 'taxonomy:vocabulary:3:name', + 'textgroup' => 'taxonomy', + 'source' => 'Test Vocabulary', + 'context' => 'vocabulary:3:name', + 'version' => '1', +)) +->values(array( + 'lid' => '86', + 'location' => 'taxonomy:vocabulary:3:description', + 'textgroup' => 'taxonomy', + 'source' => 'This is the vocabulary description', + 'context' => 'vocabulary:3:description', + 'version' => '1', +)) +->values(array( + 'lid' => '87', + 'location' => 'taxonomy:vocabulary:7:name', + 'textgroup' => 'taxonomy', + 'source' => 'VocabFixed', + 'context' => 'vocabulary:7:name', + 'version' => '1', +)) +->values(array( + 'lid' => '88', + 'location' => 'taxonomy:vocabulary:7:description', + 'textgroup' => 'taxonomy', + 'source' => 'Vocabulary fixed option', + 'context' => 'vocabulary:7:description', + 'version' => '1', +)) +->values(array( + 'lid' => '89', + 'location' => 'taxonomy:vocabulary:5:name', + 'textgroup' => 'taxonomy', + 'source' => 'VocabLocalized', + 'context' => 'vocabulary:5:name', + 'version' => '1', +)) +->values(array( + 'lid' => '90', + 'location' => 'taxonomy:vocabulary:5:description', + 'textgroup' => 'taxonomy', + 'source' => 'Vocabulary localize option', + 'context' => 'vocabulary:5:description', + 'version' => '1', +)) +->values(array( + 'lid' => '91', + 'location' => 'taxonomy:vocabulary:6:name', + 'textgroup' => 'taxonomy', + 'source' => 'VocabTranslate', + 'context' => 'vocabulary:6:name', + 'version' => '1', +)) +->values(array( + 'lid' => '92', + 'location' => 'taxonomy:vocabulary:6:description', + 'textgroup' => 'taxonomy', + 'source' => 'Vocabulary translate option', + 'context' => 'vocabulary:6:description', + 'version' => '1', +)) +->values(array( + 'lid' => '93', + 'location' => 'taxonomy:vocabulary:4:name', + 'textgroup' => 'taxonomy', + 'source' => 'vocabulary name clearly different than machine name and much longer than thirty two characters', + 'context' => 'vocabulary:4:name', + 'version' => '1', +)) +->values(array( + 'lid' => '94', + 'location' => 'taxonomy:vocabulary:4:description', + 'textgroup' => 'taxonomy', + 'source' => 'description of vocabulary name much longer than thirty two characters', + 'context' => 'vocabulary:4:description', + 'version' => '1', +)) ->execute(); $connection->schema()->createTable('locales_target', array( 'fields' => array( @@ -18501,6 +18665,22 @@ 'plural' => '0', 'i18n_status' => '0', )) +->values(array( + 'lid' => '77', + 'translation' => 'fr - DS9 (localized)', + 'language' => 'fr', + 'plid' => '0', + 'plural' => '0', + 'i18n_status' => '0', +)) +->values(array( + 'lid' => '78', + 'translation' => 'fr - Terok Nor (localized)', + 'language' => 'fr', + 'plid' => '0', + 'plural' => '0', + 'i18n_status' => '0', +)) ->values(array( 'lid' => '85', 'translation' => 'fr - VocabLocalized', @@ -18677,6 +18857,22 @@ 'plural' => '0', 'i18n_status' => '0', )) +->values(array( + 'lid' => '79', + 'translation' => 'Jupiter Station', + 'language' => 'is', + 'plid' => '0', + 'plural' => '0', + 'i18n_status' => '0', +)) +->values(array( + 'lid' => '80', + 'translation' => 'is - Holographic research. (localized)', + 'language' => 'is', + 'plid' => '0', + 'plural' => '0', + 'i18n_status' => '0', +)) ->values(array( 'lid' => '87', 'translation' => 'is - VocabTranslate', @@ -52532,29 +52728,29 @@ 'bootstrap' => '0', 'schema_version' => '-1', 'weight' => '0', - 'info' => 'a:12:{s:4:"name";s:24:"Synchronize translations";s:11:"description";s:73:"Synchronizes taxonomy and fields across translations of the same content.";s:12:"dependencies";a:2:{i:0;s:4:"i18n";i:1;s:11:"translation";}s:7:"package";s:35:"Multilingual - Internationalization";s:4:"core";s:3:"7.x";s:5:"files";a:5:{i:0;s:16:"i18n_sync.module";i:1;s:17:"i18n_sync.install";i:2;s:20:"i18n_sync.module.inc";i:3;s:18:"i18n_sync.node.inc";i:4;s:14:"i18n_sync.test";}s:7:"version";s:8:"7.x-1.26";s:7:"project";s:4:"i18n";s:9:"datestamp";s:10:"1534531985";s:5:"mtime";i:1534531985;s:3:"php";s:5:"5.2.4";s:9:"bootstrap";i:0;}', + 'info' => 'a:12:{s:4:"name";s:24:"Synchronize translations";s:11:"description";s:73:"Synchronizes taxonomy and fields across translations of the same content.";s:12:"dependencies";a:2:{i:0;s:4:"i18n";i:1;s:11:"translation";}s:7:"package";s:35:"Multilingual - Internationalization";s:4:"core";s:3:"7.x";s:5:"files";a:5:{i:0;s:16:"i18n_sync.module";i:1;s:17:"i18n_sync.install";i:2;s:20:"i18n_sync.module.inc";i:3;s:18:"i18n_sync.node.inc";i:4;s:14:"i18n_sync.test";}s:7:"version";s:8:"7.x-1.25";s:7:"project";s:4:"i18n";s:9:"datestamp";s:10:"1531342125";s:5:"mtime";i:1537747251;s:3:"php";s:5:"5.2.4";s:9:"bootstrap";i:0;}', )) ->values(array( 'filename' => 'sites/all/modules/i18n/i18n_taxonomy/i18n_taxonomy.module', 'name' => 'i18n_taxonomy', 'type' => 'module', 'owner' => '', - 'status' => '0', + 'status' => '1', 'bootstrap' => '0', - 'schema_version' => '-1', - 'weight' => '0', - 'info' => 'a:12:{s:4:"name";s:20:"Taxonomy translation";s:11:"description";s:30:"Enables multilingual taxonomy.";s:12:"dependencies";a:3:{i:0;s:8:"taxonomy";i:1;s:11:"i18n_string";i:2;s:16:"i18n_translation";}s:7:"package";s:35:"Multilingual - Internationalization";s:4:"core";s:3:"7.x";s:5:"files";a:4:{i:0;s:17:"i18n_taxonomy.inc";i:1;s:23:"i18n_taxonomy.pages.inc";i:2;s:23:"i18n_taxonomy.admin.inc";i:3;s:18:"i18n_taxonomy.test";}s:7:"version";s:8:"7.x-1.26";s:7:"project";s:4:"i18n";s:9:"datestamp";s:10:"1534531985";s:5:"mtime";i:1534531985;s:3:"php";s:5:"5.2.4";s:9:"bootstrap";i:0;}', + 'schema_version' => '7004', + 'weight' => '5', + 'info' => 'a:12:{s:4:"name";s:20:"Taxonomy translation";s:11:"description";s:30:"Enables multilingual taxonomy.";s:12:"dependencies";a:3:{i:0;s:8:"taxonomy";i:1;s:11:"i18n_string";i:2;s:16:"i18n_translation";}s:7:"package";s:35:"Multilingual - Internationalization";s:4:"core";s:3:"7.x";s:5:"files";a:4:{i:0;s:17:"i18n_taxonomy.inc";i:1;s:23:"i18n_taxonomy.pages.inc";i:2;s:23:"i18n_taxonomy.admin.inc";i:3;s:18:"i18n_taxonomy.test";}s:7:"version";s:8:"7.x-1.25";s:7:"project";s:4:"i18n";s:9:"datestamp";s:10:"1531342125";s:5:"mtime";i:1537747251;s:3:"php";s:5:"5.2.4";s:9:"bootstrap";i:0;}', )) ->values(array( 'filename' => 'sites/all/modules/i18n/i18n_translation/i18n_translation.module', 'name' => 'i18n_translation', 'type' => 'module', 'owner' => '', - 'status' => '0', + 'status' => '1', 'bootstrap' => '0', 'schema_version' => '-1', 'weight' => '0', - 'info' => 'a:12:{s:4:"name";s:16:"Translation sets";s:11:"description";s:47:"Simple translation sets API for generic objects";s:12:"dependencies";a:1:{i:0;s:4:"i18n";}s:7:"package";s:35:"Multilingual - Internationalization";s:4:"core";s:3:"7.x";s:5:"files";a:1:{i:0;s:20:"i18n_translation.inc";}s:7:"version";s:8:"7.x-1.26";s:7:"project";s:4:"i18n";s:9:"datestamp";s:10:"1534531985";s:5:"mtime";i:1534531985;s:3:"php";s:5:"5.2.4";s:9:"bootstrap";i:0;}', + 'info' => 'a:12:{s:4:"name";s:16:"Translation sets";s:11:"description";s:47:"Simple translation sets API for generic objects";s:12:"dependencies";a:1:{i:0;s:4:"i18n";}s:7:"package";s:35:"Multilingual - Internationalization";s:4:"core";s:3:"7.x";s:5:"files";a:1:{i:0;s:20:"i18n_translation.inc";}s:7:"version";s:8:"7.x-1.25";s:7:"project";s:4:"i18n";s:9:"datestamp";s:10:"1531342125";s:5:"mtime";i:1537747251;s:3:"php";s:5:"5.2.4";s:9:"bootstrap";i:0;}', )) ->values(array( 'filename' => 'sites/all/modules/i18n/i18n_user/i18n_user.module', diff --git a/core/modules/migrate_drupal_ui/tests/src/Functional/d7/Upgrade7Test.php b/core/modules/migrate_drupal_ui/tests/src/Functional/d7/Upgrade7Test.php index 865f11f2d029..7e7bd5502a48 100644 --- a/core/modules/migrate_drupal_ui/tests/src/Functional/d7/Upgrade7Test.php +++ b/core/modules/migrate_drupal_ui/tests/src/Functional/d7/Upgrade7Test.php @@ -185,6 +185,8 @@ protected function getMissingPaths() { 'i18n', 'i18n_field', 'i18n_string', + 'i18n_taxonomy', + 'i18n_translation', 'locale', 'variable', 'variable_realm', diff --git a/core/modules/taxonomy/src/Plugin/migrate/source/d7/TermLocalizedTranslation.php b/core/modules/taxonomy/src/Plugin/migrate/source/d7/TermLocalizedTranslation.php new file mode 100644 index 000000000000..0d1c47d27feb --- /dev/null +++ b/core/modules/taxonomy/src/Plugin/migrate/source/d7/TermLocalizedTranslation.php @@ -0,0 +1,109 @@ +<?php + +namespace Drupal\taxonomy\Plugin\migrate\source\d7; + +use Drupal\content_translation\Plugin\migrate\source\I18nQueryTrait; +use Drupal\migrate\Row; + +/** + * Gets i18n taxonomy terms from source database. + * + * @MigrateSource( + * id = "d7_term_localized_translation", + * source_module = "i18n_taxonomy" + * ) + */ +class TermLocalizedTranslation extends Term { + + use I18nQueryTrait; + + /** + * {@inheritdoc} + */ + public function query() { + // Ideally, the query would return rows for each language for each taxonomy + // term with the translations for both the name and description or just the + // name translation or just the description translation. That query quickly + // became complex and would be difficult to maintain. + // Therefore, build a query based on i18nstrings table where each row has + // the translation for only one property, either name or description. The + // method prepareRow() is then used to obtain the translation for the other + // property. + $query = parent::query(); + $query->addField('td', 'language', 'td.language'); + + // Add in the property, which is either name or description. + // Cast td.tid as char for PostgreSQL compatibility. + $query->leftJoin('i18n_string', 'i18n', 'CAST(td.tid AS CHAR(255)) = i18n.objectid'); + $query->condition('i18n.type', 'term'); + $query->isNotNull('i18n.lid'); + $query->addField('i18n', 'lid'); + $query->addField('i18n', 'property'); + + // Add in the translation for the property. + $query->innerJoin('locales_target', 'lt', 'i18n.lid = lt.lid'); + $query->addField('lt', 'language', 'lt.language'); + $query->addField('lt', 'translation'); + return $query; + } + + /** + * {@inheritdoc} + */ + public function prepareRow(Row $row) { + $language = $row->getSourceProperty('ltlanguage'); + $tid = $row->getSourceProperty('tid'); + + // If this row has been migrated it is a duplicate then skip it. + if ($this->idMap->lookupDestinationIds(['tid' => $tid, 'language' => $language])) { + return FALSE; + } + + // Save the translation for the property already in the row. + $property_in_row = $row->getSourceProperty('property'); + $row->setSourceProperty($property_in_row . '_translated', $row->getSourceProperty('translation')); + + // Get the translation for the property not already in the row and save it + // in the row. + $property_not_in_row = ($property_in_row == 'name') ? 'description' : 'name'; + + // Get the translation, if one exists, for the property not already in the + // row. + $query = $this->select('i18n_string', 'i18n') + ->fields('i18n', ['lid']) + ->condition('i18n.property', $property_not_in_row); + $query->leftJoin('locales_target', 'lt', 'i18n.lid = lt.lid'); + $query->condition('lt.language', $language); + $query->addField('lt', 'translation'); + $results = $query->execute()->fetchAssoc(); + if (!$results) { + $row->setSourceProperty($property_not_in_row . '_translated', NULL); + } + else { + $row->setSourceProperty($property_not_in_row . '_translated', $results['translation']); + } + parent::prepareRow($row); + } + + /** + * {@inheritdoc} + */ + public function fields() { + $fields = [ + 'language' => $this->t('Language for this term.'), + 'name_translated' => $this->t('Term name translation.'), + 'description_translated' => $this->t('Term description translation.'), + ]; + return parent::fields() + $fields; + } + + /** + * {@inheritdoc} + */ + public function getIds() { + $ids['language']['type'] = 'string'; + $ids['language']['alias'] = 'lt'; + return parent::getIds() + $ids; + } + +} diff --git a/core/modules/taxonomy/tests/src/Kernel/Migrate/d7/MigrateTermLocalizedTranslationTest.php b/core/modules/taxonomy/tests/src/Kernel/Migrate/d7/MigrateTermLocalizedTranslationTest.php new file mode 100644 index 000000000000..06da1ad1dff3 --- /dev/null +++ b/core/modules/taxonomy/tests/src/Kernel/Migrate/d7/MigrateTermLocalizedTranslationTest.php @@ -0,0 +1,135 @@ +<?php + +namespace Drupal\Tests\taxonomy\Kernel\Migrate\d7; + +use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase; +use Drupal\taxonomy\Entity\Term; +use Drupal\taxonomy\TermInterface; + +/** + * Tests migration of localized translated taxonomy terms. + * + * @group migrate_drupal_7 + */ +class MigrateTermLocalizedTranslationTest extends MigrateDrupal7TestBase { + + /** + * {@inheritdoc} + */ + public static $modules = [ + 'content_translation', + 'language', + // Required for translation migrations. + 'migrate_drupal_multilingual', + 'taxonomy', + 'text', + ]; + + /** + * The cached taxonomy tree items, keyed by vid and tid. + * + * @var array + */ + protected $treeData = []; + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + $this->installEntitySchema('taxonomy_term'); + $this->installConfig(static::$modules); + $this->executeMigrations([ + 'language', + 'd7_taxonomy_vocabulary', + 'd7_taxonomy_term', + 'd7_taxonomy_term_localized_translation', + ]); + } + + /** + * Validates a migrated term contains the expected values. + * + * @param int $id + * Entity ID to load and check. + * @param string $expected_language + * The language code for this term. + * @param string $expected_label + * The label the migrated entity should have. + * @param string $expected_vid + * The parent vocabulary the migrated entity should have. + * @param string $expected_description + * The description the migrated entity should have. + * @param string $expected_format + * The format the migrated entity should have. + * @param int $expected_weight + * The weight the migrated entity should have. + * @param array $expected_parents + * The parent terms the migrated entity should have. + * @param int $expected_field_integer_value + * The value the migrated entity field should have. + * @param int $expected_term_reference_tid + * The term reference ID the migrated entity field should have. + */ + protected function assertEntity($id, $expected_language, $expected_label, $expected_vid, $expected_description = '', $expected_format = NULL, $expected_weight = 0, array $expected_parents = [], $expected_field_integer_value = NULL, $expected_term_reference_tid = NULL) { + /** @var \Drupal\taxonomy\TermInterface $entity */ + $entity = Term::load($id); + $this->assertInstanceOf(TermInterface::class, $entity); + $this->assertSame($expected_language, $entity->language()->getId()); + $this->assertSame($expected_label, $entity->label()); + $this->assertSame($expected_vid, $entity->bundle()); + $this->assertSame($expected_description, $entity->getDescription()); + $this->assertSame($expected_format, $entity->getFormat()); + $this->assertSame($expected_weight, $entity->getWeight()); + $this->assertHierarchy($expected_vid, $id, $expected_parents); + } + + /** + * Asserts that a term is present in the tree storage, with the right parents. + * + * @param string $vid + * Vocabulary ID. + * @param int $tid + * ID of the term to check. + * @param array $parent_ids + * The expected parent term IDs. + */ + protected function assertHierarchy($vid, $tid, array $parent_ids) { + if (!isset($this->treeData[$vid])) { + $tree = \Drupal::entityTypeManager()->getStorage('taxonomy_term')->loadTree($vid); + $this->treeData[$vid] = []; + foreach ($tree as $item) { + $this->treeData[$vid][$item->tid] = $item; + } + } + + $this->assertArrayHasKey($tid, $this->treeData[$vid], "Term $tid exists in taxonomy tree"); + $term = $this->treeData[$vid][$tid]; + $this->assertEquals($parent_ids, array_filter($term->parents), "Term $tid has correct parents in taxonomy tree"); + } + + /** + * Tests the Drupal 6 i18n localized taxonomy term to Drupal 8 migration. + */ + public function testTranslatedLocalizedTaxonomyTerms() { + $this->assertEntity(19, 'en', 'Jupiter Station', 'vocablocalized', 'Holographic research.', 'filtered_html', '0', []); + $this->assertEntity(20, 'en', 'DS9', 'vocablocalized', 'Terok Nor', 'filtered_html', '0', []); + + /** @var \Drupal\taxonomy\TermInterface $entity */ + $entity = Term::load(19); + $this->assertFalse($entity->hasTranslation('fr')); + $this->assertTrue($entity->hasTranslation('is')); + $translation = $entity->getTranslation('is'); + $this->assertSame('Jupiter Station', $translation->label()); + $this->assertSame('is - Holographic research. (localized)', $translation->getDescription()); + + $entity = Term::load(20); + $this->assertFalse($entity->hasTranslation('is')); + $this->assertTrue($entity->hasTranslation('fr')); + $translation = $entity->getTranslation('fr'); + $this->assertSame('fr - DS9 (localized)', $translation->label()); + $this->assertSame('fr - Terok Nor (localized)', $translation->getDescription()); + $this->assertFALSE($entity->hasTranslation('is')); + } + +} -- GitLab