feat: support configuring default locale in system setting (#7365)

#### What type of PR is this?
/kind feature
/area core
/milestone 2.20.x

#### What this PR does / why we need it:
系统设置新增首选语言设置

#### Which issue(s) this PR fixes:

Fixes #7047
Fixes https://github.com/halo-dev/halo/issues/7172
Fixes https://github.com/halo-dev/halo/issues/4086
Fixes https://github.com/halo-dev/halo/issues/7336

#### Does this PR introduce a user-facing change?

```release-note
系统设置新增首选语言设置
```
pull/7372/head^2 v2.20.19
guqing 2025-04-27 12:04:50 +08:00 committed by GitHub
parent 23951de314
commit 0676551c77
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 170 additions and 18 deletions

View File

@ -1,12 +1,16 @@
package run.halo.app.infra; package run.halo.app.infra;
import com.fasterxml.jackson.annotation.JsonIgnore;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import lombok.Data; import lombok.Data;
import lombok.experimental.Accessors; import lombok.experimental.Accessors;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.convert.ApplicationConversionService; import org.springframework.boot.convert.ApplicationConversionService;
import run.halo.app.extension.ConfigMap; import run.halo.app.extension.ConfigMap;
import run.halo.app.infra.utils.JsonUtils; import run.halo.app.infra.utils.JsonUtils;
@ -66,6 +70,14 @@ public class SystemSetting {
String subtitle; String subtitle;
String logo; String logo;
String favicon; String favicon;
String language;
@JsonIgnore
public Optional<Locale> useSystemLocale() {
return Optional.ofNullable(language)
.filter(StringUtils::isNotBlank)
.map(Locale::forLanguageTag);
}
} }
@Data @Data

View File

