Merge pull request #235 from Hexastack/feat/use-i18n-ns

feat(frontend): add i18n namespace + remove setting help & id
This commit is contained in:
Med Marrouchi 2024-10-18 17:49:10 +01:00 committed by GitHub
commit c902a8c7da
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
31 changed files with 346 additions and 207 deletions

View File

@ -130,7 +130,7 @@ export class BlockController extends BaseController<
plugin: p.id,
args: p.settings.reduce(
(acc, setting) => {
acc[setting.id] = setting.value;
acc[setting.label] = setting.value;
return acc;
},
{} as { [key: string]: any },

View File

@ -22,7 +22,7 @@ export interface CustomBlocks {}
type ChannelEvent = any;
type BlockAttrs = Partial<BlockCreateDto> & { name: string };
export type PluginSetting = Omit<Setting, 'createdAt' | 'updatedAt'>;
export type PluginSetting = Omit<Setting, 'id' | 'createdAt' | 'updatedAt'>;
export type PluginBlockTemplate = Omit<
BlockAttrs,

View File

@ -6,8 +6,7 @@
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
*/
import { Prop, Schema, SchemaFactory, ModelDefinition } from '@nestjs/mongoose';
import { Transform } from 'class-transformer';
import { ModelDefinition, Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { IsArray, IsIn } from 'class-validator';
import { BaseSchema } from '@/utils/generics/base-schema';
@ -46,13 +45,6 @@ export class Setting extends BaseSchema {
@Prop({ type: JSON, default: {} })
config?: Record<string, any>;
@Prop({
type: String,
default: '',
})
@Transform(({ obj }) => obj.help || undefined)
help?: string;
@Prop({
type: Number,
default: 0,

View File

@ -30,6 +30,7 @@
"eazychart-css": "^0.2.1-alpha.0",
"eazychart-react": "^0.8.0-alpha.0",
"hexabot-chat-widget": "*",
"i18next-http-backend": "^2.6.2",
"next": "^14.2.13",
"next-transpile-modules": "^10.0.1",
"normalizr": "^3.6.2",

View File

@ -0,0 +1,13 @@
{
"title": {
"chatbot_settings": "Chatbot"
},
"label": {
"global_fallback": "Enable Global Fallback?",
"fallback_message": "Fallback Message"
},
"help": {
"global_fallback": "Global fallback allows you to send custom messages when user entry does not match any of the block messages.",
"fallback_message": "If no fallback block is selected, then one of these messages will be sent."
}
}

View File

@ -0,0 +1,18 @@
{
"title": {
"contact": "Contact Infos"
},
"label": {
"contact_email_recipient": "Contact Email Recipient",
"company_name": "Your company's name",
"company_phone": "Phone number",
"company_email": "Email address",
"company_address1": "Address",
"company_address2": "Address 2",
"company_city": "City",
"company_zipcode": "Zipcode",
"company_state": "State",
"company_country": "Country"
},
"help": {}
}

View File

@ -0,0 +1,28 @@
{
"title": {
"live-chat-tester": "Live Chat Tester"
},
"label": {
"verification_token": "Verification Token",
"allowed_domains": "Allowed Domains",
"start_button": "Enable `Get Started`",
"input_disabled": "Disable Input",
"persistent_menu": "Display Persistent Menu",
"greeting_message": "Greeting Message",
"theme_color": "Widget Theme",
"theme_color_options": {
"orange": "Orange",
"red": "Red",
"green": "Green",
"blue": "Blue",
"dark": "Dark"
},
"window_title": "Chat Window Title",
"avatar_url": "Chatbot Avatar URL",
"show_emoji": "Enable Emoji Picker",
"show_file": "Enable Attachment Uploader",
"show_location": "Enable Geolocation Share",
"allowed_upload_size": "Max Upload Size (in bytes)",
"allowed_upload_types": "Allowed Upload Mime Types (comma separated)"
}
}

View File

@ -0,0 +1,16 @@
{
"title": {
"nlp_settings": "NLU Engine"
},
"label": {
"provider": "Provider",
"token": "API Access Token",
"endpoint": "API Endpoint URL",
"threshold": "Fallback Threshold"
},
"help": {
"endpoint": "NLU (Natural Language Understanding) API URL (Depends on the provider).",
"token": "Every API request must contain an Authorize HTTP header with a token.",
"threshold": "Threshold above which the chatbot will use its prediction (min=0 & max=1)"
}
}

View File

@ -0,0 +1,28 @@
{
"title": {
"offline": "Web Channel"
},
"label": {
"verification_token": "Verification Token",
"allowed_domains": "Allowed Domains",
"start_button": "Enable `Get Started`",
"input_disabled": "Disable Input",
"persistent_menu": "Display Persistent Menu",
"greeting_message": "Greeting Message",
"theme_color": "Widget Theme",
"theme_color_options": {
"orange": "Orange",
"red": "Red",
"green": "Green",
"blue": "Blue",
"dark": "Dark"
},
"window_title": "Chat Window Title",
"avatar_url": "Chatbot Avatar URL",
"show_emoji": "Enable Emoji Picker",
"show_file": "Enable Attachment Uploader",
"show_location": "Enable Geolocation Share",
"allowed_upload_size": "Max Upload Size (in bytes)",
"allowed_upload_types": "Allowed Upload Mime Types (comma separated)"
}
}

View File

@ -222,10 +222,6 @@
"offline": "Web Channel",
"twitter": "Twitter",
"dimelo": "Dimelo",
"contact": "Contact Infos",
"chatbot_settings": "Chatbot",
"nlp_settings": "NLU Provider",
"contact_infos": "Contact Infos",
"event_log": "Events Log",
"log_entry": "Log entry",
"dashboard": "Dashboard",
@ -471,37 +467,6 @@
},
"languages": "Available Languages",
"default_lang": "Default Language",
"default_lang_options": {
"fr": "Français",
"en": "English",
"ar": "Arabic",
"tn": "Tunisian"
},
"token": "API Access Token",
"endpoint": "API Endpoint URL",
"threshold": "Fallback Threshold",
"from": "Sender e-mail",
"mailer": "Mailer",
"mailer_options": {
"sendmail": "Sendmail",
"smtp": "SMTP Server"
},
"host": "SMTP Host",
"port": "Port",
"secure": "Use SSL ?",
"auth_user": "User",
"auth_pass": "Password",
"contact_email_recipient": "Contact Email Recipient",
"company_name": "Your company's name",
"company_phone": "Phone number",
"company_email": "Email address",
"company_address1": "Address",
"company_address2": "Address 2",
"company_city": "City",
"company_zipcode": "Zipcode",
"company_state": "State",
"company_country": "Country",
"global_fallback": "Enable Global Fallback?",
"secret": "Secret",
"verification_token": "Verification Token",
"allowed_domains": "Allowed Domains",
@ -524,21 +489,6 @@
"show_location": "Enable Geolocation Share",
"allowed_upload_size": "Max Upload Size (in bytes)",
"allowed_upload_types": "Allowed Upload Mime Types (comma seperated)",
"tw_consumer_key": "Consumer Key",
"tw_consumer_secret": "Consumer Secret",
"tw_access_token": "Access Token",
"tw_access_token_secret": "Access Token Secret",
"tw_greeting_text": "Greeting Text",
"d_api_url": "API URL",
"d_access_token": "API Access Token",
"d_verify_token": "API Verify Token",
"d_attachment_fallback_id": "Attachment Fallback ID",
"d_url_fallback": "URL Fallback",
"d_supported_domain_id": "Supported Domain ID",
"d_agent_category_id": "Flow IDs when handed-over",
"d_admin_user": "On handover, assign to (admin user ID)",
"d_test_identities": "Identity IDs used for testing",
"d_supported_sources": "Supported Sources",
"channel": "Channel",
"entry": "Entry content",
"thumbnail": "Thumbnail",
@ -673,11 +623,7 @@
"composer_input_disabled": "This means your bot can only be interacted with via the persistent menu, postbacks, buttons, and webviews.",
"get_started_button": "A bot's welcome screen can display a Get Started button. When this button is tapped, the Messenger Platform will send a postback event to your webhook (Payload = `GET_STARTED`).",
"greeting_text": "The greeting property of your bot's Messenger profile allows you to specify the greeting message people will see on the welcome screen of your bot. The welcome screen is displayed for people interacting with your bot for the first time.",
"global_fallback": "Global fallback allows you to send custom messages when user entry does not match any of the block messages.",
"fallback_message": "If no fallback block is selected, then one of these messages will be sent.",
"endpoint": "URL to which HTTP NLU requests are posted and that depends on the type of provider.",
"token": "Every API request must contain an Authorize HTTP header with a token.",
"threshold": "Threshold above which the chatbot will use its prediction (min=0 & max=1)",
"app_id": "Mandatory only if you intend to use Facebook Analytics",
"page_id": "Mandatory only if you intend to use Facebook Analytics",
"nlp_train": "You can train your chatbot by adding more examples",

View File

@ -0,0 +1,13 @@
{
"title": {
"chatbot_settings": "Chatbot"
},
"label": {
"global_fallback": "Activer le message de secours global?",
"fallback_message": "Message de secours"
},
"help": {
"global_fallback": "Le message de secours global vous permet d'envoyer des messages personnalisés lorsque le message de l'utilisateur ne déclenche aucun bloc de message.",
"fallback_message": "Si aucun bloc de secours n'est spécifié, alors de ces messages sera envoyé."
}
}

View File

@ -0,0 +1,18 @@
{
"title": {
"contact": "Contact"
},
"label": {
"contact_email_recipient": "Email de contact du destinataire",
"company_name": "Le nom de votre entreprise",
"company_phone": "Tél.",
"company_email": "Adresse e-mail",
"company_address1": "Adresse",
"company_address2": "Adresse 2",
"company_city": "Ville",
"company_zipcode": "Code postal",
"company_state": "État/Province",
"company_country": "Pays"
},
"help": {}
}

View File

@ -0,0 +1,28 @@
{
"title": {
"live-chat-tester": "Testeur Live Chat"
},
"label": {
"verification_token": "Jeton de vérification",
"allowed_domains": "Domaines autorisés",
"start_button": "Activer `Démarrer`",
"input_disabled": "Désactiver la saisie",
"persistent_menu": "Afficher le menu persistent",
"greeting_message": "Message de bienvenue",
"theme_color": "Thème du widget",
"theme_color_options": {
"orange": "Orange",
"red": "Rouge",
"green": "Vert",
"blue": "Bleu",
"dark": "Sombre"
},
"window_title": "Titre de la fenêtre de chat",
"avatar_url": "Avatar du chatbot (URL)",
"show_emoji": "Activer le sélecteur d'Emojis",
"show_file": "Activer l'upload de fichiers",
"show_location": "Activer le partage de géolocalisation",
"allowed_upload_size": "Taille maximale de téléchargement (en octets)",
"allowed_upload_types": "Types MIME autorisés pour le téléchargement (séparés par des virgules)"
}
}

View File

@ -0,0 +1,16 @@
{
"title": {
"nlp_settings": "Serveur NLU"
},
"label": {
"provider": "Fournisseur",
"token": "Token d'accès à l'API",
"endpoint": "URL de l'API",
"threshold": "Seuil de repli"
},
"help": {
"endpoint": "URL vers laquelle les requêtes de NLU HTTP sont envoyées et qui dépend du fournisseur.",
"token": "Chaque requête vers l'API doit contenir un jeton d'authentification.",
"threshold": "Seuil en dessus duquel le chatbot va utiliser sa prediction (min=0 & max=1)"
}
}

View File

@ -0,0 +1,28 @@
{
"title": {
"offline": "Canal Web"
},
"label": {
"verification_token": "Jeton de vérification",
"allowed_domains": "Domaines autorisés",
"start_button": "Activer `Démarrer`",
"input_disabled": "Désactiver la saisie",
"persistent_menu": "Afficher le menu persistent",
"greeting_message": "Message de bienvenue",
"theme_color": "Thème du widget",
"theme_color_options": {
"orange": "Orange",
"red": "Rouge",
"green": "Vert",
"blue": "Bleu",
"dark": "Sombre"
},
"window_title": "Titre de la fenêtre de chat",
"avatar_url": "Avatar du chatbot (URL)",
"show_emoji": "Activer le sélecteur d'Emojis",
"show_file": "Activer l'upload de fichiers",
"show_location": "Activer le partage de géolocalisation",
"allowed_upload_size": "Taille maximale de téléchargement (en octets)",
"allowed_upload_types": "Types MIME autorisés pour le téléchargement (séparés par des virgules)"
}
}

View File

@ -222,10 +222,6 @@
"offline": "Canal Web",
"twitter": "Twitter",
"dimelo": "Dimelo",
"contact": "Contact",
"chatbot_settings": "Paramètres du Chatbot",
"nlp_settings": "Paramètres NLU",
"contact_infos": "Informations de contact",
"event_log": "Journal des événements",
"log_entry": "Journal des entrées",
"dashboard": "Tableau de bord",
@ -471,36 +467,6 @@
},
"languages": "Langues disponibles",
"default_lang": "Langue",
"default_lang_options": {
"fr": "Français",
"en": "English",
"ar": "Arabic",
"tn": "Tunisian"
},
"token": "Token d'accès à l'API",
"endpoint": "URL de l'API",
"threshold": "Seuil de repli",
"from": "E-mail expéditeur",
"mailer": "Mailer",
"mailer_options": {
"sendmail": "Sendmail",
"smtp": "Serveur SMTP"
},
"host": "Hôte SMTP",
"port": "Port",
"secure": "Utiliser SSL ?",
"auth_user": "Utilisateur",
"auth_pass": "Mot de passe",
"contact_email_recipient": "Email de contact du destinataire",
"company_name": "Le nom de votre entreprise",
"company_phone": "Tél.",
"company_email": "Adresse e-mail",
"company_address1": "Addresse",
"company_address2": "Addresse 2",
"company_city": "Ville",
"company_zipcode": "Code postal",
"company_state": "État/Province",
"company_country": "Pays",
"global_fallback": "Activer le message de secours global?",
"secret": "Mot de passe",
"verification_token": "Jeton de vérification",
@ -524,21 +490,6 @@
"show_location": "Activer le partage de géolocalisation",
"allowed_upload_size": "Taille maximale de téléchargement (en octets)",
"allowed_upload_types": "Types MIME autorisés pour le téléchargement (séparés par des virgules)",
"tw_consumer_key": "Clé de consommation",
"tw_consumer_secret": "Mot de pase de consommation",
"tw_access_token": "Jeton d'accès",
"tw_access_token_secret": "Mot de passe du Jeton d'accès",
"tw_greeting_text": "Text de bienvenue",
"d_api_url": "URL de lAPI",
"d_access_token": "Jeton daccès API",
"d_verify_token": "Jeton de vérification API",
"d_attachment_fallback_id": "ID Piéce jointe alternative",
"d_url_fallback": "URL alternative",
"d_supported_domain_id": "ID du domain supporté",
"d_agent_category_id": "IDs des catégories en cas de prise en charge par lagent",
"d_admin_user": "Lors de la prise en charge par lagent, assigner à (ID de ladministrateur)",
"d_test_identities": "IDs d'identités utilisées pour les tests",
"d_supported_sources": "Sources supportées",
"channel": "Canal",
"entry": "Contenu de l'entrée",
"thumbnail": "Aperçu",
@ -673,11 +624,7 @@
"composer_input_disabled": "Cela signifie que votre robot ne peut interagir qu'avec le menu persistant, les valeurs de retours, les boutons et les Webviews.",
"get_started_button": "L'écran de bienvenue d'un chatbot peut afficher un bouton `Démarrer`. Lorsque ce bouton est appuyé, la plate-forme Messenger envoie un événement à votre webhook (Payload = `Démarrer`).",
"greeting_text": "Le texte de bienvenue de votre chatbot vous permet de spécifier le message d'accueil que les utilisateurs verront sur l'écran d'accueil de votre chatbot. L'écran de bienvenue s'affiche pour les personnes qui interagissent avec votre chatbot pour la première fois.",
"global_fallback": "Le message de secours global vous permet d'envoyer des messages personnalisés lorsque le message de l'utilisateur ne déclenche aucun bloc de message.",
"fallback_message": "Si aucun bloc de secours n'est spécifié, alors de ces messages sera envoyé.",
"endpoint": "URL vers laquelle les requêtes de NLU HTTP sont envoyées et qui dépend du fournisseur.",
"token": "Chaque requête vers l'API doit contenir un jeton d'authentification.",
"threshold": "Seuil en dessus duquel le chatbot va utiliser sa prediction (min=0 & max=1)",
"app_id": "Obligatoire seulement si vous comptez utiliser Facebook Analytics",
"page_id": "Obligatoire seulement si vous comptez utiliser Facebook Analytics",
"nlp_train": "Vous pouvez entraîner votre chatbot en ajoutant plusieurs exemples",

View File

@ -13,8 +13,8 @@ import {
DialogProps,
MenuItem,
} from "@mui/material";
import { useEffect, FC } from "react";
import { useForm, Controller } from "react-hook-form";
import { FC, useEffect } from "react";
import { Controller, useForm } from "react-hook-form";
import DialogButtons from "@/app-components/buttons/DialogButtons";
import { DialogTitle } from "@/app-components/dialogs/DialogTitle";
@ -23,7 +23,6 @@ import { ContentItem } from "@/app-components/dialogs/layouts/ContentItem";
import { Input } from "@/app-components/inputs/Input";
import { ToggleableInput } from "@/app-components/inputs/ToggleableInput";
import { useTranslate } from "@/hooks/useTranslate";
import { TNestedTranslation } from "@/i18n/i18n.types";
import { IMenuItem, IMenuItemAttributes, MenuType } from "@/types/menu.types";
import { isAbsoluteUrl } from "@/utils/URL";
@ -124,11 +123,9 @@ export const MenuDialog: FC<MenuDialogProps> = ({
helperText={errors.type ? errors.type.message : null}
{...rest}
>
{(
Object.keys(MenuType) as TNestedTranslation<"label">[]
).map((value, key) => (
{Object.keys(MenuType).map((value, key) => (
<MenuItem value={value} key={key}>
{t("label", value)}
{t(`label.${value}`)}
</MenuItem>
))}
</Input>

View File

@ -36,7 +36,6 @@ import { useUpdate } from "@/hooks/crud/useUpdate";
import { DialogControlProps } from "@/hooks/useDialog";
import { useToast } from "@/hooks/useToast";
import { useTranslate } from "@/hooks/useTranslate";
import { TNestedTranslation } from "@/i18n/i18n.types";
import { EntityType } from "@/services/types";
import {
ContentField,
@ -71,7 +70,7 @@ const ContentFieldInput: React.FC<ContentFieldInput> = ({
<Input
multiline={contentField.type === ContentFieldType.TEXTAREA}
rows={contentField.type === ContentFieldType.TEXTAREA ? 5 : 1}
label={t("label", contentField.name as TNestedTranslation<"label">, {
label={t(`label.${contentField.name}`, {
defaultValue: contentField.label,
})}
InputProps={
@ -93,7 +92,7 @@ const ContentFieldInput: React.FC<ContentFieldInput> = ({
case ContentFieldType.CHECKBOX:
return (
<FormControlLabel
label={t("label", contentField.name as TNestedTranslation<"label">, {
label={t(`label.${contentField.name}`, {
defaultValue: contentField.label,
})}
{...field}
@ -103,7 +102,7 @@ const ContentFieldInput: React.FC<ContentFieldInput> = ({
case ContentFieldType.FILE:
return (
<AttachmentInput
label={t("label", contentField.name as TNestedTranslation<"label">, {
label={t(`label.${contentField.name}`, {
defaultValue: contentField.label,
})}
{...field}

View File

@ -44,7 +44,6 @@ import { useHasPermission } from "@/hooks/useHasPermission";
import { useSearch } from "@/hooks/useSearch";
import { useToast } from "@/hooks/useToast";
import { useTranslate } from "@/hooks/useTranslate";
import { TNestedTranslation } from "@/i18n/i18n.types";
import { EntityType, Format } from "@/services/types";
import { ILanguage } from "@/types/language.types";
import {
@ -321,9 +320,7 @@ export default function NlpSample() {
</IconButton>
),
}),
renderValue: (value) => (
<Box>{t("label", value as TNestedTranslation<"label">)}</Box>
),
renderValue: (value) => <Box>{t(`label.${value}`)}</Box>,
}}
>
{Object.values(NlpSampleType).map((nlpSampleType, index) => (

View File

@ -17,7 +17,6 @@ import { Input } from "@/app-components/inputs/Input";
import MultipleInput from "@/app-components/inputs/MultipleInput";
import { PasswordInput } from "@/app-components/inputs/PasswordInput";
import { useTranslate } from "@/hooks/useTranslate";
import { TNestedTranslation } from "@/i18n/i18n.types";
import { EntityType, Format } from "@/services/types";
import { IBlock } from "@/types/block.types";
import { ISetting } from "@/types/setting.types";
@ -34,13 +33,13 @@ const SettingInput: React.FC<RenderSettingInputProps> = ({
field,
isDisabled = () => false,
}) => {
const { t } = useTranslate();
const nestedLabel = setting.label as TNestedTranslation<"label">;
const nestedHelp = setting.help as TNestedTranslation<"help">;
const label = t("label", nestedLabel, { defaultValue: nestedLabel });
const helperText = nestedHelp
? t("help", nestedHelp, { defaultValue: nestedHelp })
: "";
const { t } = useTranslate(setting.group);
const label = t(`label.${setting.label}`, {
defaultValue: setting.label,
});
const helperText = t(`help.${setting.label}`, {
defaultValue: "",
});
switch (setting.type) {
case "text":

View File

@ -24,7 +24,6 @@ import { useFind } from "@/hooks/crud/useFind";
import { useUpdate } from "@/hooks/crud/useUpdate";
import { useToast } from "@/hooks/useToast";
import { useTranslate } from "@/hooks/useTranslate";
import { TNestedTranslation } from "@/i18n/i18n.types";
import { PageHeader } from "@/layout/content/PageHeader";
import { EntityType } from "@/services/types";
import { ISetting } from "@/types/setting.types";
@ -70,13 +69,13 @@ function groupBy(array: ISetting[]) {
export const Settings = () => {
const { t } = useTranslate();
const { toast } = useToast();
const [selectedTab, setSelectedTab] = useState("live-chat-tester");
const [selectedTab, setSelectedTab] = useState("chatbot_settings");
const { control, watch } = useForm();
const { data: settings } = useFind(
{ entity: EntityType.SETTING },
{
hasCount: false,
initialSortState: [{ field: "weight", sort: "desc" }],
initialSortState: [{ field: "weight", sort: "asc" }],
},
);
const { mutateAsync: updateSetting } = useUpdate(EntityType.SETTING, {
@ -157,16 +156,16 @@ export const Settings = () => {
},
}}
>
{(Object.keys(groups) as TNestedTranslation<"title">[]).map(
(group, index) => (
{Object.keys(groups)
.sort((a, b) => a.localeCompare(b))
.map((group, index) => (
<StyledTab
value={group}
key={group}
label={t("title", group)}
label={t(`title.${group}`, { ns: group })}
{...a11yProps(index)}
/>
),
)}
))}
</Tabs>
<StyledForm sx={{ width: "100%", px: 3, paddingY: 2 }}>
{Object.entries(groups).map(([group, settings]) => (

View File

@ -25,7 +25,6 @@ import ButtonsIcon from "@/app-components/svg/toolbar/ButtonsIcon";
import ListIcon from "@/app-components/svg/toolbar/ListIcon";
import { useGet } from "@/hooks/crud/useGet";
import { useTranslate } from "@/hooks/useTranslate";
import { TNestedTranslation } from "@/i18n/i18n.types";
import { EntityType, Format } from "@/services/types";
import {
ContentField,
@ -68,12 +67,10 @@ const ListMessageForm = () => {
name="options.content.display_mode"
render={({ field }) => (
<RadioGroup row {...field}>
{(
[
{[
OutgoingMessageFormat.list,
OutgoingMessageFormat.carousel,
] as TNestedTranslation<"label">[]
).map((display) => (
].map((display) => (
<FormControlLabel
key={display}
value={display}
@ -83,7 +80,7 @@ const ListMessageForm = () => {
{...register("options.content.display")}
/>
}
label={t("label", display)}
label={t(`label.${display}`)}
/>
))}
</RadioGroup>
@ -106,7 +103,7 @@ const ListMessageForm = () => {
key={style}
value={style}
control={<Radio />}
label={t("label", style)}
label={t(`label.${style}`)}
/>
))}
</RadioGroup>

View File

@ -53,11 +53,11 @@ const PluginMessageForm = () => {
sx={{ display: "none" }}
/>
{(settings || []).map((setting) => (
<ContentItem key={setting.id}>
<ContentItem key={setting.label}>
<Controller
name={`message.args.${setting.id}`}
name={`message.args.${setting.label}`}
control={control}
defaultValue={message.args?.[setting.id] || setting.value}
defaultValue={message.args?.[setting.label] || setting.value}
render={({ field }) => (
<FormControl fullWidth sx={{ paddingTop: ".75rem" }}>
<SettingInput setting={setting} field={field} />

View File

@ -19,7 +19,6 @@ import {
import { useFind } from "@/hooks/crud/useFind";
import { useTranslate } from "@/hooks/useTranslate";
import { TNestedTranslation } from "@/i18n/i18n.types";
import { EntityType } from "@/services/types";
function ReplacementTokens() {
@ -94,13 +93,17 @@ function ReplacementTokens() {
</ListItem>
))}
</List>
<Typography variant="h6">{t("title.contact_infos")}</Typography>
<Typography variant="h6">
{t("title.contact", { ns: "contact" })}
</Typography>
<List>
{(contactInfos || []).map((v, index) => (
<ListItem key={index}>
<ListItemText
primary={`{contact.${v.label}}`}
secondary={t("label", v.label as TNestedTranslation<"label">)}
secondary={t(`label.${v.label}`, {
ns: "contact",
})}
/>
</ListItem>
))}

View File

@ -9,13 +9,13 @@
import { useTranslation } from "react-i18next";
import {
TOptionsBaseExtended,
TTranslateProps,
TTranslationKeys,
TOptionsBaseExtended,
} from "@/i18n/i18n.types";
export const useTranslate = () => {
const { t, i18n } = useTranslation();
export const useTranslate = (ns?: string) => {
const { t, i18n } = useTranslation(ns);
const translate: TTranslateProps = (
prop1: unknown,
prop2?: unknown,

View File

@ -8,32 +8,36 @@
// Core i18next library.
import i18n from "i18next";
import Backend from "i18next-http-backend";
// Bindings for React: allow components to
// re-render when language changes.
import getConfig from "next/config";
import { initReactI18next } from "react-i18next";
import enTranslations from "./en/translation.json";
import frTranslations from "./fr/translation.json";
const { publicRuntimeConfig } = getConfig();
i18n.use(initReactI18next).init({
i18n
.use(initReactI18next)
.use(Backend)
.init({
lng: publicRuntimeConfig.lang.default,
fallbackLng: "en",
debug: true,
resources: {
en: {
translation: enTranslations,
},
fr: {
translation: frTranslations,
},
debug: process.env.NODE_ENV === "development",
backend: {
loadPath: "/locales/{{lng}}/{{ns}}.json",
},
ns: [
"translation",
"chatbot_settings.json",
"contact",
"nlp_settings",
"offline",
"live-chat-tester",
],
interpolation: {
escapeValue: false,
},
});
});
i18n.services.formatter?.add("dateFormat", (value, lng, options) =>
new Intl.DateTimeFormat(lng, options?.formatParams?.val).format(

View File

@ -8,16 +8,27 @@ import { TOptionsBase } from "i18next";
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
*/
import { translations } from ".";
import { TFilterNestedKeysOfType } from "@/types/common/object.types";
type TEnTranslation = (typeof translations)["en"];
type TFrTranslation = (typeof translations)["fr"];
export type TTranslation = string;
export type TTranslation = TEnTranslation & TFrTranslation;
export type TTranslationKeys = TFilterNestedKeysOfType<TEnTranslation> &
TFilterNestedKeysOfType<TFrTranslation>;
export type TTranslationPrefix =
| "message"
| "menu"
| "title"
| "label"
| "placeholder"
| "button"
| "input"
| "link"
| "help"
| "charts"
| "datetime"
| "visual_editor";
export type TTranslationKeys =
| `${TTranslationPrefix}`
| `${TTranslationPrefix}.${TTranslation}`;
export type TNestedTranslation<T extends keyof TTranslation> =
TFilterNestedKeysOfType<TTranslation[T]>;

View File

@ -1,12 +0,0 @@
/*
* Copyright © 2024 Hexastack. All rights reserved.
*
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
*/
import en from "@/i18n/en/translation.json";
import fr from "@/i18n/fr/translation.json";
export const translations = { en, fr } as const;

View File

@ -276,7 +276,7 @@ export const CustomBlockSettingEntity = new schema.Entity(
EntityType.CUSTOM_BLOCK_SETTINGS,
undefined,
{
idAttribute: ({ id }) => id,
idAttribute: ({ id, label }) => id || label,
},
);

View File

@ -92,8 +92,6 @@ export interface ISettingAttributes {
options?: string[];
config?: Record<string, any>;
weight?: number;
title?: string;
help?: string;
}
export interface ISettingStub extends IBaseSchema, ISettingAttributes {}

55
package-lock.json generated
View File

@ -64,6 +64,7 @@
"eazychart-css": "^0.2.1-alpha.0",
"eazychart-react": "^0.8.0-alpha.0",
"hexabot-chat-widget": "*",
"i18next-http-backend": "^2.6.2",
"next": "^14.2.13",
"next-transpile-modules": "^10.0.1",
"normalizr": "^3.6.2",
@ -4167,6 +4168,14 @@
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
"dev": true
},
"node_modules/cross-fetch": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz",
"integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==",
"dependencies": {
"node-fetch": "^2.6.12"
}
},
"node_modules/cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@ -6440,6 +6449,14 @@
"@babel/runtime": "^7.23.2"
}
},
"node_modules/i18next-http-backend": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-2.6.2.tgz",
"integrity": "sha512-Hp/kd8/VuoxIHmxsknJXjkTYYHzivAyAF15pzliKzk2TiXC25rZCEerb1pUFoxz4IVrG3fCvQSY51/Lu4ECV4A==",
"dependencies": {
"cross-fetch": "4.0.0"
}
},
"node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@ -7633,6 +7650,25 @@
"enhanced-resolve": "^5.10.0"
}
},
"node_modules/node-fetch": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/node-releases": {
"version": "2.0.18",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz",
@ -9173,6 +9209,11 @@
"node": ">=8.0"
}
},
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
"node_modules/ts-api-utils": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz",
@ -9665,6 +9706,20 @@
"vscode-uri": "^3.0.8"
}
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",