jekyll-l10n 2.0.0 → 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 +4 -4
- 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 +48 -28
- 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 +10 -22
- data/lib/jekyll-l10n/jekyll/localized_page.rb +27 -49
- data/lib/jekyll-l10n/jekyll/post_write_html_reprocessor.rb +42 -25
- data/lib/jekyll-l10n/jekyll/post_write_processor.rb +2 -1
- data/lib/jekyll-l10n/jekyll/regeneration_checker.rb +16 -10
- data/lib/jekyll-l10n/jekyll/url_filter.rb +8 -7
- 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/request_slots.rb +69 -0
- data/lib/jekyll-l10n/translation/translation_progress.rb +77 -0
- data/lib/jekyll-l10n/translation/translator.rb +20 -17
- 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 +126 -107
- data/lib/jekyll-l10n/utils/po_entry_converter.rb +0 -43
- data/lib/jekyll-l10n/utils/site_config_accessor.rb +56 -54
- 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 +9 -6
- metadata +12 -3
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative '../constants'
|
|
4
|
+
|
|
3
5
|
module Jekyll
|
|
4
6
|
module L10n
|
|
5
7
|
# Formats and conditionally logs messages with component prefixes.
|
|
@@ -25,7 +27,7 @@ module Jekyll
|
|
|
25
27
|
# @param message [String] Message to log
|
|
26
28
|
# @return [void]
|
|
27
29
|
def self.info(component, message)
|
|
28
|
-
Jekyll.logger.info
|
|
30
|
+
Jekyll.logger.info Constants::LOG_PREFIX, "[#{component}] #{message}"
|
|
29
31
|
end
|
|
30
32
|
|
|
31
33
|
# Log a debug message with component prefix.
|
|
@@ -34,7 +36,7 @@ module Jekyll
|
|
|
34
36
|
# @param message [String] Message to log
|
|
35
37
|
# @return [void]
|
|
36
38
|
def self.debug(component, message)
|
|
37
|
-
Jekyll.logger.debug
|
|
39
|
+
Jekyll.logger.debug Constants::LOG_PREFIX, "[#{component}] #{message}"
|
|
38
40
|
end
|
|
39
41
|
|
|
40
42
|
# Log a warning message with component prefix.
|
|
@@ -43,7 +45,7 @@ module Jekyll
|
|
|
43
45
|
# @param message [String] Message to log
|
|
44
46
|
# @return [void]
|
|
45
47
|
def self.warn(component, message)
|
|
46
|
-
Jekyll.logger.warn
|
|
48
|
+
Jekyll.logger.warn Constants::LOG_PREFIX, "[#{component}] #{message}"
|
|
47
49
|
end
|
|
48
50
|
|
|
49
51
|
# Log an error message with component prefix.
|
|
@@ -52,7 +54,7 @@ module Jekyll
|
|
|
52
54
|
# @param message [String] Message to log
|
|
53
55
|
# @return [void]
|
|
54
56
|
def self.error(component, message)
|
|
55
|
-
Jekyll.logger.error
|
|
57
|
+
Jekyll.logger.error Constants::LOG_PREFIX, "[#{component}] #{message}"
|
|
56
58
|
end
|
|
57
59
|
|
|
58
60
|
# Check if debug logging is enabled.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../constants'
|
|
4
|
+
require_relative 'html_parser'
|
|
5
|
+
|
|
6
|
+
module Jekyll
|
|
7
|
+
module L10n
|
|
8
|
+
# Checks that a msgstr carries the same markup as its msgid.
|
|
9
|
+
#
|
|
10
|
+
# A block's msgid holds placeholders (<g id="N">, <x id="N"></x>) and
|
|
11
|
+
# literal elements such as <code>. Translation writes the msgstr into the
|
|
12
|
+
# page as HTML, so a msgstr that drops or repeats a placeholder, or adds
|
|
13
|
+
# markup of its own (a <script>, an attribute), would change the page
|
|
14
|
+
# structure. Such entries are dropped and the page shows the fallback.
|
|
15
|
+
#
|
|
16
|
+
# Both strings are parsed, and their elements are compared with their
|
|
17
|
+
# attributes, in any order and nesting: a translation may reorder
|
|
18
|
+
# placeholders, but not change them.
|
|
19
|
+
#
|
|
20
|
+
# @example
|
|
21
|
+
# MsgstrValidator.valid?('See <g id="1">docs</g>.', 'Voir <g id="1">docs</g>.') # => true
|
|
22
|
+
# MsgstrValidator.valid?('See <g id="1">docs</g>.', 'Voir docs.') # => false
|
|
23
|
+
module MsgstrValidator
|
|
24
|
+
module_function
|
|
25
|
+
|
|
26
|
+
# @param msgid [String]
|
|
27
|
+
# @param msgstr [String]
|
|
28
|
+
# @return [Boolean] true when both hold the same elements and attributes
|
|
29
|
+
def valid?(msgid, msgstr)
|
|
30
|
+
return true unless msgid.include?('<') || msgstr.include?('<')
|
|
31
|
+
|
|
32
|
+
# The answer depends only on the two strings, and a build checks the
|
|
33
|
+
# same pairs each time a compendium is loaded again.
|
|
34
|
+
results.fetch([msgid, msgstr]) { results[[msgid, msgstr]] = markup(msgid) == markup(msgstr) }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Drop the entries whose msgstr markup does not match their msgid.
|
|
38
|
+
#
|
|
39
|
+
# A compendium also keeps entries for text the site no longer has, so
|
|
40
|
+
# each one is only logged at debug level here; CoverageReport warns about
|
|
41
|
+
# the ones the site uses, once per build.
|
|
42
|
+
#
|
|
43
|
+
# @param translations [Hash{String => String}] msgid => msgstr
|
|
44
|
+
# @param po_path [String] The PO file the entries come from, for the log
|
|
45
|
+
# @return [Hash{String => String}] The valid entries
|
|
46
|
+
def reject_invalid(translations, po_path)
|
|
47
|
+
translations.select do |msgid, msgstr|
|
|
48
|
+
next true if msgstr.empty? || valid?(msgid, msgstr)
|
|
49
|
+
|
|
50
|
+
log_invalid(msgid, po_path)
|
|
51
|
+
false
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def markup(text)
|
|
56
|
+
HtmlParser.parse_fragment(text).css('*').map do |element|
|
|
57
|
+
[element.name, element.attribute_nodes.map { |attr| [attr.name, attr.value] }.sort]
|
|
58
|
+
end.sort
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def log_invalid(msgid, po_path)
|
|
62
|
+
Jekyll.logger.debug Constants::LOG_PREFIX,
|
|
63
|
+
"#{po_path}: msgstr markup does not match its msgid, dropping the " \
|
|
64
|
+
"entry: #{msgid[0..Constants::LOG_TRUNCATE_LONG]}"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def results
|
|
68
|
+
@results ||= {}
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private_class_method :markup, :log_invalid, :results
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative '../configuration'
|
|
3
4
|
require_relative '../constants'
|
|
4
5
|
|
|
5
6
|
module Jekyll
|
|
6
7
|
module L10n
|
|
7
|
-
# Wraps
|
|
8
|
-
# the defaults from
|
|
8
|
+
# Wraps the plugin's settings and answers questions about them, filling in
|
|
9
|
+
# the defaults from Schema::SETTINGS for anything not set.
|
|
9
10
|
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
11
|
+
# When LibreTranslate is enabled but its URL, source locale or format is
|
|
12
|
+
# invalid, it warns once and turns machine translation off, so the build
|
|
13
|
+
# still finishes. Bad locale codes never reach it: Configuration::Schema
|
|
14
|
+
# drops them, with a warning.
|
|
12
15
|
#
|
|
13
16
|
# Build it from the right data:
|
|
14
17
|
# - For one page, use SiteConfigAccessor.effective_page_data(page), never raw
|
|
15
18
|
# `page.data`; otherwise the page loses the site defaults.
|
|
16
|
-
# - For site-wide settings, use
|
|
17
|
-
#
|
|
19
|
+
# - For site-wide settings (the `jekyll_l10n` key), use
|
|
20
|
+
# SiteConfigAccessor.site_config(site). A page's config has none of
|
|
21
|
+
# them, so its site-wide readers return the defaults.
|
|
18
22
|
#
|
|
19
23
|
# @example
|
|
20
24
|
# config = PageLocalesConfig.new(SiteConfigAccessor.effective_page_data(page))
|
|
@@ -22,40 +26,24 @@ module Jekyll
|
|
|
22
26
|
# config.fallback_mode # => "english"
|
|
23
27
|
#
|
|
24
28
|
class PageLocalesConfig
|
|
25
|
-
# Shortcuts to the defaults in Constants.
|
|
26
|
-
LOCALE_PATTERN = Constants::LOCALE_PATTERN
|
|
27
|
-
DEFAULT_LOCALES_DIR = Constants::DEFAULT_LOCALES_DIR
|
|
28
|
-
DEFAULT_FALLBACK_MODE = Constants::DEFAULT_FALLBACK_MODE
|
|
29
|
-
DEFAULT_TRANSLATABLE_ATTRIBUTES = Constants::DEFAULT_TRANSLATABLE_ATTRIBUTES
|
|
30
|
-
DEFAULT_LIBRETRANSLATE_TIMEOUT = Constants::DEFAULT_LIBRETRANSLATE_TIMEOUT
|
|
31
|
-
DEFAULT_LIBRETRANSLATE_BATCH_SIZE = Constants::DEFAULT_LIBRETRANSLATE_BATCH_SIZE
|
|
32
|
-
DEFAULT_LIBRETRANSLATE_RETRY_ATTEMPTS = Constants::DEFAULT_LIBRETRANSLATE_RETRY_ATTEMPTS
|
|
33
|
-
DEFAULT_LIBRETRANSLATE_RETRY_DELAY = Constants::DEFAULT_LIBRETRANSLATE_RETRY_DELAY
|
|
34
|
-
DEFAULT_LIBRETRANSLATE_STOP_ON_ERROR = Constants::DEFAULT_LIBRETRANSLATE_STOP_ON_ERROR
|
|
35
|
-
DEFAULT_LIBRETRANSLATE_PROGRESS_INTERVAL = Constants::DEFAULT_LIBRETRANSLATE_PROGRESS_INTERVAL
|
|
36
|
-
DEFAULT_LIBRETRANSLATE_SOURCE_LOCALE = Constants::DEFAULT_LIBRETRANSLATE_SOURCE_LOCALE
|
|
37
|
-
DEFAULT_LIBRETRANSLATE_FORMAT = Constants::DEFAULT_LIBRETRANSLATE_FORMAT
|
|
38
|
-
|
|
39
29
|
# @!attribute [r] data
|
|
40
30
|
# The hash this config was built from
|
|
41
31
|
# @return [Hash]
|
|
42
32
|
attr_reader :data
|
|
43
33
|
|
|
44
|
-
# @param page_data [Hash] A hash with a `with_locales_data` key
|
|
45
|
-
# optionally `path` (used in error messages)
|
|
46
|
-
# @raise [Jekyll::Errors::InvalidConfigurationError] If a locale code or a
|
|
47
|
-
# LibreTranslate setting is invalid
|
|
34
|
+
# @param page_data [Hash] A hash with a `with_locales_data` key
|
|
48
35
|
def initialize(page_data)
|
|
49
|
-
|
|
36
|
+
# Cleaned without warnings: Configuration warns where values are written.
|
|
37
|
+
# Both scopes are kept, so SiteConfigAccessor.site_config can pass the
|
|
38
|
+
# site-wide settings merged in.
|
|
39
|
+
@config = Configuration::Schema.normalize(page_data[Configuration::PAGE_DATA_KEY], source: nil, scope: :all)
|
|
50
40
|
@data = page_data
|
|
51
|
-
@
|
|
52
|
-
|
|
53
|
-
validate_locales!
|
|
54
|
-
validate_libretranslate!
|
|
41
|
+
@libretranslate_problem = libretranslate_problem if libretranslate_requested?
|
|
42
|
+
warn_libretranslate_off if @libretranslate_problem
|
|
55
43
|
end
|
|
56
44
|
|
|
57
|
-
# Locales to translate into.
|
|
58
|
-
#
|
|
45
|
+
# Locales to translate into. Configuration::Schema has already dropped
|
|
46
|
+
# any code that does not match Constants::LOCALE_PATTERN.
|
|
59
47
|
#
|
|
60
48
|
# @return [Array<String>] Locale codes such as ["es", "pt_BR"], or []
|
|
61
49
|
def locales
|
|
@@ -64,35 +52,35 @@ module Jekyll
|
|
|
64
52
|
|
|
65
53
|
# @return [String] Directory for PO files, relative to the site source
|
|
66
54
|
def locales_dir
|
|
67
|
-
@config['locales_dir'] ||
|
|
55
|
+
@config['locales_dir'] || schema_default('locales_dir')
|
|
68
56
|
end
|
|
69
57
|
|
|
70
58
|
# @return [Boolean] false only when `extract_on_build` is false
|
|
71
59
|
def extract_on_build?
|
|
72
|
-
@config
|
|
60
|
+
@config.fetch('extract_on_build', schema_default('extract_on_build'))
|
|
73
61
|
end
|
|
74
62
|
|
|
75
63
|
# Whether to merge page PO files into the locale compendium. A site-wide
|
|
76
|
-
# setting: read it from
|
|
64
|
+
# setting: read it from SiteConfigAccessor.site_config, not from a page.
|
|
77
65
|
#
|
|
78
66
|
# @return [Boolean] false only when `update_compendium` is false
|
|
79
67
|
def update_compendium?
|
|
80
|
-
@config
|
|
68
|
+
@config.fetch('update_compendium', schema_default('update_compendium'))
|
|
81
69
|
end
|
|
82
70
|
|
|
83
71
|
# Whether to print the extraction summary at the end of the build. A
|
|
84
|
-
# site-wide setting:
|
|
72
|
+
# site-wide setting: read it from SiteConfigAccessor.site_config.
|
|
85
73
|
#
|
|
86
74
|
# @return [Boolean] false only when `logging.show_statistics` is false
|
|
87
75
|
def show_statistics?
|
|
88
|
-
|
|
76
|
+
logging_setting('show_statistics')
|
|
89
77
|
end
|
|
90
78
|
|
|
91
79
|
# Turns on HtmlTranslator's log of each translation lookup for this page.
|
|
92
80
|
#
|
|
93
81
|
# @return [Boolean] true when `logging.debug` is true
|
|
94
82
|
def debug_logging?
|
|
95
|
-
|
|
83
|
+
logging_setting('debug')
|
|
96
84
|
end
|
|
97
85
|
|
|
98
86
|
# Turns on per-entry LibreTranslate logging. Those lines go to Jekyll's
|
|
@@ -100,7 +88,7 @@ module Jekyll
|
|
|
100
88
|
#
|
|
101
89
|
# @return [Boolean] true when `logging.trace` or `logging.debug` is true
|
|
102
90
|
def trace_logging?
|
|
103
|
-
|
|
91
|
+
logging_setting('trace') || debug_logging?
|
|
104
92
|
end
|
|
105
93
|
|
|
106
94
|
# What to show when a string has no translation: "english" (the original
|
|
@@ -108,15 +96,32 @@ module Jekyll
|
|
|
108
96
|
#
|
|
109
97
|
# @return [String] The fallback mode
|
|
110
98
|
def fallback_mode
|
|
111
|
-
@config.dig('translation', 'fallback') ||
|
|
99
|
+
@config.dig('translation', 'fallback') || schema_default('translation.fallback')
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# The share of each locale's compendium that must be translated, or the
|
|
103
|
+
# build fails. A site-wide setting: read it from
|
|
104
|
+
# SiteConfigAccessor.site_config.
|
|
105
|
+
#
|
|
106
|
+
# @return [Integer, nil] A percentage, or nil when no minimum is set
|
|
107
|
+
def min_coverage
|
|
108
|
+
@config.dig('translation', 'min_coverage')
|
|
112
109
|
end
|
|
113
110
|
|
|
114
111
|
# HTML attributes whose values are extracted and translated. Defaults to
|
|
115
|
-
# DEFAULT_TRANSLATABLE_ATTRIBUTES.
|
|
112
|
+
# Constants::DEFAULT_TRANSLATABLE_ATTRIBUTES.
|
|
116
113
|
#
|
|
117
114
|
# @return [Array<String>] Attribute names
|
|
118
115
|
def translatable_attributes
|
|
119
|
-
@config.dig('extraction', 'translatable_attributes') ||
|
|
116
|
+
@config.dig('extraction', 'translatable_attributes') || schema_default('extraction.translatable_attributes')
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# CSS selectors of elements left out of extraction. Defaults to
|
|
120
|
+
# Constants::DEFAULT_EXCLUDE_SELECTORS.
|
|
121
|
+
#
|
|
122
|
+
# @return [Array<String>] Selectors
|
|
123
|
+
def exclude_selectors
|
|
124
|
+
@config.dig('extraction', 'exclude_selectors') || schema_default('extraction.exclude_selectors')
|
|
120
125
|
end
|
|
121
126
|
|
|
122
127
|
# @return [Boolean] true when at least one locale is configured
|
|
@@ -128,37 +133,34 @@ module Jekyll
|
|
|
128
133
|
# `libretranslate_enabled` always wins. Without it, only a
|
|
129
134
|
# `libretranslate_api_url` you set yourself turns it on; the default URL
|
|
130
135
|
# does not, so an unconfigured site never tries to reach a server.
|
|
136
|
+
# Either way, it stays off while its URL, source locale or format is
|
|
137
|
+
# invalid.
|
|
131
138
|
#
|
|
132
139
|
# @return [Boolean] true if LibreTranslate is enabled
|
|
133
140
|
def libretranslate_enabled?
|
|
134
|
-
|
|
135
|
-
return libretranslate_config['libretranslate_enabled'] == true
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
url = libretranslate_config['libretranslate_api_url']
|
|
139
|
-
url.is_a?(String) && !url.strip.empty?
|
|
141
|
+
libretranslate_requested? && @libretranslate_problem.nil?
|
|
140
142
|
end
|
|
141
143
|
|
|
142
144
|
# @return [String] Locale code of the original content. Defaults to
|
|
143
|
-
# DEFAULT_LIBRETRANSLATE_SOURCE_LOCALE.
|
|
145
|
+
# Constants::DEFAULT_LIBRETRANSLATE_SOURCE_LOCALE.
|
|
144
146
|
def libretranslate_source_locale
|
|
145
|
-
libretranslate_setting('libretranslate_source_locale'
|
|
147
|
+
libretranslate_setting('libretranslate_source_locale')
|
|
146
148
|
end
|
|
147
149
|
|
|
148
150
|
# How strings are sent to LibreTranslate. "html" keeps inline markup intact.
|
|
149
151
|
#
|
|
150
|
-
# @return [String] "text" or "html".
|
|
152
|
+
# @return [String] "text" or "html". Default from Configuration::Schema::SETTINGS.
|
|
151
153
|
def libretranslate_format
|
|
152
|
-
libretranslate_setting('libretranslate_format'
|
|
154
|
+
libretranslate_setting('libretranslate_format')
|
|
153
155
|
end
|
|
154
156
|
|
|
155
157
|
# Base URL of the LibreTranslate server, without `/translate`, which
|
|
156
158
|
# LibreTranslator appends. Returning the default here does not turn
|
|
157
159
|
# LibreTranslate on; see {#libretranslate_enabled?}.
|
|
158
160
|
#
|
|
159
|
-
# @return [String] The URL.
|
|
161
|
+
# @return [String] The URL. Default from Configuration::Schema::SETTINGS.
|
|
160
162
|
def libretranslate_api_url
|
|
161
|
-
libretranslate_setting('libretranslate_api_url'
|
|
163
|
+
libretranslate_setting('libretranslate_api_url')
|
|
162
164
|
end
|
|
163
165
|
|
|
164
166
|
# @return [String, nil] API key for servers that require one, or nil
|
|
@@ -167,45 +169,60 @@ module Jekyll
|
|
|
167
169
|
end
|
|
168
170
|
|
|
169
171
|
# @return [Integer] Request timeout in seconds. Defaults to
|
|
170
|
-
# DEFAULT_LIBRETRANSLATE_TIMEOUT.
|
|
172
|
+
# Constants::DEFAULT_LIBRETRANSLATE_TIMEOUT.
|
|
171
173
|
def libretranslate_timeout
|
|
172
|
-
libretranslate_setting('libretranslate_timeout'
|
|
174
|
+
libretranslate_setting('libretranslate_timeout')
|
|
173
175
|
end
|
|
174
176
|
|
|
175
|
-
#
|
|
176
|
-
#
|
|
177
|
+
# Entries a worker takes from the queue at a time. Each entry is still
|
|
178
|
+
# its own request.
|
|
177
179
|
#
|
|
178
|
-
# @return [Integer]
|
|
180
|
+
# @return [Integer] Default from Configuration::Schema::SETTINGS.
|
|
179
181
|
def libretranslate_batch_size
|
|
180
|
-
libretranslate_setting('libretranslate_batch_size'
|
|
182
|
+
libretranslate_setting('libretranslate_batch_size')
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Requests sent to LibreTranslate at once. 1 sends them one at a time.
|
|
186
|
+
#
|
|
187
|
+
# @return [Integer] Default from Configuration::Schema::SETTINGS.
|
|
188
|
+
def libretranslate_concurrency
|
|
189
|
+
libretranslate_setting('libretranslate_concurrency')
|
|
181
190
|
end
|
|
182
191
|
|
|
183
192
|
# @return [Integer] Retries for a failed request. Defaults to
|
|
184
|
-
# DEFAULT_LIBRETRANSLATE_RETRY_ATTEMPTS.
|
|
193
|
+
# Constants::DEFAULT_LIBRETRANSLATE_RETRY_ATTEMPTS.
|
|
185
194
|
def libretranslate_retry_attempts
|
|
186
|
-
libretranslate_setting('libretranslate_retry_attempts'
|
|
195
|
+
libretranslate_setting('libretranslate_retry_attempts')
|
|
187
196
|
end
|
|
188
197
|
|
|
189
198
|
# @return [Integer] Seconds to wait between retries. Defaults to
|
|
190
|
-
# DEFAULT_LIBRETRANSLATE_RETRY_DELAY.
|
|
199
|
+
# Constants::DEFAULT_LIBRETRANSLATE_RETRY_DELAY.
|
|
191
200
|
def libretranslate_retry_delay
|
|
192
|
-
libretranslate_setting('libretranslate_retry_delay'
|
|
201
|
+
libretranslate_setting('libretranslate_retry_delay')
|
|
193
202
|
end
|
|
194
203
|
|
|
195
204
|
# When true, the first API error stops machine translation. When false, the
|
|
196
205
|
# error is logged and the remaining entries are still translated.
|
|
197
206
|
#
|
|
198
|
-
# @return [Boolean]
|
|
207
|
+
# @return [Boolean] Default from Configuration::Schema::SETTINGS.
|
|
199
208
|
def libretranslate_stop_on_error?
|
|
200
209
|
val = libretranslate_config['libretranslate_stop_on_error']
|
|
201
|
-
val.nil? ?
|
|
210
|
+
val.nil? ? schema_default('translation.libretranslate_stop_on_error') : val != false
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# When true, machine translations are written as fuzzy entries: pages
|
|
214
|
+
# keep the fallback until a person reviews each one and removes the flag.
|
|
215
|
+
#
|
|
216
|
+
# @return [Boolean] Default from Configuration::Schema::SETTINGS.
|
|
217
|
+
def libretranslate_mark_fuzzy?
|
|
218
|
+
libretranslate_config.fetch('libretranslate_mark_fuzzy', schema_default('translation.libretranslate_mark_fuzzy'))
|
|
202
219
|
end
|
|
203
220
|
|
|
204
221
|
# Log progress after every this many translated entries; 0 turns it off.
|
|
205
222
|
#
|
|
206
|
-
# @return [Integer]
|
|
223
|
+
# @return [Integer] Default from Configuration::Schema::SETTINGS.
|
|
207
224
|
def libretranslate_progress_interval
|
|
208
|
-
libretranslate_setting('libretranslate_progress_interval'
|
|
225
|
+
libretranslate_setting('libretranslate_progress_interval')
|
|
209
226
|
end
|
|
210
227
|
|
|
211
228
|
private
|
|
@@ -214,55 +231,57 @@ module Jekyll
|
|
|
214
231
|
@config['translation'] || {}
|
|
215
232
|
end
|
|
216
233
|
|
|
217
|
-
|
|
218
|
-
|
|
234
|
+
# Reads a logging setting by its leaf key, falling back to the default
|
|
235
|
+
# recorded in Schema::SETTINGS for "logging.<key>".
|
|
236
|
+
#
|
|
237
|
+
# @param key [String] leaf key under the `logging` section
|
|
238
|
+
# @return [Boolean]
|
|
239
|
+
def logging_setting(key)
|
|
240
|
+
(@config['logging'] || {}).fetch(key, schema_default("logging.#{key}"))
|
|
219
241
|
end
|
|
220
242
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
Jekyll.logger.error('Localization', error_msg)
|
|
229
|
-
|
|
230
|
-
raise Jekyll::Errors::InvalidConfigurationError, error_msg
|
|
243
|
+
# Returns the configured value for a libretranslate setting, or the default
|
|
244
|
+
# recorded in Schema::SETTINGS for "translation.<key>".
|
|
245
|
+
#
|
|
246
|
+
# @param key [String] leaf key under the `translation` section
|
|
247
|
+
# @return [Object]
|
|
248
|
+
def libretranslate_setting(key)
|
|
249
|
+
libretranslate_config[key] || schema_default("translation.#{key}")
|
|
231
250
|
end
|
|
232
251
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
Valid pattern: ^[a-z]{2}(_[A-Z]{2})?$
|
|
252
|
+
# Whether the settings ask for LibreTranslate, before they are checked.
|
|
253
|
+
def libretranslate_requested?
|
|
254
|
+
if libretranslate_config.key?('libretranslate_enabled')
|
|
255
|
+
return libretranslate_config['libretranslate_enabled'] == true
|
|
256
|
+
end
|
|
239
257
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
- Use underscore for country: 'pt_BR' not 'pt-BR'
|
|
243
|
-
- Use ISO codes: 'es' not 'español'
|
|
244
|
-
- Minimum 2 characters: 'es' not 'e'
|
|
245
|
-
- Country code uppercase: 'pt_BR' not 'pt_br'
|
|
246
|
-
ERROR
|
|
258
|
+
url = libretranslate_config['libretranslate_api_url']
|
|
259
|
+
url.is_a?(String) && !url.strip.empty?
|
|
247
260
|
end
|
|
248
261
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
if libretranslate_api_url.
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
raise Jekyll::Errors::InvalidConfigurationError,
|
|
259
|
-
"Invalid libretranslate_source_locale: #{libretranslate_source_locale}"
|
|
262
|
+
# @return [String, nil] Why the LibreTranslate settings cannot be used,
|
|
263
|
+
# or nil when they can
|
|
264
|
+
def libretranslate_problem
|
|
265
|
+
if libretranslate_api_url.strip.empty?
|
|
266
|
+
'libretranslate_api_url is empty'
|
|
267
|
+
elsif !Constants::LOCALE_PATTERN.match?(libretranslate_source_locale)
|
|
268
|
+
"libretranslate_source_locale #{libretranslate_source_locale.inspect} is not a locale code such as en or pt_BR"
|
|
269
|
+
elsif !%w[text html].include?(libretranslate_format)
|
|
270
|
+
"libretranslate_format #{libretranslate_format.inspect} must be \"text\" or \"html\""
|
|
260
271
|
end
|
|
272
|
+
end
|
|
261
273
|
|
|
262
|
-
|
|
274
|
+
def warn_libretranslate_off
|
|
275
|
+
Configuration.warn_once("LibreTranslate is turned off: #{@libretranslate_problem}; " \
|
|
276
|
+
'fix it under `jekyll_l10n.translation` in _config.yml.')
|
|
277
|
+
end
|
|
263
278
|
|
|
264
|
-
|
|
265
|
-
|
|
279
|
+
# Returns the default value for a setting path from Schema::SETTINGS.
|
|
280
|
+
#
|
|
281
|
+
# @param path [String] dotted setting path, e.g. "locales_dir"
|
|
282
|
+
# @return [Object] the default value
|
|
283
|
+
def schema_default(path)
|
|
284
|
+
Configuration::Schema::SETTINGS.fetch(path)[:default]
|
|
266
285
|
end
|
|
267
286
|
end
|
|
268
287
|
end
|
|
@@ -14,40 +14,12 @@ module Jekyll
|
|
|
14
14
|
# * Convert translation hashes to POEntry objects
|
|
15
15
|
# * Convert POEntry objects back to hash format
|
|
16
16
|
# * Preserve metadata (references, fuzzy flags) during conversion
|
|
17
|
-
# * Handle both hash and array return formats
|
|
18
17
|
#
|
|
19
18
|
# @example
|
|
20
19
|
# hash = { "Hello" => { msgstr: "Hola", reference: "file.html:10" } }
|
|
21
20
|
# entries = PoEntryConverter.hash_to_po_entry_array(hash)
|
|
22
21
|
# # entries is array of GetText::POEntry objects
|
|
23
22
|
class PoEntryConverter
|
|
24
|
-
# Convert translation hash to POEntry hash.
|
|
25
|
-
#
|
|
26
|
-
# Converts simple translation hash { msgid => msgstr } or metadata format
|
|
27
|
-
# { msgid => { msgstr: "...", reference: "...", fuzzy: false } } to a
|
|
28
|
-
# hash of GetText::POEntry objects keyed by msgid.
|
|
29
|
-
#
|
|
30
|
-
# @param hash [Hash] Translation hash
|
|
31
|
-
# @return [Hash] Hash of { msgid => POEntry }
|
|
32
|
-
def self.hash_to_po_entries(hash)
|
|
33
|
-
return {} if hash.nil? || hash.empty?
|
|
34
|
-
|
|
35
|
-
hash.each_with_object({}) do |(key, value), result|
|
|
36
|
-
entry = ::GetText::POEntry.new(:normal)
|
|
37
|
-
entry.msgid = key
|
|
38
|
-
|
|
39
|
-
if value.is_a?(Hash)
|
|
40
|
-
entry.msgstr = value[:msgstr]
|
|
41
|
-
entry.add_comment(value[:reference]) if value[:reference]
|
|
42
|
-
entry.flag = 'fuzzy' if value[:fuzzy]
|
|
43
|
-
else
|
|
44
|
-
entry.msgstr = value
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
result[key] = entry
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
|
|
51
23
|
# Convert translation hash to array of POEntry objects.
|
|
52
24
|
#
|
|
53
25
|
# Converts hash format to array of GetText::POEntry objects, preserving
|
|
@@ -75,21 +47,6 @@ module Jekyll
|
|
|
75
47
|
entry
|
|
76
48
|
end
|
|
77
49
|
|
|
78
|
-
# Convert POEntry objects to translation hash.
|
|
79
|
-
#
|
|
80
|
-
# @param entries [Array<GetText::POEntry>] Array of POEntry objects
|
|
81
|
-
# @return [Hash] Hash of { msgid => { msgstr: "...", reference: "..." } }
|
|
82
|
-
def self.po_entries_to_hash(entries)
|
|
83
|
-
return {} if entries.nil? || entries.empty?
|
|
84
|
-
|
|
85
|
-
entries.to_h do |entry|
|
|
86
|
-
[entry.msgid, {
|
|
87
|
-
msgstr: entry.msgstr.to_s,
|
|
88
|
-
reference: entry.extracted_comment
|
|
89
|
-
}]
|
|
90
|
-
end
|
|
91
|
-
end
|
|
92
|
-
|
|
93
50
|
# Convert POEntry objects to array of hashes.
|
|
94
51
|
#
|
|
95
52
|
# Each entry becomes a hash with :msgid and :msgstr (and optional :reference).
|