al_rtl 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b48eef7e66cac25d28edfececc9293a043c4981e1a219a4641489a6f4b24cb26
4
+ data.tar.gz: 0d5b07d7c777f12cc6334a467707b673739be6e6fd4e9f7ffb27e2618934dec4
5
+ SHA512:
6
+ metadata.gz: 747e7d41d33005ca58ff32a9700d5856b8cd17c9c659e85757be059da7a0b530015df13b2758da30cd396af89cbb148fd84e241abc5d4a2eb01896aecf1afc50
7
+ data.tar.gz: 63b522431bc2a711ba98d14b97936ce4d0cd256f6c639834c3d4a87f50a8074ff27f5c9e08e52930491e5dfca23f258b7af40c17827ea5e4a00baa300d9520bc
data/CHANGELOG.md ADDED
@@ -0,0 +1,11 @@
1
+ # Changelog
2
+
3
+ ## 1.0.0 — 2026-08-02
4
+
5
+ - Initial release of `al_rtl`, implementing the plugin proposed in
6
+ [alshedivat/al-folio#3544](https://github.com/alshedivat/al-folio/issues/3544) (source PR
7
+ [#946](https://github.com/alshedivat/al-folio/pull/946)).
8
+ - Direction is set with `dir` on `<html>` rather than on a wrapper `div`, so page chrome mirrors too and CSS logical
9
+ properties work.
10
+ - Detection handles region subtags and casing (`fa-IR`, `FA`), with a per-page `lang` override.
11
+ - Code, shell transcripts and rendered maths are held left-to-right inside RTL prose.
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) al-folio maintainers
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 all
13
+ 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 THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,77 @@
1
+ # al_rtl
2
+
3
+ Right-to-left language support for [al-folio](https://github.com/alshedivat/al-folio) v1.x. Implements the plugin
4
+ proposed in [al-folio#3544](https://github.com/alshedivat/al-folio/issues/3544).
5
+
6
+ ## Install
7
+
8
+ ```ruby
9
+ gem "al_rtl", "= 1.0.0"
10
+ ```
11
+
12
+ and in `_config.yml` — **both lists, or the plugin is inert**:
13
+
14
+ ```yaml
15
+ plugins:
16
+ - al_rtl
17
+ ```
18
+
19
+ That is all the configuration most sites need. Direction follows the page's `lang`, falling back to `site.lang`:
20
+
21
+ ```yaml
22
+ ---
23
+ layout: post
24
+ title: یک پست نمونه
25
+ lang: fa
26
+ ---
27
+ ```
28
+
29
+ Recognised by default: `ar arc az ckb dv fa he ku ps sd ug ur yi`. To narrow or extend that list:
30
+
31
+ ```yaml
32
+ al_rtl:
33
+ langs: [fa, ar]
34
+ ```
35
+
36
+ ## What it does
37
+
38
+ - Adds `dir="rtl"` and a correct `lang` to `<html>` on RTL pages.
39
+ - Loads a small RTL stylesheet, **only** on those pages.
40
+ - Provides `{% if_rtl %}…{% endif_rtl %}` and an `al_rtl_direction` filter for layouts that need to branch.
41
+
42
+ ## Differences from the source PR
43
+
44
+ The 2022 proposal targeted the Bootstrap-era theme, and three things did not port.
45
+
46
+ **`dir` goes on `<html>`, not a wrapper `div`.** This is the substantive change. Direction on a wrapper leaves
47
+ everything outside it — navbar, footer, skip links, scrollbar placement — laid out left-to-right, and it is also what
48
+ CSS logical properties and Tailwind's `rtl:` variant key off. Setting it on the root element is what makes the browser's
49
+ own bidi handling apply to the whole document.
50
+
51
+ **`align="right"` is gone.** It was deprecated in HTML 4.01, does nothing under a Tailwind reset, and conflates
52
+ alignment with direction — RTL text is right-aligned _because_ of direction, and forcing it breaks centred headings.
53
+
54
+ **No manual sidebar flipping.** The original swapped the TOC sidebar's column order in Liquid. With `dir` set correctly
55
+ the grid mirrors on its own, so that logic is not needed.
56
+
57
+ One thing the original did not handle: **code and maths stay left-to-right.** Source code, shell transcripts, BibTeX and
58
+ rendered equations are LTR content no matter what language surrounds them, and letting them inherit `rtl` reorders
59
+ operators and punctuation into nonsense.
60
+
61
+ ## Fonts
62
+
63
+ No webfont is bundled. The source PR pulled Vazirmatn from Google Fonts for every page, which is a third-party request
64
+ and a privacy consideration that should be the site owner's choice. Add one through al-folio's normal font
65
+ configuration if you want it.
66
+
67
+ ## Development
68
+
69
+ ```bash
70
+ bundle install
71
+ bundle exec rake test
72
+ npm ci && npm run lint:prettier
73
+ ```
74
+
75
+ ## License
76
+
77
+ MIT
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AlRtl
4
+ VERSION = "1.0.0"
5
+ end
data/lib/al_rtl.rb ADDED
@@ -0,0 +1,162 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cgi"
4
+ require "jekyll"
5
+ require "liquid"
6
+ require_relative "al_rtl/version"
7
+
8
+ # Right-to-left language support.
9
+ #
10
+ # Direction is set with `dir` on the <html> element rather than on a wrapper
11
+ # div, which is what the original v0.x proposal did. That matters for more than
12
+ # tidiness: `dir` on <html> is what makes CSS logical properties, the `rtl:`
13
+ # Tailwind variant, form controls, scrollbar placement and the browser's own
14
+ # bidi algorithm behave. A wrapper div leaves the chrome outside it — navbar,
15
+ # footer, skip links — still laid out left-to-right.
16
+ #
17
+ # It also drops the `align="right"` the original used. That attribute was
18
+ # deprecated in HTML 4.01, does nothing in modern engines under a Tailwind
19
+ # reset, and conflates alignment with direction: RTL text is right-aligned as a
20
+ # *consequence* of direction, and forcing it breaks centred headings.
21
+ module AlRtl
22
+ PLUGIN_ROOT = File.expand_path("..", __dir__)
23
+ # Jekyll writes a StaticFile to <dest>/<dir>/<name>, where <dir> is relative
24
+ # to the base below. That base must be lib/, not the gem root, or assets land
25
+ # at /lib/assets/... while every tag in this file points at /assets/... .
26
+ LIB_ROOT = __dir__
27
+ ASSETS_ROOT = File.join(LIB_ROOT, "assets")
28
+
29
+ # ISO 639 codes for the scripts written right-to-left. Overridable via
30
+ # `al_rtl.langs`, but shipping a default means most sites configure nothing.
31
+ DEFAULT_LANGS = %w[ar arc az ckb dv fa he ku ps sd ug ur yi].freeze
32
+
33
+ class PluginStaticFile < Jekyll::StaticFile; end
34
+
35
+ module_function
36
+
37
+ def config(site)
38
+ return {} unless site
39
+
40
+ site.config["al_rtl"] || {}
41
+ end
42
+
43
+ def languages(site)
44
+ configured = config(site)["langs"]
45
+ list = configured.is_a?(Array) && !configured.empty? ? configured : DEFAULT_LANGS
46
+ list.map { |code| normalize(code) }
47
+ end
48
+
49
+ # "fa-IR" and "FA" both mean Persian. Compare on the primary subtag, lowercased,
50
+ # or a site that writes the region form gets silently left-to-right.
51
+ def normalize(code)
52
+ code.to_s.strip.downcase.split(/[-_]/).first.to_s
53
+ end
54
+
55
+ # The page's own `lang` wins over the site default, so a single RTL post on an
56
+ # otherwise English site renders correctly.
57
+ #
58
+ # Reads through `[]` rather than testing for Hash: at render time Jekyll hands
59
+ # `registers[:page]` a Drop (Jekyll::Drops::DocumentDrop), not a Hash, so an
60
+ # `is_a?(Hash)` guard silently discards every page's front matter and falls
61
+ # back to the site language.
62
+ def page_value(page, key)
63
+ return nil unless page.respond_to?(:[])
64
+
65
+ page[key]
66
+ rescue StandardError
67
+ nil
68
+ end
69
+
70
+ def language_for(site, page)
71
+ page_lang = page_value(page, "lang")
72
+ return page_lang unless page_lang.to_s.strip.empty?
73
+
74
+ site&.config&.[]("lang")
75
+ end
76
+
77
+ def rtl?(site, page)
78
+ lang = normalize(language_for(site, page))
79
+ return false if lang.empty?
80
+
81
+ languages(site).include?(lang)
82
+ end
83
+
84
+ # Emits the raw language tag for the `lang` attribute, preserving any region
85
+ # subtag ("fa-IR"), since that is legitimate content for `lang`.
86
+ def language_tag(site, page)
87
+ language_for(site, page).to_s.strip
88
+ end
89
+
90
+ # [relative_dir, filename] for everything this gem publishes. Exposed so the
91
+ # destination path can be asserted without booting a full Jekyll site.
92
+ def asset_entries
93
+ Dir.glob(File.join(ASSETS_ROOT, "**", "*")).sort.reject { |p| File.directory?(p) }.map do |source_path|
94
+ [File.dirname(source_path).sub("#{LIB_ROOT}/", ""), File.basename(source_path)]
95
+ end
96
+ end
97
+
98
+ class AssetsGenerator < Jekyll::Generator
99
+ safe true
100
+ priority :low
101
+
102
+ def generate(site)
103
+ Dir.glob(File.join(ASSETS_ROOT, "**", "*")).sort.each do |source_path|
104
+ next if File.directory?(source_path)
105
+
106
+ relative_dir = File.dirname(source_path).sub("#{LIB_ROOT}/", "")
107
+ site.static_files << PluginStaticFile.new(site, LIB_ROOT, relative_dir, File.basename(source_path))
108
+ end
109
+ end
110
+ end
111
+
112
+ # {% al_rtl_html_attrs %} — goes inside the <html> tag.
113
+ #
114
+ # Always emits a `lang`, and adds `dir="rtl"` only for RTL pages. Emitting
115
+ # `dir="ltr"` explicitly would be harmless but noisy; the default is already
116
+ # ltr.
117
+ class HtmlAttrsTag < Liquid::Tag
118
+ def render(context)
119
+ site = context.registers[:site]
120
+ page = context.registers[:page]
121
+
122
+ attributes = []
123
+ tag = AlRtl.language_tag(site, page)
124
+ attributes << %(lang="#{CGI.escapeHTML(tag)}") unless tag.empty?
125
+ attributes << %(dir="rtl") if AlRtl.rtl?(site, page)
126
+ attributes.join(" ")
127
+ end
128
+ end
129
+
130
+ # {% al_rtl_styles %} — the RTL stylesheet, only on RTL pages.
131
+ class StylesTag < Liquid::Tag
132
+ def render(context)
133
+ site = context.registers[:site]
134
+ page = context.registers[:page]
135
+ return "" unless AlRtl.rtl?(site, page)
136
+
137
+ baseurl = site.config["baseurl"] || ""
138
+ %(<link rel="stylesheet" href="#{baseurl}/assets/al_rtl/css/rtl.css">\n)
139
+ end
140
+ end
141
+
142
+ # {% if_rtl %}…{% endif_rtl %} for layouts that need to branch.
143
+ class IfRtlBlock < Liquid::Block
144
+ def render(context)
145
+ return "" unless AlRtl.rtl?(context.registers[:site], context.registers[:page])
146
+
147
+ super
148
+ end
149
+ end
150
+
151
+ module Filters
152
+ # `{{ page.lang | al_rtl_direction }}` -> "rtl" / "ltr"
153
+ def al_rtl_direction(value)
154
+ AlRtl.languages(@context.registers[:site]).include?(AlRtl.normalize(value)) ? "rtl" : "ltr"
155
+ end
156
+ end
157
+ end
158
+
159
+ Liquid::Template.register_tag("al_rtl_html_attrs", AlRtl::HtmlAttrsTag)
160
+ Liquid::Template.register_tag("al_rtl_styles", AlRtl::StylesTag)
161
+ Liquid::Template.register_tag("if_rtl", AlRtl::IfRtlBlock)
162
+ Liquid::Template.register_filter(AlRtl::Filters)
@@ -0,0 +1,80 @@
1
+ /* RTL corrections, loaded only on pages whose language is right-to-left.
2
+ Plain CSS rather than Sass: _sass/ is gem-owned by al_folio_core and a plugin
3
+ must not add to the theme's build pipeline.
4
+
5
+ Scoped under [dir="rtl"] so this file is inert if it is ever served on an LTR
6
+ page. Most layout already mirrors itself once `dir` is set — the browser's
7
+ bidi algorithm and any logical properties (margin-inline, padding-inline,
8
+ inset-inline) handle it. What is left below are the places al-folio uses
9
+ physical properties, which do not mirror on their own. */
10
+
11
+ [dir="rtl"] {
12
+ text-align: right;
13
+ }
14
+
15
+ /* Lists: the marker moves side with direction, but the indent is physical. */
16
+ [dir="rtl"] ul,
17
+ [dir="rtl"] ol {
18
+ padding-right: 2rem;
19
+ padding-left: 0;
20
+ }
21
+
22
+ /* Blockquotes are marked with a left border throughout the theme. */
23
+ [dir="rtl"] blockquote {
24
+ border-left: none;
25
+ border-right: 4px solid var(--global-theme-color, currentColor);
26
+ padding-left: 0;
27
+ padding-right: 1rem;
28
+ }
29
+
30
+ /* The TOC sidebar sits to one side of the content; flip which one. */
31
+ [dir="rtl"] #toc-sidebar {
32
+ border-left: none;
33
+ border-right: 1px solid var(--global-divider-color, rgba(0, 0, 0, 0.1));
34
+ }
35
+
36
+ [dir="rtl"] #toc-sidebar a {
37
+ padding-right: 0.75rem;
38
+ padding-left: 0;
39
+ }
40
+
41
+ /* Code and output stay left-to-right: source code, shell transcripts and
42
+ numeric output are LTR content regardless of the surrounding prose, and
43
+ letting them inherit rtl reorders operators and punctuation into nonsense. */
44
+ [dir="rtl"] pre,
45
+ [dir="rtl"] code,
46
+ [dir="rtl"] kbd,
47
+ [dir="rtl"] samp,
48
+ [dir="rtl"] .highlight,
49
+ [dir="rtl"] .highlighter-rouge {
50
+ direction: ltr;
51
+ text-align: left;
52
+ }
53
+
54
+ /* Same reasoning for rendered maths and citation keys. */
55
+ [dir="rtl"] .MathJax,
56
+ [dir="rtl"] mjx-container,
57
+ [dir="rtl"] .katex,
58
+ [dir="rtl"] .bibtex {
59
+ direction: ltr;
60
+ text-align: left;
61
+ }
62
+
63
+ /* Tables mirror their column order, but cell text alignment is physical. */
64
+ [dir="rtl"] th,
65
+ [dir="rtl"] td {
66
+ text-align: right;
67
+ }
68
+
69
+ /* Progress/scroll indicators anchored to a physical edge. */
70
+ [dir="rtl"] .progress-container .progress-bar {
71
+ left: auto;
72
+ right: 0;
73
+ }
74
+
75
+ /* Author/affiliation blocks and post metadata use left margins for spacing. */
76
+ [dir="rtl"] .post-meta,
77
+ [dir="rtl"] .post-tags {
78
+ margin-left: 0;
79
+ margin-right: 0.5rem;
80
+ }
metadata ADDED
@@ -0,0 +1,141 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: al_rtl
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - al-folio maintainers
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-08-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: jekyll
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '3.9'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '5.0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '3.9'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '5.0'
33
+ - !ruby/object:Gem::Dependency
34
+ name: liquid
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '4.0'
40
+ - - "<"
41
+ - !ruby/object:Gem::Version
42
+ version: '6.0'
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '4.0'
50
+ - - "<"
51
+ - !ruby/object:Gem::Version
52
+ version: '6.0'
53
+ - !ruby/object:Gem::Dependency
54
+ name: bundler
55
+ requirement: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: '2.0'
60
+ - - "<"
61
+ - !ruby/object:Gem::Version
62
+ version: '3.0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '2.0'
70
+ - - "<"
71
+ - !ruby/object:Gem::Version
72
+ version: '3.0'
73
+ - !ruby/object:Gem::Dependency
74
+ name: rake
75
+ requirement: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - "~>"
78
+ - !ruby/object:Gem::Version
79
+ version: '13.0'
80
+ type: :development
81
+ prerelease: false
82
+ version_requirements: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - "~>"
85
+ - !ruby/object:Gem::Version
86
+ version: '13.0'
87
+ - !ruby/object:Gem::Dependency
88
+ name: minitest
89
+ requirement: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - "~>"
92
+ - !ruby/object:Gem::Version
93
+ version: '5.0'
94
+ type: :development
95
+ prerelease: false
96
+ version_requirements: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - "~>"
99
+ - !ruby/object:Gem::Version
100
+ version: '5.0'
101
+ description: Sets document direction and RTL styling for pages written in right-to-left
102
+ scripts.
103
+ email:
104
+ - maintainers@al-folio.dev
105
+ executables: []
106
+ extensions: []
107
+ extra_rdoc_files: []
108
+ files:
109
+ - CHANGELOG.md
110
+ - LICENSE
111
+ - README.md
112
+ - lib/al_rtl.rb
113
+ - lib/al_rtl/version.rb
114
+ - lib/assets/al_rtl/css/rtl.css
115
+ homepage: https://github.com/al-org-dev/al-rtl
116
+ licenses:
117
+ - MIT
118
+ metadata:
119
+ allowed_push_host: https://rubygems.org
120
+ homepage_uri: https://github.com/al-org-dev/al-rtl
121
+ source_code_uri: https://github.com/al-org-dev/al-rtl
122
+ post_install_message:
123
+ rdoc_options: []
124
+ require_paths:
125
+ - lib
126
+ required_ruby_version: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '2.7'
131
+ required_rubygems_version: !ruby/object:Gem::Requirement
132
+ requirements:
133
+ - - ">="
134
+ - !ruby/object:Gem::Version
135
+ version: '0'
136
+ requirements: []
137
+ rubygems_version: 3.5.22
138
+ signing_key:
139
+ specification_version: 4
140
+ summary: Right-to-left language support for al-folio v1.x
141
+ test_files: []