Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
d0d7d4e
Add fallback content generation for unsupported languages
liberaldev Dec 25, 2025
ccc1687
Add Tailwind classes for "sky" color palette to styles, config, and f…
liberaldev Dec 25, 2025
3c014cf
Add `lang` attribute handling to TOC list items for improved language…
liberaldev Dec 25, 2025
3c6ca2e
Add tests for fallback content generation and validation
liberaldev Dec 25, 2025
5eb30bc
Add tests for fallback generator plugin and extend default test task
liberaldev Dec 25, 2025
9eca3ea
Update test fallback generator to replace 'zz' with 'xz' as test lang…
liberaldev Dec 25, 2025
be36f33
Standardize spacing around `lang` attribute in fallback conditionals
liberaldev Dec 25, 2025
8d33a35
Merge branch 'master' into add-fallback-en-page
liberaldev Dec 25, 2025
52cb3b1
Refactor `lang` attribute selectors to exclude nested conflicting lan…
liberaldev Dec 25, 2025
bd3c1b6
Simplify `lang` attribute selectors and consolidate logic for improve…
liberaldev Dec 25, 2025
143746a
Add `body` selectors for CJK `lang` attribute to ensure consistent fo…
liberaldev Dec 25, 2025
913fcf2
Add `body` selectors to CJK `lang` rules for complete font coverage
liberaldev Dec 25, 2025
382889b
Exclude posts with `fallback` attribute from translation status tracking
liberaldev Dec 25, 2025
04c2569
Update `translation_status.rb` to fix fallback handling and add `uk` …
liberaldev Dec 25, 2025
6b925cb
Add tests for `translation_status` plugin and extend default test task
liberaldev Dec 25, 2025
56ea7ed
Update Japanese test post content and title in `translation_status` t…
liberaldev Dec 25, 2025
9795c1e
Extend linter exclusions to include `node_modules` and `_site` direct…
liberaldev Dec 25, 2025
3785575
Merge branch 'master' into add-fallback-en-page
liberaldev Dec 26, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ CONFIG = "_config.yml"
task default: [:build]

desc "Run tests (test-linter, lint, build)"
task test: %i[test-news-plugin test-linter lint build]
task test: %i[test-news-plugin test-fallback-generator test-translation-status test-linter lint build]

desc "Build the Jekyll site"
task :build do
Expand Down Expand Up @@ -129,3 +129,19 @@ Rake::TestTask.new(:"test-news-plugin") do |t|
t.test_files = FileList['test/test_plugin_news.rb']
t.verbose = true
end

require "rake/testtask"
Rake::TestTask.new(:"test-fallback-generator") do |t|
t.description = "Run tests for the fallback generator plugin"
t.libs = ["test"]
t.test_files = FileList['test/test_fallback_generator.rb']
t.verbose = true
end

require "rake/testtask"
Rake::TestTask.new(:"test-translation-status") do |t|
t.description = "Run tests for the translation status plugin"
t.libs = ["test"]
t.test_files = FileList['test/test_translation_status.rb']
t.verbose = true
end
1 change: 1 addition & 0 deletions _data/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ month_names:

posted_by: Posted by AUTHOR on %-d %b %Y
translated_by: Translated by
fallback_notice: "This content is not currently available in the language you selected"

feed:
title: Ruby News
Expand Down
1 change: 1 addition & 0 deletions _data/locales/ko.yml
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ month_names:

posted_by: '작성자: AUTHOR (%Y-%m-%d)'
translated_by: '번역자:'
fallback_notice: "이 콘텐츠는 아직 한국어 번역이 제공되지 않아 영어로 표시됩니다"

feed:
title: Ruby 뉴스
Expand Down
6 changes: 3 additions & 3 deletions _includes/home/news_security.html
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,13 @@ <h2 class="text-3xl md:text-4xl font-bold mb-10">
<div class="space-y-6">
{% for post in news_posts %}
<article class="pb-6 border-b border-stone-200 dark:border-stone-700">
<h3 class="text-2xl font-bold mb-3">
<h3 class="text-2xl font-bold mb-3"{% if post.fallback %} lang="en"{% endif %}>
<a href="{{ post.url }}" class="text-stone-900 dark:text-stone-100 hover:text-ruby-600 dark:hover:text-ruby-400 transition-colors">
{{ post.title }}
</a>
</h3>

