From 7132e45a64d029eee3743c4c32cba0bde8b6a1ec Mon Sep 17 00:00:00 2001
From: plowsof <plowsof@protonmail.com>
Date: Tue, 9 Jul 2024 21:36:09 +0100
Subject: [PATCH] plugins: local jekyll-multiple-language-plugin

---
 Gemfile                                      |   1 -
 Gemfile.lock                                 |   2 -
 _config.yml                                  |   3 +-
 _plugins/jekyll-multiple-languages-plugin.rb | 687 +++++++++++++++++++
 _plugins/plugin/version.rb                   |   6 +
 5 files changed, 694 insertions(+), 5 deletions(-)
 create mode 100644 _plugins/jekyll-multiple-languages-plugin.rb
 create mode 100644 _plugins/plugin/version.rb

diff --git a/Gemfile b/Gemfile
index 19285c9d..1f50ebe9 100644
--- a/Gemfile
+++ b/Gemfile
@@ -4,5 +4,4 @@ gem 'jekyll'
 gem 'jekyll-paginate'
 gem 'builder'
 gem 'wdm', '>= 0.1.0' if Gem.win_platform?
-gem 'jekyll-multiple-languages-plugin', '= 1.7.0'
 gem 'jekyll-feed'
diff --git a/Gemfile.lock b/Gemfile.lock
index 9fd69f18..3741043a 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -34,8 +34,6 @@ GEM
       webrick (~> 1.7)
     jekyll-feed (0.17.0)
       jekyll (>= 3.7, < 5.0)
-    jekyll-multiple-languages-plugin (1.7.0)
-      jekyll (>= 2.0, < 5.0)
     jekyll-paginate (1.1.0)
     jekyll-sass-converter (3.0.0)
       sass-embedded (~> 1.54)
diff --git a/_config.yml b/_config.yml
index 49318a3d..34b5904b 100644
--- a/_config.yml
+++ b/_config.yml
@@ -21,7 +21,6 @@ paginate_path: blog/page:num/
 
 plugins:
   - jekyll-paginate
-  - jekyll-multiple-languages-plugin
   - jekyll-feed
 
 feed:
@@ -29,7 +28,7 @@ feed:
 
 # jekyll-multiple-languages-plugin settings:
 languages: ["en", "es", "it", "pl", "fr", "ar", "ru", "de", "nl", "pt-br", "tr", "zh-cn", "zh-tw", "nb-no", "el"]
-
+verbose: true
 
 exclude_from_localizations: ["img", "css", "fonts", "media", "404.html", "feed.xml", "meta", "_posts", "legal", "blog"]
 
