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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f6934d1bf9c34ff4b1806a1b3dde6c19398ee10d261a7a3aa7925716e63f556f
4
- data.tar.gz: fde2a5801b9558384d7d6b0f9854526af3d5759d567cddce5aaa94c9f0873c78
3
+ metadata.gz: 9fb15328cbef2fac51cff8993c0f7e2eb36487de5471531fbb8fa2b0278bb6a1
4
+ data.tar.gz: a0600bbcb9b82c6b60ca47d0d73dccc23d90807bbcc5e2134e5ddda53a864dea
5
5
  SHA512:
6
- metadata.gz: 5fd0cf1f00fdd2a0b986540f4c8aeefd2cff50fac3122b6285a92a9f45b317042b28d8f72d6eadadbdc90e1c283ad792b18edd681000799541b53a75dbdbef92
7
- data.tar.gz: ec515247b53374ca812d0195815150276ce39c078356383f667a926ef313ec50fa909b316c90e03f85c3a1ede8b753f399c63542e290b3eda12035cf9b56973a
6
+ metadata.gz: b80419f2e724e61082cc72e39550d5bcefba0205cb659a3887eb7fb0f1d22ffa90dd3f20570fc90233f32b83cca5347d506098b62651aa86be96fd186f32ec7a
7
+ data.tar.gz: 8b8700a04de094d68752a6a70c1a20328c6e55ad7a1c70764f8756195b461703598cbf4d58e048224af2ac16df256ad219c8103f5b6112c81b2a3ebfb2f55a3d
data/README.md CHANGED
@@ -13,4 +13,4 @@ For complete documentation, configuration options, and examples, visit the
13
13
 
14
14
  ## License
15
15
 
16
- MIT License - see LICENSE file for details.
16
+ MIT License - see [LICENSE](LICENSE) file for details.
@@ -6,19 +6,12 @@ require_relative '../utils/site_config_accessor'
6
6
 
7
7
  module Jekyll
8
8
  module L10n
9
- # Loads and validates extraction configuration for files during build.
9
+ # Finds the settings for a built HTML file during extraction.
10
10
  #
11
- # ExtractionConfigLoader finds extraction configuration for generated HTML
12
- # files by matching them against Jekyll site pages. It validates whether
13
- # extraction is enabled and configured for a file, loads page-specific
14
- # settings, and identifies files to skip (localized page variants).
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
- # Initialize a new ExtractionConfigLoader.
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
- # Check if a file is valid for extraction.
39
- #
40
- # Matches the file to a Jekyll page and verifies extraction is enabled
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
- # Load extraction configuration for a file.
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
- # Find page configuration for a file.
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 to generated file
71
- # @return [Hash, nil] Page front matter data if found, nil otherwise
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.data
59
+ return SiteConfigAccessor.effective_page_data(page)
80
60
  end
81
61
 
82
62
  nil
83
63
  end
84
64
 
85
- # Check if a file is a localized page variant that should be skipped.
86
- #
87
- # Localized pages are generated copies in locale subdirectories that
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 to file to check
96
- # @return [Boolean] True if file is in a locale subdirectory
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
- # Extract CSS selectors for element exclusion from configuration.
105
- #
106
- # Returns CSS selectors of elements to exclude from extraction (e.g.,
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
- # String Extraction Orchestrator - Finds translatable strings in generated HTML
13
+ # Collects translatable strings from the built site into PO files.
14
14
  #
15
- # The Extractor is the main entry point for the string extraction workflow. It scans all
16
- # generated HTML files after Jekyll's build, identifies translatable content (text nodes
17
- # and configurable HTML attributes), and creates or updates GNU Gettext PO files with
18
- # the extracted strings.
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
- # The extraction workflow:
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
- # Key responsibilities:
28
- # - Load and validate extraction configuration from pages
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 Jekyll::L10n::ExtractionResultSaver for PO file creation and updates
40
- # @see Jekyll::L10n::HtmlStringExtractor for text and attribute extraction
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
- # Initialize the string extractor
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
- # Extract all translatable strings from the generated site
48
+ # Runs all the steps in the class description.
62
49
  #
63
- # Main entry point for extraction. Scans all HTML files in the build output,
64
- # extracts translatable strings and attributes, creates/updates PO files,
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
- # Localized Page Generator - Creates locale-prefixed copies of pages during Jekyll build
10
+ # Adds a copy of each localizable page for every configured locale.
10
11
  #
11
- # This is the main Jekyll integration point for the localization plugin. It runs as a
12
- # low-priority generator during the Jekyll build process, creating duplicate pages for each
13
- # configured locale. Each localized page inherits content and metadata from the source page
14
- # but includes locale-prefixed URLs.
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
- # Key responsibilities:
17
- # - Identify pages marked for localization (with_locales: true)
18
- # - Extract configured locales from page front matter
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
- # The generator respects Jekyll's incremental build mode and only regenerates pages when
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, pt, de]
26
+ # locales: [es, fr]
36
27
  # ---
37
28
  #
38
29
  class Generator < Jekyll::Generator
39
30
  priority :low
40
31
 
41
- # Generate localized pages for all marked pages in the Jekyll site
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
- # Returns early with no output if no pages are marked for localization.
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 for details on page structure
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
- # Only localize HTML pages - exclude static assets
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.data['with_locales_data']
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
- # Applies translations to localized HTML pages after Jekyll build.
12
+ # Translates the localized HTML files again after extraction.
13
13
  #
14
- # PostWriteHtmlReprocessor finds all localized HTML pages generated by the
15
- # Generator, loads appropriate translations, applies them using HtmlTranslator,
16
- # and rewrites the HTML files. It processes pages after Jekyll's standard
17
- # build completes, during the post-write phase.
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
- # Key responsibilities:
20
- # * Find localized HTML pages in the build output
21
- # * Load page translations (compendium + page-specific)
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
- # reprocessor = PostWriteHtmlReprocessor.new(site)
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
- # Initialize a new PostWriteHtmlReprocessor.
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
- # Reprocess all localized pages with translations.
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.data)
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
- # Regeneration Checker - Optimizes incremental builds by skipping unchanged pages
11
+ # Decides whether a localized page needs rebuilding when incremental mode is on.
10
12
  #
11
- # This class implements incremental build optimization for localized pages. When Jekyll's
12
- # incremental build mode is enabled, it checks whether a specific locale variant of a page
13
- # needs to be regenerated based on source modification times.
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
- # The checker compares modification times of:
16
- # - Source page file
17
- # - Page-specific PO translation file
18
- # - Compendium (shared translations) PO file
19
- # - Jekyll configuration (_config.yml)
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
- # If any of these are newer than the output HTML, the page is regenerated.
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
- # Initialize the regeneration checker
35
- #
36
- # @param site [Jekyll::Site] The Jekyll site object
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
- # Determine if a localized page variant should be regenerated
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 to check
58
- # @param locale [String] The locale code for the variant (e.g., 'es', 'fr')
59
- # @return [Boolean] true if the page should be regenerated, false if output is current
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
- localization_config = @site.config.dig('localization_gettext', 'with_locales_data',
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
- # Replicate LocalizedPage destination logic without instantiation.
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
- page_mtime = File.mtime(page.path)
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
- # Check page-specific PO file
104
- page_po_path = PoPathBuilder.build(@source, @locales_dir, locale, page.path)
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