mirror of
https://github.com/discourse/discourse.git
synced 2026-08-10 23:59:31 +08:00
## Problem
The AI localization feature currently generates `hreflang` tags using
underscores (e.g., `pt_BR`). According to ISO 639-1 and SEO standards,
search engines like Google expect hyphens (e.g., `pt-BR`). This causes
validation errors in tools like PageSpeed Insights and Google Search
Console ("Unexpected language code").
## Solution
This PR applies `.tr('_', '-')` to the `locale` string within the
`hreflang` attribute. This ensures that:
1. The `href` URL remains functional (still using the internal
underscore format for the parameter).
2. The `hreflang` metadata becomes SEO-compliant by using the hyphen
format.
## Validation
Confirmed that this change transforms codes like `pt_BR` into `pt-BR` in
the rendered HTML header, resolving the "Unexpected language code"
error.
---------
Co-authored-by: Natalie Tay <natalie.tay@gmail.com>
140 lines
5.3 KiB
Ruby
Vendored
140 lines
5.3 KiB
Ruby
Vendored
# frozen_string_literal: true
|
|
|
|
describe "Crawler hreflang tags" do
|
|
fab!(:user)
|
|
fab!(:post) { Fabricate(:post, user:) }
|
|
|
|
describe "when viewing a topic as crawler" do
|
|
before do
|
|
SiteSetting.content_localization_enabled = true
|
|
SiteSetting.content_localization_crawler_param = true
|
|
SiteSetting.content_localization_supported_locales = "en|ja|es"
|
|
end
|
|
|
|
it "includes hreflang tags when viewed as crawler" do
|
|
get "/t/#{post.topic.slug}/#{post.topic.id}",
|
|
headers: {
|
|
"User-Agent" => "Googlebot/2.1 (+http://www.google.com/bot.html)",
|
|
}
|
|
|
|
expect(response.body).to include('<link rel="alternate" href=')
|
|
expect(response.body).to include('hreflang="x-default"')
|
|
expect(response.body).to include('hreflang="en"')
|
|
expect(response.body).to include('hreflang="ja"')
|
|
expect(response.body).to include('hreflang="es"')
|
|
expect(response.body).to include("?#{Discourse::LOCALE_PARAM}=ja")
|
|
end
|
|
|
|
it "uses hyphens instead of underscores in hreflang attribute for region-qualified locales" do
|
|
SiteSetting.content_localization_supported_locales = "en|pt_BR|zh_CN"
|
|
|
|
get "/t/#{post.topic.slug}/#{post.topic.id}",
|
|
headers: {
|
|
"User-Agent" => "Googlebot/2.1 (+http://www.google.com/bot.html)",
|
|
}
|
|
|
|
expect(response.body).to include('hreflang="pt-BR"')
|
|
expect(response.body).to include('hreflang="zh-CN"')
|
|
expect(response.body).not_to include('hreflang="pt_BR"')
|
|
expect(response.body).not_to include('hreflang="zh_CN"')
|
|
|
|
expect(response.body).to include("?#{Discourse::LOCALE_PARAM}=pt_BR")
|
|
expect(response.body).to include("?#{Discourse::LOCALE_PARAM}=zh_CN")
|
|
end
|
|
|
|
it "doesn't include hreflang tags for normal users" do
|
|
get "/t/#{post.topic.slug}/#{post.topic.id}"
|
|
|
|
expect(response.body).not_to include('hreflang="x-default"')
|
|
end
|
|
|
|
it "doesn't include hreflang tags when settings are disabled" do
|
|
SiteSetting.content_localization_enabled = false
|
|
get "/t/#{post.topic.slug}/#{post.topic.id}",
|
|
headers: {
|
|
"User-Agent" => "Googlebot/2.1 (+http://www.google.com/bot.html)",
|
|
}
|
|
|
|
expect(response.body).not_to include('hreflang="x-default"')
|
|
end
|
|
|
|
it "self-canonicalizes translated topic pages when ?tl= is present" do
|
|
get "/t/#{post.topic.slug}/#{post.topic.id}?#{Discourse::LOCALE_PARAM}=ja",
|
|
headers: {
|
|
"User-Agent" => "Googlebot/2.1 (+http://www.google.com/bot.html)",
|
|
}
|
|
|
|
expect(response.body).to include(
|
|
%(<link rel="canonical" href="#{Discourse.base_url}/t/#{post.topic.slug}/#{post.topic.id}?#{Discourse::LOCALE_PARAM}=ja">),
|
|
)
|
|
expect(response.headers["X-Robots-Tag"].to_s).not_to include("noindex")
|
|
end
|
|
|
|
it "self-canonicalizes translated list pages when ?tl= is present" do
|
|
get "/latest?#{Discourse::LOCALE_PARAM}=ja",
|
|
headers: {
|
|
"User-Agent" => "Googlebot/2.1 (+http://www.google.com/bot.html)",
|
|
}
|
|
|
|
expect(response.body).to include(
|
|
%(<link rel="canonical" href="#{Discourse.base_url}/latest?#{Discourse::LOCALE_PARAM}=ja">),
|
|
)
|
|
end
|
|
|
|
it "does not append ?tl= to canonical when locale param is absent" do
|
|
get "/t/#{post.topic.slug}/#{post.topic.id}",
|
|
headers: {
|
|
"User-Agent" => "Googlebot/2.1 (+http://www.google.com/bot.html)",
|
|
}
|
|
|
|
expect(response.body).to match(
|
|
%r{<link rel="canonical" href="#{Regexp.escape(Discourse.base_url)}/t/#{post.topic.slug}/#{post.topic.id}"\s*/?>},
|
|
)
|
|
end
|
|
|
|
it "ignores ?tl= when the locale is not in the supported list" do
|
|
get "/t/#{post.topic.slug}/#{post.topic.id}?#{Discourse::LOCALE_PARAM}=xyz",
|
|
headers: {
|
|
"User-Agent" => "Googlebot/2.1 (+http://www.google.com/bot.html)",
|
|
}
|
|
|
|
expect(response.body).to include(
|
|
%(<link rel="canonical" href="#{Discourse.base_url}/t/#{post.topic.slug}/#{post.topic.id}">),
|
|
)
|
|
end
|
|
|
|
it "uses & as separator when another allowed param is present" do
|
|
Fabricate.times(35, :post, topic: post.topic, user:)
|
|
|
|
get "/t/#{post.topic.slug}/#{post.topic.id}?page=2&#{Discourse::LOCALE_PARAM}=ja",
|
|
headers: {
|
|
"User-Agent" => "Googlebot/2.1 (+http://www.google.com/bot.html)",
|
|
}
|
|
|
|
expect(response.body).to include(
|
|
%(<link rel="canonical" href="#{Discourse.base_url}/t/#{post.topic.slug}/#{post.topic.id}?page=2&#{Discourse::LOCALE_PARAM}=ja">),
|
|
)
|
|
end
|
|
|
|
it "does not modify canonical when content_localization_crawler_param is disabled" do
|
|
SiteSetting.content_localization_crawler_param = false
|
|
|
|
get "/t/#{post.topic.slug}/#{post.topic.id}?#{Discourse::LOCALE_PARAM}=ja",
|
|
headers: {
|
|
"User-Agent" => "Googlebot/2.1 (+http://www.google.com/bot.html)",
|
|
}
|
|
|
|
expect(response.body).to match(
|
|
%r{<link rel="canonical" href="#{Regexp.escape(Discourse.base_url)}/t/#{post.topic.slug}/#{post.topic.id}"\s*/?>},
|
|
)
|
|
end
|
|
|
|
it "does not append ?tl= to canonical for non-crawler requests" do
|
|
get "/t/#{post.topic.slug}/#{post.topic.id}?#{Discourse::LOCALE_PARAM}=ja"
|
|
|
|
expect(response.body).to match(
|
|
%r{<link rel="canonical" href="#{Regexp.escape(Discourse.base_url)}/t/#{post.topic.slug}/#{post.topic.id}"\s*/?>},
|
|
)
|
|
end
|
|
end
|
|
end
|