jekyll-l10n 1.7.1 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +44 -0
- data/lib/jekyll-l10n/configuration/schema.rb +308 -0
- data/lib/jekyll-l10n/configuration.rb +165 -0
- data/lib/jekyll-l10n/constants.rb +45 -10
- data/lib/jekyll-l10n/errors.rb +7 -0
- data/lib/jekyll-l10n/extraction/compendium_merger.rb +9 -11
- data/lib/jekyll-l10n/extraction/compendium_translator.rb +27 -11
- data/lib/jekyll-l10n/extraction/config_loader.rb +28 -58
- data/lib/jekyll-l10n/extraction/coverage_report.rb +129 -0
- data/lib/jekyll-l10n/extraction/dom_text_extractor.rb +2 -31
- data/lib/jekyll-l10n/extraction/extractor.rb +72 -65
- data/lib/jekyll-l10n/extraction/html_string_extractor.rb +24 -10
- data/lib/jekyll-l10n/extraction/logger.rb +6 -5
- data/lib/jekyll-l10n/extraction/result_saver.rb +5 -10
- data/lib/jekyll-l10n/instrumentation.rb +172 -139
- data/lib/jekyll-l10n/jekyll/generator.rb +47 -61
- data/lib/jekyll-l10n/jekyll/localized_page.rb +27 -49
- data/lib/jekyll-l10n/jekyll/post_write_html_reprocessor.rb +53 -48
- data/lib/jekyll-l10n/jekyll/post_write_processor.rb +2 -1
- data/lib/jekyll-l10n/jekyll/regeneration_checker.rb +70 -50
- data/lib/jekyll-l10n/jekyll/url_filter.rb +13 -13
- data/lib/jekyll-l10n/po_file/entry_parser.rb +155 -0
- data/lib/jekyll-l10n/po_file/fuzzy_matcher.rb +67 -53
- data/lib/jekyll-l10n/po_file/loader.rb +20 -11
- data/lib/jekyll-l10n/po_file/manager.rb +32 -17
- data/lib/jekyll-l10n/po_file/merger.rb +2 -1
- data/lib/jekyll-l10n/po_file/path_builder.rb +11 -1
- data/lib/jekyll-l10n/po_file/reader.rb +40 -508
- data/lib/jekyll-l10n/po_file/writer.rb +31 -16
- data/lib/jekyll-l10n/translation/block_text_extractor.rb +3 -7
- data/lib/jekyll-l10n/translation/html_translator.rb +136 -157
- data/lib/jekyll-l10n/translation/libre_translate_languages.rb +73 -0
- data/lib/jekyll-l10n/translation/libre_translator.rb +134 -78
- data/lib/jekyll-l10n/translation/page_translation_loader.rb +13 -29
- data/lib/jekyll-l10n/translation/request_slots.rb +69 -0
- data/lib/jekyll-l10n/translation/translation_progress.rb +77 -0
- data/lib/jekyll-l10n/translation/translator.rb +49 -78
- data/lib/jekyll-l10n/utils/baseurl_normalizer.rb +108 -0
- data/lib/jekyll-l10n/utils/debug_logger.rb +4 -51
- data/lib/jekyll-l10n/utils/error_handler.rb +9 -24
- data/lib/jekyll-l10n/utils/external_link_icon_preserver.rb +5 -62
- data/lib/jekyll-l10n/utils/html_parser.rb +24 -31
- data/lib/jekyll-l10n/utils/html_text_utils.rb +146 -91
- data/lib/jekyll-l10n/utils/logger_formatter.rb +6 -4
- data/lib/jekyll-l10n/utils/msgstr_validator.rb +74 -0
- data/lib/jekyll-l10n/utils/page_locales_config.rb +165 -223
- data/lib/jekyll-l10n/utils/po_entry_converter.rb +0 -43
- data/lib/jekyll-l10n/utils/site_config_accessor.rb +82 -42
- data/lib/jekyll-l10n/utils/translation_resolver.rb +56 -6
- data/lib/jekyll-l10n/utils/url_path_builder.rb +5 -27
- data/lib/jekyll-l10n/utils/url_transformer.rb +5 -11
- data/lib/jekyll-l10n/version.rb +1 -1
- data/lib/jekyll-l10n.rb +67 -176
- metadata +12 -3
|
@@ -4,7 +4,6 @@ require 'fileutils'
|
|
|
4
4
|
require_relative '../po_file/manager'
|
|
5
5
|
require_relative '../po_file/fuzzy_matcher'
|
|
6
6
|
require_relative '../utils/page_locales_config'
|
|
7
|
-
require_relative '../utils/site_config_accessor'
|
|
8
7
|
|
|
9
8
|
module Jekyll
|
|
10
9
|
module L10n
|
|
@@ -35,8 +34,6 @@ module Jekyll
|
|
|
35
34
|
# @param site [Jekyll::Site] Jekyll site object
|
|
36
35
|
def initialize(site)
|
|
37
36
|
@site = site
|
|
38
|
-
with_locales_data = SiteConfigAccessor.extract_locales_data(@site)
|
|
39
|
-
@site_config = PageLocalesConfig.new({ 'with_locales_data' => with_locales_data })
|
|
40
37
|
end
|
|
41
38
|
|
|
42
39
|
# Merge page-specific PO files into compendia for all locales.
|
|
@@ -49,8 +46,11 @@ module Jekyll
|
|
|
49
46
|
# @param config [PageLocalesConfig] Localization configuration with locales list
|
|
50
47
|
# @return [void]
|
|
51
48
|
def merge_compendia(po_manager, config)
|
|
49
|
+
# Fuzzy scores depend only on the two English strings, and every
|
|
50
|
+
# locale compares the same new strings, so they are scored once per run.
|
|
51
|
+
@fuzzy_scores = {}
|
|
52
52
|
config.locales.each do |locale|
|
|
53
|
-
process_locale(locale, po_manager
|
|
53
|
+
process_locale(locale, po_manager)
|
|
54
54
|
end
|
|
55
55
|
end
|
|
56
56
|
|
|
@@ -58,9 +58,8 @@ module Jekyll
|
|
|
58
58
|
|
|
59
59
|
# Process a single locale: merge existing compendium with new page translations
|
|
60
60
|
# Skips writing when translations are unchanged to avoid unnecessary file rewrites
|
|
61
|
-
def process_locale(locale, po_manager
|
|
62
|
-
|
|
63
|
-
existing_compendium = load_existing_compendium(compendium_path)
|
|
61
|
+
def process_locale(locale, po_manager)
|
|
62
|
+
existing_compendium = load_existing_compendium(po_manager.compendium_path(locale))
|
|
64
63
|
merged = po_manager.merge_po_files(locale)
|
|
65
64
|
|
|
66
65
|
combined = build_combined_hash(existing_compendium)
|
|
@@ -70,7 +69,7 @@ module Jekyll
|
|
|
70
69
|
combined_entries = format_compendium_entries(combined)
|
|
71
70
|
po_manager.save_compendium(locale, combined_entries)
|
|
72
71
|
end
|
|
73
|
-
cleanup_locale_directory(locale
|
|
72
|
+
cleanup_locale_directory(po_manager.locale_dir(locale))
|
|
74
73
|
end
|
|
75
74
|
|
|
76
75
|
# Check if compendium content is unchanged (same msgids and msgstrs)
|
|
@@ -146,7 +145,7 @@ module Jekyll
|
|
|
146
145
|
end
|
|
147
146
|
|
|
148
147
|
def resolve_new_entry(combined, msgid, entry, fuzzy_candidates, matched_old_msgids)
|
|
149
|
-
match = PoFuzzyMatcher.find_match(msgid, fuzzy_candidates)
|
|
148
|
+
match = PoFuzzyMatcher.find_match(msgid, fuzzy_candidates, scores: @fuzzy_scores)
|
|
150
149
|
if match
|
|
151
150
|
combined[msgid] = create_fuzzy_entry(entry, match)
|
|
152
151
|
matched_old_msgids << match[:msgid]
|
|
@@ -195,8 +194,7 @@ module Jekyll
|
|
|
195
194
|
|
|
196
195
|
# IMPORTANT: Remove locale-specific directory after compendium merge
|
|
197
196
|
# This cleans up page-specific PO files that have been merged into the compendium
|
|
198
|
-
def cleanup_locale_directory(
|
|
199
|
-
locale_dir = File.join(@site.source, config.locales_dir, locale)
|
|
197
|
+
def cleanup_locale_directory(locale_dir)
|
|
200
198
|
FileUtils.rm_rf(locale_dir) if File.directory?(locale_dir)
|
|
201
199
|
end
|
|
202
200
|
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative '../constants'
|
|
4
|
+
require_relative '../instrumentation'
|
|
3
5
|
require_relative '../po_file/manager'
|
|
4
6
|
require_relative '../utils/page_locales_config'
|
|
5
7
|
require_relative '../utils/po_entry_converter'
|
|
6
|
-
require_relative '../utils/site_config_accessor'
|
|
7
8
|
require_relative '../utils/logger_formatter'
|
|
8
9
|
require_relative '../translation/libre_translator'
|
|
9
10
|
|
|
@@ -33,18 +34,19 @@ module Jekyll
|
|
|
33
34
|
# @param site [Jekyll::Site] Jekyll site object
|
|
34
35
|
def initialize(site)
|
|
35
36
|
@site = site
|
|
36
|
-
with_locales_data = SiteConfigAccessor.extract_locales_data(@site)
|
|
37
|
-
@site_config = PageLocalesConfig.new({ 'with_locales_data' => with_locales_data })
|
|
38
37
|
end
|
|
39
38
|
|
|
40
39
|
# Translate compendia for all configured locales.
|
|
41
40
|
#
|
|
42
41
|
# Checks if LibreTranslate is enabled. If so, for each configured locale,
|
|
43
42
|
# loads the compendium PO file, identifies untranslated entries, sends them
|
|
44
|
-
# to LibreTranslate API, and saves the updated file.
|
|
43
|
+
# to LibreTranslate API, and saves the updated file. Locales are
|
|
44
|
+
# translated at once; their requests share libretranslate_concurrency.
|
|
45
45
|
#
|
|
46
46
|
# @param config [PageLocalesConfig] Localization configuration
|
|
47
47
|
# @return [void]
|
|
48
|
+
# @raise [LibreTranslator::TranslationError] If a locale failed, once
|
|
49
|
+
# every locale has finished; the locales that finished are saved
|
|
48
50
|
def translate_compendia(config)
|
|
49
51
|
po_manager = PoFileManager.new(@site, config.locales_dir)
|
|
50
52
|
translate_compendia_for_locale(po_manager, config)
|
|
@@ -59,15 +61,29 @@ module Jekyll
|
|
|
59
61
|
log_translation_start(config)
|
|
60
62
|
translator = LibreTranslator.new(config)
|
|
61
63
|
|
|
62
|
-
config.locales.
|
|
63
|
-
|
|
64
|
+
threads = config.locales.map do |locale|
|
|
65
|
+
Instrumentation.traced_thread do
|
|
66
|
+
Thread.current.report_on_exception = false
|
|
67
|
+
process_single_locale(locale, translator, po_manager)
|
|
68
|
+
end
|
|
64
69
|
end
|
|
70
|
+
join_locales(threads)
|
|
65
71
|
|
|
66
72
|
log_translation_complete(config)
|
|
67
73
|
end
|
|
68
74
|
|
|
69
|
-
def
|
|
70
|
-
|
|
75
|
+
def join_locales(threads)
|
|
76
|
+
first_error = nil
|
|
77
|
+
threads.each do |thread|
|
|
78
|
+
thread.join
|
|
79
|
+
rescue StandardError => e
|
|
80
|
+
first_error ||= e
|
|
81
|
+
end
|
|
82
|
+
raise first_error if first_error
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def process_single_locale(locale, translator, po_manager)
|
|
86
|
+
compendium_path = po_manager.compendium_path(locale)
|
|
71
87
|
LoggerFormatter.debug_if_enabled('CompendiumTranslator',
|
|
72
88
|
"Processing compendium file: #{compendium_path}")
|
|
73
89
|
return unless File.exist?(compendium_path)
|
|
@@ -119,17 +135,17 @@ module Jekyll
|
|
|
119
135
|
|
|
120
136
|
def log_translation_start(config)
|
|
121
137
|
locales = config.locales.join(', ')
|
|
122
|
-
Jekyll.logger.info
|
|
138
|
+
Jekyll.logger.info Constants::LOG_PREFIX,
|
|
123
139
|
"Starting LibreTranslate translation for locales: #{locales}"
|
|
124
140
|
end
|
|
125
141
|
|
|
126
142
|
def log_translation_complete(config)
|
|
127
143
|
locales = config.locales.join(', ')
|
|
128
|
-
Jekyll.logger.info
|
|
144
|
+
Jekyll.logger.info Constants::LOG_PREFIX,
|
|
129
145
|
"LibreTranslate translation complete for locales: #{locales}"
|
|
130
146
|
end
|
|
131
147
|
|
|
132
|
-
private :translate_compendia_for_locale, :process_single_locale,
|
|
148
|
+
private :translate_compendia_for_locale, :join_locales, :process_single_locale,
|
|
133
149
|
:log_compendia_enabled_check, :log_compendium_stats,
|
|
134
150
|
:log_translation_complete_for_locale, :log_compendium_saved,
|
|
135
151
|
:count_empty_entries, :log_translation_start, :log_translation_complete
|
|
@@ -1,24 +1,18 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative '../configuration'
|
|
3
4
|
require_relative '../utils/page_locales_config'
|
|
4
5
|
require_relative '../utils/locale_url_formatter'
|
|
5
6
|
require_relative '../utils/site_config_accessor'
|
|
6
7
|
|
|
7
8
|
module Jekyll
|
|
8
9
|
module L10n
|
|
9
|
-
#
|
|
10
|
+
# Finds the settings for a built HTML file during extraction.
|
|
10
11
|
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
# Key responsibilities:
|
|
17
|
-
# * Match generated HTML files to Jekyll site pages
|
|
18
|
-
# * Validate that extraction is enabled for a file
|
|
19
|
-
# * Load page-specific extraction configuration
|
|
20
|
-
# * Identify localized page variants to skip
|
|
21
|
-
# * Extract CSS selectors for element exclusion
|
|
12
|
+
# Extraction works on output files, but settings belong to pages. This
|
|
13
|
+
# class matches an output file back to its source page and returns that
|
|
14
|
+
# page's merged settings. It also tells the Extractor which files to skip:
|
|
15
|
+
# the localized copies under locale folders such as `es/`.
|
|
22
16
|
#
|
|
23
17
|
# @example
|
|
24
18
|
# loader = ExtractionConfigLoader.new(site, '_site')
|
|
@@ -26,22 +20,16 @@ module Jekyll
|
|
|
26
20
|
# config = loader.load_page_config(file_path)
|
|
27
21
|
# end
|
|
28
22
|
class ExtractionConfigLoader
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
# @param site [Jekyll::Site] Jekyll site object
|
|
32
|
-
# @param dest [String] Destination build directory
|
|
23
|
+
# @param site [Jekyll::Site] The Jekyll site
|
|
24
|
+
# @param dest [String] The site destination directory
|
|
33
25
|
def initialize(site, dest)
|
|
34
26
|
@site = site
|
|
35
27
|
@dest = dest
|
|
36
28
|
end
|
|
37
29
|
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
-
# for that page in the configuration.
|
|
42
|
-
#
|
|
43
|
-
# @param file_path [String] Path to file to check
|
|
44
|
-
# @return [Boolean] True if file matches a page with extraction enabled
|
|
30
|
+
# @param file_path [String] Path of a built HTML file
|
|
31
|
+
# @return [Boolean] true if the file comes from a localized page with at
|
|
32
|
+
# least one locale and `extract_on_build` not set to false
|
|
45
33
|
def valid_for_extraction?(file_path)
|
|
46
34
|
page_config = find_page_config_for_file(file_path)
|
|
47
35
|
return false unless page_config
|
|
@@ -50,50 +38,37 @@ module Jekyll
|
|
|
50
38
|
config.enabled? && config.extract_on_build?
|
|
51
39
|
end
|
|
52
40
|
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
# Finds the Jekyll page matching this file and returns its localization
|
|
56
|
-
# configuration wrapped in PageLocalesConfig.
|
|
57
|
-
#
|
|
58
|
-
# @param file_path [String] Path to file
|
|
59
|
-
# @return [PageLocalesConfig] Localization configuration for the page
|
|
41
|
+
# @param file_path [String] Path of a built HTML file
|
|
42
|
+
# @return [PageLocalesConfig] Settings of the page that produced the file
|
|
60
43
|
def load_page_config(file_path)
|
|
61
44
|
page_config = find_page_config_for_file(file_path)
|
|
62
45
|
PageLocalesConfig.new(page_config)
|
|
63
46
|
end
|
|
64
47
|
|
|
65
|
-
#
|
|
66
|
-
#
|
|
67
|
-
# Matches a generated file path to a Jekyll page with extraction enabled.
|
|
68
|
-
# Returns the page's front matter data, or nil if no match found.
|
|
48
|
+
# Finds the localized page whose output is this file and returns its data
|
|
49
|
+
# with merged `with_locales_data` (see SiteConfigAccessor.effective_page_data).
|
|
69
50
|
#
|
|
70
|
-
# @param file_path [String] Path
|
|
71
|
-
# @return [Hash, nil]
|
|
51
|
+
# @param file_path [String] Path of a built HTML file
|
|
52
|
+
# @return [Hash, nil] The page data, or nil when no localized page matches
|
|
72
53
|
def find_page_config_for_file(file_path)
|
|
73
54
|
@site.pages.each do |page|
|
|
74
|
-
next unless page
|
|
55
|
+
next unless Configuration.localizable?(page)
|
|
75
56
|
|
|
76
57
|
page_output = page.output_ext ? page.destination('') : page.destination('/')
|
|
77
58
|
next unless file_path.end_with?(page_output.sub(%r{/$}, '/index.html'))
|
|
78
59
|
|
|
79
|
-
return page
|
|
60
|
+
return SiteConfigAccessor.effective_page_data(page)
|
|
80
61
|
end
|
|
81
62
|
|
|
82
63
|
nil
|
|
83
64
|
end
|
|
84
65
|
|
|
85
|
-
#
|
|
86
|
-
#
|
|
87
|
-
#
|
|
88
|
-
# shouldn't be re-extracted (extraction happens on original pages only).
|
|
89
|
-
# Since commit 3d2d4cd, country-subtag locales (e.g. es_ES) are written
|
|
90
|
-
# to BCP 47 hyphenated, lowercased URL directories (e.g. es-es/). Each
|
|
91
|
-
# configured locale is normalized through LocaleUrlFormatter.to_url_segment
|
|
92
|
-
# before comparison so both language-only (es) and country-subtag
|
|
93
|
-
# (es_ES → es-es) locales are correctly recognized.
|
|
66
|
+
# Localized copies are built from the English pages, so we extract only
|
|
67
|
+
# from the English files. Locale folders use the URL form of the locale
|
|
68
|
+
# code (es_ES becomes es-es), so compare against that form.
|
|
94
69
|
#
|
|
95
|
-
# @param file_path [String] Path
|
|
96
|
-
# @return [Boolean]
|
|
70
|
+
# @param file_path [String] Path of a built HTML file
|
|
71
|
+
# @return [Boolean] true if the file is inside a locale folder
|
|
97
72
|
def skip_localized_page?(file_path)
|
|
98
73
|
relative_path = file_path.sub(@dest, '')
|
|
99
74
|
|
|
@@ -101,16 +76,11 @@ module Jekyll
|
|
|
101
76
|
all_locales.any? { |locale| relative_path.start_with?("/#{LocaleUrlFormatter.to_url_segment(locale)}/") }
|
|
102
77
|
end
|
|
103
78
|
|
|
104
|
-
#
|
|
105
|
-
#
|
|
106
|
-
#
|
|
107
|
-
# script, style, code blocks). Defaults to sensible defaults if not configured.
|
|
108
|
-
#
|
|
109
|
-
# @param config [Hash] Page front matter data
|
|
110
|
-
# @return [Array<String>] CSS selectors for excluded elements
|
|
79
|
+
# @param config [PageLocalesConfig] The page's settings
|
|
80
|
+
# @return [Array<String>] CSS selectors of elements to leave out of
|
|
81
|
+
# extraction (see PageLocalesConfig#exclude_selectors)
|
|
111
82
|
def extract_exclude_selectors(config)
|
|
112
|
-
config.
|
|
113
|
-
['script', 'style', 'code.language-plaintext', 'pre code']
|
|
83
|
+
config.exclude_selectors
|
|
114
84
|
end
|
|
115
85
|
end
|
|
116
86
|
end
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../constants'
|
|
4
|
+
require_relative '../errors'
|
|
5
|
+
require_relative '../po_file/path_builder'
|
|
6
|
+
require_relative '../po_file/reader'
|
|
7
|
+
require_relative '../utils/msgstr_validator'
|
|
8
|
+
|
|
9
|
+
module Jekyll
|
|
10
|
+
module L10n
|
|
11
|
+
# Counts how much of each locale's compendium is translated.
|
|
12
|
+
#
|
|
13
|
+
# Each entry counts once, as translated, fuzzy, untranslated, or invalid
|
|
14
|
+
# (a msgstr whose markup MsgstrValidator rejects, so pages use the
|
|
15
|
+
# fallback). Extractor warns about each invalid entry, prints the counts
|
|
16
|
+
# with the extraction summary, and fails the build when a locale is below
|
|
17
|
+
# `translation.min_coverage`.
|
|
18
|
+
#
|
|
19
|
+
# Only entries whose msgid this build extracted count: a compendium also
|
|
20
|
+
# keeps entries for text the site no longer has. The counts come from the
|
|
21
|
+
# compendium files as they are after extraction, so with
|
|
22
|
+
# `update_compendium: false` they leave out strings no compendium holds
|
|
23
|
+
# yet.
|
|
24
|
+
#
|
|
25
|
+
# @example
|
|
26
|
+
# report = CoverageReport.new(site, SiteConfigAccessor.site_config(site, all_locales: true),
|
|
27
|
+
# extracted_msgids)
|
|
28
|
+
# report.warn_invalid
|
|
29
|
+
# report.log
|
|
30
|
+
# report.enforce!(90) # raises Errors::CoverageError below 90%
|
|
31
|
+
class CoverageReport
|
|
32
|
+
# Entry counts for one locale.
|
|
33
|
+
Counts = Struct.new(:locale, :translated, :fuzzy, :untranslated, :invalid) do
|
|
34
|
+
# @return [Integer] Entries in the compendium
|
|
35
|
+
def total
|
|
36
|
+
translated + fuzzy + untranslated + invalid
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# @return [Float] Translated entries as a percentage; 100 when the
|
|
40
|
+
# compendium is empty
|
|
41
|
+
def percent
|
|
42
|
+
total.zero? ? 100.0 : translated * 100.0 / total
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# @return [String] e.g. "fr: 92.5% translated (1200 of 1297; 40 fuzzy, 50 untranslated, 7 invalid)"
|
|
46
|
+
def to_s
|
|
47
|
+
"#{locale}: #{percent.round(1)}% translated (#{translated} of #{total}; " \
|
|
48
|
+
"#{fuzzy} fuzzy, #{untranslated} untranslated, #{invalid} invalid)"
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# @param site [Jekyll::Site]
|
|
53
|
+
# @param config [PageLocalesConfig] The site config, for locales and locales_dir
|
|
54
|
+
# @param msgids [Set<String>] The msgids this build extracted
|
|
55
|
+
def initialize(site, config, msgids)
|
|
56
|
+
@site = site
|
|
57
|
+
@config = config
|
|
58
|
+
@msgids = msgids
|
|
59
|
+
@invalid = Hash.new { |hash, locale| hash[locale] = [] }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# @return [Array<Counts>] One entry per locale that has a compendium
|
|
63
|
+
def counts
|
|
64
|
+
@counts ||= @config.locales.filter_map do |locale|
|
|
65
|
+
path = PoPathBuilder.build(@site.source, @config.locales_dir, locale, nil)
|
|
66
|
+
count(locale, PoFileReader.parse_for_merge(path)) if File.exist?(path)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Warn about each entry whose msgstr markup does not match its msgid.
|
|
71
|
+
#
|
|
72
|
+
# @return [void]
|
|
73
|
+
def warn_invalid
|
|
74
|
+
counts.each do |locale_counts|
|
|
75
|
+
@invalid[locale_counts.locale].each do |msgid|
|
|
76
|
+
Jekyll.logger.warn Constants::LOG_PREFIX,
|
|
77
|
+
"#{locale_counts.locale}: msgstr markup does not match its msgid, " \
|
|
78
|
+
"so pages show the fallback: #{msgid[0..Constants::LOG_TRUNCATE_LONG]}"
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Print one line per locale.
|
|
84
|
+
#
|
|
85
|
+
# @return [void]
|
|
86
|
+
def log
|
|
87
|
+
return if counts.empty?
|
|
88
|
+
|
|
89
|
+
Jekyll.logger.info Constants::LOG_PREFIX, 'Translation coverage:'
|
|
90
|
+
counts.each { |locale_counts| Jekyll.logger.info Constants::LOG_PREFIX, " #{locale_counts}" }
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# @param minimum [Integer, nil] Required percentage, or nil for none
|
|
94
|
+
# @return [void]
|
|
95
|
+
# @raise [Errors::CoverageError] When any locale is below the minimum
|
|
96
|
+
def enforce!(minimum)
|
|
97
|
+
return if minimum.nil?
|
|
98
|
+
|
|
99
|
+
below = counts.select { |locale_counts| locale_counts.percent < minimum }
|
|
100
|
+
return if below.empty?
|
|
101
|
+
|
|
102
|
+
raise Errors::CoverageError,
|
|
103
|
+
"Translation coverage is below translation.min_coverage (#{minimum}%): #{below.join('; ')}"
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
private
|
|
107
|
+
|
|
108
|
+
def count(locale, entries)
|
|
109
|
+
tally = Hash.new(0)
|
|
110
|
+
entries.each do |msgid, entry|
|
|
111
|
+
next unless @msgids.include?(msgid)
|
|
112
|
+
|
|
113
|
+
entry_state = state(msgid, entry)
|
|
114
|
+
tally[entry_state] += 1
|
|
115
|
+
@invalid[locale] << msgid if entry_state == :invalid
|
|
116
|
+
end
|
|
117
|
+
Counts.new(locale, tally[:translated], tally[:fuzzy], tally[:untranslated], tally[:invalid])
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def state(msgid, entry)
|
|
121
|
+
if entry[:fuzzy] then :fuzzy
|
|
122
|
+
elsif entry[:msgstr].to_s.empty? then :untranslated
|
|
123
|
+
elsif MsgstrValidator.valid?(msgid, entry[:msgstr]) then :translated
|
|
124
|
+
else :invalid
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
@@ -3,8 +3,6 @@
|
|
|
3
3
|
require_relative '../utils/text_normalizer'
|
|
4
4
|
require_relative '../utils/xpath_reference_generator'
|
|
5
5
|
require_relative '../utils/html_text_utils'
|
|
6
|
-
require_relative '../utils/html_elements'
|
|
7
|
-
require_relative '../utils/text_validator'
|
|
8
6
|
|
|
9
7
|
module Jekyll
|
|
10
8
|
module L10n
|
|
@@ -27,7 +25,7 @@ module Jekyll
|
|
|
27
25
|
# entry = DomTextExtractor.extract(node, 'docs/index.html', '_site')
|
|
28
26
|
# # Returns hash with :msgid, :msgstr, :reference if valid text found
|
|
29
27
|
module DomTextExtractor
|
|
30
|
-
|
|
28
|
+
module_function
|
|
31
29
|
|
|
32
30
|
# Extract text content from an HTML element.
|
|
33
31
|
#
|
|
@@ -56,34 +54,7 @@ module Jekyll
|
|
|
56
54
|
end
|
|
57
55
|
|
|
58
56
|
def extract_block_text(node)
|
|
59
|
-
|
|
60
|
-
return nil if HtmlTextUtils.layout_block_children?(node)
|
|
61
|
-
|
|
62
|
-
text = HtmlTextUtils.extract_with_inline_tags(node)
|
|
63
|
-
TextValidator.valid?(text) ? text : nil
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def only_contains_block_elements?(node)
|
|
67
|
-
node.children.each do |child|
|
|
68
|
-
return false if non_empty_text?(child)
|
|
69
|
-
return false if non_block_element?(child)
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
block_element_children?(node)
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
private
|
|
76
|
-
|
|
77
|
-
def non_empty_text?(child)
|
|
78
|
-
child.text? && !child.content.strip.empty?
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
def non_block_element?(child)
|
|
82
|
-
child.element? && !HtmlTextUtils::ALL_BLOCK_ELEMENTS.include?(child.name)
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
def block_element_children?(node)
|
|
86
|
-
node.children.any? { |c| c.element? && HtmlTextUtils::ALL_BLOCK_ELEMENTS.include?(c.name) }
|
|
57
|
+
HtmlTextUtils.block_text(node)
|
|
87
58
|
end
|
|
88
59
|
end
|
|
89
60
|
end
|