<div class="text-stone-900 dark:text-stone-300 text-sm mb-1">
<div class="text-stone-900 dark:text-stone-300 text-sm mb-1"{% if post.fallback %} lang="en"{% endif %}>
{{ post.excerpt | strip_html | truncatewords: 30 }}
</div>

Expand Down Expand Up @@ -86,7 +86,7 @@ <h2 class="text-3xl md:text-4xl font-bold mb-10">
<div class="space-y-6">
{% for post in security_posts %}
<article class="pb-6 border-b border-stone-200 dark:border-stone-700">
<h3 class="text-base font-bold mb-3">
<h3 class="text-base font-bold mb-3"{% if post.fallback %} lang="en"{% endif %}>
<a href="{{ post.url }}" class="text-stone-900 dark:text-stone-100 hover:text-ruby-600 dark:hover:text-ruby-400 transition-colors">
{{ post.title }}
</a>
Expand Down
4 changes: 2 additions & 2 deletions _includes/recent_news.html
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,13 @@ <h2 class="text-2xl font-bold mb-6">
<div class="space-y-4">
{% for post in recent_posts %}
<article class="pb-4 border-b border-stone-200 dark:border-stone-700 last:border-b-0">
<h3 class="text-lg font-bold mb-2">
<h3 class="text-lg font-bold mb-2"{% if post.fallback %} lang="en"{% endif %}>
<a href="{{ post.url }}" class="text-stone-900 dark:text-stone-100 hover:text-ruby-600 dark:hover:text-ruby-400 transition-colors">
{{ post.title }}
</a>
</h3>

<p class="text-stone-600 dark:text-stone-400 text-sm mb-2 line-clamp-2">
<p class="text-stone-600 dark:text-stone-400 text-sm mb-2 line-clamp-2"{% if post.fallback %} lang="en"{% endif %}>
{{ post.excerpt | strip_html | truncatewords: 25 }}
</p>

Expand Down
2 changes: 1 addition & 1 deletion _includes/title.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{% if page.header != null %}
{{ page.header | markdownify }}
{% else %}
<h1>{{ page.title }}</h1>
<h1{% if page.fallback %} lang="en"{% endif %}>{{ page.title }}</h1>
{% endif %}
4 changes: 2 additions & 2 deletions _layouts/news.html
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,13 @@
<div id="content">
{% for post in page.posts %}
<div class="not-prose pb-8 mb-8 border-b border-stone-200 dark:border-stone-700 first:pt-8 first:mt-8 first:border-t last:border-b-0 last:mb-0 last:pb-0">
<h3 class="text-3xl font-bold mb-4">
<h3 class="text-3xl font-bold mb-4"{% if post.fallback %} lang="en"{% endif %}>
<a href="{{ post.url }}" class="text-stone-900 dark:text-stone-100 hover:text-ruby-600 dark:hover:text-ruby-400 transition-colors">
{{ post.title }}
</a>
</h3>

<div class="text-stone-900 dark:text-stone-300">
<div class="text-stone-900 dark:text-stone-300"{% if post.fallback %} lang="en"{% endif %}>
{{ post.excerpt | markdownify }}
</div>

Expand Down
122 changes: 122 additions & 0 deletions _plugins/fallback_generator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# frozen_string_literal: true

require 'set'

module Jekyll
class FallbackGenerator < Generator
priority :high

def generate(site)
@site = site
@languages = site.data['languages'].map { |l| l['code'] }

fallback_posts
fallback_pages
end

def fallback_posts
en_posts = @site.posts.docs.select { |p| p.data['lang'] == 'en' }

existing_posts_by_lang = {}
@site.posts.docs.each do |post|
lang = post.data['lang']
next unless lang
existing_posts_by_lang[lang] ||= Set.new
existing_posts_by_lang[lang] << File.basename(post.path)
end

new_posts = []
en_posts.each do |en_post|
filename = File.basename(en_post.path)

@languages.each do |lang|
next if lang == 'en'
next if existing_posts_by_lang[lang]&.include?(filename)

new_posts << create_fallback_doc(en_post, lang)
end
end

@site.posts.docs.concat(new_posts)
@site.posts.docs.sort!
@site.instance_variable_set(:@categories, nil)
@site.instance_variable_set(:@tags, nil)
end

def fallback_pages
en_pages = @site.pages.select { |p| p.data['lang'] == 'en' }

existing_pages_by_lang = {}
@site.pages.each do |page|
lang = page.data['lang']
next unless lang
existing_pages_by_lang[lang] ||= Set.new

