Nous sommes un restaurant situé à Paris . Ceci est notre menu.
Nous sommes un restaurant situé à Paris . Ceci est notre menu.
Cliquez ici pour aller à l'entrée du site. ``` #### Disabling Url Relativizing _New in 1.4.0_ If you dont want a href attribute to be relativized (such as for making [a language switcher](https://github.com/untra/polyglot/blob/master/site/_includes/sidebar.html#L40)), you can use the `{% static_href %}href="..."{% endstatic_href %}` block tag. ```html click this static link ``` that will generate `click this static link` which is what you would normally use to create a url unmangled by invisible language relativization. Combine with a [html minifier](https://github.com/digitalsparky/jekyll-minifier) for a polished and production ready website. #### Exclusive site language generation _New in 1.4.0_ If you want to control which languages a document can be generated for, you can specify `lang-exclusive: [ ]` frontmatter. If you include this frontmatter in your post, it will only generate for the specified site languages. For Example, the following frontmatter will only generate in the `en` and `fr` site language builds: ``` --- lang-exclusive: ['en', 'fr'] --- ``` #### Machine-aware site building _New in 1.5.0_ Polyglot will only start builds after it confirms there is a cpu core ready to accept the build thread. This ensures that jekll will build large sites efficiently streamlining build processes instead of overloading machines with process thrash. #### Localized site.data There are cases when `site.data` localization is required. For instance: you might need to localize `_data/navigation.yml` that holds "navigation menu". In order to localize it, just place language-specific files in `_data/:lang/...` folder, and Polyglot will bring those keys to the top level. ## How It Works This plugin makes modifications to existing Jekyll classes and modules, namely `Jekyll::StaticFile` and `Jekyll::Site`. These changes are as lightweight and slim as possible. The biggest change is in `Jekyll::Site.process`. Polyglot overwrites this method to instead spawn a separate process for each language you intend to process the site for. Each of those processes calls the original `Jekyll::Site.process` method with its language in mind, ensuring your website scales to support any number of languages, while building all of your site languages simultaneously. `Jekyll::Site.process` is the entry point for the Jekyll build process. Take care whatever other plugins you use do not also attempt to overwrite this method. You may have problems. ## Features This plugin stands out from other I18n Jekyll plugins. - automatically corrects your relative links, keeping your *french* visitors on your *french* website, even when content has to fallback to the `default_lang`. - builds all versions of your website *simultaneously*, allowing big websites to scale efficiently. - provides the liquid tag `{{ site.languages }}` to get an array of your I18n strings. - provides the liquid tag `{{ site.default_lang }}` to get the default_lang I18n string. - provides the liquid tag `{{ site.active_lang }}` to get the I18n language string the website was built for. Alternative names for `active_lang` can be configured via `config.lang_vars`. - provides the liquid tag `{{ I18n_Headers https://yourwebsite.com/ }}` to append SEO bonuses to your website. - provides the liquid tag `{{ Unrelativized_Link href="/hello" }}` to make urls that do not get influenced by url correction regexs. - provides `site.data` localization for efficient rich text replacement. - a creator that will answer all of your questions and issues. ## SEO Recipes Jekyll-polyglot has a few spectacular [Search Engine Optimization techniques](https://untra.github.io/polyglot/seo) to ensure your Jekyll blog gets the most out of it's multilingual audience. Check them out! ### Other Websites Built with Polyglot Feel free to open a PR and list your multilingual blog here you may want to share: * [Polyglot project website](https://polyglot.untra.io) * [LogRhythm Corporate Website](https://logrhythm.com) * [All Over Earth](https://allover.earth/) * [Hanare Cafe in Toshijima, Japan](https://hanarecafe.com) * [F-Droid](https://f-droid.org) * [Ubuntu MATE](https://ubuntu-mate.org) ## Compatibility Currently supports Jekyll 3.0 , and Jekyll 4.0 * Windows users will need to disable parallel_localization on their machines by setting `parallel_localization: false` in the `_config.yml` * In Jekyll 4.0 , SCSS source maps will generate improperly due to how Polyglot operates. The workaround is to disable the CSS sourcemaps. Adding the following to your `config.yml` will disable sourcemap generation: ```yaml sass: sourcemap: never ``` ## Contributions Please! I need all the support I can get! 🙏 But for real I would appreciate any contributions and support. This started as an open-source side-project and has gotten bigger than I'd ever imagine! If you have something you'd like to contribute to jekyll-polyglot, please open a PR! ## 2.0 Roadmap * [ ] - **site language**: portuguese `pt_BR` `pt_PT` * [ ] - **site language**: arabic `ar` * [ ] - **site language**: japanese `ja` * [x] - **site language**: russian `ru` * [ ] - **site language**: korean `ko` * [ ] - **site language**: hebrew `he` * [ ] - get whitelisted as an official github-pages jekyll plugin * [x] - update CI provider ## Copyright Copyright (c) Samuel Volin 2021. License: MIT polyglot-1.5.0/Rakefile 0000664 0000000 0000000 00000000131 14074562261 0015035 0 ustar 00root root 0000000 0000000 require "rspec/core/rake_task" RSpec::Core::RakeTask.new(:spec) task :default => [:spec] polyglot-1.5.0/jekyll-polyglot.gemspec 0000664 0000000 0000000 00000001107 14074562261 0020102 0 ustar 00root root 0000000 0000000 Gem::Specification.new do |s| s.name = 'jekyll-polyglot' s.version = '1.5.0' s.date = '2021-07-17' s.summary = 'I18n plugin for Jekyll Blogs' s.description = 'Fast open source i18n plugin for Jekyll blogs.' s.authors = ['Samuel Volin'] s.email = 'untra.sam@gmail.com' s.files = ['README.md', 'LICENSE'] + Dir['lib/**/*'] s.homepage = 'https://polyglot.untra.io/' s.license = 'MIT' s.add_runtime_dependency('jekyll', '>= 3.0') s.required_ruby_version = '>= 2.4.0' s.required_rubygems_version = '>= 2.7.0' end polyglot-1.5.0/lib/ 0000775 0000000 0000000 00000000000 14074562261 0014143 5 ustar 00root root 0000000 0000000 polyglot-1.5.0/lib/jekyll-polyglot.rb 0000664 0000000 0000000 00000000032 14074562261 0017624 0 ustar 00root root 0000000 0000000 require "jekyll/polyglot" polyglot-1.5.0/lib/jekyll/ 0000775 0000000 0000000 00000000000 14074562261 0015435 5 ustar 00root root 0000000 0000000 polyglot-1.5.0/lib/jekyll/polyglot.rb 0000664 0000000 0000000 00000000237 14074562261 0017635 0 ustar 00root root 0000000 0000000 require 'jekyll' require_relative 'polyglot/liquid' require_relative 'polyglot/patches' require_relative 'polyglot/hooks' require_relative 'polyglot/version' polyglot-1.5.0/lib/jekyll/polyglot/ 0000775 0000000 0000000 00000000000 14074562261 0017306 5 ustar 00root root 0000000 0000000 polyglot-1.5.0/lib/jekyll/polyglot/hooks.rb 0000664 0000000 0000000 00000000105 14074562261 0020752 0 ustar 00root root 0000000 0000000 require_relative 'hooks/coordinate' require_relative 'hooks/process' polyglot-1.5.0/lib/jekyll/polyglot/hooks/ 0000775 0000000 0000000 00000000000 14074562261 0020431 5 ustar 00root root 0000000 0000000 polyglot-1.5.0/lib/jekyll/polyglot/hooks/assets-toggle.rb 0000664 0000000 0000000 00000000332 14074562261 0023535 0 ustar 00root root 0000000 0000000 # Jekyll::Hooks.register :site, :after_init do |site| # if site.config['assets'] # if site.active_lang != site.default_lang # then jekyll.sprockets.asset_config['autowrite'] = false # end # end # end polyglot-1.5.0/lib/jekyll/polyglot/hooks/coordinate.rb 0000664 0000000 0000000 00000001656 14074562261 0023115 0 ustar 00root root 0000000 0000000 # hook to coordinate blog posts and pages into distinct urls, # and remove duplicate multilanguage posts and pages Jekyll::Hooks.register :site, :post_read do |site| hook_coordinate(site) end def hook_coordinate(site) # Copy the language specific data, by recursively merging it with the default data. # Favour active_lang first, then default_lang, then any non-language-specific data. # See: https://www.ruby-forum.com/topic/142809 merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 } if site.data.include?(site.default_lang) site.data = site.data.merge(site.data[site.default_lang], &merger) end if site.data.include?(site.active_lang) site.data = site.data.merge(site.data[site.active_lang], &merger) end site.collections.each do |_, collection| collection.docs = site.coordinate_documents(collection.docs) end site.pages = site.coordinate_documents(site.pages) end polyglot-1.5.0/lib/jekyll/polyglot/hooks/process.rb 0000664 0000000 0000000 00000000441 14074562261 0022433 0 ustar 00root root 0000000 0000000 # hook to make a call to process rendered documents, Jekyll::Hooks.register :site, :post_render do |site| hook_process(site) end def hook_process(site) site.collections.each do |_, collection| site.process_documents(collection.docs) end site.process_documents(site.pages) end polyglot-1.5.0/lib/jekyll/polyglot/liquid.rb 0000664 0000000 0000000 00000000247 14074562261 0021125 0 ustar 00root root 0000000 0000000 module Jekyll module Polyglot module Liquid require_relative 'liquid/tags/i18n_headers' require_relative 'liquid/tags/static_href' end end end polyglot-1.5.0/lib/jekyll/polyglot/liquid/ 0000775 0000000 0000000 00000000000 14074562261 0020575 5 ustar 00root root 0000000 0000000 polyglot-1.5.0/lib/jekyll/polyglot/liquid/tags/ 0000775 0000000 0000000 00000000000 14074562261 0021533 5 ustar 00root root 0000000 0000000 polyglot-1.5.0/lib/jekyll/polyglot/liquid/tags/i18n_headers.rb 0000664 0000000 0000000 00000002130 14074562261 0024326 0 ustar 00root root 0000000 0000000 module Jekyll module Polyglot module Liquid class I18nHeadersTag < ::Liquid::Tag def initialize(tag_name, text, tokens) super @url = text @url.strip! @url.chomp! '/' end def render(context) site = context.registers[:site] permalink = context.registers[:page]['permalink'] site_url = @url.empty? ? site.config['url'] : @url i18n = "\n" i18n += "\n" site.languages.each do |lang| next if lang == site.default_lang i18n += "\n" end i18n end end end end end Liquid::Template.register_tag('I18n_Headers', Jekyll::Polyglot::Liquid::I18nHeadersTag) Liquid::Template.register_tag('i18n_headers', Jekyll::Polyglot::Liquid::I18nHeadersTag) polyglot-1.5.0/lib/jekyll/polyglot/liquid/tags/static_href.rb 0000664 0000000 0000000 00000002021 14074562261 0024346 0 ustar 00root root 0000000 0000000 module Jekyll module Polyglot module Liquid class StaticHrefTag < :: Liquid::Block def initialize(tag_name, params, tokens) super end def render(context) text = super href_attrs = text.strip.split '=' valid = (href_attrs.length == 2 && href_attrs[0] == 'href') && href_attrs[1].start_with?('"') && href_attrs[1].end_with?('"') unless valid raise Liquid::SyntaxError, "static_href parameters must include match href=\"...\" attribute param, eg. href=\"http://example.com\, href=\"/about\", href=\"/\" , instead got:\n#{text}" end href_value = href_attrs[1] # href writes out as ferh="..." explicitly wrong, to be cauaght by seperate processor for nonrelativized links "ferh=#{href_value}" end end end end end Liquid::Template.register_tag('Static_Href', Jekyll::Polyglot::Liquid::StaticHrefTag) Liquid::Template.register_tag('static_href', Jekyll::Polyglot::Liquid::StaticHrefTag) polyglot-1.5.0/lib/jekyll/polyglot/patches.rb 0000664 0000000 0000000 00000000125 14074562261 0021260 0 ustar 00root root 0000000 0000000 require_relative 'patches/jekyll/site' require_relative 'patches/jekyll/static_file' polyglot-1.5.0/lib/jekyll/polyglot/patches/ 0000775 0000000 0000000 00000000000 14074562261 0020735 5 ustar 00root root 0000000 0000000 polyglot-1.5.0/lib/jekyll/polyglot/patches/jekyll/ 0000775 0000000 0000000 00000000000 14074562261 0022227 5 ustar 00root root 0000000 0000000 polyglot-1.5.0/lib/jekyll/polyglot/patches/jekyll/site.rb 0000664 0000000 0000000 00000016756 14074562261 0023537 0 ustar 00root root 0000000 0000000 require 'etc' include Process module Jekyll class Site attr_reader :default_lang, :languages, :exclude_from_localization, :lang_vars attr_accessor :file_langs, :active_lang def prepare @file_langs = {} fetch_languages @parallel_localization = config.fetch('parallel_localization', true) @lang_from_path = config.fetch('lang_from_path', false) @exclude_from_localization = config.fetch('exclude_from_localization', []).map do |e| if File.directory?(e) && e[-1] != '/' e + '/' else e end end end def fetch_languages @default_lang = config.fetch('default_lang', 'en') @languages = config.fetch('languages', ['en']).uniq @keep_files += (@languages - [@default_lang]) @active_lang = @default_lang @lang_vars = config.fetch('lang_vars', []) end alias_method :process_orig, :process def process prepare all_langs = (@languages + [@default_lang]).uniq if @parallel_localization nproc = Etc.nprocessors pids = {} begin all_langs.each do |lang| pids[lang] = fork do process_language lang end while pids.length >= (lang == all_langs[-1] ? 1 : nproc) sleep 0.1 pids.map do |lang, pid| pids.delete lang if waitpid pid, Process::WNOHANG end end end rescue Interrupt all_langs.each do |lang| next unless pids.key? lang puts "Killing #{pids[lang]} : #{lang}" kill('INT', pids[lang]) end end else all_langs.each do |lang| process_language lang end end end alias_method :site_payload_orig, :site_payload def site_payload payload = site_payload_orig payload['site']['default_lang'] = default_lang payload['site']['languages'] = languages payload['site']['active_lang'] = active_lang lang_vars.each do |v| payload['site'][v] = active_lang end payload end def process_language(lang) @active_lang = lang config['active_lang'] = @active_lang lang_vars.each do |v| config[v] = @active_lang end if @active_lang == @default_lang then process_default_language else process_active_language end end def process_default_language old_include = @include process_orig @include = old_include end def process_active_language old_dest = @dest old_exclude = @exclude @file_langs = {} @dest = @dest + '/' + @active_lang @exclude += @exclude_from_localization process_orig @dest = old_dest @exclude = old_exclude end def derive_lang_from_path(doc) if !@lang_from_path return nil end segments = doc.relative_path.split('/') if doc.relative_path[0] == '_' \ && segments.length > 2 \ && segments[1] =~ /^[a-z]{2,3}(:?[_-](:?[A-Za-z]{2}){1,2}){0,2}$/ return segments[1] elsif segments.length > 1 \ && segments[0] =~ /^[a-z]{2,3}(:?[_-](:?[A-Za-z]{2}){1,2}){0,2}$/ return segments[0] else return nil end end # assigns natural permalinks to documents and prioritizes documents with # active_lang languages over others. If lang is not set in front matter, # then this tries to derive from the path, if the lang_from_path is set. # otherwise it will assign the document to the default_lang def coordinate_documents(docs) regex = document_url_regex approved = {} docs.each do |doc| lang = doc.data['lang'] || derive_lang_from_path(doc) || @default_lang lang_exclusive = doc.data['lang-exclusive'] || [] url = doc.url.gsub(regex, '/') doc.data['permalink'] = url # skip this document if it has already been processed next if @file_langs[url] == @active_lang # skip this document if it has a fallback and it isn't assigned to the active language next if @file_langs[url] == @default_lang && lang != @active_lang # skip this document if it has lang-exclusive defined and the active_lang is not included next if !lang_exclusive.empty? && !lang_exclusive.include?(@active_lang) approved[url] = doc @file_langs[url] = lang end approved.values end # performs any necesarry operations on the documents before rendering them def process_documents(docs) # return if @active_lang == @default_lang url = config.fetch('url', false) rel_regex = relative_url_regex(false) abs_regex = absolute_url_regex(url, false) non_rel_regex = relative_url_regex(true) non_abs_regex = absolute_url_regex(url, true) docs.each do |doc| unless @active_lang == @default_lang then relativize_urls(doc, rel_regex) end correct_nonrelativized_urls(doc, non_rel_regex) if url unless @active_lang == @default_lang then relativize_absolute_urls(doc, abs_regex, url) end correct_nonrelativized_absolute_urls(doc, non_abs_regex, url) end end end # a regex that matches urls or permalinks with i18n prefixes or suffixes # matches /en/foo , .en/foo , foo.en/ and other simmilar default urls # made by jekyll when parsing documents without explicitly set permalinks def document_url_regex regex = '' @languages.each do |lang| regex += "([\/\.]#{lang}[\/\.])|" end regex.chomp! '|' %r{#{regex}} end # a regex that matches relative urls in a html document # matches href="baseurl/foo/bar-baz" href="/foo/bar-baz" and others like it # avoids matching excluded files. prepare makes sure # that all @exclude dirs have a trailing slash. def relative_url_regex(disabled = false) regex = '' unless disabled @exclude.each do |x| regex += "(?!#{x})" end @languages.each do |x| regex += "(?!#{x}\/)" end end start = disabled ? 'ferh' : 'href' %r{#{start}=\"?#{@baseurl}\/((?:#{regex}[^,'\"\s\/?\.#]+\.?)*(?:\/[^\]\[\)\(\"\'\s]*)?)\"} end # a regex that matches absolute urls in a html document # matches href="http://baseurl/foo/bar-baz" and others like it # avoids matching excluded files. prepare makes sure # that all @exclude dirs have a trailing slash. def absolute_url_regex(url, disabled = false) regex = '' unless disabled @exclude.each do |x| regex += "(?!#{x})" end @languages.each do |x| regex += "(?!#{x}\/)" end end start = disabled ? 'ferh' : 'href' %r{(?