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.
@@ -5,6 +5,7 @@ require_relative '../constants'
5
5
  require_relative '../utils/error_handler'
6
6
  require_relative '../utils/locale_name_resolver'
7
7
  require_relative '../utils/locale_url_formatter'
8
+ require_relative '../utils/site_config_accessor'
8
9
 
9
10
  module Jekyll
10
11
  module L10n
@@ -192,29 +193,27 @@ module Jekyll
192
193
  return false if locale.nil? || locale.empty?
193
194
  return true if locale == 'en' # English is always valid
194
195
 
195
- # Get configured locales from page or site config
196
196
  locales_config = configured_locales
197
197
 
198
- # Normalize both the target locale and each configured locale to BCP 47 form
199
- # before comparing, so 'es-ES' matches 'es_ES' and vice versa.
198
+ # Compare URL forms so 'es-ES' matches 'es_ES' and the other way round.
200
199
  url_locale = LocaleUrlFormatter.to_url_segment(locale)
201
200
  locales_config.any? { |l| LocaleUrlFormatter.to_url_segment(l) == url_locale }
202
201
  end
203
202
 
203
+ # Locales of the page being rendered. A Hash comes from Liquid and already
204
+ # has the site defaults merged in; a page object needs the same merge.
204
205
  def configured_locales
205
206
  page = @context.registers[:page]
206
207
 
207
- # Try to get from page data first (most specific)
208
208
  if page
209
209
  locales = if page.is_a?(Hash)
210
210
  page.dig('with_locales_data', 'locales')
211
211
  else
212
- page.data&.dig('with_locales_data', 'locales')
212
+ SiteConfigAccessor.page_locales_data(page)['locales']
213
213
  end
214
214
  return locales if locales && !locales.empty?
215
215
  end
216
216
 
217
- # Fallback to empty array if not configured
218
217
  []
219
218
  end
220
219
 
@@ -297,7 +297,7 @@ module Jekyll
297
297
  end
298
298
 
299
299
  # Unified metadata extraction: extracts reference, fuzzy flag, and previous msgid.
300
- # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
300
+ # rubocop:disable-next Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
301
301
  def self.extract_metadata_before_msgid(lines, msgid_idx, include_fuzzy: false)
302
302
  reference = nil
303
303
  fuzzy = false
@@ -317,7 +317,6 @@ module Jekyll
317
317
 
318
318
  include_fuzzy ? [reference, fuzzy, previous_msgid] : reference
319
319
  end
320
- # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
321
320
 
322
321
  def self.extract_reference_from_line(comment_line)
323
322
  comment_line.sub(/^#:\s*/, '').strip if comment_line.start_with?('#:')
@@ -457,7 +456,7 @@ module Jekyll
457
456
  previous_msgid, with_metadata: with_metadata)
458
457
  end
459
458
 
460
- # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
459
+ # rubocop:disable-next Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
461
460
  def self.build_translation_entry(msgstr, reference, fuzzy, previous_msgid = nil,
462
461
  with_metadata: false)
463
462
  # Simple format when no metadata requested and none provided
@@ -471,7 +470,6 @@ module Jekyll
471
470
  entry[:comment] = nil if !fuzzy.nil? || !reference.nil?
472
471
  entry
473
472
  end
474
- # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
475
473
 
476
474
  # Kept for backward compatibility with existing tests
477
475
  # Supports both positional and keyword argument calling styles
@@ -8,39 +8,23 @@ require_relative '../utils/logger_formatter'
8
8
 
9
9
  module Jekyll
10
10
  module L10n
11
- # Loads and merges page-specific and compendium translations.
11
+ # Reads the translations one page needs for one locale.
12
12
  #
13
- # PageTranslationLoader combines compendium (site-wide) translations with
14
- # page-specific translations for a given locale and URL. It loads both
15
- # translation sources from PO files, merges them (page-specific takes
16
- # precedence), and returns the combined translation hash for use by
17
- # HtmlTranslator.
18
- #
19
- # Key responsibilities:
20
- # * Load compendium translations for a locale
21
- # * Convert page URLs to PO file paths
22
- # * Load page-specific translations
23
- # * Merge compendium and page-specific translations
24
- # * Filter empty translations (untranslated entries)
25
- # * Log loading progress at debug level
13
+ # It combines two PO files: the locale compendium (`es.po`) and the page's
14
+ # own file (`es/<page url>/index.html.po`). When both have a string, the
15
+ # page file wins. With the default `update_compendium: true` the page file
16
+ # no longer exists after extraction, so the compendium provides everything.
26
17
  #