diff --git a/_plugins/jekyll-multiple-languages-plugin.rb b/_plugins/jekyll-multiple-languages-plugin.rb
new file mode 100644
index 00000000..229bcdf1
--- /dev/null
+++ b/_plugins/jekyll-multiple-languages-plugin.rb
@@ -0,0 +1,687 @@
+=begin
+
+Jekyll  Multiple  Languages  is  an  internationalization  plugin for Jekyll. It
+compiles  your  Jekyll site for one or more languages with a similar approach as
+Rails does. The different sites will be stored in sub folders with the same name
+as the language it contains.
+
+Please visit https://github.com/screeninteraction/jekyll-multiple-languages-plugin
+for more details.
+
+=end
+
+
+
+require_relative "plugin/version"
+
+module Jekyll
+
+  #*****************************************************************************
+  # :site, :post_render hook
+  #*****************************************************************************
+  Jekyll::Hooks.register :site, :pre_render do |site, payload|
+      lang = site.config['lang']
+  end
+
+  #*****************************************************************************
+  # :site, :post_write hook
+  #*****************************************************************************
+  Jekyll::Hooks.register :site, :post_write do |site|
+
+    # Moves excluded paths from the default lang subfolder to the root folder
+    #===========================================================================
+    default_lang = site.config["default_lang"]
+    current_lang = site.config["lang"]
+    exclude_paths = site.config["exclude_from_localizations"]
+
+    if (default_lang == current_lang && site.config["default_locale_in_subfolder"])
+      files = Dir.glob(File.join("_site/" + current_lang + "/", "*"))
+      files.each do |file_path|
+        parts = file_path.split('/')
+        f_path = parts[2..-1].join('/')
+        if (f_path == 'base.html')
+          new_path = parts[0] + "/index.html"
+          puts "Moving '" + file_path + "' to '" + new_path + "'"
+          File.rename file_path, new_path
+        else
+          exclude_paths.each do |exclude_path|
+            if (exclude_path == f_path)
+              new_path = parts[0] + "/" + f_path
+              puts "Moving '" + file_path + "' to '" + new_path + "'"
+              if (Dir.exists?(new_path))
+                FileUtils.rm_r new_path
+              end
+              File.rename file_path, new_path
+            end
+          end
+        end
+      end
+    end
+
+    #===========================================================================
+
+  end
+
+  Jekyll::Hooks.register :site, :post_render do |site, payload|
+    
+    # Removes all static files that should not be copied to translated sites.
+    #===========================================================================
+    default_lang  = payload["site"]["default_lang"]
+    current_lang  = payload["site"][        "lang"]
+    
+    static_files  = payload["site"]["static_files"]
+    exclude_paths = payload["site"]["exclude_from_localizations"]
+    
+    default_locale_in_subfolder = site.config["default_locale_in_subfolder"]
+    
+    if default_lang != current_lang
+      static_files.delete_if do |static_file|
+        next true if (static_file.name == 'base.html' && default_locale_in_subfolder)
+
+        # Remove "/" from beginning of static file relative path
+        if static_file.instance_variable_get(:@relative_path) != nil
+          static_file_r_path = static_file.instance_variable_get(:@relative_path).dup
+          if static_file_r_path
+            static_file_r_path[0] = ''
+
+            exclude_paths.any? do |exclude_path|
+              Pathname.new(static_file_r_path).descend do |static_file_path|
+                break(true) if (Pathname.new(exclude_path) <=> static_file_path) == 0
+              end
+            end
+          end
+        end
+      end
+    end
+    
+    #===========================================================================
+    
+  end
+
+  #*****************************************************************************
+  # :site, :pre_render hook
+  #*****************************************************************************
+  Jekyll::Hooks.register :site, :pre_render do |site, payload|
+
+    # Localize front matter data of every page.
+    #===========================================================================
+    (site.pages + site.documents).each do |item|
+      translate_props(item.data, site)
+     end
+  end
+
+  ##############################################################################
+  # class Site
+  ##############################################################################
+  class Site
+    
+    attr_accessor :parsed_translations   # Hash that stores parsed translations read from YAML files.
+    
+    alias :process_org :process
+    
+    #======================================
+    # process
+    #
+    # Reads Jekyll and plugin configuration parameters set on _config.yml, sets
+    # main parameters and processes the website for each language.
+    #======================================
+    def process
+      # Check if plugin settings are set, if not, set a default or quit.
+      #-------------------------------------------------------------------------
+      self.parsed_translations ||= {}
+      
+      self.config['exclude_from_localizations'] ||= []
+
+      self.config['default_locale_in_subfolder'] ||= false
+      
+      if ( !self.config['languages']         or
+            self.config['languages'].empty?  or
+           !self.config['languages'].all?
+         )
+          puts 'You must provide at least one language using the "languages" setting on your _config.yml.'
+          
+          exit
+      end
+      
+      
+      # Variables
+      #-------------------------------------------------------------------------
+      
+      # Original Jekyll configurations
+      baseurl_org                 = self.config[ 'baseurl' ].to_s # Baseurl set on _config.yml
+      dest_org                    = self.dest                     # Destination folder where the website is generated
+      
+      # Site building only variables
+      languages                   = self.config['languages'] # List of languages set on _config.yml
+      
+      # Site wide plugin configurations
+      self.config['default_lang'] = languages.first          # Default language (first language of array set on _config.yml)
+      self.config[        'lang'] = languages.first          # Current language being processed
+      self.config['baseurl_root'] = baseurl_org              # Baseurl of website root (without the appended language code)
+      self.config['translations'] = self.parsed_translations # Hash that stores parsed translations read from YAML files. Exposes this hash to Liquid.
+      
+      # Build the website for all languages
+      #-------------------------------------------------------------------------
+      
+      # Remove .htaccess file from included files, so it wont show up on translations folders.
+      self.include -= [".htaccess"]
+      
+      #Preload all languages
+      languages.each do |lang|
+        puts "Loading translation from file #{self.config['source']}/_i18n/#{lang}.yml"
+        self.parsed_translations[lang] = YAML.load_file("#{self.config['source']}/_i18n/#{lang}.yml")  
+      end
+
+      languages.each do |lang|
+        
+        # Language specific config/variables
+        if lang != self.config['default_lang'] || self.config['default_locale_in_subfolder']
+          @dest                  = dest_org    + "/" + lang
+          self.config['baseurl'] = baseurl_org + "/" + lang
+          self.config['lang']    =                     lang
+        end
+        
+        # Translate site attributes to current language
+        translate_props(self.config, self)
+
+        puts "Building site for language: \"#{self.config['lang']}\" to: #{self.dest}"
+        
+        process_org
+      end
+      
+      # Revert to initial Jekyll configurations (necessary for regeneration)
+      self.config[ 'baseurl' ] = baseurl_org  # Baseurl set on _config.yml
+      @dest                    = dest_org     # Destination folder where the website is generated
+      
+      puts 'Build complete'
+    end
+
+
+
+    if Gem::Version.new(Jekyll::VERSION) < Gem::Version.new("3.0.0")
+      alias :read_posts_org :read_posts
+
+      #======================================
+      # read_posts
+      #======================================
+      def read_posts(dir)
+        translate_posts = !self.config['exclude_from_localizations'].include?("_posts")
+        
+        if dir == '' && translate_posts
+          read_posts("_i18n/#{self.config['lang']}/")
+        else
+          read_posts_org(dir)
+        end
+        
+      end
+    end
+
+  end
+
+
+
+  ##############################################################################
+  # class PageReader
+  ##############################################################################
+  class PageReader
+    alias :read_org :read
+
+    #======================================
+    # read
+    #
+    # Monkey patched this method to remove excluded languages.
+    #======================================
+    def read(files)
+      read_org(files).reject do |page|
+        page.data['languages'] && !page.data['languages'].include?(site.config['lang'])
+      end
+    end
+  end
+
+
+
+  ##############################################################################
+  # class PostReader
+  ##############################################################################
+  class PostReader
+  
+    if Gem::Version.new(Jekyll::VERSION) >= Gem::Version.new("3.0.0")
+      alias :read_posts_org :read_posts
+      
+      #======================================
+      # read_posts
+      #======================================
+      def read_posts(dir)
+        translate_posts = !site.config['exclude_from_localizations'].include?("_posts")
+        if dir == '' && translate_posts
+          read_posts("_i18n/#{site.config['lang']}/")
+        else
+          read_posts_org(dir)
+        end
+      end
+    end
+  end
+  
+  
+  
+  #-----------------------------------------------------------------------------
+  #
+  # Include (with priority—prepend)the translated
+  # permanent link for Page and document
+  #
+  #-----------------------------------------------------------------------------
+
+  module Permalink
+    #======================================
+    # permalink
+    #======================================
+    def permalink
+      return nil if data.nil? || data['permalink'].nil?
+      
+      if site.config['relative_permalinks']
+        File.join(@dir,  data['permalink'])
+      elsif site.config['lang']
+        # Look if there's a permalink overwrite specified for this lang
+        data['permalink_' + site.config['lang']] || data['permalink']
+      else
+        data['permalink']
+      end
+      
+    end
+  end
+
+  Page.prepend(Permalink)
+  Document.prepend(Permalink)
+
+
+  ##############################################################################
+  # class Post
+  ##############################################################################
+  class Post
+  
+    if Gem::Version.new(Jekyll::VERSION) < Gem::Version.new("3.0.0")
+      alias :populate_categories_org :populate_categories
+      
+      #======================================
+      # populate_categories
+      #
+      # Monkey patched this method to remove unwanted strings
+      # ("_i18n" and language code) that are prepended to posts categories
+      # because of how the multilingual posts are arranged in subfolders.
+      #======================================
+      def populate_categories
+        categories_from_data = Utils.pluralized_array_from_hash(data, 'category', 'categories')
+        self.categories = (
+          Array(categories) + categories_from_data
+        ).map {|c| c.to_s.downcase}.flatten.uniq
+        
+        self.categories.delete("_i18n")
+        self.categories.delete(site.config['lang'])
+        
+        return self.categories
+      end
+    end
+  end
+
+
+
+  ##############################################################################
+  # class Document
+  ##############################################################################
+  class Document
+    
+    if Gem::Version.new(Jekyll::VERSION) >= Gem::Version.new("3.0.0")
+      alias :populate_categories_org :populate_categories
+      
+      #======================================
+      # populate_categories
+      #
+      # Monkey patched this method to remove unwanted strings
+      # ("_i18n" and language code) that are prepended to posts categories
+      # because of how the multilingual posts are arranged in subfolders.
+      #======================================
+      def populate_categories
+        data['categories'].delete("_i18n")
+        data['categories'].delete(site.config['lang'])
+        
+        merge_data!({
+          'categories' => (
+            Array(data['categories']) + Utils.pluralized_array_from_hash(data, 'category', 'categories')
+          ).map(&:to_s).flatten.uniq
+        })
+      end
+    end
+  end
+  
+  
+  
+  #-----------------------------------------------------------------------------
+  #
+  # The next classes implements the plugin Liquid Tags and/or Filters
+  #
+  #-----------------------------------------------------------------------------
+
+
+  ##############################################################################
+  # class LocalizeTag
+  #
+  # Localization by getting localized text from YAML files.
+  # User must use the "t" or "translate" liquid tags.
+  ##############################################################################
+  class LocalizeTag < Liquid::Tag
+  
+    #======================================
+    # initialize
+    #======================================
+    def initialize(tag_name, key, tokens)
+      super
+      @key = key.strip
+    end
+    
+    
+    
+    #======================================
+    # render
+    #======================================
+    def render(context)
+      if      "#{context[@key]}" != "" # Check for page variable
+        key = "#{context[@key]}"
+      else
+        key =            @key
+      end
+      
+      key = Liquid::Template.parse(key).render(context)  # Parses and renders some Liquid syntax on arguments (allows expansions)
+      
+      site = context.registers[:site] # Jekyll site object
+      
+      lang = site.config['lang']
+      
+      translation = site.parsed_translations[lang].access(key) if key.is_a?(String)
+      
+      if translation.nil? or translation.empty?
+         translation = site.parsed_translations[site.config['default_lang']].access(key)
+        
+        if site.config["verbose"]
+          puts "Missing i18n key: #{lang}:#{key}"
+          puts "Using translation '%s' from default language: %s" %[translation, site.config['default_lang']]
+        end
+      end
+
+      TranslatedString.translate(key, lang, site)
+      
+      translation
+    end
+  end
+
+
+
+  ##############################################################################
+  # class LocalizeInclude
+  #
+  # Localization by including whole files that contain the localization text.
+  # User must use the "tf" or "translate_file" liquid tags.
+  ##############################################################################
+  module Tags
+    class LocalizeInclude < IncludeTag
+    
+      #======================================
+      # render
+      #======================================
+      def render(context)
+        if       "#{context[@file]}" != "" # Check for page variable
+          file = "#{context[@file]}"
+        else
+          file =            @file
+        end
+        
+        file = Liquid::Template.parse(file).render(context)  # Parses and renders some Liquid syntax on arguments (allows expansions)
+        
+        site = context.registers[:site] # Jekyll site object
+        
+        default_lang = site.config['default_lang']
+
+        validate_file_name(file)
+
+        includes_dir = File.join(site.source, '_i18n/' + site.config['lang'])
+
+        # If directory doesn't exist, go to default lang
+        if !Dir.exist?(includes_dir)
+          includes_dir = File.join(site.source, '_i18n/' + default_lang)
+        elsif
+          # If file doesn't exist, go to default lang
+          Dir.chdir(includes_dir) do
+            choices = Dir['**/*'].reject { |x| File.symlink?(x) }
+            if !choices.include?(  file)
+              includes_dir = File.join(site.source, '_i18n/' + default_lang)
+            end
+          end
+        end
+        
+        Dir.chdir(includes_dir) do
+          choices = Dir['**/*'].reject { |x| File.symlink?(x) }
+          
+          if choices.include?(  file)
+            source  = File.read(file)
+            partial = Liquid::Template.parse(source)
+            
+            context.stack do
+              context['include'] = parse_params(  context) if @params
+              contents           = partial.render(context)
+              ext                = File.extname(file)
+              
+              converter = site.converters.find { |c| c.matches(ext) }
+              contents  = converter.convert(contents) unless converter.nil?
+              
+              contents
+            end
+          else
+            raise IOError.new "Included file '#{file}' not found in #{includes_dir} directory"
+          end
+          
+        end
+      end
+    end
+
+    # Override of core Jekyll functionality, to get rid of deprecation
+    # warning. See https://github.com/jekyll/jekyll/pull/7117 for more
+    # details.
+    class PostComparer
+      def initialize(name)
+        @name = name
+
+        all, @path, @date, @slug = *name.sub(%r!^/!, "").match(MATCHER)
+        unless all
+          raise Jekyll::Errors::InvalidPostNameError,
+                "'#{name}' does not contain valid date and/or title."
+        end
+
+        escaped_slug = Regexp.escape(slug)
+        @name_regex = %r!_posts/#{path}#{date}-#{escaped_slug}\.[^.]+|
+          ^#{path}_posts/?#{date}-#{escaped_slug}\.[^.]+!x
+      end
+    end
+  end
+
+
+
+  ##############################################################################
+  # class LocalizeLink
+  #
+  # Creates links or permalinks for translated pages.
+  # User must use the "tl" or "translate_link" liquid tags.
+  ##############################################################################
+  class LocalizeLink < Liquid::Tag
+
+    #======================================
+    # initialize
+    #======================================
+    def initialize(tag_name, key, tokens)
+      super
+      @key = key
+    end
+    
+    
+    
+    #======================================
+    # render
+    #======================================
+    def render(context)
+      if      "#{context[@key]}" != "" # Check for page variable
+        key = "#{context[@key]}"
+      else
+        key = @key
+      end
+      
+      key = Liquid::Template.parse(key).render(context)  # Parses and renders some Liquid syntax on arguments (allows expansions)
+      
+      site = context.registers[:site] # Jekyll site object
+      
+      key          = key.split
+      namespace    = key[0]
+      lang         = key[1] || site.config[        'lang']
+      default_lang =           site.config['default_lang']
+      baseurl      =           site.baseurl
+      pages        =           site.pages
+      url          = "";
+      
+      if default_lang != lang || site.config['default_locale_in_subfolder']
+        baseurl = baseurl + "/" + lang
+      end
+      
+      collections = site.collections.values.collect{|x| x.docs}.flatten
+      pages = site.pages + collections
+      
+      for p in pages
+        unless             p['namespace'].nil?
+          page_namespace = p['namespace']
+          
+          if namespace == page_namespace
+            permalink = p['permalink_'+lang] || p['permalink']
+            url       = baseurl + permalink
+          end
+        end
+      end
+      
+      url
+    end
+  end
+  
+  
+end # End module Jekyll
+
+
+
+################################################################################
+# class Hash
+################################################################################
+unless Hash.method_defined? :access
+  class Hash
+  
+    #======================================
+    # access
+    #======================================
+    def access(path)
+      ret = self
+      
+      path.split('.').each do |p|
+      
+        if p.to_i.to_s == p
+          ret = ret[p.to_i]
+        else
+          ret = ret[p.to_s] || ret[p.to_sym]
+        end
+        
+        break unless ret
+      end
+      
+      ret
+    end
+  end
+end
+
+
+
+#======================================
+# translate_key
+#
+# Translate given key to given language.
+#======================================
+def translate_key(key, lang, site)
+  translation = site.parsed_translations[lang].access(key) if key.is_a?(String)
+
+  if translation.nil? or translation.empty?
+    translation = site.parsed_translations[site.config['default_lang']].access(key)
+
+    puts "Missing i18n key: #{lang}:#{key}"
+    puts "Using translation '%s' from default language: %s" %[translation, site.config['default_lang']]
+  end
+
+  translation
+end
+
+
+################################################################################
+# class TranslatedString
+################################################################################
+class TranslatedString < String
+  #======================================
+  # initialize
+  #======================================
+  def initialize(*several_variants, key)
+    super(*several_variants)
+    @key = key
+  end
+
+  def key
+    @key
+  end
+
+  #======================================
+  # translate
+  #======================================
+  def self.translate(str, lang, site)
+    if str.is_a?(TranslatedString)
+      key = str.key
+    else
+      key = str
+    end
+    return TranslatedString.new(translate_key(key, lang, site), key = key)
+  end
+end
+
+
+#======================================
+# translate_props
+#
+# Perform translation of properties defined in translation property list.
+#======================================
+def translate_props(data, site, props_key_name = 'translate_props')
+  lang = site.config['lang']
+  (data[props_key_name] || []).each do |prop_name|
+    if prop_name.is_a?(String)
+      prop_name = prop_name.strip
+      if prop_name.empty?
+        puts "There is an empty property defined in '#{props_key_name}'"
+      else
+        prop_value = data[prop_name]
+        if prop_value.is_a?(String) and !prop_value.empty?
+          data[prop_name] = TranslatedString.translate(prop_value, lang, site)
+        end
+      end
+    else
+      puts "Incorrect property name '#{prop_name}'. Must be a string"
+    end
+  end
+end
+
+
+
+################################################################################
+# Liquid tags definitions
+
+Liquid::Template.register_tag('t',              Jekyll::LocalizeTag          )
+Liquid::Template.register_tag('translate',      Jekyll::LocalizeTag          )
+Liquid::Template.register_tag('tf',             Jekyll::Tags::LocalizeInclude)
+Liquid::Template.register_tag('translate_file', Jekyll::Tags::LocalizeInclude)
+Liquid::Template.register_tag('tl',             Jekyll::LocalizeLink         )
+Liquid::Template.register_tag('translate_link', Jekyll::LocalizeLink         )
\ No newline at end of file
diff --git a/_plugins/plugin/version.rb b/_plugins/plugin/version.rb
new file mode 100644
index 00000000..0254e6bf
--- /dev/null
+++ b/_plugins/plugin/version.rb
@@ -0,0 +1,6 @@
+module Jekyll
+  module MultipleLanguagesPlugin
+    VERSION = "1.8.0"
+  end
+end
+