jekyll-l10n 1.7.0 → 2.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 +1 -1
- data/lib/jekyll-l10n/extraction/config_loader.rb +25 -55
- data/lib/jekyll-l10n/extraction/extractor.rb +29 -42
- data/lib/jekyll-l10n/jekyll/generator.rb +38 -40
- data/lib/jekyll-l10n/jekyll/post_write_html_reprocessor.rb +14 -26
- data/lib/jekyll-l10n/jekyll/regeneration_checker.rb +59 -45
- data/lib/jekyll-l10n/jekyll/url_filter.rb +5 -6
- data/lib/jekyll-l10n/po_file/reader.rb +2 -4
- data/lib/jekyll-l10n/translation/page_translation_loader.rb +13 -29
- data/lib/jekyll-l10n/translation/translator.rb +29 -61
- data/lib/jekyll-l10n/utils/page_locales_config.rb +72 -149
- data/lib/jekyll-l10n/utils/site_config_accessor.rb +77 -39
- data/lib/jekyll-l10n/version.rb +1 -1
- data/lib/jekyll-l10n.rb +64 -176
- metadata +15 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9fb15328cbef2fac51cff8993c0f7e2eb36487de5471531fbb8fa2b0278bb6a1
|
|
4
|
+
data.tar.gz: a0600bbcb9b82c6b60ca47d0d73dccc23d90807bbcc5e2134e5ddda53a864dea
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b80419f2e724e61082cc72e39550d5bcefba0205cb659a3887eb7fb0f1d22ffa90dd3f20570fc90233f32b83cca5347d506098b62651aa86be96fd186f32ec7a
|
|
7
|
+
data.tar.gz: 8b8700a04de094d68752a6a70c1a20328c6e55ad7a1c70764f8756195b461703598cbf4d58e048224af2ac16df256ad219c8103f5b6112c81b2a3ebfb2f55a3d
|
data/README.md
CHANGED
|
@@ -6,19 +6,12 @@ require_relative '../utils/site_config_accessor'
|
|
|
6
6
|
|
|
7
7
|
module Jekyll
|
|
8
8
|
module L10n
|
|
9
|
-
#
|
|
9
|
+
# Finds the settings for a built HTML file during extraction.
|
|
10
10
|
#
|
|
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
|
|
11
|
+
# Extraction works on output files, but settings belong to pages. This
|
|
12
|
+
# class matches an output file back to its source page and returns that
|
|
13
|
+
# page's merged settings. It also tells the Extractor which files to skip:
|
|
14
|
+
# the localized copies under locale folders such as `es/`.
|
|
22
15
|
#
|
|
23
16
|
# @example
|
|
24
17
|
# loader = ExtractionConfigLoader.new(site, '_site')
|
|
@@ -26,22 +19,16 @@ module Jekyll
|
|
|
26
19
|
# config = loader.load_page_config(file_path)
|
|
27
20
|
# end
|
|
28
21
|
class ExtractionConfigLoader
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
# @param site [Jekyll::Site] Jekyll site object
|
|
32
|
-
# @param dest [String] Destination build directory
|
|
22
|
+
# @param site [Jekyll::Site] The Jekyll site
|
|
23
|
+
# @param dest [String] The site destination directory
|
|
33
24
|
def initialize(site, dest)
|
|
34
25
|
@site = site
|
|
35
26
|
@dest = dest
|
|
36
27
|
end
|
|
37
28
|
|
|
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
|
|
29
|
+
# @param file_path [String] Path of a built HTML file
|
|
30
|
+
# @return [Boolean] true if the file comes from a localized page with at
|
|
31
|
+
# least one locale and `extract_on_build` not set to false
|
|
45
32
|
def valid_for_extraction?(file_path)
|
|
46
33
|
page_config = find_page_config_for_file(file_path)
|
|
47
34
|
return false unless page_config
|
|
@@ -50,25 +37,18 @@ module Jekyll
|
|
|
50
37
|
config.enabled? && config.extract_on_build?
|
|
51
38
|
end
|
|
52
39
|
|
|
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
|
|
40
|
+
# @param file_path [String] Path of a built HTML file
|
|
41
|
+
# @return [PageLocalesConfig] Settings of the page that produced the file
|
|
60
42
|
def load_page_config(file_path)
|
|
61
43
|
page_config = find_page_config_for_file(file_path)
|
|
62
44
|
PageLocalesConfig.new(page_config)
|
|
63
45
|
end
|
|
64
46
|
|
|
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.
|
|
47
|
+
# Finds the localized page whose output is this file and returns its data
|
|
48
|
+
# with merged `with_locales_data` (see SiteConfigAccessor.effective_page_data).
|
|
69
49
|
#
|
|
70
|
-
# @param file_path [String] Path
|
|
71
|
-
# @return [Hash, nil]
|
|
50
|
+
# @param file_path [String] Path of a built HTML file
|
|
51
|
+
# @return [Hash, nil] The page data, or nil when no localized page matches
|
|
72
52
|
def find_page_config_for_file(file_path)
|
|
73
53
|
@site.pages.each do |page|
|
|
74
54
|
next unless page.data['with_locales'] == true
|
|
@@ -76,24 +56,18 @@ module Jekyll
|
|
|
76
56
|
page_output = page.output_ext ? page.destination('') : page.destination('/')
|
|
77
57
|
next unless file_path.end_with?(page_output.sub(%r{/$}, '/index.html'))
|
|
78
58
|
|
|
79
|
-
return page
|
|
59
|
+
return SiteConfigAccessor.effective_page_data(page)
|
|
80
60
|
end
|
|
81
61
|
|
|
82
62
|
nil
|
|
83
63
|
end
|
|
84
64
|
|
|
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.
|
|
65
|
+
# Localized copies are built from the English pages, so we extract only
|
|
66
|
+
# from the English files. Locale folders use the URL form of the locale
|
|
67
|
+
# code (es_ES becomes es-es), so compare against that form.
|
|
94
68
|
#
|
|
95
|
-
# @param file_path [String] Path
|
|
96
|
-
# @return [Boolean]
|
|
69
|
+
# @param file_path [String] Path of a built HTML file
|
|
70
|
+
# @return [Boolean] true if the file is inside a locale folder
|
|
97
71
|
def skip_localized_page?(file_path)
|
|
98
72
|
relative_path = file_path.sub(@dest, '')
|
|
99
73
|
|
|
@@ -101,13 +75,9 @@ module Jekyll
|
|
|
101
75
|
all_locales.any? { |locale| relative_path.start_with?("/#{LocaleUrlFormatter.to_url_segment(locale)}/") }
|
|
102
76
|
end
|
|
103
77
|
|
|
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
|
|
78
|
+
# @param config [PageLocalesConfig] The page's settings
|
|
79
|
+
# @return [Array<String>] CSS selectors of elements to leave out of
|
|
80
|
+
# extraction, from `extraction.exclude_selectors` or the defaults below
|
|
111
81
|
def extract_exclude_selectors(config)
|
|
112
82
|
config.data.dig('with_locales_data', 'extraction', 'exclude_selectors') ||
|
|
113
83
|
['script', 'style', 'code.language-plaintext', 'pre code']
|
|
@@ -10,46 +10,33 @@ require_relative '../utils/error_handler'
|
|
|
10
10
|
|
|
11
11
|
module Jekyll
|
|
12
12
|
module L10n
|
|
13
|
-
#
|
|
13
|
+
# Collects translatable strings from the built site into PO files.
|
|
14
14
|
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
# the
|
|
15
|
+
# PostWriteProcessor runs it after Jekyll writes the site. Each build it:
|
|
16
|
+
# 1. Reads every English HTML file in the destination. It skips locale
|
|
17
|
+
# folders such as `es/`.
|
|
18
|
+
# 2. Writes one PO file per page with the page's text and translatable
|
|
19
|
+
# attributes.
|
|
20
|
+
# 3. Merges those files into one compendium per locale (unless
|
|
21
|
+
# `update_compendium` is false). The merge deletes the page files.
|
|
22
|
+
# 4. Machine-translates the compendia when LibreTranslate is on.
|
|
23
|
+
# 5. Prints a summary unless `logging.show_statistics` is false.
|
|
19
24
|
#
|
|
20
|
-
#
|
|
21
|
-
# 1. Scans all HTML files in Jekyll output directory (_site/)
|
|
22
|
-
# 2. For each HTML file, extracts translatable text and attributes
|
|
23
|
-
# 3. Normalizes text for consistent matching across builds
|
|
24
|
-
# 4. Creates or updates page-specific PO files in _locales/ directory
|
|
25
|
-
# 5. Optionally applies automatic translations via LibreTranslate API
|
|
25
|
+
# It always processes the whole site. Incremental mode does not affect it.
|
|
26
26
|
#
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
# - Extract text and attributes from HTML with file location references
|
|
30
|
-
# - Create and update PO files with extracted strings
|
|
31
|
-
# - Log extraction statistics and progress
|
|
32
|
-
# - Coordinate with LibreTranslate for automatic translation
|
|
33
|
-
#
|
|
34
|
-
# @example Usage (typically invoked by PostWriteProcessor)
|
|
35
|
-
# extractor = Extractor.new(site)
|
|
36
|
-
# result = extractor.extract_site
|
|
27
|
+
# @example
|
|
28
|
+
# Extractor.new(site).extract_site
|
|
37
29
|
# # => { files_processed: 42, strings_extracted: 237, po_files_created: 3 }
|
|
38
30
|
#
|
|
39
|
-
# @see
|
|
40
|
-
# @see
|
|
31
|
+
# @see ExtractionResultSaver writes and merges the PO files
|
|
32
|
+
# @see HtmlStringExtractor finds strings in one HTML file
|
|
41
33
|
#
|
|
42
34
|
class Extractor
|
|
43
35
|
# @!attribute [r] site
|
|
44
|
-
# The Jekyll site object with generated pages
|
|
45
36
|
# @return [Jekyll::Site]
|
|
46
37
|
attr_reader :site
|
|
47
38
|
|
|
48
|
-
#
|
|
49
|
-
#
|
|
50
|
-
# Sets up configuration and result saving infrastructure for extraction.
|
|
51
|
-
#
|
|
52
|
-
# @param site [Jekyll::Site] The Jekyll site object
|
|
39
|
+
# @param site [Jekyll::Site] A site that Jekyll has already written
|
|
53
40
|
def initialize(site)
|
|
54
41
|
@site = site
|
|
55
42
|
@source = SiteConfigAccessor.source(@site)
|
|
@@ -58,26 +45,17 @@ module Jekyll
|
|
|
58
45
|
@result_saver = ExtractionResultSaver.new(@site)
|
|
59
46
|
end
|
|
60
47
|
|
|
61
|
-
#
|
|
48
|
+
# Runs all the steps in the class description.
|
|
62
49
|
#
|
|
63
|
-
#
|
|
64
|
-
#
|
|
65
|
-
# and optionally translates strings via LibreTranslate API.
|
|
66
|
-
#
|
|
67
|
-
# @return [Hash<Symbol, Integer>] Statistics hash with keys:
|
|
68
|
-
# - :files_processed - Number of HTML files processed
|
|
69
|
-
# - :strings_extracted - Total strings extracted
|
|
70
|
-
# - :po_files_created - Number of PO files created/updated
|
|
71
|
-
# @example
|
|
72
|
-
# result = extractor.extract_site
|
|
73
|
-
# puts "Processed #{result[:files_processed]} files"
|
|
50
|
+
# @return [Hash<Symbol, Integer>] Counts under :files_processed,
|
|
51
|
+
# :strings_extracted, and :po_files_created (files created or updated)
|
|
74
52
|
def extract_site
|
|
75
53
|
Jekyll.logger.info 'Localization', 'Extracting translatable strings...'
|
|
76
54
|
start_time = Time.now
|
|
77
55
|
stats = process_all_html_files
|
|
78
56
|
@result_saver.finalize_compendia
|
|
79
57
|
translate_all_compendia
|
|
80
|
-
ExtractionLogger.log_summary(stats, Time.now - start_time)
|
|
58
|
+
ExtractionLogger.log_summary(stats, Time.now - start_time) if show_statistics?
|
|
81
59
|
stats
|
|
82
60
|
end
|
|
83
61
|
|
|
@@ -134,6 +112,8 @@ module Jekyll
|
|
|
134
112
|
extractor.extract(html, @dest, file_path)
|
|
135
113
|
end
|
|
136
114
|
|
|
115
|
+
# Machine translation uses the settings of the first localized page that
|
|
116
|
+
# turns LibreTranslate on, including that page's `locales`.
|
|
137
117
|
def find_libretranslate_config
|
|
138
118
|
return nil unless @site.respond_to?(:pages)
|
|
139
119
|
|
|
@@ -149,6 +129,13 @@ module Jekyll
|
|
|
149
129
|
|
|
150
130
|
private
|
|
151
131
|
|
|
132
|
+
# The summary covers the whole site, so read the site-wide setting, not a
|
|
133
|
+
# page's.
|
|
134
|
+
def show_statistics?
|
|
135
|
+
site_data = SiteConfigAccessor.extract_locales_data(@site)
|
|
136
|
+
PageLocalesConfig.new('with_locales_data' => site_data).show_statistics?
|
|
137
|
+
end
|
|
138
|
+
|
|
152
139
|
def construct_page_path(file_path)
|
|
153
140
|
file_path.sub("#{@dest}/", '')
|
|
154
141
|
end
|
|
@@ -3,52 +3,38 @@
|
|
|
3
3
|
require_relative '../constants'
|
|
4
4
|
require_relative 'regeneration_checker'
|
|
5
5
|
require_relative '../utils/locale_url_formatter'
|
|
6
|
+
require_relative '../utils/site_config_accessor'
|
|
6
7
|
|
|
7
8
|
module Jekyll
|
|
8
9
|
module L10n
|
|
9
|
-
#
|
|
10
|
+
# Adds a copy of each localizable page for every configured locale.
|
|
10
11
|
#
|
|
11
|
-
# This is the
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
12
|
+
# This is where the plugin starts in a Jekyll build. For each page with
|
|
13
|
+
# `with_locales: true`, it reads the page's merged `with_locales_data` (site
|
|
14
|
+
# defaults plus front matter, see SiteConfigAccessor.page_locales_data) and
|
|
15
|
+
# adds one LocalizedPage per locale, served under a locale prefix such as
|
|
16
|
+
# `/es/`. Translation happens later, when Jekyll renders those pages.
|
|
15
17
|
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
# - Create LocalizedPage instances for each locale variant
|
|
20
|
-
# - Optimize regeneration by skipping unchanged pages
|
|
18
|
+
# With incremental mode on (`jekyll_l10n.incremental`), RegenerationChecker
|
|
19
|
+
# decides which copies to rebuild; the Generator protects the output of the
|
|
20
|
+
# skipped ones from Jekyll's cleanup step.
|
|
21
21
|
#
|
|
22
|
-
#
|
|
23
|
-
# the source page or configuration has changed, improving rebuild performance.
|
|
24
|
-
#
|
|
25
|
-
# @example Usage in Jekyll site configuration
|
|
26
|
-
# # _config.yml
|
|
27
|
-
# plugins:
|
|
28
|
-
# - jekyll-l10n
|
|
29
|
-
#
|
|
30
|
-
# @example Marking pages for localization
|
|
31
|
-
# # src/page.md
|
|
22
|
+
# @example Front matter that localizes a page into two locales
|
|
32
23
|
# ---
|
|
33
24
|
# with_locales: true
|
|
34
25
|
# with_locales_data:
|
|
35
|
-
# locales: [es, fr
|
|
26
|
+
# locales: [es, fr]
|
|
36
27
|
# ---
|
|
37
28
|
#
|
|
38
29
|
class Generator < Jekyll::Generator
|
|
39
30
|
priority :low
|
|
40
31
|
|
|
41
|
-
#
|
|
42
|
-
#
|
|
43
|
-
# This method is automatically called by Jekyll during the generate phase.
|
|
44
|
-
# It iterates through all pages in the site, identifies those marked for localization,
|
|
45
|
-
# and creates locale-specific variants.
|
|
32
|
+
# Jekyll calls this once per build. It does nothing when no page has
|
|
33
|
+
# `with_locales: true`.
|
|
46
34
|
#
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
# @param site [Jekyll::Site] The Jekyll site object containing pages to process
|
|
35
|
+
# @param site [Jekyll::Site] The Jekyll site
|
|
50
36
|
# @return [void]
|
|
51
|
-
# @see LocalizedPage
|
|
37
|
+
# @see LocalizedPage
|
|
52
38
|
def generate(site)
|
|
53
39
|
return unless any_pages_to_localize?(site)
|
|
54
40
|
|
|
@@ -61,6 +47,8 @@ module Jekyll
|
|
|
61
47
|
def generate_localized_pages(site)
|
|
62
48
|
original_pages = site.pages.dup
|
|
63
49
|
checker = Jekyll::L10n::RegenerationChecker.new(site)
|
|
50
|
+
checker.warn_legacy_config
|
|
51
|
+
skipped_outputs = []
|
|
64
52
|
|
|
65
53
|
original_pages.each do |page|
|
|
66
54
|
next unless should_localize_page?(page)
|
|
@@ -73,23 +61,37 @@ module Jekyll
|
|
|
73
61
|
if checker.should_regenerate?(page, locale)
|
|
74
62
|
localized_page = create_localized_page(site, page, locale)
|
|
75
63
|
site.pages << localized_page
|
|
64
|
+
else
|
|
65
|
+
skipped_outputs << checker.relative_dest_path(page, locale)
|
|
76
66
|
end
|
|
77
67
|
end
|
|
78
68
|
end
|
|
69
|
+
|
|
70
|
+
keep_skipped_outputs(site, skipped_outputs)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Jekyll's cleanup step deletes any output file that no page produced in
|
|
74
|
+
# this build, which includes skipped localized pages. Listing their output
|
|
75
|
+
# in site.keep_files keeps it.
|
|
76
|
+
#
|
|
77
|
+
# `jekyll serve` reuses the site and this generator between builds, so we
|
|
78
|
+
# first drop the entries added last time; otherwise they would stay kept
|
|
79
|
+
# after their page is rebuilt or deleted. We assign a new array because
|
|
80
|
+
# Jekyll shares the old one with site.config['keep_files'].
|
|
81
|
+
def keep_skipped_outputs(site, skipped_outputs)
|
|
82
|
+
previous = @kept_outputs || []
|
|
83
|
+
site.keep_files = (Array(site.keep_files) - previous) | skipped_outputs
|
|
84
|
+
@kept_outputs = skipped_outputs
|
|
79
85
|
end
|
|
80
86
|
|
|
81
87
|
def should_localize_page?(page)
|
|
82
88
|
return false if page.data['localized'] == true
|
|
83
89
|
return false unless page.data['with_locales'] == true
|
|
84
90
|
|
|
85
|
-
#
|
|
86
|
-
# Check multiple conditions to be safe:
|
|
87
|
-
# 1. output_ext must be .html
|
|
88
|
-
# 2. path must not contain common asset directories
|
|
91
|
+
# Localize HTML pages only, never CSS, JavaScript, or other assets.
|
|
89
92
|
is_html = page.output_ext == '.html'
|
|
90
93
|
is_not_asset = !page.path.match?(%r{/(assets|vendor|node_modules|\..*)/}i)
|
|
91
94
|
|
|
92
|
-
# Debug: Log which pages are being skipped
|
|
93
95
|
if !is_html
|
|
94
96
|
Jekyll.logger.debug 'Localization',
|
|
95
97
|
"Skipping non-HTML: #{page.path} (ext: #{page.output_ext})"
|
|
@@ -101,7 +103,7 @@ module Jekyll
|
|
|
101
103
|
end
|
|
102
104
|
|
|
103
105
|
def get_page_locales(page)
|
|
104
|
-
data = page
|
|
106
|
+
data = SiteConfigAccessor.page_locales_data(page)
|
|
105
107
|
|
|
106
108
|
locales = if data.is_a?(Hash)
|
|
107
109
|
data['locales'] || []
|
|
@@ -124,10 +126,6 @@ module Jekyll
|
|
|
124
126
|
locale.match?(Constants::LOCALE_PATTERN)
|
|
125
127
|
end
|
|
126
128
|
|
|
127
|
-
# Check if any pages in the site are marked for localization
|
|
128
|
-
#
|
|
129
|
-
# @param site [Jekyll::Site] The Jekyll site object
|
|
130
|
-
# @return [Boolean] True if at least one page has with_locales set to true
|
|
131
129
|
def any_pages_to_localize?(site)
|
|
132
130
|
site.pages.any? { |page| page.data['with_locales'] == true }
|
|
133
131
|
end
|
|
@@ -9,43 +9,31 @@ require_relative '../utils/external_link_icon_preserver'
|
|
|
9
9
|
|
|
10
10
|
module Jekyll
|
|
11
11
|
module L10n
|
|
12
|
-
#
|
|
12
|
+
# Translates the localized HTML files again after extraction.
|
|
13
13
|
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
14
|
+
# Localized pages are first translated while Jekyll renders them, using the
|
|
15
|
+
# PO files as they were at the start of the build. Extraction then runs and
|
|
16
|
+
# may add translations, for example on the first build or through machine
|
|
17
|
+
# translation. PostWriteProcessor calls this class when extraction wrote PO
|
|
18
|
+
# files, so this build's output already uses the new translations.
|
|
18
19
|
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
# * Create HtmlTranslator with proper configuration
|
|
23
|
-
# * Apply translations to HTML documents
|
|
24
|
-
# * Preserve external link icons from original HTML
|
|
25
|
-
# * Write translated HTML back to files
|
|
26
|
-
# * Handle errors gracefully with logging
|
|
20
|
+
# It only covers localized pages in `site.pages`. Pages that incremental
|
|
21
|
+
# mode skipped are not there; they pick up the new translations on the
|
|
22
|
+
# next build.
|
|
27
23
|
#
|
|
28
24
|
# @example
|
|
29
|
-
#
|
|
30
|
-
# reprocessor.reprocess_localized_pages
|
|
31
|
-
# # Reads localized HTML, translates, preserves icons, writes back
|
|
25
|
+
# PostWriteHtmlReprocessor.new(site).reprocess_localized_pages
|
|
32
26
|
class PostWriteHtmlReprocessor
|
|
33
27
|
attr_reader :site
|
|
34
28
|
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
# @param site [Jekyll::Site] Jekyll site object
|
|
29
|
+
# @param site [Jekyll::Site] A site that Jekyll has already written
|
|
38
30
|
def initialize(site)
|
|
39
31
|
@site = site
|
|
40
32
|
@dest = SiteConfigAccessor.dest(@site)
|
|
41
33
|
end
|
|
42
34
|
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
# Finds all localized HTML pages that were written during the Jekyll build,
|
|
46
|
-
# loads their translations, applies translations to text and attributes,
|
|
47
|
-
# preserves external link icon styling from the original HTML, and writes
|
|
48
|
-
# the translated HTML back to disk.
|
|
35
|
+
# Reads each localized file, translates it, keeps the external link icons
|
|
36
|
+
# of the English page, and writes it back.
|
|
49
37
|
#
|
|
50
38
|
# @return [void]
|
|
51
39
|
def reprocess_localized_pages
|
|
@@ -86,7 +74,7 @@ module Jekyll
|
|
|
86
74
|
|
|
87
75
|
return unless original_page
|
|
88
76
|
|
|
89
|
-
config = PageLocalesConfig.new(original_page
|
|
77
|
+
config = PageLocalesConfig.new(SiteConfigAccessor.effective_page_data(original_page))
|
|
90
78
|
translations = PageTranslationLoader.load(@site, locale, original_url, config)
|
|
91
79
|
|
|
92
80
|
return if translations.nil? || translations.empty?
|
|
@@ -3,25 +3,27 @@
|
|
|
3
3
|
require_relative '../utils/site_config_accessor'
|
|
4
4
|
require_relative '../po_file/path_builder'
|
|
5
5
|
require_relative '../utils/locale_url_formatter'
|
|
6
|
+
require_relative '../utils/url_path_builder'
|
|
7
|
+
require_relative '../utils/logger_formatter'
|
|
6
8
|
|
|
7
9
|
module Jekyll
|
|
8
10
|
module L10n
|
|
9
|
-
#
|
|
11
|
+
# Decides whether a localized page needs rebuilding when incremental mode is on.
|
|
10
12
|
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
13
|
+
# Incremental mode is the plugin's own `jekyll_l10n.incremental` setting, not
|
|
14
|
+
# `jekyll build --incremental`. When it is on, the checker compares the
|
|
15
|
+
# previous output file with the files the page depends on, and asks for a
|
|
16
|
+
# rebuild when any of them is newer: the source page, the page PO file, the
|
|
17
|
+
# locale compendium, or `_config.yml`.
|
|
14
18
|
#
|
|
15
|
-
#
|
|
16
|
-
# -
|
|
17
|
-
# -
|
|
18
|
-
#
|
|
19
|
-
# -
|
|
19
|
+
# Limits to know about:
|
|
20
|
+
# - It compares modification times, not file contents.
|
|
21
|
+
# - It looks for PO files under the directory passed to {#initialize}. The
|
|
22
|
+
# Generator passes nothing, so a custom `locales_dir` is never checked.
|
|
23
|
+
# - The page PO file exists only when `update_compendium` is false; the
|
|
24
|
+
# compendium merge deletes it otherwise.
|
|
20
25
|
#
|
|
21
|
-
#
|
|
22
|
-
# This significantly speeds up rebuilds when only a few files have changed.
|
|
23
|
-
#
|
|
24
|
-
# @example Usage
|
|
26
|
+
# @example
|
|
25
27
|
# checker = RegenerationChecker.new(site)
|
|
26
28
|
# if checker.should_regenerate?(page, 'es')
|
|
27
29
|
# # Create localized_page and add to site.pages
|
|
@@ -31,11 +33,9 @@ module Jekyll
|
|
|
31
33
|
# @!visibility private
|
|
32
34
|
DEFAULT_LOCALES_DIR = '_locales'
|
|
33
35
|
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
# @param locales_dir [String] Directory containing PO files (defaults to
|
|
38
|
-
# DEFAULT_LOCALES_DIR = "_locales")
|
|
36
|
+
# @param site [Jekyll::Site] The Jekyll site
|
|
37
|
+
# @param locales_dir [String] Directory, relative to the site source, that
|
|
38
|
+
# holds the PO files. Defaults to DEFAULT_LOCALES_DIR.
|
|
39
39
|
def initialize(site, locales_dir = DEFAULT_LOCALES_DIR)
|
|
40
40
|
@site = site
|
|
41
41
|
@locales_dir = locales_dir
|
|
@@ -43,20 +43,12 @@ module Jekyll
|
|
|
43
43
|
@destination = @site&.config&.dig('destination') || ''
|
|
44
44
|
end
|
|
45
45
|
|
|
46
|
-
#
|
|
47
|
-
#
|
|
48
|
-
# Returns true if:
|
|
49
|
-
# - Incremental builds are disabled (always regenerate)
|
|
50
|
-
# - Output file doesn't exist yet
|
|
51
|
-
# - Source page has been modified since output was generated
|
|
52
|
-
# - PO translation files have been modified since output was generated
|
|
53
|
-
# - Jekyll configuration has been modified since output was generated
|
|
54
|
-
#
|
|
55
|
-
# Returns false if incremental builds are enabled and output is up-to-date.
|
|
46
|
+
# Returns true when incremental mode is off, when the page has no previous
|
|
47
|
+
# output, or when a file the page depends on is newer than that output.
|
|
56
48
|
#
|
|
57
|
-
# @param page [Jekyll::Page] The source page
|
|
58
|
-
# @param locale [String]
|
|
59
|
-
# @return [Boolean] true if the page
|
|
49
|
+
# @param page [Jekyll::Page] The source (English) page
|
|
50
|
+
# @param locale [String] Locale code, e.g. 'es' or 'pt_BR'
|
|
51
|
+
# @return [Boolean] true if the localized page must be rebuilt
|
|
60
52
|
def should_regenerate?(page, locale)
|
|
61
53
|
return true unless incremental_enabled?
|
|
62
54
|
return true unless dest_path_exists?(page, locale)
|
|
@@ -68,16 +60,39 @@ module Jekyll
|
|
|
68
60
|
config_modified?(dest_mtime)
|
|
69
61
|
end
|
|
70
62
|
|
|
63
|
+
# Logs a warning when `_config.yml` still has the old `localization_gettext`
|
|
64
|
+
# key. The plugin ignores that key, so without the warning a site would
|
|
65
|
+
# lose incremental mode silently. Call it once per build, not per page.
|
|
66
|
+
#
|
|
67
|
+
# @return [void]
|
|
68
|
+
def warn_legacy_config
|
|
69
|
+
return unless @site&.config.is_a?(Hash) && @site.config.key?('localization_gettext')
|
|
70
|
+
|
|
71
|
+
LoggerFormatter.warn('RegenerationChecker',
|
|
72
|
+
'The `localization_gettext` key in _config.yml is ignored. ' \
|
|
73
|
+
'Set `jekyll_l10n.incremental` instead.')
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Output file of a localized page, relative to the site destination. The
|
|
77
|
+
# Generator adds it to `site.keep_files` for skipped pages, so Jekyll's
|
|
78
|
+
# cleanup step does not delete their previous output.
|
|
79
|
+
#
|
|
80
|
+
# This must produce the same path as LocalizedPage#destination.
|
|
81
|
+
#
|
|
82
|
+
# @param page [Jekyll::Page] The source (English) page
|
|
83
|
+
# @param locale [String] Locale code, e.g. 'es' or 'pt_BR'
|
|
84
|
+
# @return [String] Path such as "es/about/index.html" (no leading slash)
|
|
85
|
+
def relative_dest_path(page, locale)
|
|
86
|
+
url_locale = LocaleUrlFormatter.to_url_segment(locale)
|
|
87
|
+
"#{url_locale}#{page.url.chomp('/')}/index.html"
|
|
88
|
+
end
|
|
89
|
+
|
|
71
90
|
private
|
|
72
91
|
|
|
73
92
|
def incremental_enabled?
|
|
74
93
|
return false if @site&.config.nil?
|
|
75
94
|
|
|
76
|
-
|
|
77
|
-
'incremental') ||
|
|
78
|
-
@site.config.dig('localization_gettext', 'incremental') ||
|
|
79
|
-
false
|
|
80
|
-
localization_config == true
|
|
95
|
+
@site.config.dig('jekyll_l10n', 'incremental') == true
|
|
81
96
|
end
|
|
82
97
|
|
|
83
98
|
def dest_path_exists?(page, locale)
|
|
@@ -85,26 +100,25 @@ module Jekyll
|
|
|
85
100
|
end
|
|
86
101
|
|
|
87
102
|
def dest_path(page, locale)
|
|
88
|
-
#
|
|
89
|
-
# Uses BCP 47 hyphenated locale segment to match LocalizedPage#destination output.
|
|
90
|
-
url_locale = LocaleUrlFormatter.to_url_segment(locale)
|
|
91
|
-
url = "/#{url_locale}#{page.url.chomp('/')}/"
|
|
92
|
-
"#{@destination}#{url}index.html"
|
|
103
|
+
"#{@destination}/#{relative_dest_path(page, locale)}"
|
|
93
104
|
end
|
|
94
105
|
|
|
95
106
|
def source_modified?(page, dest_mtime)
|
|
96
|
-
|
|
107
|
+
# Jekyll 4 gives Page#path relative to the site source. Resolve it there,
|
|
108
|
+
# not against the current directory. An absolute path stays unchanged.
|
|
109
|
+
page_mtime = File.mtime(File.expand_path(page.path, @source))
|
|
97
110
|
page_mtime > dest_mtime
|
|
98
111
|
rescue StandardError
|
|
99
112
|
true
|
|
100
113
|
end
|
|
101
114
|
|
|
102
115
|
def po_files_modified?(page, locale, dest_mtime)
|
|
103
|
-
#
|
|
104
|
-
|
|
116
|
+
# Use the same page PO path PageTranslationLoader reads, which comes
|
|
117
|
+
# from the page URL, not from the source file name.
|
|
118
|
+
po_page_path = UrlPathBuilder.url_to_po_page_path(page.url)
|
|
119
|
+
page_po_path = PoPathBuilder.build(@source, @locales_dir, locale, po_page_path)
|
|
105
120
|
return true if file_modified?(page_po_path, dest_mtime)
|
|
106
121
|
|
|
107
|
-
# Check compendium PO file
|
|
108
122
|
compendium_po_path = PoPathBuilder.build(@source, @locales_dir, locale, nil)
|
|
109
123
|
file_modified?(compendium_po_path, dest_mtime)
|
|
110
124
|
end
|