27
18
  # @example
28
- # config = PageLocalesConfig.new(page.data)
29
- # translations = PageTranslationLoader.load(site, 'es', '/docs/index.html', config)
30
- # # Returns merged translations for that page in Spanish
19
+ # config = PageLocalesConfig.new(SiteConfigAccessor.effective_page_data(page))
20
+ # PageTranslationLoader.load(site, 'es', '/docs/index.html', config)
21
+ # # => { "Welcome" => "Bienvenido", ... }
31
22
  class PageTranslationLoader
32
- # Load and merge translations for a page in a specific locale.
33
- #
34
- # Loads the compendium (site-wide) translations and page-specific translations,
35
- # merges them (page-specific entries override compendium), and returns the
36
- # combined hash. Filters out untranslated entries (empty msgstr).
37
- #
38
- # @param site [Jekyll::Site] Jekyll site object
39
- # @param locale [String] Target locale code (e.g., 'es', 'fr')
40
- # @param original_url [String] Original page URL (e.g., '/docs/index.html')
41
- # @param config [PageLocalesConfig] Localization configuration for the page
42
- # @return [Hash] Merged translation hash { msgid => msgstr }, filtered to only
43
- # include non-empty translations
23
+ # @param site [Jekyll::Site] The Jekyll site
24
+ # @param locale [String] Locale code, e.g. 'es'
25
+ # @param original_url [String] URL of the English page, e.g. '/docs/index.html'
26
+ # @param config [PageLocalesConfig] The page's settings (for `locales_dir`)
27
+ # @return [Hash] msgid => msgstr, without untranslated entries
44
28
  def self.load(site, locale, original_url, config)
45
29
  po_manager = PoFileManager.new(site, config.locales_dir)
46
30
  page_path = construct_po_page_path(original_url)
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../utils/page_locales_config'
4
+ require_relative '../utils/site_config_accessor'
4
5
  require_relative '../utils/html_parser'
5
6
  require_relative '../utils/logger_formatter'
6
7
  require_relative '../utils/error_handler'
@@ -10,65 +11,38 @@ require_relative 'page_translation_loader'
10
11
 
11
12
  module Jekyll
12
13
  module L10n
13
- # Translation Orchestrator - Applies PO file translations to localized pages
14
+ # Translates one localized page right after Jekyll renders it.
14
15
  #
15
- # The Translator is the main entry point for applying translations to pages. It's invoked
16
- # during the post_render phase of the Jekyll build (via a Jekyll hook) for each localized
17
- # page variant. The translator loads appropriate translations from PO files and applies
18
- # them to the page's HTML content.
16
+ # A `:pages, :post_render` hook in lib/jekyll-l10n.rb calls it for every
17
+ # LocalizedPage. It loads the translations for the page's locale, replaces
18
+ # the English text and attributes in the rendered HTML, and applies the
19
+ # page's fallback mode to strings without a translation.
19
20
  #
20
- # The translation workflow:
21
- # 1. Check if the page is localized and has required metadata
22
- # 2. Load translations from PO files (page-specific and compendium)
23
- # 3. Apply translations to text nodes and attributes in HTML
24
- # 4. Handle missing translations with configured fallback modes
25
- # 5. Preserve special elements like external link icons
21
+ # Settings come from the English page's merged `with_locales_data`, so a
22
+ # page can override, for example, `translation.fallback` for itself.
26
23
  #
27
- # Key responsibilities:
28
- # - Load translations for the page's locale
29
- # - Apply translations to HTML with DOM manipulation
30
- # - Handle fallback modes (english, marker, empty)
31
- # - Preserve special formatting and elements (icons, badges, etc.)
32
- # - Log translation progress and errors
24
+ # @example
25
+ # Translator.new(localized_page).translate
33
26
  #
34
- # This runs in phase 3 of the Jekyll build pipeline (Post-Render Phase). See the
35
- # "Build Pipeline" section in lib/jekyll-l10n.rb for the complete workflow.
36
- #
37
- # @example Usage (typically invoked via Jekyll hook)
38
- # # Automatically invoked for localized pages in post_render phase
39
- # translator = Translator.new(localized_page)
40
- # translator.translate
41
- #
42
- # @see Jekyll::L10n for Build Pipeline documentation
43
- # @see Jekyll::L10n::HtmlTranslator for low-level DOM translation logic
44
- # @see Jekyll::L10n::PageTranslationLoader for loading translations from PO files
27
+ # @see HtmlTranslator changes the HTML
28
+ # @see PageTranslationLoader reads the PO files
45
29
  #
