Portals can now override their name, page title and header text per locale, with a fallback chain of locale override → default locale → base value. Overrides are stored under portal.config.locale_translations and validated with a JSON schema. Editing is exposed as a "Localize content" action in each non-default locale's menu on the Locale page, and all public-facing portal views (classic + documentation layouts) render the localized values. The live chat widget is also loaded in the active portal locale. <img width="494" height="395" alt="Screenshot 2026-06-03 at 2 48 59 PM" src="https://github.com/user-attachments/assets/e55a06e8-f20f-4d8a-9352-92fde28a9a19" /> https://github.com/user-attachments/assets/f5affbd2-7ad4-415a-b376-57c759fc2aaa --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
99 lines
3.0 KiB
Vue
99 lines
3.0 KiB
Vue
<script setup>
|
|
import { ref, computed } from 'vue';
|
|
import { useI18n } from 'vue-i18n';
|
|
import { useStore } from 'dashboard/composables/store';
|
|
import { useAlert } from 'dashboard/composables';
|
|
import Dialog from 'dashboard/components-next/dialog/Dialog.vue';
|
|
import Input from 'dashboard/components-next/input/Input.vue';
|
|
|
|
const props = defineProps({
|
|
portal: {
|
|
type: Object,
|
|
default: () => ({}),
|
|
},
|
|
});
|
|
|
|
const { t } = useI18n();
|
|
const store = useStore();
|
|
|
|
const dialogRef = ref(null);
|
|
const activeLocale = ref('');
|
|
const name = ref('');
|
|
const pageTitle = ref('');
|
|
const headerText = ref('');
|
|
|
|
const localeTranslations = computed(
|
|
() => props.portal?.config?.locale_translations || {}
|
|
);
|
|
|
|
const openForLocale = localeCode => {
|
|
const existing = localeTranslations.value[localeCode] || {};
|
|
activeLocale.value = localeCode;
|
|
name.value = existing.name || '';
|
|
pageTitle.value = existing.page_title || '';
|
|
headerText.value = existing.header_text || '';
|
|
dialogRef.value?.open();
|
|
};
|
|
|
|
const onConfirm = async () => {
|
|
const translations = { ...localeTranslations.value };
|
|
const fields = {};
|
|
if (name.value.trim()) fields.name = name.value.trim();
|
|
if (pageTitle.value.trim()) fields.page_title = pageTitle.value.trim();
|
|
if (headerText.value.trim()) fields.header_text = headerText.value.trim();
|
|
|
|
if (Object.keys(fields).length) {
|
|
translations[activeLocale.value] = fields;
|
|
} else {
|
|
delete translations[activeLocale.value];
|
|
}
|
|
|
|
try {
|
|
await store.dispatch('portals/update', {
|
|
portalSlug: props.portal?.slug,
|
|
config: { locale_translations: translations },
|
|
});
|
|
dialogRef.value?.close();
|
|
useAlert(t('HELP_CENTER.LOCALES_PAGE.CONTENT_DIALOG.API.SUCCESS_MESSAGE'));
|
|
} catch (error) {
|
|
useAlert(
|
|
error?.message ||
|
|
t('HELP_CENTER.LOCALES_PAGE.CONTENT_DIALOG.API.ERROR_MESSAGE')
|
|
);
|
|
}
|
|
};
|
|
|
|
defineExpose({ openForLocale });
|
|
</script>
|
|
|
|
<template>
|
|
<Dialog
|
|
ref="dialogRef"
|
|
type="edit"
|
|
:title="t('HELP_CENTER.LOCALES_PAGE.CONTENT_DIALOG.TITLE')"
|
|
:description="t('HELP_CENTER.LOCALES_PAGE.CONTENT_DIALOG.DESCRIPTION')"
|
|
@confirm="onConfirm"
|
|
>
|
|
<div class="flex flex-col gap-4">
|
|
<div class="flex flex-col gap-1">
|
|
<label class="text-sm font-medium text-n-slate-12">
|
|
{{ t('HELP_CENTER.LOCALES_PAGE.CONTENT_DIALOG.NAME.LABEL') }}
|
|
</label>
|
|
<Input v-model="name" :placeholder="portal.name" />
|
|
</div>
|
|
<div class="flex flex-col gap-1">
|
|
<label class="text-sm font-medium text-n-slate-12">
|
|
{{ t('HELP_CENTER.LOCALES_PAGE.CONTENT_DIALOG.PAGE_TITLE.LABEL') }}
|
|
</label>
|
|
<Input v-model="pageTitle" :placeholder="portal.page_title" />
|
|
</div>
|
|
<div class="flex flex-col gap-1">
|
|
<label class="text-sm font-medium text-n-slate-12">
|
|
{{ t('HELP_CENTER.LOCALES_PAGE.CONTENT_DIALOG.HEADER_TEXT.LABEL') }}
|
|
</label>
|
|
<Input v-model="headerText" :placeholder="portal.header_text" />
|
|
</div>
|
|
</div>
|
|
</Dialog>
|
|
</template>
|