2
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2025-08-17 18:04:11 +08:00

FEATURE: Localize language names (#33790)

This PR adds localized language names to settings. The language names
are localized in the frontend, not the backend, due to setting
initialization complexity.

This change affects these areas:
- `SiteSetting.available_locales` 
- this "setting" is a lookup table to get language names. use
`languageNameLookup` service to get the name for a locale
- it returns an object that looks like the following, then gets
re-hydrated with client localized values when initializing the
`siteSettingService` in the frontend.
  ```
  [
{"native_name":"اللغة العربية","value":"ar","name":"languages.ar.name"},
    ...
  ]  
  ```

- `SiteSetting.default_locale` 
- this is a single-value `enum` setting that has always been hardcoded.
This caused quite an issue as it is not initialized the same way as
other site settings in the yml file. It has always relied on reading
directly from a `names.yml` file to load native language names, thus
bypassing the need for I18n to be initialized from the backend. A new
locale_enum type has been introduced for this setting, and any future
settings.
  
- `SiteSetting.content_localization_supported_locales` - this is a
`enum_list` setting,
  - enum_list is introduced, leveraging both `list` and `enum`
  
- theme translations

- site texts

- Wizard's default_locale choices 
- it was set up from the backend using `LocaleSiteSetting.value`. This
proved problematic, as a Japanese user would be getting the locales in
English because the values are initialized using English even without
memoization
- therefore we're now initializing the choices in the frontend using
`available_locales` as defined above
  
- content localization meta data
- post language in the composer, localization composer, post history
modal, post language tooltip, language switcher



/t/151409
This commit is contained in:
Natalie Tay 2025-07-29 11:48:45 +08:00 committed by GitHub
parent ece7f0f42c
commit 235c673fe8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
39 changed files with 419 additions and 110 deletions

View file

@ -43,6 +43,8 @@ const CUSTOM_TYPES = [
"file_size_restriction",
"file_types_list",
"font_list",
"locale_list",
"locale_enum",
];
export default class SiteSettingComponent extends Component {

View file

@ -0,0 +1,34 @@
import Component from "@glimmer/component";
import { hash } from "@ember/helper";
import { action } from "@ember/object";
import { service } from "@ember/service";
import ComboBox from "select-kit/components/combo-box";
export default class LocaleEnum extends Component {
@service languageNameLookup;
get content() {
return this.args.setting.validValues.map(({ value }) => ({
name: this.languageNameLookup.getLanguageName(value),
value,
}));
}
@action
onChangeLocale(value) {
this.args.changeValueCallback(value);
}
<template>
<ComboBox
@content={{this.content}}
@value={{@value}}
@onChange={{this.onChangeLocale}}
@valueProperty={{@setting.computedValueProperty}}
@nameProperty={{@setting.computedNameProperty}}
@options={{hash castInteger=true allowAny=@setting.allowsNone}}
/>
{{@preview}}
</template>
}

View file

@ -0,0 +1,43 @@
import Component from "@glimmer/component";
import { hash } from "@ember/helper";
import { action } from "@ember/object";
import { service } from "@ember/service";
import ListSetting from "select-kit/components/list-setting";
export default class LocaleList extends Component {
@service siteSettings;
tokenSeparator = "|";
get choices() {
const allLocales = this.siteSettings.available_locales;
return this.args.setting.validValues.map(({ value, name }) => ({
name: allLocales.find((locale) => locale.value === value)?.name || name,
value,
}));
}
get settingValue() {
return this.args.value
.toString()
.split(this.tokenSeparator)
.filter(Boolean);
}
@action
onChangeListSetting(value) {
this.args.changeValueCallback(value.join(this.tokenSeparator));
}
<template>
<ListSetting
@value={{this.settingValue}}
@settingName={{@setting.setting}}
@choices={{this.choices}}
@nameProperty="name"
@valueProperty="value"
@onChange={{this.onChangeListSetting}}
@options={{hash allowAny=@allowAny}}
/>
</template>
}

View file

@ -285,7 +285,7 @@ export default class AdminCustomizeThemesShowIndexController extends Controller
}
get availableLocales() {
return JSON.parse(this.siteSettings.available_locales);
return this.siteSettings.available_locales;
}
get locale() {

View file

@ -79,7 +79,7 @@ export default class AdminSiteTextIndexController extends Controller {
}
get availableLocales() {
return JSON.parse(this.siteSettings.available_locales);
return this.siteSettings.available_locales;
}
get fallbackLocaleFullName() {

View file

@ -15,7 +15,7 @@ export default class AdminSiteTextEditRoute extends Route {
}
setupController(controller, siteText) {
const locales = JSON.parse(this.siteSettings.available_locales);
const locales = this.siteSettings.available_locales;
const localeFullName = locales.find((locale) => {
return locale.value === controller.locale;

View file

@ -11,6 +11,7 @@ export default class LanguageSwitcher extends Component {
@service site;
@service siteSettings;
@service router;
@service languageNameLookup;
@action
async changeLocale(locale) {
@ -21,6 +22,15 @@ export default class LanguageSwitcher extends Component {
window.location.reload();
}
get content() {
return this.siteSettings.available_content_localization_locales.map(
({ value }) => ({
name: this.languageNameLookup.getLanguageName(value),
value,
})
);
}
@action
onRegisterApi(api) {
this.dMenu = api;
@ -36,10 +46,7 @@ export default class LanguageSwitcher extends Component {
>
<:content>
<DropdownMenu as |dropdown|>
{{#each
this.siteSettings.available_content_localization_locales
as |option|
}}
{{#each this.content as |option|}}
<dropdown.item
class="locale-options"
data-menu-option-id={{option.value}}

View file

@ -2,6 +2,7 @@ import Component from "@ember/component";
import EmberObject from "@ember/object";
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
import didUpdate from "@ember/render-modifiers/modifiers/did-update";
import { service } from "@ember/service";
import { htmlSafe } from "@ember/template";
import { and, eq, not, or } from "truth-helpers";
import LinksRedirect from "discourse/components/links-redirect";
@ -22,6 +23,8 @@ function tagClasses(tagChanges, state, className) {
}
export default class Revisions extends Component {
@service languageNameLookup;
get fakePreviousTagsTopic() {
// discourseTags expects a topic structure
return EmberObject.create({
@ -44,17 +47,15 @@ export default class Revisions extends Component {
}
get previousLocale() {
return (
this.get("model.locale_changes.previous") ||
i18n("post.revisions.locale.no_locale_set")
);
const locale = this.get("model.locale_changes.previous");
const language = this.languageNameLookup.getLanguageName(locale);
return language || i18n("post.revisions.locale.no_locale_set");
}
get currentLocale() {
return (
this.get("model.locale_changes.current") ||
i18n("post.revisions.locale.locale_removed")
);
const locale = this.get("model.locale_changes.current");
const language = this.languageNameLookup.getLanguageName(locale);
return language || i18n("post.revisions.locale.locale_removed");
}
<template>

View file

@ -8,6 +8,7 @@ import DMenu from "float-kit/components/d-menu";
export default class PostLanguageSelector extends Component {
@service siteSettings;
@service languageNameLookup;
get selectedLanguage() {
return (
@ -17,6 +18,15 @@ export default class PostLanguageSelector extends Component {
);
}
get content() {
return this.siteSettings.available_content_localization_locales.map(
({ value }) => ({
name: this.languageNameLookup.getLanguageName(value),
value,
})
);
}
@action
selectPostLanguage(locale) {
this.args.composerModel.locale = locale;
@ -40,10 +50,7 @@ export default class PostLanguageSelector extends Component {
>
<:content>
<DropdownMenu as |dropdown|>
{{#each
this.siteSettings.available_content_localization_locales
as |locale|
}}
{{#each this.content as |locale|}}
<dropdown.item
class="locale-options"
data-menu-option-id={{locale.value}}

View file

@ -51,12 +51,18 @@ export default class PostTranslationEditor extends Component {
}
get availableContentLocalizationLocales() {
const originalPostLocale =
this.composer.model?.post?.locale || this.siteSettings.default_locale;
const originalPostLocale = this.composer.model?.post?.locale;
return this.siteSettings.available_content_localization_locales.filter(
(locale) => locale.value !== originalPostLocale
);
return this.siteSettings.available_content_localization_locales
.filter(({ value }) => value !== originalPostLocale)
.map(({ native_name, name, value }) => {
name =
i18n(name) === native_name
? native_name
: `${i18n(name)} (${native_name})`;
return { name, value };
});
}
@action

View file

@ -1,12 +1,16 @@
import Component from "@glimmer/component";
import { service } from "@ember/service";
import { i18n } from "discourse-i18n";
import DTooltip from "float-kit/components/d-tooltip";
export default class PostMetaDataLanguage extends Component {
// TODO (glimmer-post-stream) once we switch to glimmer, we can remove `this.args.data.x` from the following 2 getters
@service languageNameLookup;
get language() {
return this.args.data?.language || this.args.post?.language;
const lang = this.args.data?.language || this.args.post?.language;
return this.languageNameLookup.getLanguageName(lang);
}
get outdated() {

View file

@ -91,7 +91,7 @@ export default class InterfaceController extends Controller {
@discourseComputed()
availableLocales() {
return JSON.parse(this.siteSettings.available_locales);
return this.siteSettings.available_locales;
}
@discourseComputed

View file

@ -73,6 +73,7 @@ export default class TopicController extends Controller {
@service siteSettings;
@service site;
@service appEvents;
@service languageNameLookup;
@tracked model;
@ -897,9 +898,10 @@ export default class TopicController extends Controller {
return this._openComposerForEdit(topic, post);
}
const language = this.languageNameLookup.getLanguageName(post.language);
return this.dialog.alert({
message: i18n("post.localizations.edit_warning.message", {
language: post.language,
language,
}),
buttons: [
{

View file

@ -0,0 +1,14 @@
import Service, { service } from "@ember/service";
import { disableImplicitInjections } from "discourse/lib/implicit-injections";
@disableImplicitInjections
export default class LanguageNameLookup extends Service {
@service siteSettings;
getLanguageName(locale) {
const name = this.siteSettings.available_locales.find(
({ value }) => value === locale
)?.name;
return name || locale;
}
}

View file

@ -1,6 +1,7 @@
import { TrackedObject } from "@ember-compat/tracked-built-ins";
import { disableImplicitInjections } from "discourse/lib/implicit-injections";
import PreloadStore from "discourse/lib/preload-store";
import i18n from "discourse-i18n";
export function createSiteSettingsFromPreloaded(
siteSettings,
@ -15,6 +16,22 @@ export function createSiteSettingsFromPreloaded(
settings.themeSiteSettingOverrides = themeSiteSettingOverrides;
}
// localize locale names here as they are not localized in the backend
// due to initialization order and caching
if (settings.available_locales) {
const locales = JSON.parse(settings.available_locales);
const localizedLocales = locales.map(({ native_name, value, name }) => {
const localized_name = i18n.t(name);
const displayName =
localized_name && localized_name !== native_name
? `${localized_name} (${native_name})`
: native_name;
return { value, name: displayName };
});
settings.available_locales = localizedLocales;
}
settings.groupSettingArray = (groupSetting) => {
const setting = settings[groupSetting];
if (!setting) {

View file

@ -1,6 +1,7 @@
import Component from "@glimmer/component";
import { hash } from "@ember/helper";
import { action, set } from "@ember/object";
import { service } from "@ember/service";
import { Choice } from "discourse/static/wizard/models/wizard";
import { i18n } from "discourse-i18n";
import ColorPalettes from "select-kit/components/color-palettes";
@ -9,6 +10,8 @@ import FontSelector from "select-kit/components/font-selector";
import HomepageStyleSelector from "select-kit/components/homepage-style-selector";
export default class Dropdown extends Component {
@service siteSettings;
constructor() {
super(...arguments);
@ -69,6 +72,16 @@ export default class Dropdown extends Component {
);
}
}
if (this.args.field.id === "default_locale") {
this.args.field.choices = this.siteSettings.available_locales.map(
(locale) =>
new Choice({
id: locale.value,
label: locale.name,
})
);
}
}
get component() {
@ -86,6 +99,15 @@ export default class Dropdown extends Component {
}
}
get nameProperty() {
switch (this.args.field.id) {
case "default_locale":
return "name";
default:
return "label";
}
}
keyPress(event) {
event.stopPropagation();
}
@ -101,7 +123,7 @@ export default class Dropdown extends Component {
class="wizard-container__dropdown"
value=@field.value
content=@field.choices
nameProperty="label"
nameProperty=this.nameProperty
tabindex="9"
onChange=this.onChangeValug
options=(hash translatedNone=false)

View file

@ -5,7 +5,7 @@ import { acceptance } from "discourse/tests/helpers/qunit-helpers";
acceptance("Admin - Site Texts", function (needs) {
needs.user();
needs.settings({
available_locales: JSON.stringify([{ name: "English", value: "en" }]),
available_locales: [{ name: "English", value: "en" }],
default_locale: "en",
});

View file

@ -292,29 +292,35 @@ module("Integration | Component | Post", function (hooks) {
test("language", async function (assert) {
this.post.is_localized = true;
this.post.language = "English";
this.post.language = "en";
this.siteSettings.available_locales = [
{ value: "en", name: "English (US)" },
];
await renderComponent(this.post);
await triggerEvent(".fk-d-tooltip__trigger", "pointermove");
assert.dom(".post-language").hasText(
i18n("post.original_language", {
language: "English",
language: "English (US)",
})
);
});
test("outdated localization", async function (assert) {
this.post.is_localized = true;
this.post.language = "English";
this.post.language = "en";
this.post.localization_outdated = true;
this.siteSettings.available_locales = [
{ value: "en", name: "English (US)" },
];
await renderComponent(this.post);
await triggerEvent(".fk-d-tooltip__trigger", "pointermove");
assert.dom(".post-language").hasText(
i18n("post.original_language_and_outdated", {
language: "English",
language: "English (US)",
})
);
});

View file

@ -1,16 +1,20 @@
# frozen_string_literal: true
class LocaleSiteSetting < EnumSiteSetting
def self.translate_names?
true
end
def self.valid_value?(val)
supported_locales.include?(val)
val.split("|").all? { |v| supported_locales.include?(v) }
end
def self.values
@values ||=
supported_locales.map do |locale|
lang = language_names[locale] || language_names[locale.split("_")[0]]
{ name: lang ? lang["nativeName"] : locale, value: locale }
end
supported_locales.map do |locale|
lang = language_names[locale] || language_names[locale.split("_").first]
native_name = lang&.dig("nativeName")
{ native_name:, value: locale, name: "languages.#{locale}.name" }
end
end
@lock = Mutex.new
@ -53,10 +57,6 @@ class LocaleSiteSetting < EnumSiteSetting
@lock.synchronize { @values = @language_names = @supported_locales = nil }
end
def self.get_language_name(locale)
values.find { |v| v[:value] == locale.to_s.sub("-", "_") }&.[](:name)
end
FALLBACKS = { en_GB: :en }
def self.fallback_locale(locale)

View file

@ -194,9 +194,8 @@ class PostRevisionSerializer < ApplicationSerializer
end
def locale_changes
prev = LocaleSiteSetting.get_language_name(previous["locale"])
cur = LocaleSiteSetting.get_language_name(current["locale"])
prev = previous["locale"].presence
cur = current["locale"].presence
{ previous: prev, current: cur }
end

View file

@ -688,7 +688,7 @@ class PostSerializer < BasicPostSerializer
end
def language
LocaleSiteSetting.get_language_name(object.locale) || locale
locale
end
def include_language?

View file

@ -155,6 +155,105 @@ en:
placeholder: date
from_placeholder: "from date"
to_placeholder: "to date"
languages:
ar:
name: Arabic
be:
name: Belarusian
bg:
name: Bulgarian
bs_BA:
name: Bosnian
ca:
name: Catalan
cs:
name: Czech
da:
name: Danish
de:
name: German
el:
name: Greek
en:
name: English (US)
en_GB:
name: English (UK)
es:
name: Spanish
et:
name: Estonian
fa_IR:
name: Persian
fi:
name: Finnish
fr:
name: French
gl:
name: Galician
he:
name: Hebrew
hr:
name: Croatian
hu:
name: Hungarian
hy:
name: Armenian
id:
name: Indonesian
it:
name: Italian
ja:
name: Japanese
ko:
name: Korean
lt:
name: Lithuanian
lv:
name: Latvian
nb_NO:
name: Norwegian Bokmål
nl:
name: Dutch
pl_PL:
name: Polish
pt:
name: Portuguese
pt_BR:
name: Portuguese
ro:
name: Romanian
ru:
name: Russian
sk:
name: Slovak
sl:
name: Slovene
sq:
name: Albanian
sr:
name: Serbian
sv:
name: Swedish
sw:
name: Swahili
te:
name: Telugu
th:
name: Thai
tr_TR:
name: Turkish
ug:
name: Uyghur
uk:
name: Ukrainian
ur:
name: Urdu
vi:
name: Vietnamese
zh_TW:
name: Chinese
zh_CN:
name: Chinese Simplified
share:
topic_html: 'Topic: <span class="topic-title">%{topicTitle}</span>'
post: "post #%{postNumber} by @%{username}"

View file

@ -1638,9 +1638,9 @@ content_localization:
default: ""
type: list
client: true
list_type: named
list_type: locale
allow_any: false
choices: "LocaleSiteSetting.values"
enum: "LocaleSiteSetting"
area: "localization"
validator: "ContentLocalizationLocalesValidator"
content_localization_max_locales:

View file

@ -273,7 +273,7 @@ module SiteSettingExtension
default: SiteSettings::DefaultsProvider::DEFAULT_LOCALE,
category: "required",
description: description("default_locale"),
type: SiteSetting.types[SiteSetting.types[:enum]],
type: SiteSetting.types[SiteSetting.types[:locale_enum]],
preview: nil,
value: self.default_locale,
valid_values: LocaleSiteSetting.values,
@ -963,10 +963,8 @@ module SiteSettingExtension
plugins[name] = opts[:plugin] if opts[:plugin]
type_supervisor.load_setting(
name,
opts.extract!(*SiteSettings::TypeSupervisor::CONSUMED_OPTS),
)
choices_opts = opts.extract!(*SiteSettings::TypeSupervisor::CONSUMED_OPTS)
type_supervisor.load_setting(name, choices_opts)
if !shadowed_val.nil?
setup_shadowed_methods(name, shadowed_val)

View file

@ -61,6 +61,7 @@ class SiteSettings::TypeSupervisor
tag_group_list: 26,
file_size_restriction: 27,
objects: 28,
locale_enum: 29,
)
end
@ -180,9 +181,10 @@ class SiteSettings::TypeSupervisor
def type_hash(name)
name = name.to_sym
type = get_type(name)
list_type = get_list_type(name)
result = { type: type.to_s }
if type == :enum
if type == :enum || list_type == "locale"
if (klass = get_enum_class(name))
result.merge!(valid_values: klass.values, translate_names: klass.translate_names?)
else
@ -231,7 +233,7 @@ class SiteSettings::TypeSupervisor
end
def validate_value(name, type, val)
if type == self.class.types[:enum]
if type == self.class.types[:enum] || get_list_type(name) == "locale"
if get_enum_class(name)
unless get_enum_class(name).valid_value?(val)
raise Discourse::InvalidParameters.new("Invalid value `#{val}` for `#{name}`")
@ -243,9 +245,7 @@ class SiteSettings::TypeSupervisor
raise Discourse::InvalidParameters.new(:value) if choice.exclude?(val)
end
end
if type == self.class.types[:list] || type == self.class.types[:string]
elsif type == self.class.types[:list] || type == self.class.types[:string]
if @allow_any.key?(name) && !@allow_any[name]
split = val.to_s.split("|")
resolved_choices = @choices[name]

View file

@ -39,17 +39,12 @@ class Wizard
value: SiteSetting.site_description,
)
languages =
step.add_field(
id: "default_locale",
type: "dropdown",
required: false,
value: SiteSetting.default_locale,
)
LocaleSiteSetting.values.each do |locale|
languages.add_choice(locale[:value], label: locale[:name])
end
step.add_field(
id: "default_locale",
type: "dropdown",
required: false,
value: SiteSetting.default_locale,
)
step.on_update do |updater|
updater.ensure_changed(:title)

View file

@ -42,9 +42,7 @@ export default class AiComposerHelperMenu extends Component {
// Since we want site default translations (and we are using: force_default_locale)
// we need to replace the translated_name with the site default locale name
const siteLocale = this.siteSettings.default_locale;
const availableLocales = JSON.parse(
this.siteSettings.available_locales
);
const availableLocales = this.siteSettings.available_locales;
const locale = availableLocales.find((l) => l.value === siteLocale);
const translatedName = i18n(
"discourse_ai.ai_helper.context_menu.translate_prompt",

View file

@ -22,7 +22,7 @@ export default class RegionInput extends ComboBoxComponent {
const localeNames = {};
let regions = [];
JSON.parse(this.siteSettings.available_locales).forEach((locale) => {
this.siteSettings.available_locales.forEach((locale) => {
localeNames[locale.value] = locale.name;
});

View file

@ -7,7 +7,7 @@ acceptance("Admin - Discourse Calendar - Holidays", function (needs) {
needs.user();
needs.settings({
calendar_enabled: true,
available_locales: JSON.stringify([{ name: "English", value: "en" }]),
available_locales: [{ name: "English", value: "en" }],
});
needs.pretender((server, helper) => {

View file

@ -8,9 +8,7 @@ module("Integration | Component | region-input", function (hooks) {
setupRenderingTest(hooks);
test("displaying the 'None' region option", async function (assert) {
this.siteSettings.available_locales = JSON.stringify([
{ name: "English", value: "en" },
]);
this.siteSettings.available_locales = [{ name: "English", value: "en" }];
await render(
<template><RegionInput @allowNoneRegion={{true}} /></template>
@ -26,9 +24,7 @@ module("Integration | Component | region-input", function (hooks) {
});
test("hiding the 'None' region option", async function (assert) {
this.siteSettings.available_locales = JSON.stringify([
{ name: "English", value: "en" },
]);
this.siteSettings.available_locales = [{ name: "English", value: "en" }];
await render(
<template><RegionInput @allowNoneRegion={{false}} /></template>

View file

@ -8,7 +8,7 @@ RSpec.describe LocaleSiteSetting do
def native_locale_name(locale)
value = LocaleSiteSetting.values.find { |v| v[:value] == locale }
value[:name]
value[:native_name]
end
describe ".valid_value?" do
@ -56,7 +56,11 @@ RSpec.describe LocaleSiteSetting do
end
after do
DiscoursePluginRegistry.reset!
DiscoursePluginRegistry.unregister_locale("foo")
DiscoursePluginRegistry.unregister_locale("bar")
DiscoursePluginRegistry.unregister_locale("de")
DiscoursePluginRegistry.unregister_locale("de_AT")
DiscoursePluginRegistry.unregister_locale("tlh")
LocaleSiteSetting.reset!
end
@ -64,6 +68,7 @@ RSpec.describe LocaleSiteSetting do
it "returns true for locales from core" do
expect(LocaleSiteSetting.valid_value?("en")).to eq(true)
expect(LocaleSiteSetting.valid_value?("de")).to eq(true)
expect(LocaleSiteSetting.valid_value?("en|de")).to eq(true)
end
it "returns true for locales added by plugins" do
@ -82,8 +87,8 @@ RSpec.describe LocaleSiteSetting do
expect(native_locale_name("de")).to eq("Deutsch")
end
it "returns the language code when no nativeName is set" do
expect(native_locale_name("tlh")).to eq("tlh")
it "returns nothing when no nativeName is set" do
expect(native_locale_name("tlh")).to eq(nil)
end
end
@ -116,19 +121,4 @@ RSpec.describe LocaleSiteSetting do
end
end
end
describe ".get_language_name" do
it "returns the language name for a valid locale" do
expect(LocaleSiteSetting.get_language_name("en")).to eq("English (US)")
expect(LocaleSiteSetting.get_language_name("es")).to eq("Español")
end
it "returns nil for a locale that doesn't exist" do
expect(LocaleSiteSetting.get_language_name("xx")).to be_nil
end
it "handles symbol locales" do
expect(LocaleSiteSetting.get_language_name(:en_GB)).to eq("English (UK)")
end
end
end

View file

@ -43,7 +43,8 @@ RSpec.describe StepsController do
end
it "returns errors if the field has them" do
put "/wizard/steps/introduction.json", params: { fields: { title: "" } }
title = SiteSetting.title
put "/wizard/steps/introduction.json", params: { fields: { title: } }
expect(response.status).to eq(422)
end

View file

@ -156,7 +156,7 @@ RSpec.describe PostRevisionSerializer do
root: false,
).as_json
expect(json[:locale_changes][:previous]).to eq("日本語")
expect(json[:locale_changes][:previous]).to eq("ja")
expect(json[:locale_changes][:current]).to eq(nil)
end
@ -173,7 +173,7 @@ RSpec.describe PostRevisionSerializer do
).as_json
expect(json[:locale_changes][:previous]).to eq(nil)
expect(json[:locale_changes][:current]).to eq("日本語")
expect(json[:locale_changes][:current]).to eq("ja")
end
end
end

View file

@ -788,7 +788,7 @@ RSpec.describe PostSerializer do
SiteSetting.content_localization_enabled = true
post.update!(locale: "ja")
expect(json[:language]).to eq("日本語")
expect(json[:language]).to eq("ja")
end
it "defaults to locale if language does not exist" do

View file

@ -0,0 +1,51 @@
# frozen_string_literal: true
describe "Admin Site Setting Locales", type: :system do
let(:settings_page) { PageObjects::Pages::AdminSiteSettings.new }
fab!(:admin)
before do
sign_in(admin)
SiteSetting.default_locale = "es"
SiteSetting.content_localization_supported_locales = "es|en"
end
context "for locale enum" do
it "allows selection of a different locale" do
settings_page.visit
settings_page.type_in_search("default locale")
expect(settings_page.find_setting("default_locale")).to have_content("Spanish (Español)")
settings_page.select_enum_value("default_locale", "en")
settings_page.save_setting("default_locale")
settings_page.type_in_search("default locale")
expect(settings_page.find_setting("default_locale")).to have_content("English (US)")
end
end
context "for locale lists" do
it "allows adding and removing locales" do
SiteSetting.content_localization_supported_locales = "ja"
sign_in(admin)
settings_page.visit("content_localization_supported_locales")
expect(settings_page.find_setting("content_localization_supported_locales")).to have_content(
"Japanese (日本語)",
)
settings_page.select_list_values("content_localization_supported_locales", %w[en])
settings_page.save_setting("content_localization_supported_locales")
expect(settings_page.find_setting("content_localization_supported_locales")).to have_content(
"Japanese (日本語), English (US)",
)
# confirm persist on reload
settings_page.visit("content_localization_supported_locales")
expect(settings_page.find_setting("content_localization_supported_locales")).to have_content(
"Japanese (日本語), English (US)",
)
end
end
end

View file

@ -45,8 +45,8 @@ RSpec.describe "Anonymous user language switcher", type: :system do
switcher.expand
expect(switcher).to have_content("English (US)")
expect(switcher).to have_content("日本語")
expect(switcher).to have_content("Español")
expect(switcher).to have_content("Japanese (日本語)")
expect(switcher).to have_content("Spanish (Español)")
SiteSetting.content_localization_supported_locales = "ja"
visit("/")

View file

@ -191,11 +191,16 @@ describe "Content Localization" do
let(:banner) { PageObjects::Components::AdminChangesBanner.new }
it "does not allow more than the maximum number of locales" do
SiteSetting.content_localization_supported_locales = "en|ja"
SiteSetting.content_localization_max_locales = 2
sign_in(admin)
settings_page.visit("content_localization_supported_locales")
settings_page.select_list_values("content_localization_supported_locales", %w[en ja es])
expect(settings_page.find_setting("content_localization_supported_locales")).to have_content(
"English (US), Japanese",
)
settings_page.select_list_values("content_localization_supported_locales", %w[es])
settings_page.save_setting("content_localization_supported_locales")
expect(settings_page.error_message("content_localization_supported_locales")).to have_content(
I18n.t(

View file

@ -41,6 +41,16 @@ module PageObjects
self
end
def select_enum_value(setting_name, value)
setting =
PageObjects::Components::SelectKit.new(
".row.setting[data-setting='#{setting_name}'] .single-select",
)
setting.expand
setting.select_row_by_value(value)
self
end
def has_setting?(setting_name)
has_css?(".row.setting[data-setting=\"#{setting_name}\"]")
end

View file

@ -28,6 +28,8 @@ describe "Post translations", type: :system do
context "when a post does not have translations" do
it "should only show the languages listed in the site setting" do
post.update!(locale: "en")
topic_page.visit_topic(topic)
find("#post_#{post.post_number} .post-action-menu__add-translation").click
translation_selector.expand
@ -156,9 +158,9 @@ describe "Post translations", type: :system do
page.find("#create-topic").click
post_language_selector.expand
expect(post_language_selector).to have_content("English (US)") # default locale
expect(post_language_selector).to have_content("Français")
expect(post_language_selector).to have_content("Español")
expect(post_language_selector).to have_content("Português (BR)")
expect(post_language_selector).to have_content("French (Français)")
expect(post_language_selector).to have_content("Spanish (Español)")
expect(post_language_selector).to have_content("Portuguese (Português (BR))")
expect(post_language_selector).to have_content(
I18n.t("js.post.localizations.post_language_selector.none"),
)