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:
parent
ece7f0f42c
commit
235c673fe8
39 changed files with 419 additions and 110 deletions
|
@ -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 {
|
||||
|
|
|
@ -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>
|
||||
}
|
|
@ -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>
|
||||
}
|
|
@ -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() {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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}}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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}}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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: [
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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",
|
||||
});
|
||||
|
||||
|
|
|
@ -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)",
|
||||
})
|
||||
);
|
||||
});
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -688,7 +688,7 @@ class PostSerializer < BasicPostSerializer
|
|||
end
|
||||
|
||||
def language
|
||||
LocaleSiteSetting.get_language_name(object.locale) || locale
|
||||
locale
|
||||
end
|
||||
|
||||
def include_language?
|
||||
|
|
|
@ -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}"
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
51
spec/system/admin_site_setting_locale_spec.rb
Normal file
51
spec/system/admin_site_setting_locale_spec.rb
Normal 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
|
|
@ -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("/")
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"),
|
||||
)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue