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, plugin: p.id,
args: p.settings.reduce( args: p.settings.reduce(
(acc, setting) => { (acc, setting) => {
acc[setting.id] = setting.value; acc[setting.label] = setting.value;
return acc; return acc;
}, },
{} as { [key: string]: any }, {} as { [key: string]: any },

View File

@ -22,7 +22,7 @@ export interface CustomBlocks {}
type ChannelEvent = any; type ChannelEvent = any;
type BlockAttrs = Partial<BlockCreateDto> & { name: string }; 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< export type PluginBlockTemplate = Omit<
BlockAttrs, 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). * 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 { ModelDefinition, Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Transform } from 'class-transformer';
import { IsArray, IsIn } from 'class-validator'; import { IsArray, IsIn } from 'class-validator';
import { BaseSchema } from '@/utils/generics/base-schema'; import { BaseSchema } from '@/utils/generics/base-schema';
@ -46,13 +45,6 @@ export class Setting extends BaseSchema {
@Prop({ type: JSON, default: {} }) @Prop({ type: JSON, default: {} })
config?: Record<string, any>; config?: Record<string, any>;
@Prop({
type: String,
default: '',
})
@Transform(({ obj }) => obj.help || undefined)
help?: string;
@Prop({ @Prop({
type: Number, type: Number,
default: 0, default: 0,

View File

@ -30,6 +30,7 @@
"eazychart-css": "^0.2.1-alpha.0", "eazychart-css": "^0.2.1-alpha.0",
"eazychart-react": "^0.8.0-alpha.0", "eazychart-react": "^0.8.0-alpha.0",
"hexabot-chat-widget": "*", "hexabot-chat-widget": "*",
"i18next-http-backend": "^2.6.2",
"next": "^14.2.13", "next": "^14.2.13",
"next-transpile-modules": "^10.0.1", "next-transpile-modules": "^10.0.1",
"normalizr": "^3.6.2", "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", "offline": "Web Channel",
"twitter": "Twitter", "twitter": "Twitter",
"dimelo": "Dimelo", "dimelo": "Dimelo",
"contact": "Contact Infos",
"chatbot_settings": "Chatbot",
"nlp_settings": "NLU Provider",
"contact_infos": "Contact Infos",
"event_log": "Events Log", "event_log": "Events Log",
"log_entry": "Log entry", "log_entry": "Log entry",
"dashboard": "Dashboard", "dashboard": "Dashboard",
@ -471,37 +467,6 @@
}, },
"languages": "Available Languages", "languages": "Available Languages",
"default_lang": "Default Language", "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", "secret": "Secret",
"verification_token": "Verification Token", "verification_token": "Verification Token",
"allowed_domains": "Allowed Domains", "allowed_domains": "Allowed Domains",
@ -524,21 +489,6 @@
"show_location": "Enable Geolocation Share", "show_location": "Enable Geolocation Share",
"allowed_upload_size": "Max Upload Size (in bytes)", "allowed_upload_size": "Max Upload Size (in bytes)",
"allowed_upload_types": "Allowed Upload Mime Types (comma seperated)", "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", "channel": "Channel",
"entry": "Entry content", "entry": "Entry content",
"thumbnail": "Thumbnail", "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.", "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`).", "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.", "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.", "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", "app_id": "Mandatory only if you intend to use Facebook Analytics",
"page_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", "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", "offline": "Canal Web",
"twitter": "Twitter", "twitter": "Twitter",
"dimelo": "Dimelo", "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", "event_log": "Journal des événements",
"log_entry": "Journal des entrées", "log_entry": "Journal des entrées",
"dashboard": "Tableau de bord", "dashboard": "Tableau de bord",
@ -471,36 +467,6 @@
}, },
"languages": "Langues disponibles", "languages": "Langues disponibles",
"default_lang": "Langue", "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?", "global_fallback": "Activer le message de secours global?",
"secret": "Mot de passe", "secret": "Mot de passe",
"verification_token": "Jeton de vérification", "verification_token": "Jeton de vérification",
@ -524,21 +490,6 @@
"show_location": "Activer le partage de géolocalisation", "show_location": "Activer le partage de géolocalisation",
"allowed_upload_size": "Taille maximale de téléchargement (en octets)", "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)", "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", "channel": "Canal",
"entry": "Contenu de l'entrée", "entry": "Contenu de l'entrée",
"thumbnail": "Aperçu", "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.", "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`).", "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.", "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é.", "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", "app_id": "Obligatoire seulement si vous comptez utiliser Facebook Analytics",
"page_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", "nlp_train": "Vous pouvez entraîner votre chatbot en ajoutant plusieurs exemples",

View File

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

View File

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

View File

@ -44,7 +44,6 @@ import { useHasPermission } from "@/hooks/useHasPermission";
import { useSearch } from "@/hooks/useSearch"; import { useSearch } from "@/hooks/useSearch";
import { useToast } from "@/hooks/useToast"; import { useToast } from "@/hooks/useToast";
import { useTranslate } from "@/hooks/useTranslate"; import { useTranslate } from "@/hooks/useTranslate";
import { TNestedTranslation } from "@/i18n/i18n.types";
import { EntityType, Format } from "@/services/types"; import { EntityType, Format } from "@/services/types";
import { ILanguage } from "@/types/language.types"; import { ILanguage } from "@/types/language.types";
import { import {
@ -321,9 +320,7 @@ export default function NlpSample() {
</IconButton> </IconButton>
), ),
}), }),
renderValue: (value) => ( renderValue: (value) => <Box>{t(`label.${value}`)}</Box>,
<Box>{t("label", value as TNestedTranslation<"label">)}</Box>
),
}} }}
> >
{Object.values(NlpSampleType).map((nlpSampleType, index) => ( {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 MultipleInput from "@/app-components/inputs/MultipleInput";
import { PasswordInput } from "@/app-components/inputs/PasswordInput"; import { PasswordInput } from "@/app-components/inputs/PasswordInput";
import { useTranslate } from "@/hooks/useTranslate"; import { useTranslate } from "@/hooks/useTranslate";
import { TNestedTranslation } from "@/i18n/i18n.types";
import { EntityType, Format } from "@/services/types"; import { EntityType, Format } from "@/services/types";
import { IBlock } from "@/types/block.types"; import { IBlock } from "@/types/block.types";
import { ISetting } from "@/types/setting.types"; import { ISetting } from "@/types/setting.types";
@ -34,13 +33,13 @@ const SettingInput: React.FC<RenderSettingInputProps> = ({
field, field,
isDisabled = () => false, isDisabled = () => false,
}) => { }) => {
const { t } = useTranslate(); const { t } = useTranslate(setting.group);
const nestedLabel = setting.label as TNestedTranslation<"label">; const label = t(`label.${setting.label}`, {
const nestedHelp = setting.help as TNestedTranslation<"help">; defaultValue: setting.label,
const label = t("label", nestedLabel, { defaultValue: nestedLabel }); });
const helperText = nestedHelp const helperText = t(`help.${setting.label}`, {
? t("help", nestedHelp, { defaultValue: nestedHelp }) defaultValue: "",
: ""; });
switch (setting.type) { switch (setting.type) {
case "text": case "text":

View File

@ -24,7 +24,6 @@ import { useFind } from "@/hooks/crud/useFind";
import { useUpdate } from "@/hooks/crud/useUpdate"; import { useUpdate } from "@/hooks/crud/useUpdate";
import { useToast } from "@/hooks/useToast"; import { useToast } from "@/hooks/useToast";
import { useTranslate } from "@/hooks/useTranslate"; import { useTranslate } from "@/hooks/useTranslate";
import { TNestedTranslation } from "@/i18n/i18n.types";
import { PageHeader } from "@/layout/content/PageHeader"; import { PageHeader } from "@/layout/content/PageHeader";
import { EntityType } from "@/services/types"; import { EntityType } from "@/services/types";
import { ISetting } from "@/types/setting.types"; import { ISetting } from "@/types/setting.types";
@ -70,13 +69,13 @@ function groupBy(array: ISetting[]) {
export const Settings = () => { export const Settings = () => {
const { t } = useTranslate(); const { t } = useTranslate();
const { toast } = useToast(); const { toast } = useToast();
const [selectedTab, setSelectedTab] = useState("live-chat-tester"); const [selectedTab, setSelectedTab] = useState("chatbot_settings");
const { control, watch } = useForm(); const { control, watch } = useForm();
const { data: settings } = useFind( const { data: settings } = useFind(
{ entity: EntityType.SETTING }, { entity: EntityType.SETTING },
{ {
hasCount: false, hasCount: false,
initialSortState: [{ field: "weight", sort: "desc" }], initialSortState: [{ field: "weight", sort: "asc" }],
}, },
); );
const { mutateAsync: updateSetting } = useUpdate(EntityType.SETTING, { const { mutateAsync: updateSetting } = useUpdate(EntityType.SETTING, {
@ -157,16 +156,16 @@ export const Settings = () => {
}, },
}} }}
> >
{(Object.keys(groups) as TNestedTranslation<"title">[]).map( {Object.keys(groups)
(group, index) => ( .sort((a, b) => a.localeCompare(b))
.map((group, index) => (
<StyledTab <StyledTab
value={group} value={group}
key={group} key={group}
label={t("title", group)} label={t(`title.${group}`, { ns: group })}
{...a11yProps(index)} {...a11yProps(index)}
/> />
), ))}
)}
</Tabs> </Tabs>
<StyledForm sx={{ width: "100%", px: 3, paddingY: 2 }}> <StyledForm sx={{ width: "100%", px: 3, paddingY: 2 }}>
{Object.entries(groups).map(([group, settings]) => ( {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 ListIcon from "@/app-components/svg/toolbar/ListIcon";
import { useGet } from "@/hooks/crud/useGet"; import { useGet } from "@/hooks/crud/useGet";
import { useTranslate } from "@/hooks/useTranslate"; import { useTranslate } from "@/hooks/useTranslate";
import { TNestedTranslation } from "@/i18n/i18n.types";
import { EntityType, Format } from "@/services/types"; import { EntityType, Format } from "@/services/types";
import { import {
ContentField, ContentField,
@ -68,12 +67,10 @@ const ListMessageForm = () => {
name="options.content.display_mode" name="options.content.display_mode"
render={({ field }) => ( render={({ field }) => (
<RadioGroup row {...field}> <RadioGroup row {...field}>
{( {[
[ OutgoingMessageFormat.list,
OutgoingMessageFormat.list, OutgoingMessageFormat.carousel,
OutgoingMessageFormat.carousel, ].map((display) => (
] as TNestedTranslation<"label">[]
).map((display) => (
<FormControlLabel <FormControlLabel
key={display} key={display}
value={display} value={display}
@ -83,7 +80,7 @@ const ListMessageForm = () => {
{...register("options.content.display")} {...register("options.content.display")}
/> />
} }
label={t("label", display)} label={t(`label.${display}`)}
/> />
))} ))}
</RadioGroup> </RadioGroup>
@ -106,7 +103,7 @@ const ListMessageForm = () => {
key={style} key={style}
value={style} value={style}
control={<Radio />} control={<Radio />}
label={t("label", style)} label={t(`label.${style}`)}
/> />
))} ))}
</RadioGroup> </RadioGroup>

View File

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

View File

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

View File

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

View File

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

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). * 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"; import { TFilterNestedKeysOfType } from "@/types/common/object.types";
type TEnTranslation = (typeof translations)["en"]; export type TTranslation = string;
type TFrTranslation = (typeof translations)["fr"];
export type TTranslation = TEnTranslation & TFrTranslation; export type TTranslationPrefix =
export type TTranslationKeys = TFilterNestedKeysOfType<TEnTranslation> & | "message"
TFilterNestedKeysOfType<TFrTranslation>; | "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> = export type TNestedTranslation<T extends keyof TTranslation> =
TFilterNestedKeysOfType<TTranslation[T]>; 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, EntityType.CUSTOM_BLOCK_SETTINGS,
undefined, undefined,
{ {
idAttribute: ({ id }) => id, idAttribute: ({ id, label }) => id || label,
}, },
); );

View File

@ -92,8 +92,6 @@ export interface ISettingAttributes {
options?: string[]; options?: string[];
config?: Record<string, any>; config?: Record<string, any>;
weight?: number; weight?: number;
title?: string;
help?: string;
} }
export interface ISettingStub extends IBaseSchema, ISettingAttributes {} 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-css": "^0.2.1-alpha.0",
"eazychart-react": "^0.8.0-alpha.0", "eazychart-react": "^0.8.0-alpha.0",
"hexabot-chat-widget": "*", "hexabot-chat-widget": "*",
"i18next-http-backend": "^2.6.2",
"next": "^14.2.13", "next": "^14.2.13",
"next-transpile-modules": "^10.0.1", "next-transpile-modules": "^10.0.1",
"normalizr": "^3.6.2", "normalizr": "^3.6.2",
@ -4167,6 +4168,14 @@
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
"dev": true "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": { "node_modules/cross-spawn": {
"version": "7.0.3", "version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@ -6440,6 +6449,14 @@
"@babel/runtime": "^7.23.2" "@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": { "node_modules/ignore": {
"version": "5.3.2", "version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@ -7633,6 +7650,25 @@
"enhanced-resolve": "^5.10.0" "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": { "node_modules/node-releases": {
"version": "2.0.18", "version": "2.0.18",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz",
@ -9173,6 +9209,11 @@
"node": ">=8.0" "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": { "node_modules/ts-api-utils": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz",
@ -9665,6 +9706,20 @@
"vscode-uri": "^3.0.8" "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": { "node_modules/which": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",