├── .codeclimate.yml ├── .gitignore ├── .rspec ├── .rubocop.yml ├── .simplecov ├── .travis.yml ├── Gemfile ├── LICENSE.txt ├── README.md ├── appveyor.yml ├── docs ├── .gitignore ├── 404.html ├── _config.yml ├── _data │ ├── filter_examples.yml │ └── locales │ │ ├── en-US.yml │ │ └── fr.yml ├── _docs │ ├── configuration.md │ ├── filters │ │ ├── localize-date.md │ │ └── prefix-locale.md │ ├── modes │ │ ├── auto-mode.md │ │ └── manual-mode.md │ └── usage │ │ ├── advanced-usage.md │ │ └── usage.md ├── _layouts │ ├── default.html │ └── error.html ├── _localizations │ └── fr │ │ ├── _docs │ │ ├── configuration.md │ │ ├── filters │ │ │ ├── localize-date.md │ │ │ └── prefix-locale.md │ │ ├── modes │ │ │ ├── auto-mode.md │ │ │ └── manual-mode.md │ │ └── usage │ │ │ ├── advanced-usage.md │ │ │ └── usage.md │ │ ├── about.md │ │ └── index.md ├── _sass │ ├── _font-awesome.scss │ ├── _fonts.scss │ ├── _highlights.scss │ ├── _normalize.scss │ ├── _primer-tooltip.scss │ └── _style.scss ├── about.md ├── assets │ ├── css │ │ └── styles.scss │ ├── fonts │ │ ├── FontAwesome.eot │ │ ├── FontAwesome.svg │ │ ├── FontAwesome.ttf │ │ ├── FontAwesome.woff │ │ ├── lato-v14-latin-300.woff │ │ ├── lato-v14-latin-300.woff2 │ │ ├── lato-v14-latin-300italic.woff │ │ ├── lato-v14-latin-300italic.woff2 │ │ ├── lato-v14-latin-700.woff │ │ ├── lato-v14-latin-700.woff2 │ │ ├── lato-v14-latin-700italic.woff │ │ └── lato-v14-latin-700italic.woff2 │ ├── img │ │ ├── clippy.svg │ │ ├── dark_texture.png │ │ ├── jekyll-locale-icon.png │ │ ├── jekyll-locale-text.png │ │ └── jekyll-locale.png │ └── js │ │ └── clipboard.min.js ├── favicon.ico ├── icomoon-selection.json └── index.md ├── example ├── .gitignore ├── Gemfile ├── README.md ├── _config.yml ├── _data │ ├── locales │ │ ├── en.yml │ │ └── fr.yml │ └── navigation.yml ├── _includes │ └── locale_snippet.html ├── _layouts │ ├── base.html │ └── page.html ├── _locales │ └── fr │ │ ├── _posts │ │ ├── 2018 │ │ │ └── 10 │ │ │ │ └── 15 │ │ │ │ └── 2018-10-15-nested-post.md │ │ ├── 2018-10-15-hello-world.md │ │ └── 2018-10-24-front-matter-category.md │ │ ├── _puppies │ │ └── rover.md │ │ ├── about.md │ │ ├── category │ │ └── _posts │ │ │ └── 2018-10-24-superdir-category.md │ │ └── index.md ├── _posts │ ├── 2018 │ │ └── 10 │ │ │ └── 15 │ │ │ └── 2018-10-15-nested-post.md │ ├── 2018-10-15-hello-world.md │ ├── 2018-10-18-english-only.md │ ├── 2018-10-24-auto-localized.md │ └── 2018-10-24-front-matter-category.md ├── _puppies │ └── rover.md ├── _sass │ └── base.scss ├── about.md ├── category │ └── _posts │ │ └── 2018-10-24-superdir-category.md ├── css │ └── main.scss └── index.md ├── jekyll-locale.gemspec ├── lib ├── jekyll-locale.rb └── jekyll │ ├── locale │ ├── auto_page.rb │ ├── date_time_handler.rb │ ├── document.rb │ ├── drop.rb │ ├── filters.rb │ ├── handler.rb │ ├── identity.rb │ ├── mixins │ │ ├── helper.rb │ │ └── support.rb │ ├── page.rb │ └── version.rb │ └── patches │ ├── site.rb │ └── utils.rb └── spec ├── fixtures ├── _config.yml ├── _data │ └── locales │ │ ├── en.yml │ │ └── fr.yml ├── _posts │ ├── 2018 │ │ └── 10 │ │ │ └── 15 │ │ │ └── 2018-10-15-nested-post.md │ ├── 2018-10-15-hello-world.md │ ├── 2018-10-18-english-only.md │ ├── 2018-10-24-auto-localized.md │ └── 2018-10-24-front-matter-category.md ├── _puppies │ └── rover.md ├── about.md └── index.md ├── jekyll-locale ├── auto_page_spec.rb ├── document_spec.rb ├── filters_spec.rb ├── handler_spec.rb └── page_spec.rb └── spec_helper.rb /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | 3 | plugins: 4 | rubocop: 5 | enabled: false 6 | 7 | exclude_patterns: 8 | - "*.*" 9 | - ".*" 10 | - Gemfile 11 | - docs/ 12 | - example/ 13 | - spec/ 14 | - lib/jekyll/locale/version.rb 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _site 2 | .logs 3 | .sass-cache 4 | .jekyll-cache 5 | .fixtures 6 | .gems 7 | .vendor 8 | .jekyll-metadata 9 | Gemfile.lock 10 | *.gem 11 | coverage 12 | tmp 13 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --require spec_helper 3 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | require: rubocop-jekyll 2 | inherit_gem: 3 | rubocop-jekyll: .rubocop.yml 4 | 5 | Layout/EmptyLineAfterGuardClause: 6 | Enabled: false 7 | Metrics/BlockLength: 8 | Exclude: 9 | - spec/**/* 10 | Metrics/CyclomaticComplexity: 11 | Max: 7 12 | Style/ClassAndModuleChildren: 13 | Enabled: false 14 | Style/MultipleComparison: 15 | Enabled: false 16 | Style/RedundantSelf: 17 | Exclude: 18 | - lib/jekyll/locale/mixins/helper.rb 19 | -------------------------------------------------------------------------------- /.simplecov: -------------------------------------------------------------------------------- 1 | SimpleCov.start do 2 | track_files "lib/**/*.rb" 3 | add_filter %r!spec|version! 4 | end 5 | 6 | SimpleCov.at_exit do 7 | SimpleCov.result.format! 8 | end 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: ruby 3 | cache: bundler 4 | rvm: 5 | - 2.5 6 | - 2.4 7 | - 2.3 8 | 9 | branches: 10 | only: 11 | - master 12 | 13 | before_script: 14 | - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter 15 | - chmod +x ./cc-test-reporter 16 | - ./cc-test-reporter before-build 17 | 18 | script: bundle exec rspec 19 | 20 | after_script: 21 | - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT 22 | 23 | notifications: 24 | email: false 25 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | gemspec 5 | 6 | def custom_gem_source(name, nwo, branch) 7 | path = File.expand_path("../#{name}", __dir__) 8 | 9 | if Dir.exist?(path) 10 | { :path => path } 11 | else 12 | { :git => "https://github.com/#{nwo}.git", :branch => branch } 13 | end 14 | end 15 | 16 | # 17 | 18 | gem "kramdown", custom_gem_source("kramdown", "ashmaroli/kramdown", "lang-data-attr") 19 | 20 | gem "jekyll-mentions" 21 | gem "rspec" 22 | gem "rubocop-jekyll" 23 | gem "simplecov", :require => false 24 | gem "tzinfo-data", :install_if => Gem.win_platform? 25 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Ashwin Maroli 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Jekyll Locale 2 | 3 | [![Linux Build Status](https://img.shields.io/travis/ashmaroli/jekyll-locale/master.svg?label=Linux%20build)][travis] 4 | [![Windows Build status](https://img.shields.io/appveyor/ci/ashmaroli/jekyll-locale/master.svg?label=Windows%20build)][appveyor] 5 | [![Maintainability](https://api.codeclimate.com/v1/badges/3a28682ac6c6693c4e77/maintainability)][maintainability] 6 | [![Test Coverage](https://api.codeclimate.com/v1/badges/3a28682ac6c6693c4e77/test_coverage)][test_coverage] 7 | 8 | [travis]: https://travis-ci.org/ashmaroli/jekyll-locale 9 | [appveyor]: https://ci.appveyor.com/project/ashmaroli/jekyll-locale/branch/master 10 | [maintainability]: https://codeclimate.com/github/ashmaroli/jekyll-locale/maintainability 11 | [test_coverage]: https://codeclimate.com/github/ashmaroli/jekyll-locale/test_coverage 12 | 13 | A localization plugin for Jekyll. 14 | 15 | 16 | ## Features 17 | 18 | * Exposes a `{{ locale }}` object for your Liquid templates that can be used to "translate" strings in your site easily. 19 | * Depending on configured `mode`, the plugin either generates a *copy* of every page that renders into a HTML file, and 20 | every document set to be written, and renders them into dedicated urls prepended by a supported locale, or, the plugin 21 | "processes" only those files placed inside the plugin's configured **`content_dir`** . 22 | 23 | Read through the [documentation](https://jekyll-locale.netlify.com/) for details. 24 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: "{build}" 2 | clone_depth: 5 3 | build: off 4 | 5 | branches: 6 | only: 7 | - master 8 | 9 | environment: 10 | matrix: 11 | - RUBY_FOLDER_VER: "25" 12 | - RUBY_FOLDER_VER: "24" 13 | - RUBY_FOLDER_VER: "23" 14 | 15 | install: 16 | - SET PATH=C:\Ruby%RUBY_FOLDER_VER%\bin;%PATH% 17 | - bundle install --retry 5 --jobs=%NUMBER_OF_PROCESSORS% --clean --path vendor\bundle 18 | 19 | before_test: 20 | - ruby --version 21 | - gem --version 22 | - bundle --version 23 | 24 | test_script: 25 | - bundle exec rspec 26 | 27 | cache: 28 | - 'vendor\bundle -> appveyor.yml,Gemfile,jekyll-locale.gemspec' 29 | 30 | notifications: 31 | - provider: Email 32 | on_build_success: false 33 | on_build_failure: false 34 | on_build_status_changed: false 35 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | _site 2 | .logs 3 | .sass-cache 4 | .jekyll-cache 5 | .fixtures 6 | .gems 7 | .vendor 8 | .jekyll-metadata 9 | Gemfile.lock 10 | *.gem 11 | coverage 12 | tmp 13 | -------------------------------------------------------------------------------- /docs/404.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: error 3 | --- 4 | 5 |
6 |

Huh. This page seems to be
Hyde-ing...

7 |
8 | 9 |
10 |
404
11 |

Requested resource not found.

12 |
13 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | name: Jekyll Locale 2 | description: A Localization plugin for Jekyll 3 | baseurl: "" 4 | collections: 5 | docs: 6 | output: true 7 | livereload: true 8 | 9 | localization: 10 | mode: manual 11 | content_dir: _localizations 12 | locales_set: 13 | fr: 14 | label: Français 15 | en-US: 16 | label: English US 17 | 18 | defaults: 19 | - scope: 20 | path: "" 21 | values: 22 | layout: default 23 | plugins: 24 | - jekyll-locale 25 | - jekyll-mentions 26 | exclude: 27 | - vendor/ 28 | - .gitignore 29 | - .jekyll-cache 30 | - CNAME 31 | - icomoon-selection.json 32 | - readme.md 33 | -------------------------------------------------------------------------------- /docs/_data/filter_examples.yml: -------------------------------------------------------------------------------- 1 | prefix_locale: 2 | en: 3 | - input: "{{ 'about' | prefix_locale }}" 4 | output: "/about" 5 | - input: "{{ '//about//' | prefix_locale }}" 6 | output: "/about/" 7 | - input: "{{ 'about me' | prefix_locale }}" 8 | output: "/about me" 9 | - input: "{{ 'https://www.example.com/about/' | prefix_locale }}" 10 | output: "https://www.example.com/about/" 11 | 12 | fr: 13 | - input: "{{ 'about' | prefix_locale }}" 14 | output: "/fr/about" 15 | - input: "{{ '//about//' | prefix_locale }}" 16 | output: "/fr/about/" 17 | - input: "{{ 'about me' | prefix_locale }}" 18 | output: "/fr/about me" 19 | - input: "{{ 'https://www.example.com/about/' | prefix_locale }}" 20 | output: "https://www.example.com/about/" 21 | -------------------------------------------------------------------------------- /docs/_data/locales/en-US.yml: -------------------------------------------------------------------------------- 1 | sidebar: 2 | - title: Orientation 3 | docs: 4 | - title: Configuration 5 | link: /configuration/ 6 | - title: Auto Mode 7 | link: /modes/auto/ 8 | - title: Manual Mode 9 | link: /modes/manual/ 10 | - title: Usage 11 | docs: 12 | - title: Introduction 13 | link: /usage/intro/ 14 | - title: Advanced Usage 15 | link: /usage/advanced/ 16 | - title: Liquid Filters 17 | docs: 18 | - title: localize_date 19 | link: /filters/localize_date/ 20 | - title: prefix_locale 21 | link: /filters/prefix_locale/ 22 | 23 | available_locales: This Page is also available in the following locales 24 | copyright_preface: The contents of this website are 25 | license_preface: under the terms of the 26 | about_page: 27 | title: About 28 | link: /about/ 29 | translated_by: "This page has been translated by:" 30 | -------------------------------------------------------------------------------- /docs/_data/locales/fr.yml: -------------------------------------------------------------------------------- 1 | sidebar: 2 | - title: Commencer 3 | docs: 4 | - title: Configuration 5 | link: /configuration/ 6 | - title: Mode automatique 7 | link: /modes/auto/ 8 | - title: Mode manuel 9 | link: /modes/manuel/ 10 | - title: Utilisation 11 | docs: 12 | - title: Introduction 13 | link: /utilisation/intro/ 14 | - title: Utilisation avancée 15 | link: /utilisation/avancee/ 16 | - title: Filtres Liquid 17 | docs: 18 | - title: localize_date 19 | link: /filtres/localize_date/ 20 | - title: prefix_locale 21 | link: /filtres/prefix_locale/ 22 | 23 | available_locales: Cette page est aussi disponible dans les langues suivantes 24 | copyright_preface: Tous les contenus de ce site 25 | license_preface: selon les termes de la 26 | about_page: 27 | title: À propos 28 | link: /a-propos/ 29 | translated_by: "Cette page a été traduite par :" 30 | -------------------------------------------------------------------------------- /docs/_docs/configuration.md: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: /configuration/ 3 | --- 4 | 5 | The plugin has been *preconfigured* with the following: 6 | 7 | ```yaml 8 | localization: 9 | mode : manual # Sets the 'handler mode' 10 | locale : en-US # Sets the 'default locale' for the site 11 | data_dir : locales # Sets the base location for translation data, within your site's `data_dir`. 12 | content_dir: _locales # Sets the base location for placing files to be re-rendered. Ignored in auto mode. 13 | locales_set: [] # A list of locales the site will re-render files into 14 | exclude_set: [] # A list of file paths the site will not re-render in "auto" mode. 15 | ``` 16 | 17 | * ### `data_dir` 18 | 19 | This setting defines the base location for *the translation data*, within your site's configured `data_dir`. 20 | 21 | The value defaults to `"locales"`. This implies that the plugin looks for "translation data" in either `_data/locales/` or a 22 | data file named `locales`. e.g. `_data/locales.yml` or `_data/locales.json`, etc. 23 | 24 | Irrespective of the format, the data should be a Hash table / dictionary of key-value pairs where the main key should 25 | correspond to locale defined in the `locales_set` array or the default locale `en-US`, and the subkeys set to string 26 | values. 27 | 28 | #### Example of a single data file corresponding to multiple locales 29 | 30 | ```yaml 31 | # ------------------------ 32 | # _data/locales.yml 33 | # ------------------------ 34 | 35 | # English US 36 | en-US: 37 | greeting: Hello 38 | user: user 39 | navigation: 40 | home: Home 41 | about: About 42 | contact: Contact 43 | 44 | # French 45 | fr-FR: 46 | greeting: Bonjour 47 | user: l' usager 48 | navigation: 49 | home: Accueil 50 | about: À propos 51 | contact: Contacter 52 | ``` 53 | 54 | #### Example of dedicated data files corresponding to a single locale 55 | 56 | ```yaml 57 | # ------------------------- 58 | # _data/locales/en-US.yml 59 | # ------------------------- 60 | # 61 | # English US 62 | greeting: Hello 63 | user: user 64 | navigation: 65 | home: Home 66 | about: About 67 | contact: Contact 68 | ``` 69 | 70 | ```yaml 71 | # ------------------------- 72 | # _data/locales/fr-FR.yml 73 | # ------------------------- 74 | # 75 | # French 76 | greeting: Bonjour 77 | user: l' usager 78 | navigation: 79 | home: Accueil 80 | about: À propos 81 | contact: Contacter 82 | ``` 83 | 84 | * ### `content_dir` 85 | 86 | Ignored in "auto mode", this setting defines the base location for placing "the physical copies" of the canonical pages and 87 | documents. Refer `mode` sub-section below for further details. 88 | 89 | The default setting is `_locales` 90 | 91 | * ### `locales_set` 92 | 93 | Empty by default, this setting defines what locales to be used when "localizing" a site. Listing a locale (other than the 94 | default locale) will cause the entire site to render for that locale and the default locale while in "auto mode". 95 | 96 | You may also define this setting as an object with key-value pairs where keys are locale identifiers and values their metadata: 97 | 98 | ```yaml 99 | localization: 100 | locales_set: 101 | en: 102 | label: English 103 | img: img/english.png 104 | fr: 105 | label: Français 106 | img: img/french.png 107 | ``` 108 | *Metadata keys can be any string other than `id`.* 109 | 110 | * ### `locale` 111 | 112 | Set to `en-US` by default, this setting defines the default locale of the site and will not be prepended to the URL of the 113 | generated files. 114 | 115 | * ### `exclude_set` 116 | 117 | Empty by default, this setting defines what files to be *excluded* from being duplicated and re-rendered in the "auto" mode. 118 | Ignored in "manual" mode. 119 | 120 | * ### `mode` 121 | 122 | This setting defines the plugin's operation strategy. 123 | 124 | When set to **`auto`**, the plugin will initialize a generator that will simply duplicate *every page and document set to be 125 | written to destination*, and re-render them into destination, for every locale defined in the `localization.locales_set` 126 | setting. 127 | 128 | This mode will increase your build times in proportion to the total number pages, writable-documents and locales listed but 129 | will result in simply the same canonical output and the canonical url prepended with an iterated locale. 130 | 131 | For example, if one were to configure the plugin with `locales_set: ["de", "fr", "en-US", "es"]`, then a file named `about.md` 132 | will result in the following files: 133 | * `_site/about.html` 134 | * `_site/de/about.html` 135 | * `_site/es/about.html` 136 | * `_site/fr/about.html` 137 | 138 | Setting `mode` to any other value will automatically default to `"manual"` which **requires** you to create physical files in a 139 | special directory (as configured under `localization["content_dir"]`) to render localized copies. 140 | 141 | Refer the page on [manual mode](../modes/manual/) for more details. 142 | -------------------------------------------------------------------------------- /docs/_docs/filters/localize-date.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Localize Date Filter 3 | permalink: /filters/localize_date/ 4 | --- 5 |
6 |
7 | 8 | 9 | Adapted from 10 | 12 | localize filter by @borisschapira 13 | 14 | 15 |
16 |
17 | 18 | This plugin provides a `localize_date` filter to aid in localizing valid date strings. It takes an optional parameter to specify 19 | the format of the output string. 20 | 21 | The filter technically delegates to the `I18n` module and therefore requires the translation data to follow a certain convention 22 | to pass through without errors. 23 | 24 | ```yaml 25 | date: 26 | day_names : # Array of Day names in full. e.g. "Sunday", "Monday", ... 27 | month_names : # Array of Month names in full. e.g. "January", "February", ... 28 | abbr_day_names : # Array of abbreviations of above Day names. e.g. "Sun", "Mon", ... 29 | abbr_month_names : # Array of abbreviations of above Month names. e.g. "Jan", "Feb", ... 30 | time: 31 | am: "am" # Placeholder for Ante-Meridian 32 | pm: "pm" # Placeholder for Post-Meridian 33 | formats: # A set of predefined strftime formats 34 | default: "%B %d, %Y %l:%M:%S %p %z" # Used by default if no other `format` has been specified. 35 | # my_format: # A valid strftime format of your choice. 36 | # Usage: {% raw %}{{ your_date | localize_date: ":my_format" }}{% endraw %} 37 | ``` 38 | 39 | #### Requirements 40 | 41 | The plugin also places a few conventions to streamline usage: 42 | * All datetime data should be encompassed under a `locale_date` key for each locale except the default locale, for which, the 43 | datetime data has been set by default. But you're free to *redefine* it when necessary. 44 | * The array of names are filled in by default using values defined in Ruby's `Date` class. 45 | * The array of full day names and full month names have `nil` as the first entry. So locales for non-English languages should 46 | have `nil` as the first entry. (In YAML, null list item can be written as simply `~`) 47 | * The optional parameter for the filter, `format` should either be a string that corresponds to the symbol of the `formats` 48 | subkey (e.g. `":default"`) or a valid `strftime` format. 49 | -------------------------------------------------------------------------------- /docs/_docs/filters/prefix-locale.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Prefix Locale Filter 3 | permalink: /filters/prefix_locale/ 4 | --- 5 | 6 | This is a basic helper to convert a simple string devoid of whitespaces, into a URI relative to the server root and current 7 | locale. The generated URI will essentially have `/[locale]` (or just `/` for the default locale) prepended to the given `input`. 8 | 9 | The only validations this filter involve are checking if the given `input` is a String or if the input is an absolute URI. 10 | Additionally, the filter strips away multiple slashes in the input string. 11 | 12 |
13 | 14 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | {% for example in site.data.filter_examples.prefix_locale.en %} 26 | 27 | 30 | 33 | 34 | {% endfor %} 35 | 36 |
15 |
Default Locale: en
16 |
Current Locale: en
17 |
InputOutput
28 | {{ example.input }} 29 | 31 | {{ example.output }} 32 |
37 | 38 | 39 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | {% for example in site.data.filter_examples.prefix_locale.fr %} 51 | 52 | 55 | 58 | 59 | {% endfor %} 60 | 61 |
40 |
Default Locale: en
41 |
Current Locale: fr
42 |
InputOutput
53 | {{ example.input }} 54 | 56 | {{ example.output }} 57 |
62 |
63 | -------------------------------------------------------------------------------- /docs/_docs/modes/auto-mode.md: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: /modes/auto/ 3 | --- 4 | 5 | This mode generates a localized version of *every published page / document* in your site, for each locale supported, by default. 6 | In other words, if your site contains 1 post and 2 standalone pages published and the site is configured to render in 3 locales, 7 | then this "mode" will generate a total of 1 x 2 x 3 web pages. 8 | 9 | To prevent a certain file from being localized automatically, you may blacklist the file by listing it under the 10 | `localization.exclude_set` key in your config file. 11 | 12 | ```yaml 13 | localization: 14 | mode: auto 15 | locale: en 16 | locales_set: ["en", "fr"] 17 | exclude_set: 18 | - _posts/2018-10-18-english-only.md 19 | - _puppies 20 | ``` 21 | 22 | ### Gotchas 23 | 24 | This mode was originally intended for those sites that offer the same content with multiple localized interfaces. That is, sites 25 | in which only the "*presentational*" aspects are localized and the content remains the same. 26 | 27 | As a result, this mode has the following shortcomings: 28 | * The localized page has the same attributes as the canonical page / document — they have the same `path`, 29 | `relative_path`, `data`, `content`, etc. The only attribute they differ in is their `url` and their Liquid representation. 30 | * These attributes cannot be modified. 31 | * The localized object is a descendant of `Jekyll::Page` even if the canonical object is a Post or any other collection 32 | document. This means that the localized object may not respond to certain attributes of the canonical object. 33 | (e.g. a document's `date`) 34 | -------------------------------------------------------------------------------- /docs/_docs/modes/manual-mode.md: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: /modes/manual/ 3 | --- 4 | 5 | The key features of this mode are: 6 | * The physical files are handled like any other file in the site, but, they **should partially mirror** their 7 | counterpart — **their relative_paths should match**. 8 | * If an original file contains front matter, the physical copy should contain front matter as well. 9 | * Physical copies can render different content and into a different layout, if desired. 10 | * Physical copies of posts and other writable *documents* can be rendered to a different `slug` by defining the `slug` key in 11 | the front matter. 12 | 13 | This mode requires the user to provide physical files in the configured `content_dir` with sub-directories that match 14 | the defined locales. 15 | 16 | For example, the following directory structure 17 | 18 | ``` 19 | . 20 | ├── _config.yml 21 | ├── _locales 22 | | ├── es 23 | | | ├── tips/ 24 | | | | └── optimized-site.md 25 | | | ├── _posts/ 26 | | | ├──└── 2018-09-30-hello-world.md 27 | | ├── fr 28 | | | ├── _posts/ 29 | | └──└──└── 2018-09-30-hello-world.md 30 | ├── _posts 31 | | └── 2018-09-30-hello-world.md 32 | ├── english 33 | | └── its-a-new-day.md 34 | ├── tips 35 | | ├── optimized-site.md 36 | └──└── url-filters-in-templates.md 37 | ``` 38 | will result in the generation of just the following files: 39 | 40 | ``` 41 | _site/2018-09-30-hello-world.html 42 | _site/english/its-a-new-day.html 43 | _site/es/2018-09-30-hello-world.html 44 | _site/es/tips/optimized-site.html 45 | _site/fr/2018-09-30-hello-world.html 46 | _site/tips/optimized-site.html 47 | _site/tips/url-filters-in-templates.html 48 | ``` 49 | 50 | #### Prerequisites 51 | 52 | * The physical files should reside inside sub-folders that match the desired locale. For example, to "localize" a post at path 53 | `movies/_posts/2018-09-24-hello.markdown` with locale `fr`, you should create the physical copy at path 54 | `_locales/fr/movies/_posts/2018-09-24-hello.markdown`. 55 | 56 | * The file path of *an original page* relative to the *site's source*, should match the file path of the corresponding *locale 57 | page* relative to the `localization.content_dir` config value. 58 | 59 | This means that the locale page which would correspond to *an original page* `about.md` will be **`_locales/fr/about.md`** 60 | instead of **`_locales/fr/apropos.md`**. To force the locale page to render into a different url, you'll need to either 61 | explicitly set a `permalink` key or a `slug` key in the locale page's front matter. 62 | 63 | * **Files must exist in the default language.** 64 | 65 | The file `_locales/fr/about.md` will be **read** if and only if the file `about.md` exists. 66 | -------------------------------------------------------------------------------- /docs/_docs/usage/advanced-usage.md: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: /usage/advanced/ 3 | --- 4 | 5 | ## Configuring Locale Metadata 6 | 7 |
8 |
9 | 10 | 11 | Adapted from a 12 | 13 | suggestion by @letrastudio 14 | 15 | 16 |
17 |
18 | 19 | The default metadata for every locale configured via the `locales_set` array is simply an `id` referencing the locale name. 20 | However, you may define custom metadata for your locales by slightly altering the `locales_set` setting: 21 | 22 | ```yaml 23 | localization: 24 | locale: en-US 25 | locales_set: 26 | en-US: 27 | label: English 28 | dir: ltr 29 | img: /assets/img/en-US.png 30 | fr-FR: 31 | label: Francais 32 | dir: ltr 33 | img: /assets/img/fr_FR.jpg 34 | ``` 35 | 36 | *The metadata keys and values may be whatever your want it to be. It is not validated by the plugin. 37 | However, you will not be able to change the `id` attribute.* 38 | 39 | Once configured properly, you will be able to reference them via `{% raw %}{{ page.locale }}{% endraw %}` in your templates: 40 | 41 | ```html 42 | {%- raw -%} 43 | 44 | 45 | 46 | {% endraw %} 47 | ``` 48 | 49 | ## Setting up hreflangs 50 | 51 | Each generated locale page is aware of its canonical page and its sibling locale page (for sites rendering three or more locales) 52 | and can be used to render meta tags for optimal SEO. 53 | 54 | Adding the following Liquid construct inside your `` tag will render the markup that tells the crawler how the pages are 55 | related to each other: 56 | 57 | ```html 58 | {% raw %}{% for item in page.hreflangs %} 59 | 60 | {% endfor %}{% endraw %} 61 | ``` 62 | 63 | For example, `about.md` page with `permalink: /about/` in a site setup to render for locales `["en-US", "es", "fr"]` with `en-US` 64 | as the default locale and hosted at `http://example.com` will render with the following hreflangs: 65 | 66 | ```html 67 | 68 | 69 | 70 | ``` 71 | Likewise, each of the *localized version* of the About page will also render with the **same hreflangs set as above**. 72 | 73 | 74 | ## Setting up a Locale Switcher 75 | 76 | Since hreflang data includes a self-referencing entry, iterating through `page.hreflangs` will result in a confusing linkset. 77 | Instead a subset of the hreflang data is available as `page.locale_siblings`: 78 | 79 | ```html 80 | 85 | ``` 86 | 87 | *If you've set up metadata for your locales, you may also reference a property here instead.* 88 | 89 | ```html 90 | 94 | 95 | 100 | ``` 101 | -------------------------------------------------------------------------------- /docs/_docs/usage/usage.md: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: /usage/intro/ 3 | --- 4 | 5 | The plugin does not literally translate content. However, it provides the means to output site content in multiple locales and 6 | languages by leveraging the versatility of Jekyll's Data files. 7 | 8 | ## Locale Data Files 9 | 10 | Data files for defining locales should reside in the directory configured as `localization.data_dir` (*Default: `locales`*) which 11 | in turn should reside in the site's `data_dir` (*Default: `_data`*). 12 | In other words, the default locale data directory is at `./_data/locales`. 13 | Data files can be of any format supported by Jekyll but should be named according to the configured locales. 14 | 15 | **NOTE: Top-level keys are stored internally as case-sensitive "underscore-delimited strings". This means that 16 | `_data/locales/en-US.yml` and `_data/locales/en_US.yml` map to the same locale `en-US`.** 17 | 18 | ## Templating 19 | 20 | A Liquid object `{% raw %}{{ locale }}{% endraw %}` serves as the main interface to this plugin. Technically, this object acts as 21 | an alias for the following: 22 | 23 | ```html 24 | {%- raw -%} 25 | {% assign current_locale = 'fr' %} 26 | {{ site.data.locales[current_locale] }} 27 | {% endraw %} 28 | ``` 29 | The plugin sets the "*current locale*" attribute under the hood, automatically in sync with the current page's locale. 30 | Consequently allowing one to *simplify their Liquid templates*: 31 | 32 | ```html 33 | {%- raw -%} 34 | {{ locale.foo_bar }} 35 | {% endraw %} 36 | ``` 37 | 38 | Apart from the `{% raw %}{{ locale }}{% endraw %}` object, the plugin also provides some filters to further ease Liquid 39 | templating. 40 | -------------------------------------------------------------------------------- /docs/_layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | {% for item in page.hreflangs -%} 13 | 14 | {% endfor %} 15 | 16 | 17 |
18 |
19 | 23 |
24 |
25 | 26 |
27 |
28 | 40 | 41 | {%- if page.hreflangs.size > 1 %} 42 |
43 | 49 |
50 | {%- endif %} 51 |
52 | 53 |
54 |
55 | {% if page.title %}

{{ page.title }}

{% endif %} 56 | {{ content }} 57 |
58 | 59 | {% unless page.locale.id == site.localizations.locale %}{% if page.translators %} 60 |
61 | 62 | {{ locale.translated_by }}   63 | 64 | {% for translator in page.translators %} 65 | {% if translator.link %} 66 | {{ translator.name }} 67 | {% if page.translators.size > 1 and forloop.last == false %}, {% endif %} 68 | {% else %} 69 | {{ translator.name }} 70 | {% if page.translators.size > 1 and forloop.last == false %}, {% endif %} 71 | {% endif %} 72 | {% endfor %} 73 | 74 | 75 |
76 | {% endif %}{% endunless %} 77 |
78 | 79 | 123 |
124 | 125 | 139 | 140 | 141 | 174 | 175 | 176 | 177 | 178 | 222 | 223 | 224 | 225 | -------------------------------------------------------------------------------- /docs/_layouts/error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 19 |
20 |
{{ content }}
21 | 22 | 23 | -------------------------------------------------------------------------------- /docs/_localizations/fr/_docs/configuration.md: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: /configuration/ 3 | translators: 4 | - name: DirtyF 5 | link: https://github.com/DirtyF 6 | --- 7 | 8 | Le plugin est *pré-configuré* avec les valeurs suivantes : 9 | 10 | ```yaml 11 | localization: 12 | mode : manual # mode à utiliser ('auto' ou 'manual') 13 | locale : en-US # langue/région par défaut du site (ex: 'en', 'fr' ou 'en-GB', 'fr-CA') 14 | data_dir : locales # emplacement des données de traduction dans le dossier `data_dir` de Jekyll (défaut: `_data/locales`). 15 | content_dir: _locales # emplacement de l'ensemble des fichiers traduits à générer. Ignoré en mode automatique. 16 | locales_set: [] # liste des langues à générer 17 | exclude_set: [] # liste des chemins à exclure de la génération en mode automatique. 18 | ``` 19 | 20 | * ### `data_dir` 21 | 22 | Ce paramètre précise l'emplacement des données de traduction des chaînes de caractères dans le dossier de Jekyll qui abrite 23 | les données. 24 | 25 | Par défaut ce dossier est nommée `locales`. Cela veut dire que le plugin ira chercher les fichiers de traduction de chaque 26 | langue dans `_data/locales/` ou dans un fichier unique de données nommé `locales`, par exemple `_data/locales.yml` ou 27 | `_data/locales.json`, etc. 28 | 29 | Les données doivent être agencées sous forme de dictionnaire de paires clé-valeur, dans lequel la clef principale doit 30 | correspondre à la locale définie dans le tableau `locales_set` ou à la locale par défaut `en-US`; les sous-clefs désignent 31 | ensuite des chaînes de caractères. 32 | 33 | #### Exemple de fichier unique avec plusieurs langues : 34 | 35 | ```yaml 36 | # ------------------------ 37 | # _data/locales.yml 38 | # ------------------------ 39 | 40 | # English US 41 | en-US: 42 | greeting: Hello 43 | user: user 44 | navigation: 45 | home: Home 46 | about: About 47 | contact: Contact 48 | 49 | # French 50 | fr-FR: 51 | greeting: Bonjour 52 | user: utilisateur 53 | navigation: 54 | home: Accueil 55 | about: À propos 56 | contact: Contact 57 | ``` 58 | 59 | #### Exemples de fichiers de données dédiés à une seule langue 60 | 61 | ```yaml 62 | # ------------------------ 63 | # _data/locales/en-US.yml 64 | # ------------------------ 65 | # 66 | # English US 67 | greeting: Hello 68 | user: user 69 | navigation: 70 | home: Home 71 | about: About 72 | contact: Contact 73 | ``` 74 | 75 | ```yaml 76 | # ------------------------ 77 | # _data/locales/fr-FR.yml 78 | # ------------------------ 79 | # 80 | # French 81 | greeting: Bonjour 82 | user: utilisateur 83 | navigation: 84 | home: Accueil 85 | about: À propos 86 | contact: Contact 87 | ``` 88 | 89 | 90 | * ### `content_dir` 91 | 92 | En mode manuel, vos fichiers et documents traduits sont à placer dans le dossier `_locales` situé à la racine du projet. 93 | 94 | Ce paramètre est ignoré en mode automatique. 95 | Reportez-vous à la sous-section `mode` ci-dessous pour plus de détails. 96 | 97 | * ### `locales_set` 98 | 99 | Ce paramètre permet de définir les différentes langues et paramètres régionaux supportés, outre la locale par défaut. Toute 100 | locale ajoutée dans ce tableau sera entraînera une génération automatique du rendu de cette langue en "mode auto". 101 | 102 | Vous pouvez aussi écrire cette option de configuration sous forme d'objet avec des paires clé-valeurs, pour déclarer 103 | les identifiants de votre choix et leur affecter des valeurs : 104 | 105 | ```yaml 106 | localization: 107 | locales_set: 108 | en: 109 | label: English 110 | img: img/english.png 111 | fr: 112 | label: Français 113 | img: img/french.png 114 | ``` 115 | 116 | _La clef `id` est une valeur réservée par le plugin, vous ne pouvez pas l'utiliser_. 117 | 118 | * ### `locale` 119 | 120 | `en-US` par default, ce paramètre permet de définir la langue par défaut du site. La langue par défaut ne sera pas insérée 121 | dans les URLs du site. 122 | 123 | * ### `exclude_set` 124 | 125 | Ignoré en mode "manuel", ce paramètre permet de définir les fichiers à exclure de la copie lorsque le mode "auto" est activé. 126 | 127 | * ### `mode` 128 | 129 | Ce paramètre vous permet de choisir entre deux stratégies de traitement des fichiers par le plugin. 130 | 131 | Lorsqu'il est défini à **`auto`**, le plugin va initialiser une génération, qui va générer, pour chaque locale présente dans le 132 | tableau `locales_set`, *toutes les pages et les documents dans le dossier de destination*. 133 | 134 | Ce mode fera donc varier vos temps de génération en fonction du nombre total de pages et du nombre de locales à générer. 135 | Les URLs des différentes langues différeront simplement par le code de langue inséré dans l'URL. 136 | 137 | Par exemple, si on configure le plugin avec les valeurs suivantes pour `locales_set: ["de", "fr", "en-US", "es"]`, alors 138 | un fichier nommé `about.md` entraînera la création des fichiers suivants : 139 | * `_site/about.html` 140 | * `_site/de/about.html` 141 | * `_site/es/about.html` 142 | * `_site/fr/about.html` 143 | 144 | Si le mode n'est pas défini à `auto` alors c'est le mode "manuel" qui sera actif, et ce sera à vous de créer les fichiers dans 145 | le répertoire défini dans 'content_dir'. 146 | 147 | Reportez-vous à la section sur la [configuration du mode manuel](../modes/manuel/) pour plus de détails. 148 | -------------------------------------------------------------------------------- /docs/_localizations/fr/_docs/filters/localize-date.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Filtre de localisation de date 3 | permalink: /filtres/localize_date/ 4 | translators: 5 | - name: DirtyF 6 | link: https://github.com/DirtyF 7 | --- 8 | 9 |
10 |
11 | 12 | 13 | Adapté du 14 | 16 | filtre de localisation de @borisschapira 17 | 18 | 19 |
20 |
21 | 22 | 23 | Le filtre `localize_date` vous aide à localiser des dates. Optionnellement, il prend en paramètre le format de sortie désiré. 24 | 25 | Le filtre reprend les conventions du module `i18n`, que vous devez donc respecter : 26 | 27 | ```yaml 28 | date: 29 | day_names : # Tableau des jours de la semaine, ex: "Lundi", "Mardi", etc. 30 | month_names : # Tableau des mois de l'année, ex: "Janvier", "Février", etc. 31 | abbr_day_names : # Tableau des jours de la semaine, forme abrégée, ex: "Lun", "Mar", etc. 32 | abbr_month_names : # Tableau des mois de l'année, forme abrégée, ex: "Jan", "Fév", etc. 33 | time: 34 | am: "am" # Placeholder pour Ante-Meridian 35 | pm: "pm" # Placeholder pour Post-Meridian 36 | formats: # Liste des formats préféfinis 37 | default: "%B %d, %Y %l:%M:%S %p %z" # Valeur par défault si aucun autre `format` n'est spécifié 38 | # my_format: # Un format valide strftime de votre choix. 39 | # Usage : {% raw %}{{ votre_date | localize_date: ":my_format" }}{% endraw %} 40 | ``` 41 | 42 | #### Conventions 43 | 44 | Avec ce plugin, vous devez respecter les conventions suivantes : 45 | 46 | * Toutes les paramètres de localisation de date peuvent être définis à l'aide de la clé `locale_date` dans les fichiers 47 | de données de chaque langue. La langue par défaut est déjà pré-configurée mais vous êtes libre de redéfinir ses valeurs 48 | si besoin. 49 | * Le tableau des noms est rempli par défaut avec les valeurs de la classe `date` de Ruby. 50 | * Le premier élément des listes des noms complets des jours et des mois pour les langues non-anglo-saxonnes doit être `nil`. 51 | En YAML, on peut aussi utiliser `~` pour désigner la valeur `nil`. 52 | * Le paramètre optionnel `format` du filtre est soit une chaîne de caractères qui correspond à la valeur de la sous-clef 53 | de `formats` (ex: `":default"`) soit une valeur `strftime` valide. 54 | -------------------------------------------------------------------------------- /docs/_localizations/fr/_docs/filters/prefix-locale.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Filtre préfixe locale 3 | permalink: /filtres/prefix_locale/ 4 | translators: 5 | - name: DirtyF 6 | link: https://github.com/DirtyF 7 | --- 8 | 9 | Ce filtre vous permet de convertir une chaîne de caractères sans espace en une URI relative à la racine du site et à la langue 10 | courante. 11 | 12 | L'URI générée se verra préfixée avec `/[langue]` (ou `/` pour la langue par défaut). 13 | 14 | Le filtre vérifie que la valeur en entrée est bien une chaîne de caractères ou alors une URI absolue. 15 | 16 | Le filtre s'occupera également de nettoyer la valeur si elles comporte plusieurs slashs d'affilée. 17 | 18 | ## Exemples 19 | 20 |
21 | 22 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | {% for example in site.data.filter_examples.prefix_locale.en %} 34 | 35 | 38 | 41 | 42 | {% endfor %} 43 | 44 |
23 |
Langue par défaut : en
24 |
Langue actuelle : en
25 |
EntréeSortie
36 | {{ example.input }} 37 | 39 | {{ example.output }} 40 |
45 | 46 | 47 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | {% for example in site.data.filter_examples.prefix_locale.fr %} 59 | 60 | 63 | 66 | 67 | {% endfor %} 68 | 69 |
48 |
Langue par défaut : en
49 |
Langue courante : fr
50 |
EntréeSortie
61 | {{ example.input }} 62 | 64 | {{ example.output }} 65 |
70 |
71 | -------------------------------------------------------------------------------- /docs/_localizations/fr/_docs/modes/auto-mode.md: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: /modes/auto/ 3 | title: Mode automatique 4 | translators: 5 | - name: DirtyF 6 | link: https://github.com/DirtyF 7 | --- 8 | 9 | Le mode automatique va générer des versions locales de **toutes les pages et documents présents** dans le site par défaut. 10 | En d'autres termes, si votre site comporte 10 articles, 3 pages et est configuré pour être traduit dans 3 langues, ce mode 11 | va alors générer (10 + 3) x 3, soit 39 pages web. 12 | 13 | Pour empêcher la copie de certains fichiers, vous avez la possibilité d'exclure des dossiers et des fichiers dans la directive 14 | de configuration `localization.exclude_set` : 15 | 16 | ```yaml 17 | localization: 18 | mode: auto 19 | locale: en 20 | locales_set: ["en", "fr"] 21 | exclude_set: 22 | - _posts/2018-10-18-english-only.md 23 | - _puppies 24 | ``` 25 | 26 | ### Conventions 27 | 28 | Ce mode a été pensé pour les sites qui veulent avoir des versions _identiques_ pour chaque langue. 29 | 30 | En conséquence, ce mode adopte les conventions suivantes : 31 | * La page traduite possède les mêmes attributs que la page de la langue par défaut, elles possèdent les mêmes attributs `path`, 32 | `relative_path`, `data`, `content`, etc. Seul l'attribut `url` diffère. 33 | * Ces attributs ne peuvent donc être modifiés. 34 | * L'object localisé sera toujours un descendant de `Jekyll::Page`, même si le document d'origine est un post ou un document 35 | appartenant à une collection.
36 | Cela signifie qu'un object localisé ne répondra pas forcément à certains attributs de l'objet d'origine (par exemple 37 | la `date` d'un document) 38 | -------------------------------------------------------------------------------- /docs/_localizations/fr/_docs/modes/manual-mode.md: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: /modes/manuel/ 3 | title: Mode Manuel 4 | translators: 5 | - name: DirtyF 6 | link: https://github.com/DirtyF 7 | --- 8 | 9 | Les principales fonctionnalités de ce mode sont : 10 | * les fichiers présents seront traités comme n'importe quel autre fichier du site mais ils devront **refléter en partie 11 | l'arborescence** de la langue par défaut. 12 | * si le fichier d'origine contient du front matter, alors sa version localisée aussi. 13 | * Une copie peut avoir un contenu distinct et utiliser un layout distinct si besoin. 14 | * les versions localisées des articles ou de tout autre document peuvent utiliser un slug différent en personnalisant 15 | la clé `slug` dans l'entête front matter. 16 | 17 | En mode manuel, tous les sous-dossiers et fichiers présents dans le répertoire `content_dir` seront traités lors de 18 | la génération du site. 19 | 20 | Par défaut ce dossier `_locales` se trouve à la racine. Ainsi pour l'arborescence suivante : 21 | 22 | ``` 23 | . 24 | ├── _config.yml 25 | ├── _locales 26 | | ├── es 27 | | | ├── tips/ 28 | | | | └── optimized-site.md 29 | | | ├── _posts/ 30 | | | ├──└── 2018-09-30-hello-world.md 31 | | ├── fr 32 | | | ├── _posts/ 33 | | └──└──└── 2018-09-30-hello-world.md 34 | ├── _posts 35 | | └── 2018-09-30-hello-world.md 36 | ├── english 37 | | └── its-a-new-day.md 38 | ├── tips 39 | | ├── optimized-site.md 40 | └──└── url-filters-in-templates.md 41 | ``` 42 | 43 | les fichiers suivants seront générés dans le dossier de destination : 44 | 45 | ``` 46 | _site/2018-09-30-hello-world.html 47 | _site/english/its-a-new-day.html 48 | _site/es/2018-09-30-hello-world.html 49 | _site/es/tips/optimized-site.html 50 | _site/fr/2018-09-30-hello-world.html 51 | _site/tips/optimized-site.html 52 | _site/tips/url-filters-in-templates.html 53 | ``` 54 | 55 | #### Pré-requis 56 | 57 | * Les fichiers doivent être regroupés dans un sous-dossier de premier niveau qui correspond à la locale souhaitée. Par exemple 58 | pour générer une version d'un article qui se trouve dans `movies/_posts/2018-09-24-hello.markdown` dans la langue `fr`, vous 59 | devez créer une copie dans `_locales/fr/movies/_posts/2018-09-24-hello.markdown`. 60 | 61 | * Chaque page traduite doit se trouver **au même endroit** que dans l'arborescence du site par défaut, elle doit également avoir 62 | **le même nom** que le fichier par défaut. 63 | 64 | Par exemple si le fichier par défaut se nomme `about.md`, la traduction en français devra se trouver dans 65 | `_locales/fr/about.md` — et non dans `_locales/fr/a-propos.md`. 66 | Vous pouvez définir une URL personnalisée à l'aide des variables `permalink` ou `slug` dans le front matter. 67 | 68 | * **Les fichiers doivent exister dans la langue par défaut.** 69 | 70 | Le fichier `_locales/fr/about.md` sera pris en compte **si et seulement si** le fichier `about.md` existe. 71 | -------------------------------------------------------------------------------- /docs/_localizations/fr/_docs/usage/advanced-usage.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Utilisation avancée 3 | permalink: /utilisation/avancee/ 4 | translators: 5 | - name: DirtyF 6 | link: https://github.com/DirtyF 7 | --- 8 | 9 | ## Configuration des méta-données de langues 10 | 11 |
12 |
13 | 14 | 15 | Adaptaté d'une 16 | 17 | suggestion de @letrastudio 18 | 19 | 20 |
21 |
22 | 23 | La méta-donnée par défaut pour chaque langue configurée à l'aide du tableau `locales_set` est un simple identifiant du nom de 24 | la langue. 25 | 26 | Il est cependant possible de définir des méta-données supplémentaires en modifiant quelque peu les valeurs de `locales_set` : 27 | 28 | ```yaml 29 | localization: 30 | locale: en-US 31 | locales_set: 32 | en-US: 33 | label: English 34 | dir: ltr 35 | img: /assets/img/en-US.png 36 | fr-FR: 37 | label: Français 38 | dir: ltr 39 | img: /assets/img/fr_FR.jpg 40 | ``` 41 | 42 | Vous êtes libres d'ajouter les clefs de votre choix, rien n'est imposé ou validé par le plugin, à l'exception de l'`id` de 43 | la langue. 44 | 45 | Une fois définies, vous pourrez faire appel à ces valeurs dans vos gabarits via `{% raw %}{{ page.locale }}{% endraw %}` : 46 | 47 | ```html 48 | {%- raw -%} 49 | 50 | 51 | 52 | {% endraw %} 53 | ``` 54 | 55 | ## Relier les pages avec hreflangs 56 | 57 | Chaque page de langue générée possède une relation avec la page de la langue par défaut et celles disponibles dans les autres 58 | langues (pour les sites en trois langues ou plus). 59 | C'est une bonne pratique SEO de lister les alternatives d'une page dans d'autres langues dans les méta-données d'une page. 60 | 61 | Pour en informer les moteurs de recherche, il vous suffit d'ajouter dans la balise `head` le code suivant : 62 | 63 | ```html 64 | {% raw %}{% for item in page.hreflangs %} 65 | 66 | {% endfor %}{% endraw %} 67 | ``` 68 | 69 | Par exemple, pour une page `about.md` en anglais américain par défaut avec la propriété `permalink: /about/` d'un site trilingue 70 | configuré avec `["en-US", "es", "fr"]` ayant pour URL `http://example.com`, cela va générer le code HTML suivant : 71 | 72 | ```html 73 | 74 | 75 | 76 | ``` 77 | 78 | Le même code HTML sera généré pour toutes les versions "localisées" de la page `about.md`. 79 | 80 | ## Ajouter un menu de langues 81 | 82 | Il est possible de lister les différentes versions d'une page traduite à l'aide de `page.locale_siblings`: 83 | 84 | ```html 85 | 90 | ``` 91 | 92 | *Si vous avez défini des méta-données supplémentaires dans votre configuration, vous pouvez également y accéder dans 93 | ce contexte.* 94 | 95 | ```html 96 | 97 | 98 | 103 | ``` 104 | -------------------------------------------------------------------------------- /docs/_localizations/fr/_docs/usage/usage.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Utilisation 3 | permalink: /utilisation/intro/ 4 | translators: 5 | - name: DirtyF 6 | link: https://github.com/DirtyF 7 | --- 8 | 9 | Ce plugin ne s'occupe pas de la traduction de vos contenus, il vous permet toutefois de générer l'arborescence de votre site dans 10 | plusieurs langues et paramètres régionaux, en tirant parti de la polyvalence des fichiers de données de Jekyll. 11 | 12 | ## Traduction des chaînes de caractères 13 | 14 | Les traductions des chaînes de caractères sont stockées dans des fichiers de données, par défaut dans le dossier 15 | `_data/locales/`. 16 | 17 | Les fichiers de données peuvent être dans n'importe quel format supporté par Jekyll (YAML, JSON, CSV) et doivent être nommés en 18 | fonction des clés utilisées dans la configuration des langues. 19 | 20 | Pour un site configuré avec les langues `en-US`, 'de', 'es', 'fr', on aura donc les fichiers de traductions suivants : 21 | 22 | ``` 23 | _data/locales/en-US.yml 24 | _data/locales/de.yml 25 | _data/locales/es.yml 26 | _data/locales/fr.yml 27 | ``` 28 | 29 | Attention, les codes de langue sont sensibles à la casse, le tiret du bas peut être utilisé comme séparateur. 30 | `_data/locales/en-US.yml` et `_data/locales/en_US.yml` feront référence à la même langue `en-US`. 31 | 32 | ## Gabarits 33 | 34 | Ce plugin met à disposition l'objet Liquid `{% raw %}{{ locale }}{% endraw %}`, un raccourci syntaxique qui donnera le même 35 | résultat que le code suivant : 36 | 37 | ```html 38 | {%- raw -%} 39 | {% assign current_locale = 'fr' %} 40 | {{ site.data.locales[current_locale] }} 41 | {% endraw %} 42 | ``` 43 | 44 | Le plugin récupère automatiquement la langue utilisée sur la page courante. 45 | Ainsi votre code Liquid sera moins verbeux : 46 | 47 | ```html 48 | {%- raw -%} 49 | {{ locale.foo_bar }} 50 | {% endraw %} 51 | ``` 52 | 53 | Outre l'objet `{% raw %}{{ locale }}{% endraw %}`, le plugin fournit également quelques [filtres](../filtres/prefix_locale/) 54 | Liquid bien [pratiques](../filtres/localize_date/). 55 | -------------------------------------------------------------------------------- /docs/_localizations/fr/about.md: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: /a-propos/ 3 | translators: 4 | - name: DirtyF 5 | link: https://github.com/DirtyF 6 | --- 7 | 8 | Ce site a été généré avec `Jekyll v{{ jekyll.version }}` à partir de la branche `master` du dépôt de `jekyll-locale`, et a été 9 | déployé sur [Netlify](https://netlify.com/). 10 | 11 | Le design du site est adapté du [site officiel de Jekyll](https://jekyllrb.com) et est maintenu par l'auteur du plugin. 12 | 13 | La texture de fond a été téléchargée sur [Subtle Patterns](https://www.toptal.com/designers/subtlepatterns/tactile-noise/). 14 | -------------------------------------------------------------------------------- /docs/_localizations/fr/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | translators: 3 | - name: ashmaroli 4 | link: https://github.com/ashmaroli 5 | - name: DirtyF 6 | link: https://github.com/DirtyF 7 | --- 8 | 9 | *Jekyll Locale est un plugin de plus qui gère la localisation de sites Jekyll.* 10 | 11 | Le plugin ne s'occupe pas de la traduction de vos contenus, il permet toutefois de générer l'arborescence de votre site dans 12 | plusieurs langues et paramètres régionaux, en tirant parti de la polyvalence des fichiers de données de Jekyll. 13 | 14 | ## Installation 15 | 16 | - Ajoutez le plugin au groupe `:jekyll_plugins` dans votre fichier `Gemfile` et lancez la commande `bundle install` pour 17 | commencer : 18 | 19 | ```ruby 20 | group :jekyll_plugins do 21 | gem "jekyll-locale", "~> 0.5" 22 | end 23 | ``` 24 | 25 | - [Configurez](configuration/) le site avec vos préférences régionales et générez votre site comme à l'accoutumée. 26 | -------------------------------------------------------------------------------- /docs/_sass/_font-awesome.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'FontAwesome'; 3 | src: url('../fonts/FontAwesome.eot?9h6hxj'); 4 | src: url('../fonts/FontAwesome.eot?9h6hxj#iefix') format('embedded-opentype'), 5 | url('../fonts/FontAwesome.woff?9h6hxj') format('woff'), 6 | url('../fonts/FontAwesome.ttf?9h6hxj') format('truetype'), 7 | url('../fonts/FontAwesome.svg?9h6hxj#FontAwesome') format('svg'); 8 | font-weight: normal; 9 | font-style: normal; 10 | } 11 | .fa { 12 | display: inline-block; 13 | font: normal normal normal 14px/1 FontAwesome; 14 | font-size: inherit; 15 | text-rendering: auto; 16 | -webkit-font-smoothing: antialiased; 17 | -moz-osx-font-smoothing: grayscale; 18 | } 19 | .fa-link:before { 20 | content: "\f0c1"; 21 | } 22 | .fa-pencil:before { 23 | content: "\f040"; 24 | } 25 | -------------------------------------------------------------------------------- /docs/_sass/_fonts.scss: -------------------------------------------------------------------------------- 1 | /* lato-300 - latin */ 2 | @font-face { 3 | font-family: 'Lato'; 4 | font-style: normal; 5 | font-weight: 300; 6 | src: local('Lato Light'), local('Lato-Light'), 7 | url('../fonts/lato-v14-latin-300.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ 8 | url('../fonts/lato-v14-latin-300.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ 9 | font-display: swap; 10 | } 11 | 12 | /* lato-300italic - latin */ 13 | @font-face { 14 | font-family: 'Lato'; 15 | font-style: italic; 16 | font-weight: 300; 17 | src: local('Lato Light Italic'), local('Lato-LightItalic'), 18 | url('../fonts/lato-v14-latin-300italic.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ 19 | url('../fonts/lato-v14-latin-300italic.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ 20 | font-display: swap; 21 | } 22 | 23 | /* lato-700 - latin */ 24 | @font-face { 25 | font-family: 'Lato'; 26 | font-style: normal; 27 | font-weight: 700; 28 | src: local('Lato Bold'), local('Lato-Bold'), 29 | url('../fonts/lato-v14-latin-700.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ 30 | url('../fonts/lato-v14-latin-700.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ 31 | font-display: swap; 32 | } 33 | 34 | /* lato-700italic - latin */ 35 | @font-face { 36 | font-family: 'Lato'; 37 | font-style: italic; 38 | font-weight: 700; 39 | src: local('Lato Bold Italic'), local('Lato-BoldItalic'), 40 | url('../fonts/lato-v14-latin-700italic.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */ 41 | url('../fonts/lato-v14-latin-700italic.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */ 42 | font-display: swap; 43 | } 44 | -------------------------------------------------------------------------------- /docs/_sass/_highlights.scss: -------------------------------------------------------------------------------- 1 | /* Code Highlighting */ 2 | 3 | pre, code { 4 | display: inline-block; 5 | margin: 0; 6 | padding: 2px 0.5em; 7 | white-space: pre; 8 | font-family: Menlo, Consolas, 'Courier New', Courier, 'Liberation Mono', monospace; 9 | font-size: 15px; 10 | letter-spacing: normal; 11 | line-height: 1.5em 12 | } 13 | 14 | .highlight, 15 | p > pre, p > code, 16 | p > nobr > code, 17 | li > pre, li > code, 18 | h5 > code, 19 | strong > code, em > code, 20 | .note > code { 21 | max-width: 100%; 22 | margin: 0 3px; 23 | color: #ccc; 24 | vertical-align: middle; 25 | background-color: #181818; 26 | border-radius: 5px; 27 | box-shadow: inset 0 1px 10px rgba(0, 0, 0, 0.3), 28 | 0 1px 0 rgba(255, 255, 255, 0.1), 29 | 0 -1px 0 rgba(0, 0, 0, 0.5); 30 | overflow-x: auto; 31 | } 32 | 33 | em > code { font-style: italic } 34 | h3 > code { font-size: inherit } 35 | 36 | .note code { 37 | margin-left: 2.5px; 38 | margin-right: 2.5px; 39 | font-size: 0.8em; 40 | background-color: #333; 41 | } 42 | 43 | .code-block { 44 | margin: 10px 0; 45 | code { background: none } 46 | } 47 | 48 | .highlight { 49 | margin: 1em 0; 50 | width: 100%; 51 | overflow: auto; 52 | &:hover .copy-btn { opacity: 1 } 53 | 54 | pre, code { line-height: 1.45em } 55 | } 56 | 57 | pre.highlight { position: relative; padding: 1em 0.5em } 58 | 59 | .highlighter-rouge .highlight { 60 | @extend .highlight; 61 | margin: 0; 62 | } 63 | 64 | .copy-btn { 65 | position: absolute; 66 | width: 24px; 67 | height: 24px; 68 | padding: 0.9em; 69 | right: 1em; 70 | bottom: 1em; 71 | background: url('../img/clippy.svg') no-repeat 60% 50% #333; 72 | background-size: 1em; 73 | border: 1px solid #454545; 74 | border-radius: 3px; 75 | opacity: 0; 76 | cursor: pointer; 77 | 78 | &:focus { outline: none } 79 | } 80 | 81 | div[class*="language-"] { 82 | code { 83 | display: block; 84 | position: relative; 85 | padding: 0 0.5em; 86 | 87 | &:before { 88 | display: block; 89 | margin: -1em 0 1em -1em; 90 | padding: 8px 1em 5px; 91 | width: calc(100% + 2em); 92 | content: attr(data-lang); 93 | color: #333; 94 | font-size: 1em; 95 | font-weight: 700; 96 | text-transform: uppercase; 97 | background: #111; 98 | } 99 | } 100 | } 101 | 102 | .highlight .hll { background-color: #272822; } 103 | .highlight .err { color: #960050; background-color: #1e0010 } /* Error */ 104 | 105 | .highlight .c { color: #75715e } /* Comment */ 106 | .highlight .k { color: #ff3a81 } /* Keyword */ 107 | .highlight .l { color: #ae81ff } /* Literal */ 108 | .highlight .n { color: #f8f8f2 } /* Name */ 109 | .highlight .o { color: #ffffff } /* Operator */ 110 | .highlight .p { color: #f8f8f2 } /* Punctuation */ 111 | .highlight .cm { color: #75715e } /* Comment.Multiline */ 112 | .highlight .cp { color: #75715e } /* Comment.Preproc */ 113 | .highlight .c1 { color: #999999 } /* Comment.Single */ 114 | .highlight .cs { color: #75715e } /* Comment.Special */ 115 | .highlight .ge { font-style: italic } /* Generic.Emph */ 116 | .highlight .gs { font-weight: bold } /* Generic.Strong */ 117 | .highlight .kc { color: #66d9ef } /* Keyword.Constant */ 118 | .highlight .kd { color: #66d9ef } /* Keyword.Declaration */ 119 | .highlight .kn { color: #f92672 } /* Keyword.Namespace */ 120 | .highlight .kp { color: #66d9ef } /* Keyword.Pseudo */ 121 | .highlight .kr { color: #66d9ef } /* Keyword.Reserved */ 122 | .highlight .kt { color: #66d9ef } /* Keyword.Type */ 123 | .highlight .ld { color: #e6db74 } /* Literal.Date */ 124 | .highlight .m { color: #ae81ff } /* Literal.Number */ 125 | .highlight .s { color: #e6db74 } /* Literal.String */ 126 | .highlight .na { color: #a6e22e } /* Name.Attribute */ 127 | .highlight .nb { color: #f8f8f2 } /* Name.Builtin */ 128 | .highlight .nc { color: #a6e22e } /* Name.Class */ 129 | .highlight .no { color: #66d9ef } /* Name.Constant */ 130 | .highlight .nd { color: #a6e22e } /* Name.Decorator */ 131 | .highlight .ni { color: #f8f8f2 } /* Name.Entity */ 132 | .highlight .ne { color: #a6e22e } /* Name.Exception */ 133 | .highlight .nf { color: #a6e22e } /* Name.Function */ 134 | .highlight .nl { color: #f8f8f2 } /* Name.Label */ 135 | .highlight .nn { color: #f8f8f2 } /* Name.Namespace */ 136 | .highlight .nx { color: #a6e22e } /* Name.Other */ 137 | .highlight .py { color: #f8f8f2 } /* Name.Property */ 138 | .highlight .nt { color: #f92672 } /* Name.Tag */ 139 | .highlight .nv { color: #f8f8f2 } /* Name.Variable */ 140 | .highlight .ow { color: #f92672 } /* Operator.Word */ 141 | .highlight .w { color: #f8f8f2 } /* Text.Whitespace */ 142 | .highlight .mf { color: #ae81ff } /* Literal.Number.Float */ 143 | .highlight .mh { color: #ae81ff } /* Literal.Number.Hex */ 144 | .highlight .mi { color: #ae81ff } /* Literal.Number.Integer */ 145 | .highlight .mo { color: #ae81ff } /* Literal.Number.Oct */ 146 | .highlight .sb { color: #e6db74 } /* Literal.String.Backtick */ 147 | .highlight .sc { color: #e6db74 } /* Literal.String.Char */ 148 | .highlight .sd { color: #e6db74 } /* Literal.String.Doc */ 149 | .highlight .s2 { color: #e6db74 } /* Literal.String.Double */ 150 | .highlight .se { color: #ae81ff } /* Literal.String.Escape */ 151 | .highlight .sh { color: #e6db74 } /* Literal.String.Heredoc */ 152 | .highlight .si { color: #e6db74 } /* Literal.String.Interpol */ 153 | .highlight .sx { color: #e6db74 } /* Literal.String.Other */ 154 | .highlight .sr { color: #e6db74 } /* Literal.String.Regex */ 155 | .highlight .s1 { color: #e6db74 } /* Literal.String.Single */ 156 | .highlight .ss { color: #b286f9 } /* Literal.String.Symbol */ 157 | .highlight .bp { color: #f8f8f2 } /* Name.Builtin.Pseudo */ 158 | .highlight .vc { color: #f8f8f2 } /* Name.Variable.Class */ 159 | .highlight .vg { color: #f8f8f2 } /* Name.Variable.Global */ 160 | .highlight .vi { color: #f8f8f2 } /* Name.Variable.Instance */ 161 | .highlight .il { color: #ae81ff } /* Literal.Number.Integer.Long */ 162 | 163 | .highlight .gh { } /* Generic Heading & Diff Header */ 164 | .highlight .gu { color: #75715e; } /* Generic.Subheading & Diff Unified/Comment? */ 165 | .highlight .gd { color: #f92672; } /* Generic.Deleted & Diff Deleted */ 166 | .highlight .gi { color: #a6e22e; } /* Generic.Inserted & Diff Inserted */ 167 | -------------------------------------------------------------------------------- /docs/_sass/_normalize.scss: -------------------------------------------------------------------------------- 1 | /*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */ 2 | 3 | /* Document 4 | ========================================================================== */ 5 | 6 | /** 7 | * 1. Correct the line height in all browsers. 8 | * 2. Prevent adjustments of font size after orientation changes in 9 | * IE on Windows Phone and in iOS. 10 | */ 11 | 12 | html { 13 | line-height: 1.35; /* 1 */ 14 | -ms-text-size-adjust: 100%; /* 2 */ 15 | -webkit-text-size-adjust: 100%; /* 2 */ 16 | } 17 | 18 | /* Sections 19 | ========================================================================== */ 20 | 21 | /** 22 | * Remove the margin in all browsers (opinionated). 23 | */ 24 | 25 | body { 26 | margin: 0; 27 | } 28 | 29 | /** 30 | * Add the correct display in IE 9-. 31 | */ 32 | 33 | article, 34 | aside, 35 | footer, 36 | header, 37 | nav, 38 | section { 39 | display: block; 40 | } 41 | 42 | /** 43 | * Correct the font size and margin on `h1` elements within `section` and 44 | * `article` contexts in Chrome, Firefox, and Safari. 45 | */ 46 | 47 | h1 { 48 | font-size: 2em; 49 | margin: 0.67em 0; 50 | } 51 | 52 | /* Grouping content 53 | ========================================================================== */ 54 | 55 | /** 56 | * Add the correct display in IE 9-. 57 | * 1. Add the correct display in IE. 58 | */ 59 | 60 | figcaption, 61 | figure, 62 | main { /* 1 */ 63 | display: block; 64 | } 65 | 66 | /** 67 | * Add the correct margin in IE 8. 68 | */ 69 | 70 | figure { 71 | margin: 1em 40px; 72 | } 73 | 74 | /** 75 | * 1. Add the correct box sizing in Firefox. 76 | * 2. Show the overflow in Edge and IE. 77 | */ 78 | 79 | hr { 80 | box-sizing: content-box; /* 1 */ 81 | height: 0; /* 1 */ 82 | overflow: visible; /* 2 */ 83 | } 84 | 85 | /** 86 | * 1. Correct the inheritance and scaling of font size in all browsers. 87 | * 2. Correct the odd `em` font sizing in all browsers. 88 | */ 89 | 90 | pre { 91 | font-family: monospace, monospace; /* 1 */ 92 | font-size: 1em; /* 2 */ 93 | } 94 | 95 | /* Text-level semantics 96 | ========================================================================== */ 97 | 98 | /** 99 | * 1. Remove the gray background on active links in IE 10. 100 | * 2. Remove gaps in links underline in iOS 8+ and Safari 8+. 101 | */ 102 | 103 | a { 104 | background-color: transparent; /* 1 */ 105 | -webkit-text-decoration-skip: objects; /* 2 */ 106 | } 107 | 108 | /** 109 | * 1. Remove the bottom border in Chrome 57- and Firefox 39-. 110 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. 111 | */ 112 | 113 | abbr[title] { 114 | border-bottom: none; /* 1 */ 115 | text-decoration: underline; /* 2 */ 116 | text-decoration: underline dotted; /* 2 */ 117 | } 118 | 119 | /** 120 | * Prevent the duplicate application of `bolder` by the next rule in Safari 6. 121 | */ 122 | 123 | b, 124 | strong { 125 | font-weight: inherit; 126 | } 127 | 128 | /** 129 | * Add the correct font weight in Chrome, Edge, and Safari. 130 | */ 131 | 132 | b, 133 | strong { 134 | font-weight: bolder; 135 | } 136 | 137 | /** 138 | * 1. Correct the inheritance and scaling of font size in all browsers. 139 | * 2. Correct the odd `em` font sizing in all browsers. 140 | */ 141 | 142 | code, 143 | kbd, 144 | samp { 145 | font-family: monospace, monospace; /* 1 */ 146 | font-size: 1em; /* 2 */ 147 | } 148 | 149 | /** 150 | * Add the correct font style in Android 4.3-. 151 | */ 152 | 153 | dfn { 154 | font-style: italic; 155 | } 156 | 157 | /** 158 | * Add the correct background and color in IE 9-. 159 | */ 160 | 161 | mark { 162 | background-color: #ff0; 163 | color: #000; 164 | } 165 | 166 | /** 167 | * Add the correct font size in all browsers. 168 | */ 169 | 170 | small { 171 | font-size: 80%; 172 | } 173 | 174 | /** 175 | * Prevent `sub` and `sup` elements from affecting the line height in 176 | * all browsers. 177 | */ 178 | 179 | sub, 180 | sup { 181 | font-size: 75%; 182 | line-height: 0; 183 | position: relative; 184 | vertical-align: baseline; 185 | } 186 | 187 | sub { 188 | bottom: -0.25em; 189 | } 190 | 191 | sup { 192 | top: -0.5em; 193 | } 194 | 195 | /* Embedded content 196 | ========================================================================== */ 197 | 198 | /** 199 | * Add the correct display in IE 9-. 200 | */ 201 | 202 | audio, 203 | video { 204 | display: inline-block; 205 | } 206 | 207 | /** 208 | * Add the correct display in iOS 4-7. 209 | */ 210 | 211 | audio:not([controls]) { 212 | display: none; 213 | height: 0; 214 | } 215 | 216 | /** 217 | * Remove the border on images inside links in IE 10-. 218 | */ 219 | 220 | img { 221 | border-style: none; 222 | } 223 | 224 | /** 225 | * Hide the overflow in IE. 226 | */ 227 | 228 | svg:not(:root) { 229 | overflow: hidden; 230 | } 231 | 232 | /* Forms 233 | ========================================================================== */ 234 | 235 | /** 236 | * 1. Change the font styles in all browsers (opinionated). 237 | * 2. Remove the margin in Firefox and Safari. 238 | */ 239 | 240 | button, 241 | input, 242 | optgroup, 243 | select, 244 | textarea { 245 | font-family: sans-serif; /* 1 */ 246 | font-size: 100%; /* 1 */ 247 | line-height: 1.15; /* 1 */ 248 | margin: 0; /* 2 */ 249 | } 250 | 251 | /** 252 | * Show the overflow in IE. 253 | * 1. Show the overflow in Edge. 254 | */ 255 | 256 | button, 257 | input { /* 1 */ 258 | overflow: visible; 259 | } 260 | 261 | /** 262 | * Remove the inheritance of text transform in Edge, Firefox, and IE. 263 | * 1. Remove the inheritance of text transform in Firefox. 264 | */ 265 | 266 | button, 267 | select { /* 1 */ 268 | text-transform: none; 269 | } 270 | 271 | /** 272 | * 1. Prevent a WebKit bug where (2) destroys native `audio` and `video` 273 | * controls in Android 4. 274 | * 2. Correct the inability to style clickable types in iOS and Safari. 275 | */ 276 | 277 | button, 278 | html [type="button"], /* 1 */ 279 | [type="reset"], 280 | [type="submit"] { 281 | -webkit-appearance: button; /* 2 */ 282 | } 283 | 284 | /** 285 | * Remove the inner border and padding in Firefox. 286 | */ 287 | 288 | button::-moz-focus-inner, 289 | [type="button"]::-moz-focus-inner, 290 | [type="reset"]::-moz-focus-inner, 291 | [type="submit"]::-moz-focus-inner { 292 | border-style: none; 293 | padding: 0; 294 | } 295 | 296 | /** 297 | * Restore the focus styles unset by the previous rule. 298 | */ 299 | 300 | button:-moz-focusring, 301 | [type="button"]:-moz-focusring, 302 | [type="reset"]:-moz-focusring, 303 | [type="submit"]:-moz-focusring { 304 | outline: 1px dotted ButtonText; 305 | } 306 | 307 | /** 308 | * Correct the padding in Firefox. 309 | */ 310 | 311 | fieldset { 312 | padding: 0.35em 0.75em 0.625em; 313 | } 314 | 315 | /** 316 | * 1. Correct the text wrapping in Edge and IE. 317 | * 2. Correct the color inheritance from `fieldset` elements in IE. 318 | * 3. Remove the padding so developers are not caught out when they zero out 319 | * `fieldset` elements in all browsers. 320 | */ 321 | 322 | legend { 323 | box-sizing: border-box; /* 1 */ 324 | color: inherit; /* 2 */ 325 | display: table; /* 1 */ 326 | max-width: 100%; /* 1 */ 327 | padding: 0; /* 3 */ 328 | white-space: normal; /* 1 */ 329 | } 330 | 331 | /** 332 | * 1. Add the correct display in IE 9-. 333 | * 2. Add the correct vertical alignment in Chrome, Firefox, and Opera. 334 | */ 335 | 336 | progress { 337 | display: inline-block; /* 1 */ 338 | vertical-align: baseline; /* 2 */ 339 | } 340 | 341 | /** 342 | * Remove the default vertical scrollbar in IE. 343 | */ 344 | 345 | textarea { 346 | overflow: auto; 347 | } 348 | 349 | /** 350 | * 1. Add the correct box sizing in IE 10-. 351 | * 2. Remove the padding in IE 10-. 352 | */ 353 | 354 | [type="checkbox"], 355 | [type="radio"] { 356 | box-sizing: border-box; /* 1 */ 357 | padding: 0; /* 2 */ 358 | } 359 | 360 | /** 361 | * Correct the cursor style of increment and decrement buttons in Chrome. 362 | */ 363 | 364 | [type="number"]::-webkit-inner-spin-button, 365 | [type="number"]::-webkit-outer-spin-button { 366 | height: auto; 367 | } 368 | 369 | /** 370 | * 1. Correct the odd appearance in Chrome and Safari. 371 | * 2. Correct the outline style in Safari. 372 | */ 373 | 374 | [type="search"] { 375 | -webkit-appearance: textfield; /* 1 */ 376 | outline-offset: -2px; /* 2 */ 377 | } 378 | 379 | /** 380 | * Remove the inner padding and cancel buttons in Chrome and Safari on macOS. 381 | */ 382 | 383 | [type="search"]::-webkit-search-cancel-button, 384 | [type="search"]::-webkit-search-decoration { 385 | -webkit-appearance: none; 386 | } 387 | 388 | /** 389 | * 1. Correct the inability to style clickable types in iOS and Safari. 390 | * 2. Change font properties to `inherit` in Safari. 391 | */ 392 | 393 | ::-webkit-file-upload-button { 394 | -webkit-appearance: button; /* 1 */ 395 | font: inherit; /* 2 */ 396 | } 397 | 398 | /* Interactive 399 | ========================================================================== */ 400 | 401 | /* 402 | * Add the correct display in IE 9-. 403 | * 1. Add the correct display in Edge, IE, and Firefox. 404 | */ 405 | 406 | details, /* 1 */ 407 | menu { 408 | display: block; 409 | } 410 | 411 | /* 412 | * Add the correct display in all browsers. 413 | */ 414 | 415 | summary { 416 | display: list-item; 417 | } 418 | 419 | /* Scripting 420 | ========================================================================== */ 421 | 422 | /** 423 | * Add the correct display in IE 9-. 424 | */ 425 | 426 | canvas { 427 | display: inline-block; 428 | } 429 | 430 | /** 431 | * Add the correct display in IE. 432 | */ 433 | 434 | template { 435 | display: none; 436 | } 437 | 438 | /* Hidden 439 | ========================================================================== */ 440 | 441 | /** 442 | * Add the correct display in IE 10-. 443 | */ 444 | 445 | [hidden] { 446 | display: none; 447 | } 448 | -------------------------------------------------------------------------------- /docs/_sass/_primer-tooltip.scss: -------------------------------------------------------------------------------- 1 | /* 2 | Adapted from GitHub's Primer 3 | https://raw.githubusercontent.com/primer/primer/master/modules/primer-tooltips/lib/tooltips.scss 4 | */ 5 | 6 | $tooltip-background-color: #111; 7 | 8 | // The tooltip bubble 9 | .tooltipped::after { 10 | position: absolute; 11 | z-index: 1000000; 12 | display: none; 13 | padding: 0.5em 0.75em; 14 | color: #ccc; 15 | text-align: center; 16 | pointer-events: none; 17 | -webkit-font-smoothing: subpixel-antialiased; 18 | content: attr(data-clippy-status); 19 | background: $tooltip-background-color; 20 | border-bottom: 1px solid #333; 21 | border-radius: 5px; 22 | opacity: 0; 23 | } 24 | 25 | // The tooltip arrow 26 | .tooltipped::before { 27 | position: absolute; 28 | z-index: 1000001; 29 | display: none; 30 | width: 0; 31 | height: 0; 32 | color: $tooltip-background-color; 33 | pointer-events: none; 34 | content: ""; 35 | border: 6px solid transparent; 36 | opacity: 0; 37 | } 38 | 39 | // delay animation for tooltip 40 | @keyframes tooltip-appear { 41 | from { 42 | opacity: 0; 43 | } 44 | 45 | to { 46 | opacity: 1; 47 | } 48 | } 49 | 50 | // This will indicate when we'll activate the tooltip 51 | .tooltipped:hover, 52 | .tooltipped:active, 53 | .tooltipped:focus { 54 | &::before, 55 | &::after { 56 | display: inline-block; 57 | text-decoration: none; 58 | animation-name: tooltip-appear; 59 | animation-duration: 0.1s; 60 | animation-fill-mode: forwards; 61 | animation-timing-function: ease-in; 62 | animation-delay: 0s; 63 | } 64 | } 65 | 66 | // Tooltips above the object 67 | .tooltipped-n { 68 | &::after { 69 | right: 50%; 70 | bottom: 100%; 71 | margin-bottom: 6px; 72 | } 73 | 74 | &::before { 75 | top: -7px; 76 | right: 50%; 77 | bottom: auto; 78 | margin-right: -6px; 79 | border-top-color: $tooltip-background-color; 80 | } 81 | } 82 | 83 | .tooltipped-n::after { transform: translateX(20%) } 84 | -------------------------------------------------------------------------------- /docs/_sass/_style.scss: -------------------------------------------------------------------------------- 1 | /* Base */ 2 | 3 | html { 4 | box-sizing: border-box; 5 | line-height: 1.575 6 | } 7 | 8 | *, 9 | *:before, 10 | *:after { 11 | box-sizing: inherit; 12 | } 13 | 14 | body { 15 | margin: 0; 16 | font-family: Raleway, Lato, 'Helvetica Neue', Helvetica, Arial, sans-serif; 17 | font-size: 18px; 18 | font-feature-settings: 'kern' 1, 'liga' 0, 'lnum' 1; 19 | font-kerning: normal; 20 | letter-spacing: 0.01em; 21 | color: #bebebe; 22 | text-shadow: 0 1px 3px rgba(0, 0, 0, 0.5); 23 | background: url('../img/dark_texture.png') repeat 0% 0% #333; 24 | } 25 | 26 | main { 27 | display: flex; 28 | max-width: 1290px; 29 | margin: 0 auto; 30 | padding: 20px; 31 | font-size: 1em; 32 | } 33 | 34 | #content { 35 | width: calc(100% - 260px); 36 | padding: 30px; 37 | } 38 | 39 | #sidebar { 40 | padding: 30px; 41 | width: 260px 42 | } 43 | 44 | .show-on-mobiles { display: none } 45 | 46 | @media (max-width: 799px) { 47 | main { flex-direction: column } 48 | 49 | #content { 50 | width: 100%; 51 | padding: 40px 0; 52 | } 53 | 54 | .center-on-mobiles { 55 | margin: 0 auto; 56 | text-align: center !important; 57 | } 58 | 59 | a .show-on-mobiles { display: inline !important } 60 | .show-on-mobiles { display: block !important } 61 | .hide-on-mobiles { display: none !important } 62 | } 63 | 64 | /* Header */ 65 | 66 | header { 67 | max-width: 1230px; 68 | margin: 0 auto; 69 | padding: 5px 30px; 70 | background: #181818; 71 | box-shadow: 0 4px 6px rgba(0, 0, 0, 0.45); 72 | 73 | h1, 74 | nav { display: inline-block } 75 | 76 | .logo { 77 | display: block; 78 | img { 79 | max-width: 100%; 80 | margin: 5px auto -5px 81 | } 82 | } 83 | 84 | .search { 85 | width: 100%; 86 | .svg-icons { display: none } 87 | } 88 | } 89 | 90 | p { letter-spacing: 0.05em } 91 | b, strong { 92 | font-weight: 700; 93 | } 94 | 95 | /* 96 | * This code is courtesy Ben Balter 97 | * http://ben.balter.com/2014/03/13/pages-anchor-links/ 98 | */ 99 | .header-link { 100 | position: relative; 101 | left: 0.5em; 102 | font-size: 0.8em; 103 | color: #fff; 104 | opacity: 0; 105 | transition: opacity 0.2s ease-in-out 0.1s; 106 | } 107 | 108 | h2:hover .header-link, 109 | h3:hover .header-link, 110 | h4:hover .header-link, 111 | h5:hover .header-link, 112 | h6:hover .header-link { opacity: 0.8 } 113 | 114 | /* Footer */ 115 | 116 | footer { 117 | margin-top: 30px; 118 | padding: 30px 15px; 119 | font-size: 0.8em; 120 | color: #999; 121 | background-color: #181818; 122 | box-shadow: inset 0 3px 10px rgba(0, 0, 0, 0.3); 123 | 124 | a { 125 | color: #a4a4a4; 126 | 127 | &:hover { 128 | color: #ddd; 129 | font-weight: inherit; 130 | img { opacity: 1 } 131 | } 132 | } 133 | 134 | .align-right { 135 | p { display: inline-block } 136 | } 137 | 138 | img { 139 | display: inline-block; 140 | vertical-align: middle; 141 | margin-left: 5px; 142 | padding: 1px; 143 | opacity: 0.8; 144 | transition: opacity 0.2s; 145 | } 146 | 147 | ul#footer-links { 148 | display: table; 149 | margin: 5px auto 15px; 150 | padding: 0; 151 | li { 152 | float: left; 153 | padding: 1px 10px; 154 | list-style: none; 155 | border: 1px solid transparent; 156 | border-right-color: #000; 157 | border-left-color: #333; 158 | &:first-of-type { border-left-color: transparent } 159 | &:last-of-type { border-right-color: transparent } 160 | } 161 | } 162 | } 163 | 164 | .sticky { 165 | top: 50px; 166 | position: -webkit-sticky; 167 | position: sticky; 168 | } 169 | 170 | /* Article */ 171 | 172 | article { 173 | h2 { margin: 1em 0 } 174 | h2 + div:first-of-type { 175 | margin-top: -0.75em; 176 | blockquote { 177 | a { 178 | color: #fc0; 179 | &:hover { font-weight: inherit } 180 | } 181 | } 182 | } 183 | h3, h4, h5, h6 { margin: 1em 0 0 } 184 | h4 { color: #fff } 185 | 186 | ul { 187 | margin-top: -10px; 188 | li { 189 | margin-bottom: 20px; 190 | 191 | ul { margin-top: 10px !important } 192 | div.highlighter-rouge { margin-top: 10px; margin-left: 15px } 193 | h3 { 194 | margin-top: 2em; 195 | color: #fafafa; 196 | 197 | + p:first-of-type { margin-top: 8px } 198 | } 199 | h4 { margin-left: 15px } 200 | p { margin: 24px 14px } 201 | blockquote { margin: 10px 0 } 202 | } 203 | } 204 | 205 | > ul > li, 206 | > ol > li { 207 | margin-bottom: 0.5em; 208 | } 209 | 210 | a { 211 | padding-bottom: 1px; 212 | color: #fc0; 213 | border-bottom: 1px dashed 214 | } 215 | } 216 | 217 | #translator { 218 | margin: 45px 0 -60px; 219 | padding-top: 15px; 220 | font-size: 0.8em; 221 | color: #727272; 222 | border-top: 1px solid #111; 223 | box-shadow: inset 0 1px 0 0 #333; 224 | 225 | a { 226 | color: inherit; 227 | &:hover { font-weight: inherit } 228 | } 229 | } 230 | 231 | 232 | /* Right-side nav - used by both docs and news */ 233 | 234 | aside { 235 | padding-bottom: 30px; 236 | 237 | h4 { 238 | display: inline-block; 239 | padding: 0; 240 | font-size: 14px; 241 | color: #ddd; 242 | text-transform: uppercase; 243 | } 244 | 245 | ul { 246 | margin: 0 0 0 30px; 247 | padding-left: 0; 248 | 249 | &:first-child { margin-top: 0 } 250 | } 251 | 252 | li { 253 | font-size: 16px; 254 | list-style-type: none; 255 | 256 | a { position: relative } 257 | 258 | &.current { 259 | a { 260 | color: #fc0; 261 | font-weight: 700; 262 | 263 | &:before { 264 | position: absolute; 265 | top: 0; 266 | left: -30px; 267 | width: 0; 268 | height: 0; 269 | content: ''; 270 | border: 10px solid transparent; 271 | } 272 | } 273 | } 274 | } 275 | } 276 | 277 | .improve { 278 | padding-top: 25px; 279 | font-size: 16px; 280 | a { 281 | color: #999; 282 | } 283 | } 284 | 285 | .docs-nav-mobile select { 286 | margin-top: 5px; 287 | padding: 5px; 288 | width: 100%; 289 | font-size: 0.85em; 290 | color: #aaa; 291 | background: darken(#333, 3%); 292 | border-color: transparent; 293 | border-radius: 5px; 294 | box-shadow: inset 0 1px 1px 0 #4d4d4d, 0 1px 1px 0 #151515; 295 | 296 | &:focus { 297 | outline: none; 298 | } 299 | 300 | option { 301 | padding: 3px; 302 | font-size: 0.85em; 303 | line-height: 1.25; 304 | } 305 | } 306 | 307 | /* HTML Elements */ 308 | 309 | h1, h2, h3, h4, h5, h6 { margin: 0 } 310 | h1 { 311 | padding-bottom: 2px; 312 | font-size: 2.5em; 313 | color: #cca300; 314 | border-bottom: 1px solid #111; 315 | box-shadow: 0 1px 0 0 #353535; 316 | 317 | + h2:first-of-type { margin-top: 0.75em } 318 | } 319 | 320 | h2 { 321 | font-size: 1.75em; 322 | font-weight: 400; 323 | color: #fc0; 324 | } 325 | 326 | a { 327 | color: #999; 328 | text-decoration: none; 329 | transition: all 0.25s; 330 | &:hover { color: #fc0 } 331 | } 332 | 333 | .left { float: left } 334 | .right { float: right } 335 | .align-right { text-align: right } 336 | .align-left { text-align: left } 337 | .align-center { text-align: center } 338 | 339 | h5, h6 { 340 | font-size: 1em; 341 | font-style: italic; 342 | } 343 | 344 | blockquote { 345 | border-left: 2px solid #777; 346 | padding-left: 20px; 347 | font-style: italic; 348 | font-size: 18px; 349 | } 350 | 351 | 352 | /* Helper class taken from Bootstrap. 353 | Hides an element to all devices except screen readers. 354 | */ 355 | .sr-only { 356 | position: absolute; 357 | width: 1px; 358 | height: 1px; 359 | padding: 0; 360 | margin: -1px; 361 | overflow: hidden; 362 | clip: rect(0, 0, 0, 0); 363 | border: 0; 364 | } 365 | 366 | div.locale-switcher { 367 | font-size: 0.9em; 368 | padding-top: 15px; 369 | border-top: 1px solid #353535; 370 | box-shadow: 0 -1px 0 0 #111; 371 | } 372 | 373 | ul#locale-list { 374 | display: table; 375 | margin: 10px 5px; 376 | padding-left: 5px; 377 | list-style: none; 378 | li { 379 | float: left; 380 | font-size: 0.75em; 381 | a { 382 | display: block; 383 | margin: 0 5px; 384 | padding: 6px; 385 | font-weight: 400; 386 | text-align: center; 387 | text-decoration: none; 388 | border: 1px solid 389 | } 390 | } 391 | } 392 | 393 | ol div.highlighter-rouge { margin: 8px 0 10px 0 } 394 | 395 | /* WebKit Scrollbar */ 396 | 397 | ::-webkit-scrollbar{ 398 | width: 0.825rem; 399 | height: 0.825rem 400 | } 401 | 402 | ::-webkit-scrollbar-thumb { 403 | box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3); 404 | border-radius: 5px; 405 | background: #111; 406 | } 407 | 408 | ::-webkit-scrollbar-track { 409 | box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3); 410 | border-radius: 5px; 411 | border: 2px solid #454545; 412 | background: #444; 413 | } 414 | 415 | #error { 416 | font-family: Lato, 'Helvetica Neue', Helvetica, Arial, sans-serif; 417 | font-weight: 300; 418 | header, main { display: block } 419 | header { 420 | margin-bottom: 25px; 421 | img { max-width: 100% } 422 | } 423 | p { letter-spacing: normal } 424 | .intro { 425 | p { 426 | margin: 0; 427 | font-size: 2.7em; 428 | line-height: 1.25em; 429 | } 430 | } 431 | #e404 { 432 | font-size: 7em; 433 | font-weight: 700; 434 | color: #fc0; 435 | } 436 | } 437 | 438 | @media (max-width: 568px) { 439 | #error { 440 | header img { max-width: 100% } 441 | .intro p { font-size: 1.5em } 442 | #e404 { font-size: 4em } 443 | } 444 | } 445 | 446 | 447 | /* Responsive tables */ 448 | 449 | @media (max-width: 768px) { 450 | .mobile-side-scroller { 451 | overflow-x: scroll; 452 | margin: 0 -40px; 453 | padding: 0 10px; 454 | } 455 | } 456 | 457 | /* Tables */ 458 | 459 | table, thead, tbody, tr { border-color: transparent } 460 | th, td { border: 1px solid #212121 } 461 | 462 | table { 463 | width: 100%; 464 | margin: 0.5em 0; 465 | background-color: #181818; 466 | border-collapse: collapse; 467 | box-shadow: 0 1px 0 0 #353535; 468 | 469 | caption { 470 | padding: 0.5em 0.75em; 471 | color: #888; 472 | background: #111; 473 | box-sizing: border-box; 474 | border: 0.5px solid #242424; 475 | border-bottom-color: transparent; 476 | } 477 | 478 | code { box-shadow: none } 479 | 480 | thead { 481 | color: #fff; 482 | background: #111; 483 | } 484 | 485 | th { 486 | position: relative; 487 | padding: 0.5em 0.75em; 488 | font-size: 16px; 489 | color: #888; 490 | text-transform: uppercase; 491 | } 492 | 493 | td { 494 | padding: 0.5em 0.75em; 495 | p { margin: 0 } 496 | } 497 | 498 | tbody td { 499 | ul { padding-left: 1em } 500 | p, ul { 501 | font-size: 16px; 502 | code { font-size: 14px } 503 | } 504 | } 505 | } 506 | 507 | code.option, code.filter, 508 | th .option, th .filter { 509 | color: #50B600; 510 | } 511 | 512 | code.flag, code.output, 513 | th .flag, th .output { 514 | color: #049DCE; 515 | } 516 | 517 | code.option, code.flag, 518 | code.filter, code.output { 519 | margin-bottom: 2px; 520 | } 521 | -------------------------------------------------------------------------------- /docs/about.md: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: /about/ 3 | --- 4 | 5 | This site was generated using `Jekyll v{{ jekyll.version }}` with the `master` branch of `jekyll-locale` repository, and has been 6 | deployed at [Netlify](https://netlify.com/). 7 | 8 | Site design is an adaptation of the [Official Jekyll Documentation site](https://jekyllrb.com) and is maintained by the plugin 9 | author. 10 | The background texture was downloaded from: [Subtle Patterns](https://www.toptal.com/designers/subtlepatterns/tactile-noise/). 11 | -------------------------------------------------------------------------------- /docs/assets/css/styles.scss: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | @mixin user-select($select...) { 5 | -webkit-user-select: $select; /* Chrome all / Safari all */ 6 | -moz-user-select: $select; /* Firefox all */ 7 | -ms-user-select: $select; /* IE 10+ */ 8 | -o-user-select: $select; 9 | user-select: $select; 10 | } 11 | 12 | @import "normalize"; 13 | @import "font-awesome"; 14 | @import "fonts"; 15 | @import "highlights"; 16 | @import "primer-tooltip"; 17 | @import "style"; 18 | -------------------------------------------------------------------------------- /docs/assets/fonts/FontAwesome.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashmaroli/jekyll-locale/dc5c46cadd09e3f13fb4c71b43aa082d90cca6a1/docs/assets/fonts/FontAwesome.eot -------------------------------------------------------------------------------- /docs/assets/fonts/FontAwesome.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Generated by IcoMoon 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs/assets/fonts/FontAwesome.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashmaroli/jekyll-locale/dc5c46cadd09e3f13fb4c71b43aa082d90cca6a1/docs/assets/fonts/FontAwesome.ttf -------------------------------------------------------------------------------- /docs/assets/fonts/FontAwesome.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashmaroli/jekyll-locale/dc5c46cadd09e3f13fb4c71b43aa082d90cca6a1/docs/assets/fonts/FontAwesome.woff -------------------------------------------------------------------------------- /docs/assets/fonts/lato-v14-latin-300.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashmaroli/jekyll-locale/dc5c46cadd09e3f13fb4c71b43aa082d90cca6a1/docs/assets/fonts/lato-v14-latin-300.woff -------------------------------------------------------------------------------- /docs/assets/fonts/lato-v14-latin-300.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashmaroli/jekyll-locale/dc5c46cadd09e3f13fb4c71b43aa082d90cca6a1/docs/assets/fonts/lato-v14-latin-300.woff2 -------------------------------------------------------------------------------- /docs/assets/fonts/lato-v14-latin-300italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashmaroli/jekyll-locale/dc5c46cadd09e3f13fb4c71b43aa082d90cca6a1/docs/assets/fonts/lato-v14-latin-300italic.woff -------------------------------------------------------------------------------- /docs/assets/fonts/lato-v14-latin-300italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashmaroli/jekyll-locale/dc5c46cadd09e3f13fb4c71b43aa082d90cca6a1/docs/assets/fonts/lato-v14-latin-300italic.woff2 -------------------------------------------------------------------------------- /docs/assets/fonts/lato-v14-latin-700.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashmaroli/jekyll-locale/dc5c46cadd09e3f13fb4c71b43aa082d90cca6a1/docs/assets/fonts/lato-v14-latin-700.woff -------------------------------------------------------------------------------- /docs/assets/fonts/lato-v14-latin-700.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashmaroli/jekyll-locale/dc5c46cadd09e3f13fb4c71b43aa082d90cca6a1/docs/assets/fonts/lato-v14-latin-700.woff2 -------------------------------------------------------------------------------- /docs/assets/fonts/lato-v14-latin-700italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashmaroli/jekyll-locale/dc5c46cadd09e3f13fb4c71b43aa082d90cca6a1/docs/assets/fonts/lato-v14-latin-700italic.woff -------------------------------------------------------------------------------- /docs/assets/fonts/lato-v14-latin-700italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashmaroli/jekyll-locale/dc5c46cadd09e3f13fb4c71b43aa082d90cca6a1/docs/assets/fonts/lato-v14-latin-700italic.woff2 -------------------------------------------------------------------------------- /docs/assets/img/clippy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/assets/img/dark_texture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashmaroli/jekyll-locale/dc5c46cadd09e3f13fb4c71b43aa082d90cca6a1/docs/assets/img/dark_texture.png -------------------------------------------------------------------------------- /docs/assets/img/jekyll-locale-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashmaroli/jekyll-locale/dc5c46cadd09e3f13fb4c71b43aa082d90cca6a1/docs/assets/img/jekyll-locale-icon.png -------------------------------------------------------------------------------- /docs/assets/img/jekyll-locale-text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashmaroli/jekyll-locale/dc5c46cadd09e3f13fb4c71b43aa082d90cca6a1/docs/assets/img/jekyll-locale-text.png -------------------------------------------------------------------------------- /docs/assets/img/jekyll-locale.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashmaroli/jekyll-locale/dc5c46cadd09e3f13fb4c71b43aa082d90cca6a1/docs/assets/img/jekyll-locale.png -------------------------------------------------------------------------------- /docs/assets/js/clipboard.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * clipboard.js v2.0.2 3 | * https://zenorocha.github.io/clipboard.js 4 | * 5 | * Licensed MIT © Zeno Rocha 6 | */ 7 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(window,function(){return function(t){var e={};function n(o){if(e[o])return e[o].exports;var r=e[o]={i:o,l:!1,exports:{}};return t[o].call(r.exports,r,r.exports,n),r.l=!0,r.exports}return n.m=t,n.c=e,n.d=function(t,e,o){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:o})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var o=Object.create(null);if(n.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var r in t)n.d(o,r,function(e){return t[e]}.bind(null,r));return o},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=0)}([function(t,e,n){"use strict";var o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},r=function(){function t(t,e){for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:{};this.action="function"==typeof t.action?t.action:this.defaultAction,this.target="function"==typeof t.target?t.target:this.defaultTarget,this.text="function"==typeof t.text?t.text:this.defaultText,this.container="object"===o(t.container)?t.container:document.body}},{key:"listenClick",value:function(t){var e=this;this.listener=(0,c.default)(t,"click",function(t){return e.onClick(t)})}},{key:"onClick",value:function(t){var e=t.delegateTarget||t.currentTarget;this.clipboardAction&&(this.clipboardAction=null),this.clipboardAction=new i.default({action:this.action(e),target:this.target(e),text:this.text(e),container:this.container,trigger:e,emitter:this})}},{key:"defaultAction",value:function(t){return s("action",t)}},{key:"defaultTarget",value:function(t){var e=s("target",t);if(e)return document.querySelector(e)}},{key:"defaultText",value:function(t){return s("text",t)}},{key:"destroy",value:function(){this.listener.destroy(),this.clipboardAction&&(this.clipboardAction.destroy(),this.clipboardAction=null)}}],[{key:"isSupported",value:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:["copy","cut"],e="string"==typeof t?[t]:t,n=!!document.queryCommandSupported;return e.forEach(function(t){n=n&&!!document.queryCommandSupported(t)}),n}}]),e}();function s(t,e){var n="data-clipboard-"+t;if(e.hasAttribute(n))return e.getAttribute(n)}t.exports=l},function(t,e,n){"use strict";var o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},r=function(){function t(t,e){for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:{};this.action=t.action,this.container=t.container,this.emitter=t.emitter,this.target=t.target,this.text=t.text,this.trigger=t.trigger,this.selectedText=""}},{key:"initSelection",value:function(){this.text?this.selectFake():this.target&&this.selectTarget()}},{key:"selectFake",value:function(){var t=this,e="rtl"==document.documentElement.getAttribute("dir");this.removeFake(),this.fakeHandlerCallback=function(){return t.removeFake()},this.fakeHandler=this.container.addEventListener("click",this.fakeHandlerCallback)||!0,this.fakeElem=document.createElement("textarea"),this.fakeElem.style.fontSize="12pt",this.fakeElem.style.border="0",this.fakeElem.style.padding="0",this.fakeElem.style.margin="0",this.fakeElem.style.position="absolute",this.fakeElem.style[e?"right":"left"]="-9999px";var n=window.pageYOffset||document.documentElement.scrollTop;this.fakeElem.style.top=n+"px",this.fakeElem.setAttribute("readonly",""),this.fakeElem.value=this.text,this.container.appendChild(this.fakeElem),this.selectedText=(0,i.default)(this.fakeElem),this.copyText()}},{key:"removeFake",value:function(){this.fakeHandler&&(this.container.removeEventListener("click",this.fakeHandlerCallback),this.fakeHandler=null,this.fakeHandlerCallback=null),this.fakeElem&&(this.container.removeChild(this.fakeElem),this.fakeElem=null)}},{key:"selectTarget",value:function(){this.selectedText=(0,i.default)(this.target),this.copyText()}},{key:"copyText",value:function(){var t=void 0;try{t=document.execCommand(this.action)}catch(e){t=!1}this.handleResult(t)}},{key:"handleResult",value:function(t){this.emitter.emit(t?"success":"error",{action:this.action,text:this.selectedText,trigger:this.trigger,clearSelection:this.clearSelection.bind(this)})}},{key:"clearSelection",value:function(){this.trigger&&this.trigger.focus(),window.getSelection().removeAllRanges()}},{key:"destroy",value:function(){this.removeFake()}},{key:"action",set:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"copy";if(this._action=t,"copy"!==this._action&&"cut"!==this._action)throw new Error('Invalid "action" value, use either "copy" or "cut"')},get:function(){return this._action}},{key:"target",set:function(t){if(void 0!==t){if(!t||"object"!==(void 0===t?"undefined":o(t))||1!==t.nodeType)throw new Error('Invalid "target" value, use a valid Element');if("copy"===this.action&&t.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if("cut"===this.action&&(t.hasAttribute("readonly")||t.hasAttribute("disabled")))throw new Error('Invalid "target" attribute. You can\'t cut text from elements with "readonly" or "disabled" attributes');this._target=t}},get:function(){return this._target}}]),t}();t.exports=a},function(t,e){t.exports=function(t){var e;if("SELECT"===t.nodeName)t.focus(),e=t.value;else if("INPUT"===t.nodeName||"TEXTAREA"===t.nodeName){var n=t.hasAttribute("readonly");n||t.setAttribute("readonly",""),t.select(),t.setSelectionRange(0,t.value.length),n||t.removeAttribute("readonly"),e=t.value}else{t.hasAttribute("contenteditable")&&t.focus();var o=window.getSelection(),r=document.createRange();r.selectNodeContents(t),o.removeAllRanges(),o.addRange(r),e=o.toString()}return e}},function(t,e){function n(){}n.prototype={on:function(t,e,n){var o=this.e||(this.e={});return(o[t]||(o[t]=[])).push({fn:e,ctx:n}),this},once:function(t,e,n){var o=this;function r(){o.off(t,r),e.apply(n,arguments)}return r._=e,this.on(t,r,n)},emit:function(t){for(var e=[].slice.call(arguments,1),n=((this.e||(this.e={}))[t]||[]).slice(),o=0,r=n.length;o 0.5" 17 | end 18 | ``` 19 | 20 | - [Configure](configuration/) the site with your preferred locales and build your site as usual. 21 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | _site 2 | .logs 3 | .sass-cache 4 | .jekyll-cache 5 | .fixtures 6 | .gems 7 | .vendor 8 | .jekyll-metadata 9 | Gemfile.lock 10 | *.gem 11 | -------------------------------------------------------------------------------- /example/Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "jekyll", :path => "../../jekyll" if Dir.exist?("../../jekyll") 6 | 7 | group :jekyll_plugins do 8 | gem "jekyll-feed" 9 | gem "jekyll-sitemap" 10 | gem "jekyll-seo-tag" 11 | gem "jekyll-locale", :path => ".." 12 | end 13 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | An example setup to test drive this plugin. 2 | 3 | 1. Clone the repository locally 4 | 2. `cd` into this directory 5 | 3. `bundle install` to optionally install dependencies 6 | 4. `bundle exec jekyll serve` to start preview server. 7 | 5. Point your browser to `http://localhost:4000` 8 | -------------------------------------------------------------------------------- /example/_config.yml: -------------------------------------------------------------------------------- 1 | title: Localization Example 2 | baseurl: "/example" 3 | collections: 4 | puppies: 5 | output: true 6 | localization: 7 | mode: manual 8 | locale: en 9 | locales_set: 10 | en: 11 | label: English 12 | fr: 13 | label: Français 14 | exclude_set: # For 'auto' mode 15 | - _posts/2018-10-18-english-only.md 16 | exclude: 17 | - Gemfile 18 | - Gemfile.lock 19 | - node_modules 20 | - vendor 21 | - README.md 22 | - .jekyll-cache 23 | -------------------------------------------------------------------------------- /example/_data/locales/en.yml: -------------------------------------------------------------------------------- 1 | welcome: Hello 2 | -------------------------------------------------------------------------------- /example/_data/locales/fr.yml: -------------------------------------------------------------------------------- 1 | welcome: Bonjour 2 | locale_date: 3 | date: 4 | abbr_day_names: 5 | - dim 6 | - lun 7 | - mar 8 | - mer 9 | - jeu 10 | - ven 11 | - sam 12 | abbr_month_names: 13 | - ~ 14 | - jan. 15 | - fév. 16 | - mar. 17 | - avr. 18 | - mai 19 | - juin 20 | - juil. 21 | - août 22 | - sept. 23 | - oct. 24 | - nov. 25 | - déc. 26 | day_names: 27 | - dimanche 28 | - lundi 29 | - mardi 30 | - mercredi 31 | - jeudi 32 | - vendredi 33 | - samedi 34 | month_names: 35 | - ~ 36 | - janvier 37 | - février 38 | - mars 39 | - avril 40 | - mai 41 | - juin 42 | - juillet 43 | - août 44 | - septembre 45 | - octobre 46 | - novembre 47 | - décembre 48 | time: 49 | formats: 50 | default: "%d %B %Y %H h %M min %S s" 51 | -------------------------------------------------------------------------------- /example/_data/navigation.yml: -------------------------------------------------------------------------------- 1 | - name: Home 2 | url : / 3 | - name: About 4 | url : /about.html 5 | - name: Hello World 6 | url : /2018/10/15/hello-world.html 7 | - name: Nested Post 8 | url : /2018/10/15/nested-post.html 9 | - name: Categorized Post - I 10 | url : /category/2018/10/24/superdir-category.html 11 | - name: Categorized Post - II 12 | url : /category/2018/10/24/front-matter-category.html 13 | - name: Auto Localized 14 | url : /2018/10/24/auto-localized.html 15 | - name: English Only 16 | url : /2018/10/18/english-only.html 17 | - name: Rover 18 | url : /puppies/rover.html 19 | - name: Atom Feed 20 | url : /feed.xml 21 | - name: Sitemap 22 | url : /sitemap.xml 23 | -------------------------------------------------------------------------------- /example/_includes/locale_snippet.html: -------------------------------------------------------------------------------- 1 |
2 | {% assign mode = site.localization.mode %} 3 | {% capture data_table %} 4 | Locale Mode | : | {{ mode }} 5 | Page Locale | : | {{ page.locale.id }} 6 | {% endcapture %} 7 | 8 | {% capture notes %} 9 |
Things to note:
10 | {% if page.locale.id != site.localization.locale %} 11 | * The page url **{{ page.url }}** begins with **`/{{ page.locale.id }}`** 12 | * The relative filepath to this page is {%- if mode == 'auto' %} the same as the canonical page{% endif %}: 13 | **{{ page.path }}** 14 | {% if mode == 'auto' -%} 15 | * The page's rendered content is same as that of the canonical page 16 | {% if page.path contains '_posts/' %}* This page **will not be included** in your site's feed.{% endif %} 17 | {% endif %} 18 | {% else %} 19 | * The page url **{{ page.url }}** is not modified 20 | * The relative filepath to this page is: **{{ page.path }}** 21 | {% endif -%} 22 | {% endcapture %} 23 | 24 |
25 |
{{ data_table | markdownify }}
26 |
{{ notes | markdownify }}
27 | 28 |
29 | 30 | {% if page.locale_siblings == empty %} 31 |
This Page has no other versions 32 | {%- if mode == 'auto' %} as this page has been explicitly listed under the `exclude_set` {% endif %}. 33 |
34 | {% else %} 35 |
This Page is also available under the following locales: 36 | 41 |
42 | {% endif %} 43 | 44 |
{{ 'now' | localize_date }}
45 |
46 | -------------------------------------------------------------------------------- /example/_layouts/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {% feed_meta %} 8 | 12 | {% seo %} 13 | 14 | 15 |
16 | {{ content }} 17 |
18 | 19 | 20 | -------------------------------------------------------------------------------- /example/_layouts/page.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: base 3 | --- 4 | 5 | 14 | 15 | {{ content }} 16 | 17 | {% include locale_snippet.html %} 18 | -------------------------------------------------------------------------------- /example/_locales/fr/_posts/2018-10-15-hello-world.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | --- 4 | 5 | {{ locale.welcome }} ! 6 | Ce document est destiné à présenter le fonctionnement du plugin `jekyll-locale`. 7 | -------------------------------------------------------------------------------- /example/_locales/fr/_posts/2018-10-24-front-matter-category.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | category: category 4 | --- 5 | 6 | {{ locale.welcome }}! 7 | This document is meant to showcase the workings of the `jekyll-locale` plugin. 8 | -------------------------------------------------------------------------------- /example/_locales/fr/_posts/2018/10/15/2018-10-15-nested-post.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | --- 4 | 5 | {{ locale.welcome }} ! 6 | Ce document est destiné à présenter le fonctionnement du plugin `jekyll-locale`. 7 | -------------------------------------------------------------------------------- /example/_locales/fr/_puppies/rover.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | --- 4 | 5 | {{ locale.welcome }} ! 6 | Ce document est destiné à présenter le fonctionnement du plugin `jekyll-locale`. 7 | -------------------------------------------------------------------------------- /example/_locales/fr/about.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | --- 4 | 5 | {{ locale.welcome }} ! 6 | Cette page est destinée à présenter le fonctionnement du plugin `jekyll-locale`. 7 | -------------------------------------------------------------------------------- /example/_locales/fr/category/_posts/2018-10-24-superdir-category.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | --- 4 | 5 | {{ locale.welcome }}! 6 | This document is meant to showcase the workings of the `jekyll-locale` plugin. 7 | -------------------------------------------------------------------------------- /example/_locales/fr/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | --- 4 | 5 | {{ locale.welcome }} ! 6 | Cette page est destinée à présenter le fonctionnement du plugin `jekyll-locale`. 7 | -------------------------------------------------------------------------------- /example/_posts/2018-10-15-hello-world.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | --- 4 | 5 | {{ locale.welcome }}! 6 | This document is meant to showcase the workings of the `jekyll-locale` plugin. 7 | -------------------------------------------------------------------------------- /example/_posts/2018-10-18-english-only.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | --- 4 | 5 | {{ locale.welcome }}! 6 | This document is meant to showcase the workings of the `jekyll-locale` plugin. 7 | -------------------------------------------------------------------------------- /example/_posts/2018-10-24-auto-localized.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | --- 4 | 5 | {{ locale.welcome }}! 6 | This document is localized automatically under the `auto` mode. 7 | -------------------------------------------------------------------------------- /example/_posts/2018-10-24-front-matter-category.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | category: category 4 | --- 5 | 6 | {{ locale.welcome }}! 7 | This document is meant to showcase the workings of the `jekyll-locale` plugin. 8 | -------------------------------------------------------------------------------- /example/_posts/2018/10/15/2018-10-15-nested-post.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | --- 4 | 5 | {{ locale.welcome }}! 6 | This document is meant to showcase the workings of the `jekyll-locale` plugin. 7 | -------------------------------------------------------------------------------- /example/_puppies/rover.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | --- 4 | 5 | {{ locale.welcome }}! 6 | This site is meant to showcase the workings of the `jekyll-locale` plugin. 7 | -------------------------------------------------------------------------------- /example/_sass/base.scss: -------------------------------------------------------------------------------- 1 | * { box-sizing: border-box } 2 | hr { margin: 1.25em 0; border-color: #fafafa; } 3 | ul { margin: 0; padding: 0 } 4 | li { list-style: none } 5 | body { 6 | font: $base-font-weight #{$base-font-size}/#{$base-line-height} $base-font-family; 7 | font-feature-settings: "kern" 1; 8 | font-kerning: normal; 9 | color: $text-color; 10 | background-color: $background-color; 11 | } 12 | code { 13 | color: #ce2d5d; 14 | padding: 2px 5px; 15 | background-color: darken($background-color, 3%) 16 | } 17 | 18 | #content { 19 | margin: 0 auto; 20 | padding: 15px 10px; 21 | max-width: 800px; 22 | } 23 | 24 | #locale-info { 25 | table { 26 | margin: 15px 0; 27 | padding: 5px 10px; 28 | background-color: darken($background-color, 3%); 29 | border-spacing: 0; 30 | tr { 31 | td:last-of-type { 32 | padding-left: 10px; 33 | font-weight: bold; 34 | } 35 | } 36 | } 37 | } 38 | 39 | #navbar { 40 | &:after { 41 | display: table; 42 | clear: both; 43 | content: "" 44 | } 45 | ul { 46 | display: table; 47 | margin: 0 auto; 48 | background-color: #f9f9f9; 49 | border: 1px solid #ededed; 50 | 51 | li { 52 | float: left; 53 | border: 1px solid transparent; 54 | border-right-color: #ededed; 55 | border-left-color: #fcfcfc; 56 | &:hover { 57 | border-right-color: #eaeaea; 58 | } 59 | &:last-of-type { 60 | border-right-color: transparent; 61 | } 62 | 63 | a { 64 | display: block; 65 | padding: 5px 15px; 66 | text-decoration: none; 67 | background-color: #f9f9f9; 68 | &:hover { 69 | background-color: #fcfcfc; 70 | } 71 | } 72 | } 73 | } 74 | } 75 | 76 | #xtra-shots { color: lighten($text-color, 45%) } 77 | 78 | ul#locale-list { 79 | display: inline-block; 80 | margin: 0 5px; 81 | vertical-align: middle; 82 | li { 83 | float: left; 84 | a { 85 | display: block; 86 | margin: 0 5px; 87 | padding: 4px 8px; 88 | min-width: 35px; 89 | min-height: 35px; 90 | font-weight: 500; 91 | text-align: center; 92 | text-decoration: none; 93 | border: 1px solid 94 | } 95 | } 96 | } 97 | 98 | #notes { 99 | ul { 100 | margin: 0 15px 15px; 101 | padding-left: 15px; 102 | li { 103 | list-style: initial 104 | } 105 | } 106 | } 107 | 108 | #date { 109 | max-width: 600px; 110 | margin: 45px auto; 111 | font-weight: 700; 112 | text-align: center; 113 | } 114 | -------------------------------------------------------------------------------- /example/about.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | --- 4 | 5 | {{ locale.welcome }}! 6 | This page is meant to showcase the workings of the `jekyll-locale` plugin. 7 | -------------------------------------------------------------------------------- /example/category/_posts/2018-10-24-superdir-category.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | --- 4 | 5 | {{ locale.welcome }}! 6 | This document is meant to showcase the workings of the `jekyll-locale` plugin. 7 | -------------------------------------------------------------------------------- /example/css/main.scss: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | // Define defaults for each variable. 5 | 6 | $base-font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol" !default; 7 | $base-font-size: 16px !default; 8 | $base-font-weight: 400 !default; 9 | $small-font-size: $base-font-size * 0.875 !default; 10 | $base-line-height: 1.5 !default; 11 | 12 | $spacing-unit: 30px !default; 13 | 14 | $text-color: #111 !default; 15 | $background-color: #fdfdfd !default; 16 | $brand-color: #2a7ae2 !default; 17 | 18 | $grey-color: #828282 !default; 19 | $grey-color-light: lighten($grey-color, 40%) !default; 20 | $grey-color-dark: darken($grey-color, 25%) !default; 21 | 22 | @import "base"; 23 | -------------------------------------------------------------------------------- /example/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | --- 4 | 5 | {{ locale.welcome }}! 6 | This page is meant to showcase the workings of the `jekyll-locale` plugin. 7 | -------------------------------------------------------------------------------- /jekyll-locale.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | lib = File.expand_path("lib", __dir__) 4 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 5 | require "jekyll/locale/version" 6 | 7 | Gem::Specification.new do |spec| 8 | spec.name = "jekyll-locale" 9 | spec.version = Jekyll::Locale::VERSION 10 | spec.authors = ["Ashwin Maroli"] 11 | spec.email = ["ashmaroli@gmail.com"] 12 | 13 | spec.summary = "A localization plugin for Jekyll" 14 | spec.homepage = "https://github.com/ashmaroli/jekyll-locale" 15 | spec.license = "MIT" 16 | 17 | spec.metadata = { "allowed_push_host" => "https://rubygems.org" } 18 | 19 | spec.files = `git ls-files -z`.split("\x0").select do |f| 20 | f.match(%r!^(lib/|(LICENSE|README)((\.(txt|md|markdown)|$)))!i) 21 | end 22 | 23 | spec.require_paths = ["lib"] 24 | spec.required_ruby_version = ">= 2.3.0" 25 | 26 | spec.add_runtime_dependency "jekyll", ">= 3.8", "< 5.0" 27 | end 28 | -------------------------------------------------------------------------------- /lib/jekyll-locale.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jekyll 4 | module Locale 5 | autoload :Drop, "jekyll/locale/drop" 6 | autoload :Document, "jekyll/locale/document" 7 | autoload :AutoPage, "jekyll/locale/auto_page" 8 | autoload :Page, "jekyll/locale/page" 9 | autoload :Handler, "jekyll/locale/handler" 10 | autoload :Identity, "jekyll/locale/identity" 11 | end 12 | end 13 | 14 | require_relative "jekyll/patches/site" 15 | require_relative "jekyll/patches/utils" 16 | 17 | require_relative "jekyll/locale/date_time_handler" 18 | require_relative "jekyll/locale/filters" 19 | 20 | require_relative "jekyll/locale/mixins/support" 21 | require_relative "jekyll/locale/mixins/helper" 22 | 23 | # Enhance Jekyll::Page and Jekyll::Document classes 24 | [Jekyll::Page, Jekyll::Document].each do |klass| 25 | klass.include Jekyll::Locale::Support 26 | end 27 | 28 | Jekyll::Hooks.register :site, :after_reset do |site| 29 | handler = site.locale_handler 30 | handler.reset 31 | I18n.config.enforce_available_locales = false 32 | end 33 | 34 | Jekyll::Hooks.register :site, :post_read do |site| 35 | handler = site.locale_handler 36 | handler.setup 37 | handler.read 38 | end 39 | 40 | Jekyll::Hooks.register [:pages, :documents], :pre_render do |document, payload| 41 | handler = document.site.locale_handler 42 | handler.current_locale = document.locale 43 | document.setup_hreflangs if document.setup_hreflangs? 44 | payload["page"]["locale"] = document.locale || handler.default_locale 45 | payload["page"]["hreflangs"] = document.hreflangs 46 | payload["page"]["locale_siblings"] = document.locale_siblings 47 | end 48 | -------------------------------------------------------------------------------- /lib/jekyll/locale/auto_page.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jekyll 4 | class Locale::AutoPage < Page 5 | include Locale::Helper 6 | 7 | attr_reader :path 8 | attr_accessor :data, :content, :output 9 | 10 | def initialize(canon, locale) 11 | setup(canon, locale) 12 | @path = canon.path 13 | @content = canon.content 14 | @data = canon.data 15 | @name = File.basename(@path) 16 | @relative_path = canon.relative_path 17 | process(@name) 18 | end 19 | 20 | def url 21 | @url ||= File.join("", locale.id, canon.url) 22 | end 23 | 24 | def to_liquid 25 | @to_liquid ||= configure_payload(canon.to_liquid) 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/jekyll/locale/date_time_handler.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jekyll 4 | module Locale::DateTimeHandler 5 | DATETIME_DEFAULTS = { 6 | :date => { 7 | :day_names => Date::DAYNAMES, 8 | :month_names => Date::MONTHNAMES, 9 | :abbr_day_names => Date::ABBR_DAYNAMES, 10 | :abbr_month_names => Date::ABBR_MONTHNAMES, 11 | }, 12 | :time => { 13 | :am => "am", 14 | :pm => "pm", 15 | :formats => { 16 | :default => "%B %d, %Y %l:%M:%S %p %z", 17 | }, 18 | }, 19 | }.freeze 20 | 21 | class << self 22 | extend Forwardable 23 | [:config, :backend].each do |method| 24 | private def_delegator I18n, method 25 | end 26 | 27 | def bootstrap(handler) 28 | @handler = handler 29 | config.available_locales = @handler.available_locales 30 | end 31 | 32 | def localize(input, format) 33 | object = date_cache(input) 34 | locale = @handler.current_locale.id.to_sym 35 | data = @handler.locale_dates[locale.to_s] || {} 36 | store_translations(locale, data) unless translations.key?(locale) 37 | backend.localize(locale, object, format) 38 | end 39 | 40 | def store_translations(locale, data) 41 | backend.store_translations( 42 | locale, 43 | Utils.deep_merge_hashes( 44 | DATETIME_DEFAULTS, Utils.recursive_symbolize_hash_keys(data) 45 | ) 46 | ) 47 | end 48 | 49 | private 50 | 51 | def translations 52 | backend.send(:translations) 53 | end 54 | 55 | def date_cache(input) 56 | @date_cache ||= {} 57 | @date_cache[input] ||= Liquid::Utils.to_date(input) 58 | end 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /lib/jekyll/locale/document.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jekyll 4 | class Locale::Document < Document 5 | attr_reader :type 6 | include Locale::Helper 7 | 8 | def initialize(canon, locale) 9 | setup(canon, locale) 10 | @collection = canon.collection 11 | @extname = File.extname(relative_path) 12 | @has_yaml_header = nil 13 | @type = @collection.label.to_sym 14 | read 15 | 16 | special_dir = draft? ? "_drafts" : @collection.relative_directory 17 | categories_from_path(special_dir) 18 | 19 | configure_data 20 | end 21 | 22 | def cleaned_relative_path 23 | @cleaned_relative_path ||= begin 24 | rel_path = relative_path[0..-extname.length - 1] 25 | rel_path.sub!(@locale_page_dir, "") 26 | rel_path.sub!(collection.relative_directory, "") 27 | rel_path.gsub!(%r!\.*\z!, "") 28 | rel_path 29 | end 30 | end 31 | 32 | def url_template 33 | @url_template ||= File.join("", locale.id, super) 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/jekyll/locale/drop.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jekyll 4 | class Locale::Drop < Drops::Drop 5 | extend Forwardable 6 | 7 | mutable false 8 | private def_delegator :@obj, :data, :fallback_data 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/jekyll/locale/filters.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jekyll 4 | module Locale 5 | module Filters 6 | def localize_date(input, format = :default) 7 | format = symbol_or_strftime(format) 8 | DateTimeHandler.localize(time(input), format) 9 | end 10 | 11 | def prefix_locale(input) 12 | return input unless input.is_a?(String) 13 | return input if Addressable::URI.parse(input).absolute? 14 | 15 | handler = @context.registers[:site].locale_handler 16 | page_locale = @context.registers[:page]["locale"] 17 | page_locale = "" if page_locale.id == handler.default_locale.id 18 | File.join("/", page_locale.to_s, input).squeeze("/") 19 | end 20 | 21 | private 22 | 23 | def symbol_or_strftime(format) 24 | return format if format.is_a?(Symbol) 25 | 26 | format = format.to_s 27 | if format.start_with?(":") 28 | format.sub(":", "").to_sym 29 | elsif format.start_with?("%") 30 | format 31 | else 32 | :default 33 | end 34 | end 35 | end 36 | end 37 | end 38 | 39 | Liquid::Template.register_filter(Jekyll::Locale::Filters) 40 | -------------------------------------------------------------------------------- /lib/jekyll/locale/handler.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jekyll 4 | class Locale::Handler 5 | attr_reader :default_locale, :available_locales, :locale_dates, :content_dirname, 6 | :user_locales 7 | attr_writer :current_locale 8 | 9 | DEFAULT_CONFIG = { 10 | "mode" => "manual", 11 | "locale" => "en-US", 12 | "data_dir" => "locales", 13 | "content_dir" => "_locales", 14 | "locales_set" => [], 15 | "exclude_set" => [], 16 | }.freeze 17 | 18 | def initialize(site) 19 | @site = site 20 | config = site.config["localization"] 21 | @config = if config.is_a?(Hash) 22 | Jekyll::Utils.deep_merge_hashes(DEFAULT_CONFIG, config) 23 | else 24 | DEFAULT_CONFIG 25 | end 26 | initialize_locales 27 | @content_dirname = mode == "auto" ? "" : fetch("content_dir") 28 | @snakeified_keys = {} 29 | end 30 | 31 | def reset 32 | @locale_data = {} 33 | @locale_dates = {} 34 | @portfolio = nil 35 | end 36 | 37 | def setup 38 | Locale::DateTimeHandler.bootstrap(self) 39 | setup_data if @locale_data.empty? 40 | end 41 | 42 | def data 43 | locale_data[snakeified_keys(current_locale)] || 44 | locale_data[snakeified_keys(default_locale)] || {} 45 | end 46 | 47 | def read 48 | mode == "auto" ? auto_localization : manual_localization 49 | end 50 | 51 | def current_locale 52 | @current_locale ||= default_locale 53 | end 54 | 55 | def inspect 56 | "#<#{self.class} @site=#{site}>" 57 | end 58 | 59 | private 60 | 61 | attr_reader :site, :config, :locale_data 62 | 63 | def setup_data 64 | locales_dir = fetch("data_dir") 65 | base_data = site.site_data[locales_dir] 66 | return @locale_data unless base_data.is_a?(Hash) 67 | 68 | base_data.each do |locale_id, data| 69 | next unless data.is_a?(Hash) 70 | process_locale_data(locale_id, data) 71 | end 72 | 73 | nil 74 | end 75 | 76 | def initialize_locales 77 | default_locale_id = fetch("locale") 78 | default_metadata = {} 79 | @available_locales = [default_locale_id] 80 | @user_locales = [] 81 | 82 | locales_set_config = @config["locales_set"] 83 | locales_set_config = case locales_set_config 84 | when Array 85 | Hash[locales_set_config.map { |locale| [locale, {}] }] 86 | when Hash 87 | locales_set_config 88 | else 89 | {} 90 | end 91 | 92 | locales_set_config.each do |locale_id, metadata| 93 | if locale_id == default_locale_id 94 | default_metadata = metadata 95 | next 96 | end 97 | 98 | @available_locales << locale_id 99 | @user_locales << Locale::Identity.new(locale_id, metadata) 100 | end 101 | 102 | @default_locale = Locale::Identity.new(default_locale_id, default_metadata) 103 | end 104 | 105 | def process_locale_data(loc, data_hash) 106 | locale = snakeified_keys(loc) 107 | @locale_data[locale] = {} 108 | 109 | date_data = Locale::DateTimeHandler::DATETIME_DEFAULTS 110 | data_hash.each do |key, value| 111 | if key == "locale_date" 112 | date_data = configure_locale_date(date_data, value) 113 | else 114 | @locale_data[locale][snakeified_keys(key)] = value 115 | end 116 | end 117 | @locale_dates[loc] = date_data 118 | end 119 | 120 | def configure_locale_date(date_defaults, data) 121 | return date_defaults unless data.is_a?(Hash) 122 | 123 | Jekyll::Utils.deep_merge_hashes( 124 | date_defaults, Utils.recursive_symbolize_hash_keys(data) 125 | ) 126 | end 127 | 128 | # Instances of Jekyll class that include `Jekyll::Locale::Support` mixin 129 | # (which are simply Jekyll::Page and Jekyll::Document) 130 | def portfolio 131 | @portfolio ||= begin 132 | html_pages = site.site_payload["site"]["html_pages"] || [] 133 | html_pages.reject! { |page| page.name == "404.html" } 134 | 135 | (site.docs_to_write + html_pages).select do |doc| 136 | doc.is_a?(Jekyll::Locale::Support) 137 | end 138 | end 139 | end 140 | 141 | def auto_localization 142 | user_locales.each do |locale| 143 | portfolio.each do |canon_doc| 144 | next if canon_doc.relative_path =~ exclusion_regex 145 | append_page(Locale::AutoPage, canon_doc, locale) 146 | end 147 | end 148 | end 149 | 150 | def manual_localization 151 | user_locales.each do |locale| 152 | portfolio.each do |canon_doc| 153 | loc_page_path = site.in_source_dir(content_dirname, locale.id, canon_doc.relative_path) 154 | next unless File.exist?(loc_page_path) 155 | next unless Jekyll::Utils.has_yaml_header?(loc_page_path) 156 | 157 | case canon_doc 158 | when Jekyll::Page 159 | append_page(Locale::Page, canon_doc, locale) 160 | when Jekyll::Document 161 | append_document(Locale::Document, canon_doc, locale) 162 | end 163 | end 164 | end 165 | end 166 | 167 | def append_page(klass, canon_page, locale) 168 | locale_page = klass.new(canon_page, locale) 169 | return unless locale_page.publish? 170 | 171 | canon_page.locale_pages << locale_page 172 | site.pages << locale_page 173 | site.pages.uniq! 174 | end 175 | 176 | def append_document(klass, canon_doc, locale) 177 | locale_doc = klass.new(canon_doc, locale) 178 | return unless locale_doc.publish? 179 | 180 | canon_doc.locale_pages << locale_doc 181 | canon_doc.collection.docs << locale_doc 182 | site.docs_to_write << locale_doc 183 | site.docs_to_write.uniq! 184 | end 185 | 186 | def snakeified_keys(key) 187 | @snakeified_keys[key] ||= Utils.snakeify(key) 188 | end 189 | 190 | def mode 191 | @mode ||= begin 192 | value = config["mode"] 193 | value == "auto" ? value : DEFAULT_CONFIG["mode"] 194 | end 195 | end 196 | 197 | def fetch(key) 198 | value = config[key] 199 | default = DEFAULT_CONFIG[key] 200 | return default unless value.class == default.class 201 | return default if value.to_s.empty? 202 | 203 | value 204 | end 205 | 206 | def exclusion_regex 207 | @exclusion_regex ||= Regexp.new("\\A(?:#{Regexp.union(Array(config["exclude_set"]))})") 208 | end 209 | end 210 | end 211 | -------------------------------------------------------------------------------- /lib/jekyll/locale/identity.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jekyll 4 | class Locale::Identity 5 | attr_reader :id, :data 6 | alias_method :to_s, :id 7 | 8 | def initialize(locale_id, metadata) 9 | @id = locale_id.to_s 10 | @data = metadata 11 | end 12 | 13 | def to_liquid 14 | @to_liquid ||= data.merge("id" => id) 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/jekyll/locale/mixins/helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jekyll 4 | module Locale::Helper 5 | attr_reader :canon, :relative_path 6 | 7 | def setup_hreflangs? 8 | true 9 | end 10 | 11 | def setup_hreflangs 12 | page_set = [canon] + canon.locale_pages 13 | @hreflangs = sibling_data(page_set) 14 | @locale_siblings = sibling_data(page_set - [self]) 15 | end 16 | 17 | def permalink 18 | canon_link = super 19 | File.join(locale.id, canon_link) if canon_link 20 | end 21 | 22 | def inspect 23 | "#<#{self.class} @canon=#{canon.inspect} @locale=#{locale.inspect}>" 24 | end 25 | alias_method :to_s, :inspect 26 | 27 | private 28 | 29 | def setup(canon, locale) 30 | @locale = locale 31 | @canon = canon 32 | @site = canon.site 33 | @extname = canon.extname 34 | @locale_page_dir = File.join(@site.locale_handler.content_dirname, locale.id, "") 35 | @relative_path = File.join(@locale_page_dir, canon.relative_path) 36 | @path = @site.in_source_dir(@relative_path) 37 | end 38 | 39 | def configure_data 40 | Array(@data["categories"]).delete_if do |category| 41 | category == @site.locale_handler.content_dirname || category == @locale.id 42 | end 43 | 44 | @data = Jekyll::Utils.deep_merge_hashes(canon.data, @data) 45 | @data.default_proc = proc do |_, key| 46 | site.frontmatter_defaults.find(relative_path, type, key) 47 | end 48 | end 49 | 50 | def configure_payload(payload) 51 | payload.to_h.tap do |data| 52 | data["path"] = self.relative_path 53 | data["url"] = self.url 54 | end 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /lib/jekyll/locale/mixins/support.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jekyll 4 | module Locale::Support 5 | attr_reader :site, :locale 6 | 7 | def setup_hreflangs? 8 | false 9 | end 10 | 11 | def locale_siblings 12 | @locale_siblings ||= sibling_data(locale_pages) 13 | end 14 | 15 | def hreflangs 16 | @hreflangs ||= sibling_data([self] + locale_pages) 17 | end 18 | 19 | def locale_pages 20 | @locale_pages ||= [] 21 | end 22 | 23 | def publish? 24 | site.publisher.publish?(self) 25 | end 26 | 27 | private 28 | 29 | def sibling_data(locale_page_set) 30 | locale_page_set.map do |locale_page| 31 | next unless locale_page.publish? 32 | 33 | locale = locale_page.locale || site.locale_handler.default_locale 34 | { 35 | "locale" => locale.to_liquid, 36 | "url" => locale_page.url, 37 | } 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/jekyll/locale/page.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jekyll 4 | class Locale::Page < Page 5 | include Locale::Helper 6 | attr_reader :path 7 | 8 | def initialize(canon, locale) 9 | setup(canon, locale) 10 | @dir, @name = File.split(relative_path) 11 | @base = site.source 12 | process(@name) 13 | read_yaml(@dir, @name) 14 | configure_data 15 | 16 | # Empty the value as it is not longer required. 17 | @dir = "" 18 | end 19 | 20 | def to_liquid 21 | @to_liquid ||= configure_payload(super) 22 | end 23 | 24 | def template 25 | @template ||= File.join("", locale.id, super) 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/jekyll/locale/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jekyll 4 | module Locale 5 | VERSION = "0.5.1" 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/jekyll/patches/site.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jekyll 4 | class Drops::UnifiedPayloadDrop 5 | def locale 6 | @locale ||= Locale::Drop.new(@obj.locale_handler) 7 | end 8 | end 9 | 10 | class Site 11 | def locale_handler 12 | @locale_handler ||= Locale::Handler.new(self) 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/jekyll/patches/utils.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jekyll 4 | module Utils 5 | extend self 6 | 7 | def snakeify(input) 8 | slug = slugify(input.to_s, :mode => "latin", :cased => true) 9 | slug.tr!("-", "_") 10 | slug 11 | end 12 | 13 | def recursive_symbolize_hash_keys(hash) 14 | result = {} 15 | hash.each do |key, value| 16 | new_key = key.to_s.to_sym 17 | result[new_key] = value.is_a?(Hash) ? recursive_symbolize_hash_keys(value) : value 18 | end 19 | result 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/fixtures/_config.yml: -------------------------------------------------------------------------------- 1 | title: Localization Example 2 | collections: 3 | puppies: 4 | output: true 5 | plugins: ["jekyll-locale"] 6 | 7 | defaults: 8 | - scope: 9 | path: about.md 10 | values: 11 | layout: none 12 | meta: about_site 13 | - scope: 14 | path: _posts 15 | values: 16 | layout: none 17 | meta: post_content 18 | - scope: 19 | type: posts 20 | values: 21 | layout: none 22 | meta: locale_post 23 | - scope: 24 | path: _locales/en/about.md 25 | values: 26 | layout: none 27 | meta: about_locale 28 | -------------------------------------------------------------------------------- /spec/fixtures/_data/locales/en.yml: -------------------------------------------------------------------------------- 1 | welcome: Hello 2 | -------------------------------------------------------------------------------- /spec/fixtures/_data/locales/fr.yml: -------------------------------------------------------------------------------- 1 | welcome: Bonjour 2 | locale_date: 3 | date: 4 | abbr_day_names: 5 | - dim 6 | - lun 7 | - mar 8 | - mer 9 | - jeu 10 | - ven 11 | - sam 12 | abbr_month_names: 13 | - ~ 14 | - jan. 15 | - fév. 16 | - mar. 17 | - avr. 18 | - mai 19 | - juin 20 | - juil. 21 | - août 22 | - sept. 23 | - oct. 24 | - nov. 25 | - déc. 26 | day_names: 27 | - dimanche 28 | - lundi 29 | - mardi 30 | - mercredi 31 | - jeudi 32 | - vendredi 33 | - samedi 34 | month_names: 35 | - ~ 36 | - janvier 37 | - février 38 | - mars 39 | - avril 40 | - mai 41 | - juin 42 | - juillet 43 | - août 44 | - septembre 45 | - octobre 46 | - novembre 47 | - décembre 48 | time: 49 | formats: 50 | default: "%d %B %Y %H h %M min %S s" 51 | short: "%d %B %Y" 52 | -------------------------------------------------------------------------------- /spec/fixtures/_posts/2018-10-15-hello-world.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | {{ locale.welcome }}! 5 | Canonical Content. 6 | -------------------------------------------------------------------------------- /spec/fixtures/_posts/2018-10-18-english-only.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | {{ locale.welcome }}! 5 | Canonical Content. 6 | -------------------------------------------------------------------------------- /spec/fixtures/_posts/2018-10-24-auto-localized.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | {{ locale.welcome }}! 5 | Canonical Content. 6 | -------------------------------------------------------------------------------- /spec/fixtures/_posts/2018-10-24-front-matter-category.md: -------------------------------------------------------------------------------- 1 | --- 2 | category: category 3 | --- 4 | 5 | {{ locale.welcome }}! 6 | Canonical Content. 7 | -------------------------------------------------------------------------------- /spec/fixtures/_posts/2018/10/15/2018-10-15-nested-post.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | {{ locale.welcome }}! 5 | Canonical Content. 6 | -------------------------------------------------------------------------------- /spec/fixtures/_puppies/rover.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | {{ locale.welcome }}! 5 | Canonical Content. 6 | -------------------------------------------------------------------------------- /spec/fixtures/about.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | {{ locale.welcome }}! 5 | Canonical Content. 6 | -------------------------------------------------------------------------------- /spec/fixtures/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | {{ locale.welcome }}! 5 | Canonical Content. 6 | -------------------------------------------------------------------------------- /spec/jekyll-locale/auto_page_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe Jekyll::Locale::AutoPage do 4 | let(:page_name) { "about.md" } 5 | let(:locale_id) { "en" } 6 | let(:metadata) { {} } 7 | let(:locale) { Jekyll::Locale::Identity.new(locale_id, metadata) } 8 | let(:locales_set) { %w(en fr ja) } 9 | let(:config) do 10 | { 11 | "title" => "Localization Test", 12 | "localization" => { 13 | "mode" => "auto", 14 | "locale" => locale_id, 15 | "locales_set" => locales_set, 16 | }, 17 | } 18 | end 19 | let(:site) { make_site(config) } 20 | let(:canon) { make_canon_page(site, page_name) } 21 | 22 | subject { described_class.new(canon, locale) } 23 | 24 | before do 25 | make_page_file("_locales/#{locale}/#{page_name}", :content => "Hello World") 26 | make_page_file("_locales/fr/#{page_name}", :content => "Hello World") 27 | make_page_file("_locales/ja/#{page_name}", :content => "Hello World") 28 | end 29 | 30 | after do 31 | content_dir = source_dir("_locales") 32 | FileUtils.rm_rf(content_dir) if File.directory?(content_dir) 33 | end 34 | 35 | it "returns the custom inspect string" do 36 | expect(subject.inspect).to eql( 37 | "#" 38 | ) 39 | end 40 | 41 | it "returns the 'locale identity' object" do 42 | expect(subject.locale).to eql(locale) 43 | expect(subject.locale.id).to eql("en") 44 | end 45 | 46 | it "matches the 'relative_path' attribute with its canonical page" do 47 | expect(subject.relative_path).to eql(canon.relative_path) 48 | end 49 | 50 | it "matches the 'url' attribute with its canonical page by default" do 51 | expect(subject.url).to eql(File.join("", "en", canon.url)) 52 | end 53 | 54 | it "returns a hash for Liquid templates" do 55 | subject.content ||= "Random content" 56 | expect(subject.to_liquid.class.name).to eql("Hash") 57 | expect(subject.to_liquid).to eql( 58 | "content" => "{{ locale.welcome }}!\nCanonical Content.\n", 59 | "dir" => "/", 60 | "layout" => "none", 61 | "meta" => "about_site", 62 | "name" => "about.md", 63 | "path" => "about.md", 64 | "url" => "/en/about.html" 65 | ) 66 | end 67 | 68 | it "returns hreflang and locale_sibling data for Liquid templates" do 69 | site.process 70 | 71 | hreflangs = [ 72 | { "locale" => { "id" => "en" }, "url" => "/about.html" }, 73 | { "locale" => { "id" => "fr" }, "url" => "/fr/about.html" }, 74 | { "locale" => { "id" => "ja" }, "url" => "/ja/about.html" }, 75 | ] 76 | canon = site.pages.find { |p| p.is_a?(Jekyll::Page) && p.url == "/about.html" } 77 | expect(canon.hreflangs).to eql(hreflangs) 78 | expect(canon.locale_siblings).to eql( 79 | [ 80 | { "locale" => { "id" => "fr" }, "url" => "/fr/about.html" }, 81 | { "locale" => { "id" => "ja" }, "url" => "/ja/about.html" }, 82 | ] 83 | ) 84 | 85 | auto_page = site.pages.find { |p| p.is_a?(described_class) && p.url == "/fr/about.html" } 86 | expect(auto_page.hreflangs).to eql(hreflangs) 87 | expect(auto_page.locale_siblings).to eql( 88 | [ 89 | { "locale" => { "id" => "en" }, "url" => "/about.html" }, 90 | { "locale" => { "id" => "ja" }, "url" => "/ja/about.html" }, 91 | ] 92 | ) 93 | end 94 | 95 | context "with locale metadata via locales_set configuration" do 96 | let(:locales_set) do 97 | { 98 | "fr" => { "label" => "Français" }, 99 | "en" => { "label" => "English" }, 100 | "ja" => { "label" => "日本語" }, 101 | } 102 | end 103 | 104 | it "returns hreflang and locale_sibling data for Liquid templates" do 105 | site.process 106 | 107 | hreflangs = [ 108 | { "locale" => { "id" => "en", "label" => "English" }, "url" => "/about.html" }, 109 | { "locale" => { "id" => "fr", "label" => "Français" }, "url" => "/fr/about.html" }, 110 | { "locale" => { "id" => "ja", "label" => "日本語" }, "url" => "/ja/about.html" }, 111 | ] 112 | canon = site.pages.find { |p| p.is_a?(Jekyll::Page) && p.url == "/about.html" } 113 | expect(canon.hreflangs).to eql(hreflangs) 114 | expect(canon.locale_siblings).to eql( 115 | [ 116 | { "locale" => { "id" => "fr", "label" => "Français" }, "url" => "/fr/about.html" }, 117 | { "locale" => { "id" => "ja", "label" => "日本語" }, "url" => "/ja/about.html" }, 118 | ] 119 | ) 120 | 121 | auto_page = site.pages.find { |p| p.is_a?(described_class) && p.url == "/fr/about.html" } 122 | expect(auto_page.hreflangs).to eql(hreflangs) 123 | expect(auto_page.locale_siblings).to eql( 124 | [ 125 | { "locale" => { "id" => "en", "label" => "English" }, "url" => "/about.html" }, 126 | { "locale" => { "id" => "ja", "label" => "日本語" }, "url" => "/ja/about.html" }, 127 | ] 128 | ) 129 | end 130 | end 131 | end 132 | -------------------------------------------------------------------------------- /spec/jekyll-locale/document_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe Jekyll::Locale::Document do 4 | let(:collection) { "posts" } 5 | let(:doc_name) { "2018-10-15-hello-world.md" } 6 | let(:locale_id) { "en" } 7 | let(:metadata) { {} } 8 | let(:locale) { Jekyll::Locale::Identity.new(locale_id, metadata) } 9 | let(:locales_set) { %w(fr en ja) } 10 | let(:config) do 11 | { 12 | "title" => "Localization Test", 13 | "localization" => { 14 | "locale" => locale_id, 15 | "locales_set" => locales_set, 16 | }, 17 | } 18 | end 19 | let(:site) { make_site(config) } 20 | let(:canon) { make_canon_document(site, doc_name, collection) } 21 | 22 | subject { described_class.new(canon, locale) } 23 | 24 | before do 25 | make_page_file("_locales/#{locale}/_posts/#{doc_name}", :content => "Hello World") 26 | make_page_file("_locales/fr/_posts/#{doc_name}", :content => "Hello World") 27 | make_page_file("_locales/ja/_posts/#{doc_name}", :content => "Hello World") 28 | end 29 | 30 | after do 31 | content_dir = source_dir("_locales") 32 | FileUtils.rm_rf(content_dir) if File.directory?(content_dir) 33 | end 34 | 35 | it "returns the custom inspect string" do 36 | expect(subject.inspect).to eql( 37 | "#" 38 | ) 39 | end 40 | 41 | it "returns the 'locale identity' object" do 42 | expect(subject.locale).to eql(locale) 43 | expect(subject.locale.id).to eql("en") 44 | end 45 | 46 | it "equals the 'cleaned_relative_path' attribute with its canonical document" do 47 | expect(subject.cleaned_relative_path).to eql(canon.cleaned_relative_path) 48 | end 49 | 50 | it "matches the 'relative_path' attribute with its canonical document" do 51 | expect(subject.relative_path).to eql(File.join("_locales", "en", canon.relative_path)) 52 | end 53 | 54 | it "matches the 'url' attribute with its canonical document by default" do 55 | expect(subject.url).to eql(File.join("", "en", canon.url)) 56 | end 57 | 58 | it "returns drop data for Liquid templates" do 59 | subject.content ||= "Random content" 60 | expect(subject.to_liquid.class.name).to eql("Jekyll::Drops::DocumentDrop") 61 | 62 | drop_data = JSON.parse(subject.to_liquid.to_json) 63 | drop_data.delete("date") 64 | expect(drop_data).to eql( 65 | "categories" => [], 66 | "collection" => "posts", 67 | "content" => "Hello World\n{{ page.url }}\n\n{{ 'now' | localize_date }}\n", 68 | "draft" => false, 69 | "excerpt" => "

Hello World\n/en/2018/10/15/hello-world.html

\n\n", 70 | "ext" => ".md", 71 | "foo" => "bar", 72 | "id" => "/en/2018/10/15/hello-world", 73 | "layout" => "none", 74 | "meta" => "locale_post", 75 | "next" => nil, 76 | "output" => nil, 77 | "path" => "_locales/en/_posts/2018-10-15-hello-world.md", 78 | "previous" => nil, 79 | "relative_path" => "_locales/en/_posts/2018-10-15-hello-world.md", 80 | "slug" => "hello-world", 81 | "tags" => [], 82 | "title" => "Hello World", 83 | "url" => "/en/2018/10/15/hello-world.html" 84 | ) 85 | end 86 | 87 | it "returns hreflang and locale_sibling data for Liquid templates" do 88 | site.process 89 | 90 | hreflangs = [ 91 | { "locale" => { "id" => "en" }, "url" => "/2018/10/15/hello-world.html" }, 92 | { "locale" => { "id" => "fr" }, "url" => "/fr/2018/10/15/hello-world.html" }, 93 | { "locale" => { "id" => "ja" }, "url" => "/ja/2018/10/15/hello-world.html" }, 94 | ] 95 | canon = site.documents.find do |doc| 96 | doc.is_a?(Jekyll::Document) && doc.url == "/2018/10/15/hello-world.html" 97 | end 98 | expect(canon.hreflangs).to eql(hreflangs) 99 | expect(canon.locale_siblings).to eql( 100 | [ 101 | { "locale" => { "id" => "fr" }, "url" => "/fr/2018/10/15/hello-world.html" }, 102 | { "locale" => { "id" => "ja" }, "url" => "/ja/2018/10/15/hello-world.html" }, 103 | ] 104 | ) 105 | 106 | locale_post = site.documents.find do |doc| 107 | doc.is_a?(described_class) && doc.url == "/fr/2018/10/15/hello-world.html" 108 | end 109 | expect(locale_post.hreflangs).to eql(hreflangs) 110 | expect(locale_post.locale_siblings).to eql( 111 | [ 112 | { "locale" => { "id" => "en" }, "url" => "/2018/10/15/hello-world.html" }, 113 | { "locale" => { "id" => "ja" }, "url" => "/ja/2018/10/15/hello-world.html" }, 114 | ] 115 | ) 116 | end 117 | 118 | # rubocop:disable Metrics/LineLength 119 | context "with locale metadata via locales_set configuration" do 120 | let(:locales_set) do 121 | { 122 | "fr" => { "label" => "Français" }, 123 | "en" => { "label" => "English" }, 124 | "ja" => { "label" => "日本語" }, 125 | } 126 | end 127 | 128 | it "returns hreflang and locale_sibling data for Liquid templates" do 129 | site.process 130 | 131 | hreflangs = [ 132 | { "locale" => { "id" => "en", "label" => "English" }, "url" => "/2018/10/15/hello-world.html" }, 133 | { "locale" => { "id" => "fr", "label" => "Français" }, "url" => "/fr/2018/10/15/hello-world.html" }, 134 | { "locale" => { "id" => "ja", "label" => "日本語" }, "url" => "/ja/2018/10/15/hello-world.html" }, 135 | ] 136 | canon = site.documents.find do |doc| 137 | doc.is_a?(Jekyll::Document) && doc.url == "/2018/10/15/hello-world.html" 138 | end 139 | expect(canon.hreflangs).to eql(hreflangs) 140 | expect(canon.locale_siblings).to eql( 141 | [ 142 | { "locale" => { "id" => "fr", "label" => "Français" }, "url" => "/fr/2018/10/15/hello-world.html" }, 143 | { "locale" => { "id" => "ja", "label" => "日本語" }, "url" => "/ja/2018/10/15/hello-world.html" }, 144 | ] 145 | ) 146 | 147 | locale_post = site.documents.find do |doc| 148 | doc.is_a?(described_class) && doc.url == "/fr/2018/10/15/hello-world.html" 149 | end 150 | expect(locale_post.hreflangs).to eql(hreflangs) 151 | expect(locale_post.locale_siblings).to eql( 152 | [ 153 | { "locale" => { "id" => "en", "label" => "English" }, "url" => "/2018/10/15/hello-world.html" }, 154 | { "locale" => { "id" => "ja", "label" => "日本語" }, "url" => "/ja/2018/10/15/hello-world.html" }, 155 | ] 156 | ) 157 | end 158 | end 159 | # rubocop:enable Metrics/LineLength 160 | end 161 | -------------------------------------------------------------------------------- /spec/jekyll-locale/filters_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe Jekyll::Locale::Filters do 4 | class FilterMock 5 | def initialize(context) 6 | @context = context 7 | end 8 | end 9 | 10 | FilterMock.include(Liquid::StandardFilters) 11 | FilterMock.include(Jekyll::Filters) 12 | FilterMock.include(described_class) 13 | 14 | let(:locale_id) { "fr" } 15 | let(:metadata) { {} } 16 | let(:locale) { Jekyll::Locale::Identity.new(locale_id, metadata) } 17 | let(:config) do 18 | { 19 | "title" => "Localization Test", 20 | "timezone" => "America/New_York", 21 | "localization" => { "locale" => "fr" }, 22 | } 23 | end 24 | let(:site) { make_site(config) } 25 | let(:page) { { "locale" => locale, "title" => "Hello World" } } 26 | let(:context) { Liquid::Context.new({}, {}, :site => site, :page => page) } 27 | 28 | subject { FilterMock.new(context) } 29 | 30 | before do 31 | site.read 32 | site.locale_handler.setup 33 | end 34 | 35 | context "with the 'localize_date' filter" do 36 | let(:input) { "2018-11-18T22:10:35-0500" } 37 | 38 | it "returns the 'default' localized date string" do 39 | expect(subject.localize_date(input)).to eql("18 novembre 2018 22 h 10 min 35 s") 40 | end 41 | 42 | it "returns the 'short' format of localized date string" do 43 | expect(subject.localize_date(input, ":short")).to eql("18 novembre 2018") 44 | end 45 | 46 | it "parses the strftime notation and returns the localized date string" do 47 | expect(subject.localize_date(input, "%Y %B %d")).to eql("2018 novembre 18") 48 | end 49 | 50 | it "uses the 'default' format with invalid format parameter" do 51 | expect(subject.localize_date(input, "short")).to eql("18 novembre 2018 22 h 10 min 35 s") 52 | end 53 | end 54 | 55 | context "with the 'prefix_locale' filter" do 56 | it "returns the input if it is not a String instance" do 57 | expect(subject.prefix_locale(nil)).to eql(nil) 58 | expect(subject.prefix_locale(site)).to eql(site) 59 | expect(subject.prefix_locale(page)).to eql(page) 60 | end 61 | 62 | it "ensures a single leading slash" do 63 | expect(subject.prefix_locale("about.html")).to eql("/about.html") 64 | expect(subject.prefix_locale("///about//")).to eql("/about/") 65 | end 66 | 67 | it "returns input if it is an absolute uri" do 68 | absolute_url = "https://www.example.com/about/" 69 | expect(subject.prefix_locale(absolute_url)).to eql(absolute_url) 70 | end 71 | 72 | context "with a non-default page locale" do 73 | let(:locale_id) { "ja" } 74 | 75 | it "returns the input if it is not a String instance" do 76 | expect(subject.prefix_locale(nil)).to eql(nil) 77 | expect(subject.prefix_locale(site)).to eql(site) 78 | expect(subject.prefix_locale(page)).to eql(page) 79 | end 80 | 81 | it "ensures a single leading slash" do 82 | expect(subject.prefix_locale("about.html")).to eql("/ja/about.html") 83 | expect(subject.prefix_locale("///about//")).to eql("/ja/about/") 84 | end 85 | 86 | it "returns input if it is an absolute uri" do 87 | absolute_url = "https://www.example.com/about/" 88 | expect(subject.prefix_locale(absolute_url)).to eql(absolute_url) 89 | end 90 | end 91 | end 92 | end 93 | -------------------------------------------------------------------------------- /spec/jekyll-locale/handler_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe Jekyll::Locale::Handler do 4 | let(:config) { { "title" => "Localization Test" } } 5 | let(:site) { make_site(config) } 6 | let(:locale_id) { "en-US" } 7 | let(:metadata) { {} } 8 | let(:locale) { Jekyll::Locale::Identity.new(locale_id, metadata) } 9 | 10 | subject { described_class.new(site) } 11 | 12 | before do 13 | site.process 14 | end 15 | 16 | it "returns the custom inspect string" do 17 | expect(subject.inspect).to eql("#") 18 | end 19 | 20 | it "returns the default 'locale identity' object" do 21 | expect(subject.default_locale.class).to eql(locale.class) 22 | expect(subject.default_locale.id).to eql(locale.id) 23 | expect(subject.default_locale.id).to eql("en-US") 24 | end 25 | 26 | it "returns the default 'locale identity' object as the current locale" do 27 | expect(subject.current_locale.class).to eql(locale.class) 28 | expect(subject.current_locale.id).to eql(locale.id) 29 | expect(subject.current_locale.id).to eql("en-US") 30 | end 31 | 32 | it "returns an array of available locale ids" do 33 | expect(subject.available_locales).to eql(["en-US"]) 34 | end 35 | 36 | it "runs in 'manual' mode by default" do 37 | expect(subject.content_dirname).to eql("_locales") 38 | end 39 | 40 | context "in 'auto' mode" do 41 | let(:locale_id) { "en" } 42 | let(:metadata) { { "label" => "English" } } 43 | let(:config) do 44 | { 45 | "localization" => { 46 | "mode" => "auto", 47 | "locale" => locale_id, 48 | "locales_set" => %w(fr en), 49 | }, 50 | } 51 | end 52 | 53 | it "returns the default 'locale identity' object" do 54 | expect(subject.default_locale.class).to eql(locale.class) 55 | expect(subject.default_locale.id).to eql("en") 56 | end 57 | 58 | it "returns an array of available locale ids" do 59 | expect(subject.available_locales).to eql(%w(en fr)) 60 | end 61 | 62 | it "returns an empty content dirname" do 63 | expect(subject.content_dirname).to eql("") 64 | end 65 | end 66 | 67 | context "in 'manual' mode" do 68 | let(:locale_id) { "fr" } 69 | let(:metadata) { { "label" => "Français" } } 70 | let(:config) do 71 | { 72 | "localization" => { 73 | "mode" => "manual", 74 | "locale" => locale_id, 75 | "locales_set" => %w(en fr), 76 | "content_dirname" => "_locales", 77 | }, 78 | } 79 | end 80 | 81 | after do 82 | content_dir = source_dir("_locales") 83 | FileUtils.rm_rf(content_dir) if File.directory?(content_dir) 84 | end 85 | 86 | it "returns the default 'locale identity' object" do 87 | expect(subject.default_locale.class).to eql(locale.class) 88 | expect(subject.default_locale.id).to eql("fr") 89 | end 90 | 91 | it "returns an array of available locale ids" do 92 | expect(subject.available_locales).to eql(%w(fr en)) 93 | end 94 | 95 | it "returns the configured content dirname" do 96 | expect(subject.content_dirname).to eql("_locales") 97 | end 98 | 99 | it "generates localized content 'lazily'" do 100 | expect(Pathname.new(dest_dir("index.html"))).to exist 101 | expect(Pathname.new(dest_dir("2018/10/15/hello-world.html"))).to exist 102 | 103 | expect(Pathname.new(dest_dir("en/index.html"))).to_not exist 104 | expect(Pathname.new(dest_dir("en/2018/10/15/hello-world.html"))).to_not exist 105 | 106 | make_page_file("_locales/en/index.md", :content => "Hello World") 107 | make_page_file("_locales/en/_posts/2018-10-15-hello-world.md", :content => "Hello World") 108 | site.process 109 | 110 | expect(Pathname.new(dest_dir("index.html"))).to exist 111 | expect(Pathname.new(dest_dir("en/index.html"))).to exist 112 | expect(Pathname.new(dest_dir("en/2018/10/15/hello-world.html"))).to exist 113 | end 114 | 115 | it "allows withelding publish until ready" do 116 | expect(Pathname.new(dest_dir("about.html"))).to exist 117 | expect(Pathname.new(dest_dir("en/about.html"))).to_not exist 118 | 119 | make_page_file( 120 | "_locales/en/about.md", 121 | :content => "Hello World", 122 | :front_matter => { "published" => true } 123 | ) 124 | site.process 125 | expect(Pathname.new(dest_dir("en/about.html"))).to exist 126 | 127 | make_page_file( 128 | "_locales/en/about.md", 129 | :content => "Hello World", 130 | :front_matter => { "published" => false } 131 | ) 132 | site.process 133 | expect(Pathname.new(dest_dir("en/about.html"))).to_not exist 134 | end 135 | end 136 | 137 | context "with locale metadata hash via locales_set configuration" do 138 | let(:config) do 139 | { 140 | "localization" => { 141 | "mode" => "manual", 142 | "locale" => "fr", 143 | "locales_set" => { 144 | "en" => { "label" => "English" }, 145 | "fr" => { "label" => "Français" }, 146 | }, 147 | "content_dirname" => "_locales", 148 | }, 149 | } 150 | end 151 | 152 | it "returns the default 'locale identity' object" do 153 | expect(subject.default_locale.class).to eql(locale.class) 154 | expect(subject.default_locale.id).to eql("fr") 155 | expect(subject.default_locale.data).to eql("label" => "Français") 156 | end 157 | 158 | it "returns the default 'locale identity' object as the current locale" do 159 | expect(subject.current_locale).to eql(subject.default_locale) 160 | end 161 | 162 | it "returns an array of available locale ids" do 163 | expect(subject.available_locales).to eql(%w(fr en)) 164 | end 165 | end 166 | 167 | context "with invalid locale metadata via locales_set configuration" do 168 | let(:config) do 169 | { 170 | "localization" => { 171 | "mode" => "manual", 172 | "locale" => "fr", 173 | "locales_set" => "fr: Français, en: English", 174 | "content_dirname" => "_locales", 175 | }, 176 | } 177 | end 178 | 179 | it "returns the default 'locale identity' object" do 180 | expect(subject.default_locale.class).to eql(locale.class) 181 | expect(subject.default_locale.id).to eql("fr") 182 | expect(subject.default_locale.data).to eql({}) 183 | end 184 | 185 | it "returns the default 'locale identity' object as the current locale" do 186 | expect(subject.current_locale).to eql(subject.default_locale) 187 | end 188 | 189 | it "returns an array of available locale ids" do 190 | expect(subject.available_locales).to eql(%w(fr)) 191 | end 192 | end 193 | end 194 | -------------------------------------------------------------------------------- /spec/jekyll-locale/page_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe Jekyll::Locale::Page do 4 | let(:page_name) { "about.md" } 5 | let(:locale_id) { "en" } 6 | let(:metadata) { {} } 7 | let(:locale) { Jekyll::Locale::Identity.new(locale_id, metadata) } 8 | let(:locales_set) { %w(en fr ja) } 9 | let(:config) do 10 | { 11 | "title" => "Localization Test", 12 | "localization" => { 13 | "locale" => locale_id, 14 | "locales_set" => locales_set, 15 | }, 16 | } 17 | end 18 | let(:site) { make_site(config) } 19 | let(:canon) { make_canon_page(site, page_name) } 20 | 21 | subject { described_class.new(canon, locale) } 22 | 23 | before do 24 | make_page_file("_locales/#{locale}/#{page_name}", :content => "Hello World") 25 | make_page_file("_locales/fr/#{page_name}", :content => "Hello World") 26 | make_page_file("_locales/ja/#{page_name}", :content => "Hello World") 27 | end 28 | 29 | after do 30 | content_dir = source_dir("_locales") 31 | FileUtils.rm_rf(content_dir) if File.directory?(content_dir) 32 | end 33 | 34 | it "returns the custom inspect string" do 35 | expect(subject.inspect).to eql( 36 | "#" 37 | ) 38 | end 39 | 40 | it "returns the 'locale identity' object" do 41 | expect(subject.locale).to eql(locale) 42 | expect(subject.locale.id).to eql("en") 43 | end 44 | 45 | it "matches the 'relative_path' attribute with its canonical page" do 46 | expect(subject.relative_path).to eql(File.join("_locales", "en", canon.relative_path)) 47 | end 48 | 49 | it "matches the 'url' attribute with its canonical page by default" do 50 | expect(subject.url).to eql(File.join("", "en", canon.url)) 51 | end 52 | 53 | it "returns a hash for Liquid templates" do 54 | subject.content ||= "Random content" 55 | expect(subject.to_liquid.class.name).to eql("Hash") 56 | expect(subject.to_liquid).to eql( 57 | "content" => "Hello World\n{{ page.url }}\n\n{{ 'now' | localize_date }}\n", 58 | "dir" => "/en/", 59 | "foo" => "bar", 60 | "layout" => "none", 61 | "meta" => "about_locale", 62 | "name" => "about.md", 63 | "path" => "_locales/en/about.md", 64 | "url" => "/en/about.html" 65 | ) 66 | end 67 | 68 | it "returns hreflang and locale_sibling data for Liquid templates" do 69 | site.process 70 | 71 | hreflangs = [ 72 | { "locale" => { "id" => "en" }, "url" => "/about.html" }, 73 | { "locale" => { "id" => "fr" }, "url" => "/fr/about.html" }, 74 | { "locale" => { "id" => "ja" }, "url" => "/ja/about.html" }, 75 | ] 76 | canon = site.pages.find { |p| p.is_a?(Jekyll::Page) && p.url == "/about.html" } 77 | expect(canon.hreflangs).to eql(hreflangs) 78 | expect(canon.locale_siblings).to eql( 79 | [ 80 | { "locale" => { "id" => "fr" }, "url" => "/fr/about.html" }, 81 | { "locale" => { "id" => "ja" }, "url" => "/ja/about.html" }, 82 | ] 83 | ) 84 | 85 | locale_page = site.pages.find { |p| p.is_a?(described_class) && p.url == "/fr/about.html" } 86 | expect(locale_page.hreflangs).to eql(hreflangs) 87 | expect(locale_page.locale_siblings).to eql( 88 | [ 89 | { "locale" => { "id" => "en" }, "url" => "/about.html" }, 90 | { "locale" => { "id" => "ja" }, "url" => "/ja/about.html" }, 91 | ] 92 | ) 93 | end 94 | 95 | context "with locale metadata via locales_set configuration" do 96 | let(:locales_set) do 97 | { 98 | "fr" => { "label" => "Français" }, 99 | "en" => { "label" => "English" }, 100 | "ja" => { "label" => "日本語" }, 101 | } 102 | end 103 | 104 | it "returns hreflang and locale_sibling data for Liquid templates" do 105 | site.process 106 | 107 | hreflangs = [ 108 | { "locale" => { "id" => "en", "label" => "English" }, "url" => "/about.html" }, 109 | { "locale" => { "id" => "fr", "label" => "Français" }, "url" => "/fr/about.html" }, 110 | { "locale" => { "id" => "ja", "label" => "日本語" }, "url" => "/ja/about.html" }, 111 | ] 112 | canon = site.pages.find { |p| p.is_a?(Jekyll::Page) && p.url == "/about.html" } 113 | expect(canon.hreflangs).to eql(hreflangs) 114 | expect(canon.locale_siblings).to eql( 115 | [ 116 | { "locale" => { "id" => "fr", "label" => "Français" }, "url" => "/fr/about.html" }, 117 | { "locale" => { "id" => "ja", "label" => "日本語" }, "url" => "/ja/about.html" }, 118 | ] 119 | ) 120 | 121 | locale_page = site.pages.find { |p| p.is_a?(described_class) && p.url == "/fr/about.html" } 122 | expect(locale_page.hreflangs).to eql(hreflangs) 123 | expect(locale_page.locale_siblings).to eql( 124 | [ 125 | { "locale" => { "id" => "en", "label" => "English" }, "url" => "/about.html" }, 126 | { "locale" => { "id" => "ja", "label" => "日本語" }, "url" => "/ja/about.html" }, 127 | ] 128 | ) 129 | end 130 | end 131 | end 132 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "simplecov" 4 | require "fileutils" 5 | require "jekyll" 6 | require_relative "../lib/jekyll-locale" 7 | 8 | Jekyll.logger.log_level = :error 9 | 10 | def dest_dir(*subdirs) 11 | File.join( 12 | File.expand_path("../tmp/dest", __dir__), 13 | subdirs 14 | ) 15 | end 16 | 17 | def source_dir(*subdirs) 18 | File.join( 19 | File.expand_path("fixtures", __dir__), 20 | subdirs 21 | ) 22 | end 23 | 24 | CONFIG_DEFAULTS = { 25 | "source" => source_dir, 26 | "destination" => dest_dir, 27 | "plugins" => ["jekyll-locale"], 28 | }.freeze 29 | 30 | def make_site(options = {}) 31 | config = Jekyll.configuration CONFIG_DEFAULTS.merge(options) 32 | Jekyll::Site.new(config) 33 | end 34 | 35 | def make_page_file(path, content: "", front_matter: nil) 36 | file_path = source_dir(path) 37 | dir_path = File.dirname(file_path) 38 | front_matter ||= { "foo" => "bar" } 39 | FileUtils.mkdir_p(dir_path) unless File.directory?(dir_path) 40 | liquid_content = "{{ page.url }}\n\n{{ 'now' | localize_date }}" 41 | File.open(file_path, "wb") do |f| 42 | f.puts "#{YAML.dump(front_matter)}\n---\n#{content}\n#{liquid_content}\n" 43 | end 44 | end 45 | 46 | def make_canon_page(site, name, options = {}) 47 | Jekyll::Page.new(site, CONFIG_DEFAULTS["source"], "", name).tap do |page| 48 | page.data = options 49 | end 50 | end 51 | 52 | def make_canon_document(site, name, collection, options = {}) 53 | relations = { :site => site, :collection => site.collections[collection] } 54 | Jekyll::Document.new(source_dir("_#{collection}/#{name}"), relations).tap do |doc| 55 | doc.read 56 | doc.merge_data!(options) 57 | end 58 | end 59 | --------------------------------------------------------------------------------