rel_path = page.path.sub(%r{^#{lang}/}, "")
existing_pages_by_lang[lang] << rel_path
end

new_pages = []
en_pages.each do |en_page|
rel_path = en_page.path.sub(%r{^en/}, "")
next if rel_path == en_page.path

@languages.each do |lang|
next if lang == 'en'
next if existing_pages_by_lang[lang]&.include?(rel_path)
next if rel_path.end_with?(".xml") || rel_path.end_with?(".rss")

new_pages << create_fallback_page(en_page, lang, rel_path)
end
end
@site.pages.concat(new_pages)
end

def create_fallback_doc(en_doc, lang)
new_doc = en_doc.clone
new_doc.instance_variable_set(:@data, en_doc.data.dup)

new_doc.data['lang'] = lang
new_doc.data['fallback'] = true
new_doc.data['content_lang'] = 'en'
new_doc.data['categories'] = [lang] + (en_doc.data['categories'] || []) - ['en']

new_path = en_doc.path.sub('/en/', "/#{lang}/")
new_doc.instance_variable_set(:@path, new_path)

wrap_content(new_doc, en_doc, lang)
new_doc
end

def create_fallback_page(en_page, lang, rel_path)
new_page = en_page.clone
new_page.instance_variable_set(:@data, en_page.data.dup)

new_dir = File.join(lang, File.dirname(rel_path))
new_page.instance_variable_set(:@dir, new_dir)
new_page.instance_variable_set(:@path, File.join(lang, rel_path))

new_page.data['lang'] = lang
new_page.data['fallback'] = true
new_page.data['content_lang'] = 'en'

wrap_content(new_page, en_page, lang)
new_page
end

def wrap_content(new_obj, en_obj, lang)
notice = @site.data['locales'][lang]['fallback_notice'] rescue nil
notice ||= @site.data['locales']['en']['fallback_notice'] rescue "Translated version not available"

# Using a combination of Tailwind classes and inline styles to ensure visibility
new_obj.content = <<~HTML
<div class="fallback-notice bg-sky-50 dark:bg-sky-900/30 border border-sky-200 dark:border-sky-800 p-4 mb-8 rounded-lg text-sky-800 dark:text-sky-200">
#{notice}
</div>
<div lang="en" markdown="1">
#{en_obj.content}
</div>
HTML
end
end
end
11 changes: 8 additions & 3 deletions _plugins/translation_status.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ module Jekyll
# Outputs HTML.
module TranslationStatus

LANGS = %w[en bg de es fr id it ja ko pl pt ru tr ua vi zh_cn zh_tw].freeze
LANGS = %w[en bg de es fr id it ja ko pl pt ru tr uk vi zh_cn zh_tw].freeze
START_DATE = "2013-04-01"

OK_CHAR = "✓"
Expand Down Expand Up @@ -107,16 +107,21 @@ def table_row(post)
end

def render(context)
@posts = Hash.new {|posts, name| posts[name] = Post.new(name) }
categories = context.registers[:site].categories
ignored_langs = categories.keys - LANGS - ["news"]

LANGS.each do |lang|
categories[lang].each do |post|
(categories[lang] || []).each do |post|
next if too_old(post.date)
if post.data["fallback"]
# puts "DEBUG: Skipping fallback post #{post.url} for lang #{lang}"
next
end

name = post.url.gsub(%r{\A/#{lang}/news/}, "")
@posts[name].translations << lang
@posts[name].security = true if post.data["tags"].include?("security")
@posts[name].security = true if post.data["tags"] && post.data["tags"].include?("security")
end
end

Expand Down
7 changes: 6 additions & 1 deletion javascripts/toc.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
}

function buildTOCHTML(headings) {
const pageLang = document.documentElement.lang;
let html = '<ul class="space-y-3 text-sm list-disc pl-5 marker:text-stone-400 dark:marker:text-stone-500">';
let currentLevel = 2;

Expand All @@ -39,13 +40,17 @@
const text = heading.textContent;
const id = heading.id;

// Check for lang attribute on heading or its ancestors
const lang = heading.getAttribute('lang') || heading.closest('[lang]')?.getAttribute('lang');
const langAttr = (lang && lang !== pageLang) ? ` lang="${lang}"` : '';

if (level > currentLevel) {
html += '<ul class="space-y-3 pl-4 mt-2 list-disc marker:text-stone-400 dark:marker:text-stone-500">';
} else if (level < currentLevel) {
html += '</ul>';
}

html += `<li class="mb-3"><a href="#${id}" class="toc-link text-stone-700 dark:text-stone-300 no-underline transition-colors" data-heading-id="${id}">${text}</a></li>`;
html += `<li class="mb-3"${langAttr}><a href="#${id}" class="toc-link text-stone-700 dark:text-stone-300 no-underline transition-colors" data-heading-id="${id}">${text}</a></li>`;
currentLevel = level;
});

Expand Down
4 changes: 3 additions & 1 deletion lib/linter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ class Linter
%r{\Aadmin/index\.md},
%r{\A[^/]*/examples/},
%r{\A_includes/},
%r{\Atest/}
%r{\Atest/},
%r{\Anode_modules/},
%r{\A_site/}
].freeze

WHITESPACE_EXCLUSIONS = [
Expand Down
37 changes: 37 additions & 0 deletions stylesheets/compiled.css
Original file line number Diff line number Diff line change
Expand Up @@ -1984,6 +1984,14 @@ body:is(.dark *){
color: rgb(250 250 249 / var(--tw-text-opacity, 1));
}

[lang]:lang(ja),[lang]:lang(ko),[lang]:lang(zh-CN),[lang]:lang(zh-TW) {
font-family: "Plus Jakarta Sans", var(--noto-sans-subset), -apple-system, BlinkMacSystemFont, sans-serif;
}

[lang]{
font-family: "Plus Jakarta Sans", -apple-system, BlinkMacSystemFont, sans-serif;
}

/* CJK fonts */

html:lang(ja),
Expand Down Expand Up @@ -4191,6 +4199,11 @@ html:lang(ja),
border-color: var(--color-gold-600);
}

.border-sky-200{
--tw-border-opacity: 1;
border-color: rgb(186 230 253 / var(--tw-border-opacity, 1));
}

.border-stone-200{
--tw-border-opacity: 1;
border-color: rgb(231 229 228 / var(--tw-border-opacity, 1));
Expand All @@ -4213,6 +4226,11 @@ html:lang(ja),
background-color: var(--color-ruby-100);
}

.bg-sky-50{
--tw-bg-opacity: 1;
background-color: rgb(240 249 255 / var(--tw-bg-opacity, 1));
}

.bg-stone-100{
--tw-bg-opacity: 1;
background-color: rgb(245 245 244 / var(--tw-bg-opacity, 1));
Expand Down Expand Up @@ -4597,6 +4615,11 @@ html:lang(ja),
color: var(--color-text-link);
}

.text-sky-800{
--tw-text-opacity: 1;
color: rgb(7 89 133 / var(--tw-text-opacity, 1));
}

.text-stone-400{
--tw-text-opacity: 1;
color: rgb(168 162 158 / var(--tw-text-opacity, 1));
Expand Down Expand Up @@ -5026,6 +5049,11 @@ html:lang(ja),
border-color: var(--color-gold-500);
}

.dark\:border-sky-800:is(.dark *){
--tw-border-opacity: 1;
border-color: rgb(7 89 133 / var(--tw-border-opacity, 1));
}

.dark\:border-stone-600:is(.dark *){
--tw-border-opacity: 1;
border-color: rgb(87 83 78 / var(--tw-border-opacity, 1));
Expand All @@ -5044,6 +5072,10 @@ html:lang(ja),
background-color: var(--color-ruby-800);
}

.dark\:bg-sky-900\/30:is(.dark *){
background-color: rgb(12 74 110 / 0.3);
}

.dark\:bg-stone-700:is(.dark *){
--tw-bg-opacity: 1;
background-color: rgb(68 64 60 / var(--tw-bg-opacity, 1));
Expand Down Expand Up @@ -5085,6 +5117,11 @@ html:lang(ja),
color: var(--color-ruby-500);
}

.dark\:text-sky-200:is(.dark *){
--tw-text-opacity: 1;
color: rgb(186 230 253 / var(--tw-text-opacity, 1));
}

.dark\:text-stone-100:is(.dark *){
--tw-text-opacity: 1;
color: rgb(245 245 244 / var(--tw-text-opacity, 1));
Expand Down
4 changes: 4 additions & 0 deletions stylesheets/tailwind.css
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
@apply dark:bg-stone-900 dark:text-stone-50;
}

[lang] {
@apply font-default;
}

/* CJK fonts */
html:lang(ja),
body:lang(ja),
Expand Down
Loading
Loading