This commit is contained in:
Daniel Derefaka 2026-01-20 06:03:21 +00:00 committed by GitHub
commit 96670211ad
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 94 additions and 9 deletions

View File

@ -0,0 +1,11 @@
exports.up = async function (knex) {
await knex.schema.alterTable("status_page", function (table) {
table.string("language", 20);
});
};
exports.down = function (knex) {
return knex.schema.alterTable("status_page", function (table) {
table.dropColumn("language");
});
};

View File

@ -450,6 +450,7 @@ class StatusPage extends BeanModel {
showCertificateExpiry: !!this.show_certificate_expiry, showCertificateExpiry: !!this.show_certificate_expiry,
showOnlyLastHeartbeat: !!this.show_only_last_heartbeat, showOnlyLastHeartbeat: !!this.show_only_last_heartbeat,
rssTitle: this.rss_title, rssTitle: this.rss_title,
language: this.language,
}; };
} }
@ -477,6 +478,7 @@ class StatusPage extends BeanModel {
showCertificateExpiry: !!this.show_certificate_expiry, showCertificateExpiry: !!this.show_certificate_expiry,
showOnlyLastHeartbeat: !!this.show_only_last_heartbeat, showOnlyLastHeartbeat: !!this.show_only_last_heartbeat,
rssTitle: this.rss_title, rssTitle: this.rss_title,
language: this.language,
}; };
} }

View File

@ -339,6 +339,11 @@ module.exports.statusPageSocketHandler = (socket) => {
statusPage.analytics_id = config.analyticsId; statusPage.analytics_id = config.analyticsId;
statusPage.analytics_script_url = config.analyticsScriptUrl; statusPage.analytics_script_url = config.analyticsScriptUrl;
statusPage.analytics_type = config.analyticsType; statusPage.analytics_type = config.analyticsType;
if (typeof config.language === "string" && config.language.trim() !== "") {
statusPage.language = config.language.trim();
} else {
statusPage.language = null;
}
await R.store(statusPage); await R.store(statusPage);

View File

@ -95,8 +95,8 @@ export function currentLocale() {
return "en"; return "en";
} }
export const localeDirection = () => { export const localeDirection = (locale = currentLocale()) => {
return rtlLangs.includes(currentLocale()) ? "rtl" : "ltr"; return rtlLangs.includes(locale) ? "rtl" : "ltr";
}; };
export const i18n = createI18n({ export const i18n = createI18n({

View File

@ -425,6 +425,9 @@
"Footer Text": "Footer Text", "Footer Text": "Footer Text",
"RSS Title": "RSS Title", "RSS Title": "RSS Title",
"Leave blank to use status page title": "Leave blank to use status page title", "Leave blank to use status page title": "Leave blank to use status page title",
"Status Page Language": "Status Page Language",
"Use browser language": "Use browser language",
"statusPageLanguageDescription": "Set the display language for anonymous visitors to this status page",
"Refresh Interval": "Refresh Interval", "Refresh Interval": "Refresh Interval",
"Refresh Interval Description": "The status page will do a full site refresh every {0} seconds", "Refresh Interval Description": "The status page will do a full site refresh every {0} seconds",
"Show Powered By": "Show Powered By", "Show Powered By": "Show Powered By",

View File

@ -6,6 +6,7 @@ export default {
data() { data() {
return { return {
language: currentLocale(), language: currentLocale(),
persistLanguage: true,
}; };
}, },
@ -17,22 +18,40 @@ export default {
watch: { watch: {
async language(lang) { async language(lang) {
await this.changeLang(lang); await this.changeLang(lang, {
persist: this.persistLanguage,
});
this.persistLanguage = true;
}, },
}, },
methods: { methods: {
/**
* Set the application language
* @param {string} lang Language code to switch to
* @param {{ persist?: boolean }} options Options for language change
* @returns {void}
*/
setLanguage(lang, options = {}) {
this.persistLanguage = options.persist !== false;
this.language = lang;
},
/** /**
* Change the application language * Change the application language
* @param {string} lang Language code to switch to * @param {string} lang Language code to switch to
* @param {{ persist?: boolean }} options Options for language change
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
async changeLang(lang) { async changeLang(lang, options = {}) {
const persist = options.persist !== false;
let message = (await langModules["../lang/" + lang + ".json"]()).default; let message = (await langModules["../lang/" + lang + ".json"]()).default;
this.$i18n.setLocaleMessage(lang, message); this.$i18n.setLocaleMessage(lang, message);
this.$i18n.locale = lang; this.$i18n.locale = lang;
if (persist) {
localStorage.locale = lang; localStorage.locale = lang;
setPageLocale(); }
setPageLocale(lang);
timeDurationFormatter.updateLocale(lang); timeDurationFormatter.updateLocale(lang);
}, },
}, },

View File

@ -201,6 +201,25 @@
</div> </div>
</div> </div>
<!-- Status Page Language -->
<div class="my-3">
<label for="status-page-language" class="form-label">{{ $t("Status Page Language") }}</label>
<select
id="status-page-language"
v-model="config.language"
class="form-select"
data-testid="language-select"
>
<option :value="null">{{ $t("Use browser language") }}</option>
<option v-for="lang in $i18n.availableLocales" :key="lang" :value="lang">
{{ $i18n.messages[lang].languageName }}
</option>
</select>
<div class="form-text">
{{ $t("statusPageLanguageDescription") }}
</div>
</div>
<!-- Custom CSS --> <!-- Custom CSS -->
<div class="my-3"> <div class="my-3">
<div class="mb-1">{{ $t("Custom CSS") }}</div> <div class="mb-1">{{ $t("Custom CSS") }}</div>
@ -905,6 +924,10 @@ export default {
if (res.ok) { if (res.ok) {
this.config = res.config; this.config = res.config;
if (!("language" in this.config)) {
this.config.language = null;
}
if (!this.config.customCSS) { if (!this.config.customCSS) {
this.config.customCSS = "body {\n" + " \n" + "}\n"; this.config.customCSS = "body {\n" + " \n" + "}\n";
} }
@ -990,6 +1013,27 @@ export default {
this.config.domainNameList = []; this.config.domainNameList = [];
} }
if (!("language" in this.config)) {
this.config.language = null;
}
if (this.config.icon) {
this.imgDataUrl = this.config.icon;
}
// Apply configured language if the visitor hasn't set their own preference
if (this.config.language && !localStorage.locale) {
this.$root.setLanguage(this.config.language, { persist: false });
}
this.incident = res.data.incident;
this.maintenanceList = res.data.maintenanceList;
this.$root.publicGroupList = res.data.publicGroupList;
if (!this.config.domainNameList) {
this.config.domainNameList = [];
}
if (this.config.icon) { if (this.config.icon) {
this.imgDataUrl = this.config.icon; this.imgDataUrl = this.config.icon;
} }

View File

@ -62,12 +62,13 @@ export function timezoneList() {
/** /**
* Set the locale of the HTML page * Set the locale of the HTML page
* @param {string} locale The locale to use
* @returns {void} * @returns {void}
*/ */
export function setPageLocale() { export function setPageLocale(locale = currentLocale()) {
const html = document.documentElement; const html = document.documentElement;
html.setAttribute("lang", currentLocale()); html.setAttribute("lang", locale);
html.setAttribute("dir", localeDirection()); html.setAttribute("dir", localeDirection(locale));
} }
/** /**