plugins: local jekyll-multiple-language-plugin

This commit is contained in:
plowsof 2024-07-09 21:36:09 +01:00
parent 55b919f8f6
commit 7132e45a64
5 changed files with 694 additions and 5 deletions

View file

@ -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'

View file

@ -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)

View file

@ -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"]

View file

@ -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 )

View file

@ -0,0 +1,6 @@
module Jekyll
module MultipleLanguagesPlugin
VERSION = "1.8.0"
end
end