@ -59,6 +59,11 @@ public class SystemConfigurableEnvironmentFetcher implements Reconciler<Reconcil
}); });
} }
public Mono<SystemSetting.Basic> getBasic() {
return fetch(SystemSetting.Basic.GROUP, SystemSetting.Basic.class)
.switchIfEmpty(Mono.just(new SystemSetting.Basic()));
}
public Mono<SystemSetting.Comment> fetchComment() { public Mono<SystemSetting.Comment> fetchComment() {
return fetch(SystemSetting.Comment.GROUP, SystemSetting.Comment.class) return fetch(SystemSetting.Comment.GROUP, SystemSetting.Comment.class)
.switchIfEmpty(Mono.just(new SystemSetting.Comment())); .switchIfEmpty(Mono.just(new SystemSetting.Comment()));

View File

@ -119,6 +119,7 @@ public class GlobalInfoServiceImpl implements GlobalInfoService {
if (basic != null) { if (basic != null) {
info.setFavicon(basic.getFavicon()); info.setFavicon(basic.getFavicon());
info.setSiteTitle(basic.getTitle()); info.setSiteTitle(basic.getTitle());
basic.useSystemLocale().ifPresent(info::setLocale);
} }
} }

View File

@ -45,7 +45,9 @@ import run.halo.app.infra.console.WebSocketRequestPredicate;
import run.halo.app.infra.properties.AttachmentProperties; import run.halo.app.infra.properties.AttachmentProperties;
import run.halo.app.infra.properties.HaloProperties; import run.halo.app.infra.properties.HaloProperties;
import run.halo.app.infra.webfilter.AdditionalWebFilterChainProxy; import run.halo.app.infra.webfilter.AdditionalWebFilterChainProxy;
import run.halo.app.infra.webfilter.LocaleChangeWebFilter;
import run.halo.app.plugin.extensionpoint.ExtensionGetter; import run.halo.app.plugin.extensionpoint.ExtensionGetter;
import run.halo.app.theme.UserLocaleRequestAttributeWriteFilter;
@Configuration @Configuration
public class WebFluxConfig implements WebFluxConfigurer { public class WebFluxConfig implements WebFluxConfigurer {
@ -219,15 +221,22 @@ public class WebFluxConfig implements WebFluxConfigurer {
@ConditionalOnProperty(name = "halo.console.proxy.enabled", havingValue = "true") @ConditionalOnProperty(name = "halo.console.proxy.enabled", havingValue = "true")
@Bean @Bean
@Order(Ordered.HIGHEST_PRECEDENCE) @Order(Ordered.HIGHEST_PRECEDENCE + 2)
ProxyFilter consoleProxyFilter() { ProxyFilter consoleProxyFilter() {
return new ProxyFilter("/console/**", haloProp.getConsole().getProxy()); return new ProxyFilter("/console/**", haloProp.getConsole().getProxy());
} }
/**
* Order of this filter is higher than
* {@link LocaleChangeWebFilter} to allow change locale in dev
* mode.
* {@link UserLocaleRequestAttributeWriteFilter} is before {@link LocaleChangeWebFilter} to
* obtain the locale
*/
@ConditionalOnProperty(name = "halo.uc.proxy.enabled", havingValue = "true") @ConditionalOnProperty(name = "halo.uc.proxy.enabled", havingValue = "true")
@Bean @Bean
@Order(Ordered.HIGHEST_PRECEDENCE) @Order(Ordered.HIGHEST_PRECEDENCE + 2)
ProxyFilter ucProxyFilter() { ProxyFilter ucProxyFilter() {
return new ProxyFilter("/uc/**", haloProp.getUc().getProxy()); return new ProxyFilter("/uc/**", haloProp.getUc().getProxy());
} }

View File

@ -1,10 +1,11 @@
package run.halo.app.infra.webfilter; package run.halo.app.infra.webfilter;
import static run.halo.app.theme.ThemeLocaleContextResolver.LANGUAGE_COOKIE_NAME; import static run.halo.app.theme.ThemeLocaleContextResolver.LANGUAGE_COOKIE_NAME;
import static run.halo.app.theme.ThemeLocaleContextResolver.LANGUAGE_PARAMETER_NAME;
import java.util.Locale; import java.util.Locale;
import java.util.Set; import java.util.Set;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseCookie; import org.springframework.http.ResponseCookie;
@ -15,18 +16,26 @@ import org.springframework.security.web.server.util.matcher.ServerWebExchangeMat
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher.MatchResult; import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher.MatchResult;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers; import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter; import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain; import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import run.halo.app.theme.ThemeLocaleContextResolver;
import run.halo.app.theme.UserLocaleRequestAttributeWriteFilter;
/**
* {@link UserLocaleRequestAttributeWriteFilter} is before {@link LocaleChangeWebFilter} to
* obtain the locale.
*/
@Component @Component
@Order(Ordered.HIGHEST_PRECEDENCE + 1)
public class LocaleChangeWebFilter implements WebFilter { public class LocaleChangeWebFilter implements WebFilter {
private final ServerWebExchangeMatcher matcher; private final ServerWebExchangeMatcher matcher;
private final ThemeLocaleContextResolver themeLocaleContextResolver;
public LocaleChangeWebFilter() { public LocaleChangeWebFilter(ThemeLocaleContextResolver themeLocaleContextResolver) {
this.themeLocaleContextResolver = themeLocaleContextResolver;
var pathMatcher = ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, "/**"); var pathMatcher = ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, "/**");
var textHtmlMatcher = new MediaTypeServerWebExchangeMatcher(MediaType.TEXT_HTML); var textHtmlMatcher = new MediaTypeServerWebExchangeMatcher(MediaType.TEXT_HTML);
textHtmlMatcher.setIgnoredMediaTypes(Set.of(MediaType.ALL)); textHtmlMatcher.setIgnoredMediaTypes(Set.of(MediaType.ALL));
@ -35,16 +44,14 @@ public class LocaleChangeWebFilter implements WebFilter {
@Override @Override
@NonNull @NonNull
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) { public Mono<Void> filter(ServerWebExchange exchange, @NonNull WebFilterChain chain) {
var request = exchange.getRequest(); var request = exchange.getRequest();
return matcher.matches(exchange) return matcher.matches(exchange)
.filter(MatchResult::isMatch) .filter(MatchResult::isMatch)
.doOnNext(result -> { .doOnNext(result -> {
var language = request var localeContext = themeLocaleContextResolver.resolveLocaleContext(exchange);
.getQueryParams() var locale = localeContext.getLocale();
.getFirst(LANGUAGE_PARAMETER_NAME); if (locale != null) {
if (StringUtils.hasText(language)) {
var locale = Locale.forLanguageTag(language);
setLanguageCookie(exchange, locale); setLanguageCookie(exchange, locale);
} }
}) })

View File

@ -20,6 +20,8 @@ import run.halo.app.core.extension.notification.ReasonType;
import run.halo.app.core.extension.notification.Subscription; import run.halo.app.core.extension.notification.Subscription;
import run.halo.app.extension.Metadata; import run.halo.app.extension.Metadata;
import run.halo.app.extension.ReactiveExtensionClient; import run.halo.app.extension.ReactiveExtensionClient;
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
import run.halo.app.infra.SystemSetting;
import run.halo.app.notification.endpoint.SubscriptionRouter; import run.halo.app.notification.endpoint.SubscriptionRouter;
/** /**
@ -41,6 +43,7 @@ public class DefaultNotificationCenter implements NotificationCenter {
private final SubscriptionRouter subscriptionRouter; private final SubscriptionRouter subscriptionRouter;
private final RecipientResolver recipientResolver; private final RecipientResolver recipientResolver;
private final SubscriptionService subscriptionService; private final SubscriptionService subscriptionService;
private final SystemConfigurableEnvironmentFetcher environmentFetcher;
@Override @Override
public Mono<Void> notify(Reason reason) { public Mono<Void> notify(Reason reason) {
@ -287,6 +290,8 @@ public class DefaultNotificationCenter implements NotificationCenter {
Mono<Locale> getLocaleFromSubscriber(Subscriber subscriber) { Mono<Locale> getLocaleFromSubscriber(Subscriber subscriber) {
// TODO get locale from subscriber // TODO get locale from subscriber
return Mono.just(Locale.getDefault()); return environmentFetcher.getBasic()
.map(SystemSetting.Basic::useSystemLocale)
.map(localeOpt -> localeOpt.orElse(Locale.getDefault()));
} }
} }

View File

@ -212,6 +212,7 @@ public class SystemSetupEndpoint {
String basic = data.getOrDefault(SystemSetting.Basic.GROUP, "{}"); String basic = data.getOrDefault(SystemSetting.Basic.GROUP, "{}");
var basicSetting = JsonUtils.jsonToObject(basic, SystemSetting.Basic.class); var basicSetting = JsonUtils.jsonToObject(basic, SystemSetting.Basic.class);
basicSetting.setTitle(body.getSiteTitle()); basicSetting.setTitle(body.getSiteTitle());
basicSetting.setLanguage(body.getLanguage());
data.put(SystemSetting.Basic.GROUP, JsonUtils.objectToJson(basicSetting)); data.put(SystemSetting.Basic.GROUP, JsonUtils.objectToJson(basicSetting));
} }
@ -272,6 +273,11 @@ public class SystemSetupEndpoint {
public String getSiteTitle() { public String getSiteTitle() {
return formData.getFirst("siteTitle"); return formData.getFirst("siteTitle");
} }
@Pattern(regexp = "^(zh-CN|zh-TW|en|es)$")
public String getLanguage() {
return formData.getFirst("language");
}
} }
Flux<Unstructured> loadPresetExtensions(String username) { Flux<Unstructured> loadPresetExtensions(String username) {

View File

@ -29,12 +29,14 @@ public class ThemeLocaleContextResolver extends AcceptHeaderLocaleContextResolve
public static final String TIME_ZONE_COOKIE_NAME = "time_zone"; public static final String TIME_ZONE_COOKIE_NAME = "time_zone";
@Override @Override
@NonNull @NonNull
public LocaleContext resolveLocaleContext(@NonNull ServerWebExchange exchange) { public LocaleContext resolveLocaleContext(@NonNull ServerWebExchange exchange) {
var request = exchange.getRequest(); var request = exchange.getRequest();
var locale = getLocaleFromQueryParameter(request) var locale = getLocaleFromQueryParameter(request)
.or(() -> getLocaleFromCookie(request)) .or(() -> getLocaleFromCookie(request))
.or(() -> UserLocaleRequestAttributeWriteFilter.getUserLocale(request))
.orElseGet(() -> super.resolveLocaleContext(exchange).getLocale()); .orElseGet(() -> super.resolveLocaleContext(exchange).getLocale());
var timeZone = getTimeZoneFromCookie(request) var timeZone = getTimeZoneFromCookie(request)
@ -62,5 +64,4 @@ public class ThemeLocaleContextResolver extends AcceptHeaderLocaleContextResolve
.filter(StringUtils::isNotBlank) .filter(StringUtils::isNotBlank)
.map(TimeZone::getTimeZone); .map(TimeZone::getTimeZone);
} }
} }

View File

@ -0,0 +1,42 @@
package run.halo.app.theme;
import java.util.Locale;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
import run.halo.app.infra.SystemSetting;
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
@RequiredArgsConstructor
public class UserLocaleRequestAttributeWriteFilter implements WebFilter {
public static final String USER_LOCALE_ATTRIBUTE =
UserLocaleRequestAttributeWriteFilter.class.getName() + ".USER_LOCALE_ATTRIBUTE";
private final SystemConfigurableEnvironmentFetcher environmentFetcher;
@Override
@NonNull
public Mono<Void> filter(@NonNull ServerWebExchange exchange, @NonNull WebFilterChain chain) {
return environmentFetcher.getBasic()
.map(SystemSetting.Basic::useSystemLocale)
.doOnNext(localeOpt -> localeOpt
.ifPresent(locale -> exchange.getAttributes().put(USER_LOCALE_ATTRIBUTE, locale))
)
.then(chain.filter(exchange));
}
public static Optional<Locale> getUserLocale(ServerHttpRequest request) {
return Optional.ofNullable((Locale) request.getAttributes()
.get(USER_LOCALE_ATTRIBUTE));
}
}

View File

@ -1,6 +1,7 @@
package run.halo.app.theme.finders.vo; package run.halo.app.theme.finders.vo;
import java.net.URL; import java.net.URL;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import lombok.Builder; import lombok.Builder;
import lombok.Value; import lombok.Value;
@ -34,6 +35,8 @@ public class SiteSettingVo {
String favicon; String favicon;
String language;
Boolean allowRegistration; Boolean allowRegistration;
PostSetting post; PostSetting post;
@ -74,6 +77,7 @@ public class SiteSettingVo {
.logo(basicSetting.getLogo()) .logo(basicSetting.getLogo())
.favicon(basicSetting.getFavicon()) .favicon(basicSetting.getFavicon())
.allowRegistration(userSetting.isAllowRegistration()) .allowRegistration(userSetting.isAllowRegistration())
.language(basicSetting.useSystemLocale().orElse(Locale.getDefault()).toLanguageTag())
.post(PostSetting.builder() .post(PostSetting.builder()
.postPageSize(postSetting.getPostPageSize()) .postPageSize(postSetting.getPostPageSize())
.archivePageSize(postSetting.getArchivePageSize()) .archivePageSize(postSetting.getArchivePageSize())

View File

@ -24,6 +24,19 @@ spec:
name: favicon name: favicon
accepts: accepts:
- 'image/*' - 'image/*'
- $formkit: select
label: "首选语言"
name: language
value: 'zh-CN'
options:
- label: 'English'
value: 'en'
- label: 'Español'
value: 'es'
- label: '简体中文'
value: 'zh-CN'
- label: '繁体中文'
value: 'zh-TW'
- group: post - group: post
label: 文章设置 label: 文章设置
formSchema: formSchema:

View File

@ -107,6 +107,19 @@
display: block; display: block;
} }
.halo-form .form-item select {
appearance: none;
font-size: var(--text-base);
box-shadow: none;
width: 100%;
height: 100%;
display: block;
outline: none;
border: none;
background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1em' height='1em' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' d='m12 13.171l4.95-4.95l1.414 1.415L12 16L5.636 9.636L7.05 8.222z'/%3E%3C/svg%3E")
right 0em center no-repeat;
}
.halo-form .form-item input:focus { .halo-form .form-item input:focus {
outline: none; outline: none;
} }

View File

@ -1,4 +1,4 @@
<!doctype html> <!DOCTYPE html>
<html <html
xmlns:th="https://www.thymeleaf.org" xmlns:th="https://www.thymeleaf.org"
th:replace="~{gateway_fragments/layout :: layout(title = |#{title} - Halo|, head = ~{::head}, body = ~{::body})}" th:replace="~{gateway_fragments/layout :: layout(title = |#{title} - Halo|, head = ~{::head}, body = ~{::body})}"
@ -22,6 +22,18 @@
<span th:text="#{form.messages.h2.content}"> </span> <span th:text="#{form.messages.h2.content}"> </span>
</div> </div>
<form th:object="${form}" th:action="@{/system/setup}" class="halo-form" method="post"> <form th:object="${form}" th:action="@{/system/setup}" class="halo-form" method="post">
<div class="form-item">
<label for="language" th:text="#{form.language.label}"></label>
<div class="form-input">
<select name="language" id="language">
<option value="en" th:selected="${#locale.toLanguageTag} == 'en'">English</option>
<option value="es" th:selected="${#locale.toLanguageTag} == 'es'">Español</option>
<option value="zh-CN" th:selected="${#locale.toLanguageTag} == 'zh-CN'">简体中文</option>
<option value="zh-TW" th:selected="${#locale.toLanguageTag} == 'zh-TW'">繁体中文</option>
</select>
</div>
</div>
<div class="form-item"> <div class="form-item">
<label for="siteTitle" th:text="#{form.siteTitle.label}"></label> <label for="siteTitle" th:text="#{form.siteTitle.label}"></label>
<div class="form-input"> <div class="form-input">
@ -112,14 +124,20 @@
</div> </div>
</form> </form>
</div> </div>
<div th:replace="~{gateway_fragments/common::languageSwitcher}"></div>
</div> </div>
<script th:inline="javascript"> <script th:inline="javascript">
document.addEventListener("DOMContentLoaded", function () { document.addEventListener("DOMContentLoaded", function () {
setupPasswordConfirmation("password", "confirmPassword"); setupPasswordConfirmation("password", "confirmPassword");
}); });
document.getElementById("language").addEventListener("change", function () {
const selectedLanguage = this.value;
const currentURL = new URL(window.location.href);
currentURL.searchParams.set("language", selectedLanguage);
history.replaceState(null, "", currentURL.toString());
window.location.reload();
});
</script> </script>
</th:block> </th:block>
</html> </html>

View File

@ -1,4 +1,5 @@
title=系统初始化 title=系统初始化
form.language.label=语言
form.siteTitle.label=站点标题 form.siteTitle.label=站点标题
form.username.label=用户名 form.username.label=用户名
form.email.label=电子邮箱 form.email.label=电子邮箱

View File

@ -1,4 +1,5 @@
title=Setup title=Setup
form.language.label=Language
form.siteTitle.label=Site title form.siteTitle.label=Site title
form.username.label=Username form.username.label=Username
form.email.label=Email form.email.label=Email

View File

@ -1,4 +1,5 @@
title=Configuración title=Configuración
form.language.label=Idioma
form.siteTitle.label=Título del Sitio form.siteTitle.label=Título del Sitio
form.username.label=Nombre de Usuario form.username.label=Nombre de Usuario
form.email.label=Correo Electrónico form.email.label=Correo Electrónico

View File

@ -1,4 +1,5 @@
title=系統初始化 title=系統初始化
form.language.label=語言
form.siteTitle.label=站點標題 form.siteTitle.label=站點標題
form.username.label=使用者名稱 form.username.label=使用者名稱
form.email.label=電子郵件 form.email.label=電子郵件

View File

@ -30,6 +30,8 @@ import run.halo.app.core.extension.notification.ReasonType;
import run.halo.app.core.extension.notification.Subscription; import run.halo.app.core.extension.notification.Subscription;
import run.halo.app.extension.Metadata; import run.halo.app.extension.Metadata;
import run.halo.app.extension.ReactiveExtensionClient; import run.halo.app.extension.ReactiveExtensionClient;
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
import run.halo.app.infra.SystemSetting;
/** /**
* Tests for {@link DefaultNotificationCenter}. * Tests for {@link DefaultNotificationCenter}.
@ -61,6 +63,9 @@ class DefaultNotificationCenterTest {
@Mock @Mock
private SubscriptionService subscriptionService; private SubscriptionService subscriptionService;
@Mock
private SystemConfigurableEnvironmentFetcher environmentFetcher;
@InjectMocks @InjectMocks
private DefaultNotificationCenter notificationCenter; private DefaultNotificationCenter notificationCenter;
@ -317,6 +322,7 @@ class DefaultNotificationCenterTest {
void getLocaleFromSubscriberTest() { void getLocaleFromSubscriberTest() {
var subscription = mock(Subscriber.class); var subscription = mock(Subscriber.class);
when(environmentFetcher.getBasic()).thenReturn(Mono.just(new SystemSetting.Basic()));
notificationCenter.getLocaleFromSubscriber(subscription) notificationCenter.getLocaleFromSubscriber(subscription)
.as(StepVerifier::create) .as(StepVerifier::create)
.expectNext(Locale.getDefault()) .expectNext(Locale.getDefault())

View File

@ -15,6 +15,7 @@ import org.springframework.mock.web.server.MockServerWebExchange;
import org.springframework.web.server.WebFilterChain; import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import run.halo.app.infra.webfilter.LocaleChangeWebFilter; import run.halo.app.infra.webfilter.LocaleChangeWebFilter;
import run.halo.app.theme.ThemeLocaleContextResolver;
class LocaleChangeWebFilterTest { class LocaleChangeWebFilterTest {
@ -22,7 +23,8 @@ class LocaleChangeWebFilterTest {
@BeforeEach @BeforeEach
void setUp() { void setUp() {
filter = new LocaleChangeWebFilter(); var themeLocaleContextResolver = new ThemeLocaleContextResolver();
filter = new LocaleChangeWebFilter(themeLocaleContextResolver);
} }
@Test @Test

View File

@ -16,7 +16,7 @@ import { useI18n } from "vue-i18n";
const SYSTEM_CONFIGMAP_NAME = "system"; const SYSTEM_CONFIGMAP_NAME = "system";
const { t } = useI18n(); const { t, locale } = useI18n();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const group = inject<Ref<string>>("activeTab", ref("basic")); const group = inject<Ref<string>>("activeTab", ref("basic"));
@ -60,6 +60,10 @@ const handleSaveConfigMap = async () => {
queryClient.invalidateQueries({ queryKey: ["system-configMap"] }); queryClient.invalidateQueries({ queryKey: ["system-configMap"] });
await useGlobalInfoStore().fetchGlobalInfo(); await useGlobalInfoStore().fetchGlobalInfo();
const language = configMapFormData.value.basic.language;
locale.value = language;
document.cookie = `language=${language}; path=/; SameSite=Lax; Secure`;
saving.value = false; saving.value = false;
}; };
</script> </script>