46
30
  class Translator
47
31
  # @!attribute [r] page
48
- # The Jekyll page being translated
49
- # @return [Jekyll::Page]
32
+ # @return [Jekyll::Page] The page being translated
50
33
  attr_reader :page
51
34
 
52
- # Initialize the translator
53
- #
54
- # @param page [Jekyll::Page] The page to translate (should be a LocalizedPage)
35
+ # @param page [Jekyll::Page] Usually a LocalizedPage
55
36
  def initialize(page)
56
37
  @page = page
57
38
  @site = page.site
58
39
  end
59
40
 
60
- # Apply translations to the page
61
- #
62
- # Main entry point for translation. Checks if the page should be translated,
63
- # loads the appropriate translations from PO files, applies them to the HTML,
64
- # and updates the page's output.
65
- #
66
- # This method is called automatically by Jekyll's post_render hook for each
67
- # LocalizedPage during the build process.
41
+ # Replaces the page output with its translated HTML. Does nothing for
42
+ # pages without `locale` and `original_url`, pages whose English page
43
+ # cannot be found, or when there are no translations.
68
44
  #
69
45
  # @return [void]
70
- # @note Only translates pages with localized: true and matching locale/original_url
71
- # @note Gracefully handles missing translations with configured fallback mode
72
46
  def translate
73
47
  return unless should_translate?
74
48
 
@@ -79,21 +53,20 @@ module Jekyll
79
53
  original_page = find_and_log_original_page(original_url)
80
54
  return unless original_page
81
55
 
82
- translations = load_and_log_translations(locale, original_url, original_page)
56
+ config = PageLocalesConfig.new(SiteConfigAccessor.effective_page_data(original_page))
57
+ translations = load_and_log_translations(locale, original_url, config)
83
58
  return if translations.nil? || translations.empty?
84
59
 
85
- apply_translations_to_page(original_page, translations, locale, baseurl)
60
+ apply_translations_to_page(original_page, config, translations, locale, baseurl)
86
61
  end
87
62
 
88
63
  private
89
64
 
90
- def load_translations_for_page(locale, original_url, original_page)
91
- config = PageLocalesConfig.new(original_page.data)
65
+ def load_translations_for_page(locale, original_url, config)
92
66
  PageTranslationLoader.load(@site, locale, original_url, config)
93
67
  end
94
68
 
95
- def apply_translations_to_page(original_page, translations, locale, baseurl)
96
- config = PageLocalesConfig.new(original_page.data)
69
+ def apply_translations_to_page(original_page, config, translations, locale, baseurl)
97
70
  translator = HtmlTranslator.new(
98
71
  config.fallback_mode,
99
72
  config.translatable_attributes,
@@ -112,16 +85,11 @@ module Jekyll
112
85
  ExternalLinkIconPreserver.preserve(original_html, translated_html)
113
86
  end
114
87
 
115
- # Apply translations to page output
116
- #
117
- # Internal helper that uses HtmlTranslator to apply translations to the page's
118
- # HTML output, with configurable locale and baseurl for URL transformation.
119
- #
120
- # @param translator [HtmlTranslator] The translator instance to use
121
- # @param translations [Hash] Translation hash mapping text to translations
122
- # @param locale [String, nil] Target locale code (defaults to nil). If nil, uses "en"
123
- # @param baseurl [String] Base URL for relative URL transformation (defaults to "")
124
- # @return [String, nil] Translated HTML string, or nil on error
88
+ # @param translator [HtmlTranslator]
89
+ # @param translations [Hash] msgid => msgstr
90
+ # @param locale [String, nil] Target locale; nil means "en"
91
+ # @param baseurl [String] Site baseurl, used when rewriting links
92
+ # @return [String, nil] Translated HTML, or nil after logging an error
125
93
  def apply_translations(translator, translations, locale = nil, baseurl = '')
126
94
  if locale && locale != 'en'
127
95
  translator.translate(@page.output, translations, locale, baseurl)
@@ -169,8 +137,8 @@ module Jekyll
169
137
  original_page
170
138
  end
171
139
 
172
- def load_and_log_translations(locale, original_url, original_page)
173
- translations = load_translations_for_page(locale, original_url, original_page)
140
+ def load_and_log_translations(locale, original_url, config)
141
+ translations = load_translations_for_page(locale, original_url, config)
174
142
  LoggerFormatter.debug_if_enabled('Translator',
175
143
  "Loaded #{translations&.length || 0} translations")
176
144
  translations