[Feature]: Added Localisation (#3746)

* Added localisation

* Closed modal after language selection

* updated transaltaion setup

* Updated language tooltip

* Added fallback language support

* Adding english library resource for translation (#3844)

* Adding English dictionary for the widget lists in the inspector

* added leftSideBar object in en.json and implemented it for leftSidebar icon text

* renamed leftSideBar to leftSidebar and added resources for tip in the left side bar

* added english translation resources for leftsidebar debugger

* added english language resources for the global settings

* added english language resources for data sources in left sidebar

* added english language resources for the share button and share modal in the editor

* added english language resources for release button, manageOrgUsers, appVersionManager

* added english language resources for Queries and Please select a widget to inspect in the editor

* added english language resources for data source list , data source manager, and query manager(partially)

* added english language resources for queryManager, transformation, preview

* added english language resources for dark mode toggle in the headers inside homepage

* added fallback message for dark mode toggle

* added resources for language change in the headers inside homepage

* added resources for notification center in the header inside homepage

* added resources for organization and manage users components in header inside homepage

* added resources in manageGroupPermission

* added resources for manageGroupPermissionsResources component

* added resources for manageSSO and generalSettings components

* added resources for google sso

* added resources for github sso

* added resources for environment variables in manageSSO

* added resources for profile and setting page

* added resources for app card and app card menu

* added resources for folder section and app list in homepage

* added resources for header section in the homepage

* added resources for pagination in homepage

* added resources for modals in the homepage

* added resources for blank page

* added resources for login page

* added resources for forgot password page

* added resources for sign up page

* added resources for onBoarding component

* added resources for reset password page

* deleted duplicate key for readDocumentation

* deleted duplicate key for cancel in en.json and added translation for cancel at few places

* removing duplicate copy of save key in en.json

* added translation for CommentActions.jsx components

* deleted duplicate copy of search key in en.json and added resources for create and search queries , select keys

* fix typo errors

* fixed typo errors

* shorten the key for loginAndSignUpAndForgotPassword to loginSignupPage in en.json file and related files

* shorten the key noLoginMethodsEnabledForThisWorkspace to noLoginMethodsEnabled

* shorten the key pleaseCheckYourEmailForConfirmationLink to emailConfirmLink

* shorten the key dontHaveAccountYet to dontHaveAccount

* shorten keys from loginSignupPage key in en.json

* shorten keys of shareModal nested object in en.json

* shorten the key in appVersionManager nested object

* shorten the keys for queryManager nested object in the en.json

* delete duplicate copy of environmentVar and shorten manageEnvironmentVariables,environmentVariables

* shorten keys in the organization nested object

* shorten keys in the homePage nested object in en.json file

* added inspector and eventManager empty object

* added resources to RedirectSSO component

* added resources for OAuth2

* added resources for TemplateCard.jsx

* added resources for TemplateLibraryModal.jsx

* added resources for ConfirmationPage.jsx

* added resources for ConfirmationPage component

* removed translation in App.jsx file

* added resources for Slack.jsx

* added resources for GoogleSheets.jsx

* added resources for CodeBuilder.jsx

* added resources for CommentBody and CommentFooter

* added resources for TestConnection component

* added resources for AllignButton.jsx

* added resources for Openapi and Stripe components

* added resources for ErrorBoundary

* added resources for Viewer.jsx

* Translation for widgets, table

Co-authored-by: Kavin Venkatachalam <kavin.saratha@gmail.com>

* Commented Language selection

* Fixed typos

* Updated fr.json file

Co-authored-by: Manish Kushare <kushare.manish9@gmail.com>
This commit is contained in:
Kavin Venkatachalam 2022-09-14 13:34:49 +05:30 committed by GitHub
parent 3f62230427
commit 7f702c1d6b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
91 changed files with 9965 additions and 1431 deletions

View file

@ -0,0 +1,921 @@
{
"globals":{
"readDocumentation":"Read documentation",
"cancel":"Cancel",
"save":"Save",
"back":"Back",
"edit":"Edit",
"search":"Search",
"update":"Update",
"delete":"Delete",
"add":"Add",
"view":"View",
"create":"Create",
"enabled":"Enabled",
"disabled":"Disabled",
"yes":"Yes",
"submit":"Submit",
"select":"Select",
"environmentVar":"Environment Variables",
"saving":"Saving...",
"saveDatasource":"Save data source",
"authorize":"Authorize",
"connect":"Connect",
"reconnect":"Reconnect",
"components":"components",
"send":"Send",
"noConnection":"could not connect",
"connectionVerifeid":"connection verified",
"left":"Left",
"center":"Center",
"right":"Right",
"justified":"Justified",
"host":"Host",
"operation":"Operation",
"header":"HEADER",
"path":"PATH",
"query":"QUERY",
"requestBody":"REQUEST BODY"
},
"errorBoundary":"Something went wrong.",
"viewer":"Sorry!. This app is under maintenance",
"app":{
"updateAvailable":"Update available",
"newVersionReleased":"A new version of ToolJet has been released.",
"readReleaseNotes":"Read release notes & update",
"skipVersion":"Skip this version"
},
"stripe":"Please wait whle we load the OpenAPI specification for Stripe.",
"openApi":{
"noValidOpenApi":"Valid OpenAPI Spec is not available!.",
"selectHost":"Select a host",
"selectOperation":"Select an operation"
},
"slack":{
"authorize":"Authorize",
"connectToolJetToSlack":"ToolJet can connect to Slack and list users, send messages, etc. Please select appropriate permission scopes.",
"chatWrite":"chat:write",
"listUsersAndSendMessage":"Your ToolJet app will be able to list users and send messages to users & channels.",
"connectSlack":"Connect to Slack"
},
"googleSheets":{
"readOnly":"Read only",
"enableReadAndWrite":"If you want your ToolJet apps to modify your Google sheets, make sure to select read and write access",
"readDataFromSheets":"Your ToolJet apps can only read data from Google sheets",
"readWrite":"Read and write",
"readModifySheets":"Your ToolJet apps can read data from sheets, modify sheets, and more.",
"toGoogleSheets":"to Google Sheets"
},
"profile": {
"profileSettings": "Profile Settings"
},
"loginSignupPage":{
"forgotPassword":"Forgot Password",
"emailAddress":"Email address",
"enterEmail":"Enter email",
"resetPassword":"Reset Password",
"dontHaveAccount":"Don't have account yet?",
"signIn":"Sign in",
"signUp":"Sign up",
"createToolJetAccount":"Create a ToolJet account",
"enterBusinessEmail":"Enter your business email",
"alreadyHaveAnAccount":"Already have an account?",
"password":"Password",
"showPassword":"show password",
"loginTo":"Login to",
"yourAccount":"your account",
"noLoginMethodsEnabled":"No login methods enabled for this workspace",
"emailConfirmLink":"Please check your email for confirmation link",
"newPassword":"New Password",
"passwordConfirmation":"Password Confirmation"
},
"editor": {
"preview": "Preview",
"share":"Share",
"shareModal": {
"makeApplicationPublic":"Make application public ?",
"shareableLink":"Get shareable link for this application",
"copy":"copy",
"embeddableLink":"Get embeddable link for this application",
"manageUsers":"Manage Users"
},
"appVersionManager":{
"version":"Version",
"currentlyReleased":"Currently Released",
"createVersion":"Create Version",
"versionName":"Version Name",
"createVersionFrom":"Create version from",
"save":"Save",
"create":"Create Version",
"editVersion": "Edit Version",
"deleteVersion":"Do you really want to delete this version?",
"enterVersionName":"Enter version name",
"versionAlreadyReleased":"Version already released. Kindly create a new version or switch to a different version to continue making changes."
},
"queries":"Queries",
"inspectComponent":"Please select a component to inspect",
"release":"Release",
"searchQueries":"Search queries",
"createQuery":"Create query",
"queryManager":{
"general":"General",
"advanced":"Advanced",
"preview":"Preview",
"Save":"Save",
"selectDatasource":"Select Datasource",
"addDatasource":"Add datasource",
"dataSourceManager":{
"toast":{
"success": {
"dataSourceAdded": "Datasource Added",
"dataSourceSaved": "Datasource Saved"
},
"error" :{
"noEmptyDsName" :"The name of datasource should not be empty"
}
},
"suggestDataSource":"Suggest Datasource",
"suggestAnIntegration":"Suggest an integration",
"whatLookingFor":"Tell us what you were looking for?",
"noResultFound":"Don't see what you were looking for?",
"suggest":"Suggest",
"addNewDataSource":"Add new datasource",
"whiteListIP":"Please white-list our IP address if the data source is not publicly accessible",
"copied":"Copied",
"copy":"Copy",
"saving":"Saving",
"noResultsFor":"No results for",
"noteTaken":"Thank you, we've taken a note of that!",
"goToAllDatasources":"Go to all Datasource",
"send":"Send"
},
"runQueryOnPageLoad":"Run this query on page load?",
"confirmBeforeQueryRun":"Request confirmation before running query?",
"notificationOnSuccess":"Show notification on success?",
"successMessage":"Success Message",
"queryRanSuccessfully":"Query ran successfully",
"notificationDuration":"Notification duration (s)",
"events":"Events",
"transformation":{
"transformationToolTip":"Transformations can be used to transform the results of queries. All the app variables are accessible from transformers and supports JS libraries such as Lodash & Moment.",
"transformations":"Transformations"
}
},
"inspector":{
"eventManager":{
"event": "Event",
"action": "Action",
"actionOptions": "Action Options",
"message": "Message",
"alertType": "Alert Type",
"url": "URL",
"modal": "Modal",
"text": "Text",
"query": "Query",
"key": "Key",
"value": "Value",
"type": "Type",
"fileName": "File name",
"data": "Data",
"table": "Table",
"pageIndex": "Page index",
"component": "Component",
"addHandler": "+ Add handler",
"addEventHandler": "+ Add event handler",
"emptyMessage": "This {{componentName}} doesn't have any event handlers"
}
}
},
"header":{
"darkModeToggle":{
"activateLightMode" :"Activate light mode",
"activateDarkMode":"Activate dark mode"
},
"languageSelection":{
"changeLanguage":"Change language",
"searchLanguage":"Search language"
},
"notificationCenter":{
"notifications":"Notifications",
"markAllAs":"Mark all as",
"read":"read",
"un":"un",
"youDontHaveany":"You don't have any",
"youAreCaughtUp":"You're all caught up!",
"view":"View"
},
"organization":{
"loadOrganizations":"Load Organizations",
"createWorkspace":"Create workspace",
"workspaceName":"workspace name",
"editWorkspace":"Edit workspace",
"menus": {
"addWorkspace":"Add workspace",
"menusList":{
"manageUsers":"Manage Users",
"manageGroups":"Manage Groups",
"manageSso":"Manage SSO",
"manageEnv":"Manage Environment Variables"
},
"manageUsers":{
"usersAndPermission":"Users & Permissions",
"inviteNewUser":"Invite new user",
"name":"NAME",
"email":"EMAIL",
"status":"STATUS",
"archive":"Archive",
"unarchive":"Unarchive",
"addNewUser":"Add new user",
"emailAddress":"Email address",
"createUser":"Create User",
"enterFirstName":"Enter First Name",
"enterLastName":"Enter Last Name",
"enterEmail":"Enter Email"
},
"manageGroups":{
"permissions":{
"userGroups":"User Groups",
"createNewGroup":"Create new group",
"udpateGroup":"Update group",
"addNewGroup":"Add new group",
"enterName":"Enter Name",
"createGroup":"Create Group",
"name":"Name"
},
"permissionResources":{
"userGroup":"User group",
"apps":"Apps",
"users":"User",
"permissions":"Permissions",
"addAppsToGroup":"Select apps to add to the group",
"name":"name",
"addUsersToGroup":"Select users to add to the group",
"email":"email",
"resource":"resource",
"createUpdateDelete":"Create/Update/Delete",
"folder":"Folder"
}
},
"manageSSO":{
"manageSso":"Manage SSO",
"generalSettings":{
"title":"General Settings",
"enableSignup":"Enable Signup",
"newAccountWillBeCreated":"New account will be created for user's first time SSO sign in",
"allowedDomains":"Allowed domains",
"enterDomains":"Enter Domains",
"supportMultiDomains":"Support multiple domains. Enter domain names separated by comma. example: tooljet.com,tooljet.io,yourorganization.com",
"loginUrl":"Login URL",
"workspaceLogin":"Use this URL to login directly to this workspace",
"allowDefaultSso":"Allow default SSO",
"ssoAuth":"Allow users to authenticate via default SSO. Default SSO configurations can be overridden by workspace level SSO."
},
"google":{
"title":"Google",
"enabled":"Enabled",
"disabled":"Disabled",
"clientId":"Client Id",
"enterClientId":"Enter Client Id",
"redirectUrl":"Redirect URL"
},
"github":{
"title":"Github",
"hostName":"Host Name",
"enterHostName":"Enter Host Name",
"requiredGithub":"Required if GitHub is self hosted",
"clientId":"Client Id",
"enterClientId":"Enter Client Id",
"clientSecret":"Client Secret",
"enterClientSecret":"Enter Client Secret",
"encrypted":"Encrypted",
"redirectUrl":"Redirect URL"
},
"passwordLogin":"Password Login",
"environmentVar" : {
"noEnvConfig":"You haven't configured any environment variables, press the 'Add new variable' button to create one",
"envWillBeDeleted":"Variable will be deleted, do you want to continue?",
"addNewVariable":"Add new variable",
"variableForm":{
"addNewVariable":"Add new variable",
"updatevariable":"Update variable",
"name":"Name",
"value":"Value",
"enterVariableName":"Enter Variable Name",
"enterValue":"Enter Value",
"type":"Type",
"enableEncryption":"Enable encryption",
"addVariable":"Add variable"
},
"variableTable":{
"name":"name",
"value":"value",
"type":"type",
"secret":"secret"
}
}
}
}
},
"profileSettingPage":{
"profileSettings":"Profile Settings",
"firstName":"First name",
"lastName":"Last name",
"enterFirstName":"Enter First Name",
"enterLastName":"Enter Last Name",
"email":"Email",
"avatar":"Avatar",
"update":"Update",
"profile":"Profile",
"changePassword":"Change password",
"currentPassword":"Current password",
"newPassword":"New password",
"confirmNewPassword":"Confirm new password",
"enterCurrentPassword":"Enter current password",
"enterNewPassword":"Enter new password"
},
"profile":"Profile",
"logout":"Logout"
},
"homePage":{
"appCard":{
"changeIcon":"Change Icon",
"addToFolder":"Add to folder",
"move":"Move",
"to":"to",
"selectFolder":"Select folder",
"deleteApp":"Delete app",
"exportApp":"Export app",
"cloneApp":"Clone app",
"launch":"Launch",
"maintenance":"Maintenance",
"noDeployedVersion":"App does not have a deployed version",
"openInAppViewer":"Open in app viewer",
"removeFromFolder":"Remove from folder"
},
"blankPage":{
"welcomeToToolJet":"Welcome to ToolJet!",
"getStartedCreateNewApp":"You can get started by creating a new application or by creating an application using a template in ToolJet Library.",
"importApplication":"Import an application"
},
"foldersSection":{
"allApplications":"All applications",
"folders":"Folders",
"createNewFolder":"+ Create new folder",
"noFolders":"You haven't created any folders. Use folders to organize your apps",
"createFolder":"Create folder",
"updateFolder":"Update folder",
"editFolder":"Edit folder",
"deleteFolder":"Delete folder",
"folderName":"Folder name",
"wishToDeleteFolder":"Are you sure you want to delete the folder? Apps within the folder will not be deleted."
},
"header":{
"createNewApplication":"Create new application",
"import":"Import",
"chooseFromTemplate":"Choose from template"
},
"pagination":{
"showing":"Showing",
"of":"of",
"to":"to"
},
"noApplicationFound":"No Applications found",
"thisFolderIsEmpty":"This folder is empty",
"deleteAppAndData":"The app and the associated data will be permanently deleted, do you want to continue?",
"removeAppFromFolder":"The app will be removed from this folder, do you want to continue?",
"change":"Change",
"templateCard":{
"use":"Use",
"preview":"Preview",
"leadGeneretion":"Lead generetion"
},
"templateLibraryModal":{
"select":"Select template",
"createAppfromTemplate":"Create application from template"
}
},
"confirmationPage":{
"setupAccount":"Set up your account",
"signupWithGoogle":"Sign up with Google",
"signupWithGitHub":"Sign up with GitHub",
"or":"OR",
"firstName":"First name",
"lastName":"Last name",
"company":"Company",
"role":"Role",
"pleaseSelect":"Please select",
"password":"Password",
"confirmPassword":"Confirm Password",
"clickAndAgree":"By clicking the button below, you agree to our",
"termsAndConditions":"Terms and Conditions",
"finishAccountSetup":"Finish account setup",
"acceptInvite":"accept invite",
"accountExists":"Already have an account?",
"and":"and"
},
"onBoarding":{
"finishToolJetInstallation":"Finish ToolJet installation",
"organization":"Organization",
"name":"Name",
"email":"Email",
"receiveUpdatesFromToolJet":"You will receive updates from the ToolJet team ( 1-2 emails every month, we do not spam )",
"finishSetup":"Finish setup",
"skip":"Skip"
},
"redirectSso":{
"upgradingTov1.13.0":"Upgrading to v1.13.0 and above.",
"fromV1.13.0":"From v1.13.0 we have introduced",
"multiWorkspace":"Multi-Workspace",
"singleSignOnConfig":"The Single Sign-On related configurations are moved from environment variables to database. Please refer this",
"link":"Link",
"toConfigureSSO":"to configure SSO.",
"haveGoogleGithubSSo":"If you have Google or GitHub SSO configurations before upgrade and disabled Multi-Workspace, then theSSO configurations will be migrated while upgrade but you have to re-configure the redirect URL in the SSO provider side. Redirect URLs for each SSO are given below.",
"isMultiWorkspaceEnabled":"If you have enabled Multi-Workspace, then the SSO configurations will not be migrated while upgrade so you have to re-configure the SSO under the respective workspace.",
"youHaveEnabled":"You have Enabled",
"setupSsoWorkspace":"Please login with password and you can setup sso using workspace",
"manageSsoMenu":"Manage SSO menu.",
"youHaveDisabled":"You have Disabled",
"configureRedirectUrl":"Please configure redirect url in SSO provider side.",
"google":"Google",
"redirectUrl":"Redirect URL:",
"gitHub":"GitHub"
},
"oAuth2": {
"pleaseWait":"Please wait...",
"authSuccess":"Auth successful, you can close this tab now.",
"authFailed":"Auth failed"
},
"widgetManager": {
"commonlyUsed": "commonly used",
"layouts" : "layouts",
"forms" : "forms",
"intregrations" : "integrations",
"others":"others",
"noResults": "No results found",
"tryAdjustingFilterMessage": "Try adjusting your search or filter to find what you're looking for.",
"clearQuery": "clear query"
},
"widget":{
"common":{
"properties": "Properties",
"events": "Events",
"layout": "Layout",
"styles": "Styles",
"general": "General",
"validation": "Validation",
"documentation": "{{componentMeta}} documentation",
"widgetNameEmptyError": "Widget name cannot be empty",
"componentNameExistsError": "Component name already exists",
"invalidWidgetName": "Invalid widget name. Should be unique and only include letters, numbers and underscore."
},
"commonProperties": {
"visibility": "Visibility",
"disable": "Disable",
"borderRadius": "Border Radius",
"boxShadow": "Box Shadow",
"tooltip": "Tooltip",
"showOnDesktop": "Show on desktop",
"showOnMobile": "Show on mobile",
"showLoadingState": "Show loading state",
"backgroundColor": "Background Color",
"textColor": "Text color",
"loaderColor": "Loader color",
"defaultValue": "Default Value",
"placeholder": "Placeholder",
"label": "Label",
"title": "Title",
"code": "Code",
"data": "Data",
"tableData": "Table data",
"tableColumns": "Table columns",
"searverSideSearch": "Server-side search",
"loadingState": "Loading State",
"serverSidePagination": "Server-side pagination",
"clientSidePagination": "Client-side pagination",
"serverSideSearch": "Server-side search",
"showSearchBox": "Show search box",
"showDownloadButton": "Show download button",
"showFilterButton": "Show filter button",
"showBulkUpdateActions": "Show bulk update actions",
"bulkSelection": "Bulk selection",
"highlightSelectedRow": "Highlight selected row",
"actionButtonRadius": "Action Button Radius",
"tableType": "Table type",
"cellSize": "Cell size",
"setPage": "Set page",
"page": "Page",
"buttonText": "Button Text",
"click": "Click",
"setText": "Set text",
"text": "Text",
"markerColor": "Marker color",
"showAxes": "Show axes",
"showGridLines": "Show grid lines",
"chartType": "Chart type",
"jsonDescription": "Json Description",
"usePlotlyJsonSchema": "Use Plotly JSON schema",
"padding": "Padding",
"hideTitleBar": "Hide title bar",
"hideCloseButton": "Hide close button",
"hideOnEscape": "Hide on escape",
"modalSize": "Modal size",
"open": "Open",
"close": "Close",
"regex": "Regex",
"minLength": "Min length",
"maxLength": "Max length",
"customValidation": "Custom validation",
"clear": "Clear",
"minimumValue": "Minimum value",
"maximumValue": "Maximum value",
"format": "Format",
"enableTimeSelection": "Enable time selection?",
"enableDateSelection": "Enable date selection?",
"disabledDates": "Disabled dates",
"setChecked": "Set checked",
"status": "status",
"defaultStatus": "Default Status",
"checkboxColor": "Checkbox Color",
"optionValues": "Option values",
"optionLabels": "Option labels",
"activeColor": "Active Color",
"selectOption": "Select option",
"option": "Option",
"toggleSwitchColor": "Toggle Switch Color",
"defaultStartDate": "Default start date",
"defaultEndDate": "Default end date",
"textSize": "Text Size",
"alignText": "Align Text",
"url": "URL",
"alternativeText": "Alternative text",
"zoomButton": "Zoom button",
"borderType": "Border type",
"imageFit": "Image fit",
"optionsLoadingState": "Options loading state",
"selectedTextColor": "Selected Text Color",
"select": "Select",
"deselectOption": "Deselect Option",
"clearSelections": "Clear selections",
"enableSelectAllOption": "Enable select All option",
"initialLocation": "Initial location",
"defaultMarkers": "Default markers",
"addNewMarkers": "Add new markers",
"searchForPlaces": "Search for places",
"setLocation": "Set Location",
"latitude": "Latitude",
"longitude": "Longitude",
"numberOfStars": "Number of stars",
"defaultNoOfSelectedStars": "Default no of selected stars",
"enableHalfStar": "Enable half star",
"tooltips": "Tooltips",
"starColor": "Star Color",
"labelColor": "Label Color",
"dividerColor": "Divider Color",
"clearFiles": "Clear Files",
"instructionText": "Instruction Text",
"useDropZone": "Use Drop zone",
"useFilePicker": "Use File Picker",
"pickMultipleFiles": "Pick multiple files",
"maxFileCount": "Max file count",
"acceptFileTypes": "Accept file types",
"maxSizeLimitBytes": "Max size limit (Bytes)",
"minSizeLimitBytes": "Min size limit (Bytes)",
"parseContent": "Parse content",
"fileType": "File type",
"dateFormat": "Date format",
"defaultDate": "Default date",
"events": "Events",
"resources": "Resources",
"defaultView": "Default view",
"startTimeOnWeekAndDayView": "Start time on week and day view",
"endTimeOnWeekAndDayView": "End time on week and day view",
"showToolbar": "Show toolbar",
"showViewSwitcher": "Show view switcher",
"highlightToday": "Highlight today",
"showPopoverWhenEventIsClicked": "Show popover when event is clicked",
"cellSizeInViewsClassifiedByResource": "Cell size in views classified by resource",
"headerDateFormatOnWeekView": "Header date format on week view",
"showLineNumber": "Show Line Number",
"mode": "Mode",
"tabs": "Tabs",
"defaultTab": "Default tab",
"hideTabs": "Hide Tabs",
"highlightColor": "Highlight Color",
"tabWidth": "Tab width",
"setCurrentTab": "Set current tab",
"id": "Id",
"timerType": "Timer type",
"listData": "List data",
"rowHeight": "Row height",
"showBottomBorder": "Show bottom border",
"tags": "Tags",
"numberOfPages": "Number of pages",
"defaultPageIndex": "Default page index",
"progress": "Progress",
"color": "Color",
"strokeWidth": "Stroke Width",
"counterClockwise": "Counter Clockwise",
"circleRatio": "Circle Ratio",
"colour": "Colour",
"size": "Size",
"primaryValueLabel": "Primary value label",
"primaryValue": "Primary value",
"hideSecondaryValue": "Hide secondary value",
"secondaryValueLabel": "Secondary value label",
"secondaryValue": "Secondary value",
"secondarySignDisplay": "Secondary sign display",
"primaryLabelColour": "Primary Label Colour",
"primaryTextColour": "Primary Text Colour",
"secondaryLabelColour": "Secondary Label Colour",
"secondaryTextColour": "Secondary Text Colour",
"min": "Min",
"max": "Max",
"value": "Value",
"twoHandles": "Two handles",
"lineColor": "Line color",
"handleColor": "Handle color",
"trackColor": "Track color",
"timelineData": "Timeline data",
"hideDate": "Hide Date",
"svgData": "Svg data",
"rawHtml": "Raw HTML",
"values": "values",
"labels": "Labels",
"defaultSelected": "Default selected",
"enableMutipleSelection": "Enable mutiple selection",
"selectedTextColour": "Selected text colour",
"selectedBackgroundColor": "Selected background color",
"fileUrl": "File URL",
"scalePageToWidth": "Scale page to width",
"showPageControls": "Show page controls",
"steps": "Steps",
"currentStep": "Current step",
"stepsSelectable": "Steps selectable",
"theme": "Theme",
"columns": "Columns",
"cardData": "Card Data",
"enableAddCard": "Enable Add Card",
"width": "Width",
"minWidth": "Min Width",
"accentColor": "Accent color",
"defaultColor": "Default Color",
"setColor": "Set Color",
"structure": "Structure",
"checkedValues": "Checked Values",
"expandedValues": "Expanded Values"
},
"Table": {
"displayName": "Table",
"description": "Display paginated tabular data",
"columnType": "Column type",
"columnName": "Column name",
"overflow": "Overflow",
"key": "key",
"textColor": "Text color",
"validation": "Validation",
"regex": "Regex",
"minLength": "Min length",
"maxLength": "Max length",
"customRule": "Custom rule",
"values": "Values",
"labels": "Labels",
"cellBgColor": "Cell Background Color",
"dateDisplayformat": "Date Display Format",
"dateParseformat": "Date Parse Format",
"showTime": "show time",
"makeEditable": "make editable",
"buttonText": "Button Text",
"buttonPosition": "Button Position",
"remove": "Remove",
"addButton": "+ Add button",
"addColumn": "+ Add column",
"noActionMessage": "This table doesn't have any action buttons"
},
"Button": {
"displayName": "Button",
"description": "Trigger actions: queries, alerts etc"
},
"Chart": {
"displayName": "Chart",
"description": "Display charts"
},
"Modal" : {
"displayName": "Modal",
"description": "Modal triggered by events"
},
"TextInput" : {
"displayName": "Text Input",
"description": "Text field for forms"
},
"NumberInput" :{
"displayName": "Number Input",
"description": "Number field for forms"
},
"PasswordInput":{
"displayName": "Password Input",
"description": "Password input field for forms"
},
"Datepicker" :{
"displayName": "Date Picker",
"description": "Select a date and time"
},
"Checkbox":{
"displayName": "Checkbox",
"description": "A single checkbox"
},
"Radio-button":{
"displayName": "Radio Button",
"description": "Radio buttons"
},
"ToggleSwitch":{
"displayName": "Toggle Switch",
"description": "Toggle Switch"
},
"Textarea":{
"displayName": "Textarea",
"description": "Text area form field"
},
"DateRangePicker":{
"displayName": "Range Picker",
"description": "Select a date range"
},
"Text":{
"displayName": "Text",
"description": "Display markdown or HTML"
},
"Image":{
"displayName": "Image",
"description": "Display an Image"
},
"Container":{
"displayName": "Container",
"description": "Wrapper for multiple components"
},
"Dropdown":{
"displayName": "Dropdown",
"description": "Select one value from options"
},
"Multiselect":{
"displayName": "Multiselect",
"description": "Select multiple values from options"
},
"RichTextEditor":{
"displayName": "Text Editor",
"description": "Rich text editor"
},
"Map":{
"displayName": "Map",
"description": "Display Google Maps"
},
"QrScanner":{
"displayName": "QR Scanner",
"description": "Scan QR codes and hold its data"
},
"StarRating":{
"displayName": "Rating",
"description": "Star rating"
},
"Divider":{
"displayName": "Divider",
"description": "Separator between components"
},
"FilePicker":{
"displayName": "File Picker",
"description": "File Picker"
},
"Calendar":{
"displayName": "Calendar",
"description": "Calendar"
},
"Iframe":{
"displayName": "Iframe",
"description": "Display an Iframe"
},
"CodeEditor":{
"displayName": "Code Editor",
"description": "Code Editor"
},
"Tabs":{
"displayName": "Tabs",
"description": "Tabs component"
},
"Timer":{
"displayName": "Timer",
"description": "timer"
},
"Listview":{
"displayName": "List View",
"description": "Wrapper for multiple components"
},
"Tags":{
"displayName": "Tags",
"description": "Content can be shown as tags"
},
"Pagination":{
"displayName": "Pagination",
"description": "Pagination "
},
"CircularProgressbar":{
"displayName": "Circular Progressbar",
"description": "Show the progress using circular progressbar"
},
"Spinner":{
"displayName": "Spinner",
"description": "Spinner can be used to display loading status"
},
"Statistics":{
"displayName": "Statistics",
"description": "Statistics can be used to display different statistical information"
},
"RangeSlider":{
"displayName": "Range Slider",
"description": "Can be used to show slider with a range"
},
"Timeline":{
"displayName": "Timeline",
"description": "Visual representation of a sequence of events"
},
"SvgImage":{
"displayName": "Svg Image",
"description": "Svg image"
},
"Html":{
"displayName": "HTML Viewer",
"description": "HTML Viewer"
},
"VerticalDivider":{
"displayName": "Vertical Divider",
"description": "Vertical Separator between components"
},
"CustomComponent":{
"displayName": "Custom Component",
"description": "Add your custom react component"
},
"ButtonGroup":{
"displayName": "Button Group",
"description": "ButtonGroup"
},
"PDF":{
"displayName": "PDF",
"description": "Embed PDF file"
},
"Steps":{
"displayName": "Steps",
"description": "Steps"
},
"KanbanBoard":{
"displayName": "Kanban Board",
"description": "Kanban Board"
},
"ColorPicker":{
"displayName": "Color Picker",
"description": "Color Picker Pallete"
},
"TreeSelect":{
"displayName": "Tree Select",
"description": "Select values from a tree view"
}
},
"leftSidebar":{
"Inspector":{
"text":"Inspector",
"tip" : "Inspector"
},
"Sources" :{
"text":"Sources",
"tip" : "Add or edit datasources",
"dataSources":"Data sources",
"addDataSource":"+ add data source"
},
"Debugger":{
"text":"Debugger",
"tip" : "Debugger",
"errors":"Errors",
"noErrors":"No errors found",
"clear":"clear"
},
"Comments":{
"text":"Comments",
"tip" : "toggle comments",
"commentBody":"There are no comments to display",
"typeComment":"Type your comment here"
},
"Settings":{
"text":"Settings",
"tip" : "Global Settings",
"hideHeader": "Hide header for launched apps",
"maintenanceMode":"Maintenance mode",
"maxWidthOfCanvas":"Max width of canvas",
"maxHeightOfCanvas" :"Max height of canvas",
"backgroundColorOfCanvas":"Background color of canvas"
},
"Back":{
"text":"Back",
"tip" : "Back to Home"
}
}
}

View file

@ -0,0 +1,921 @@
{
"globals":{
"readDocumentation":"Read documentation",
"cancel":"Cancel",
"save":"Save",
"back":"Back",
"edit":"Edit",
"search":"Search",
"update":"Update",
"delete":"Delete",
"add":"Add",
"view":"View",
"create":"Create",
"enabled":"Enabled",
"disabled":"Disabled",
"yes":"Yes",
"submit":"Submit",
"select":"Select",
"environmentVar":"Environment Variables",
"saving":"Saving...",
"saveDatasource":"Save data source",
"authorize":"Authorize",
"connect":"Connect",
"reconnect":"Reconnect",
"components":"components",
"send":"Send",
"noConnection":"could not connect",
"connectionVerifeid":"connection verified",
"left":"Left",
"center":"Center",
"right":"Right",
"justified":"Justified",
"host":"Host",
"operation":"Operation",
"header":"HEADER",
"path":"PATH",
"query":"QUERY",
"requestBody":"REQUEST BODY"
},
"errorBoundary":"Something went wrong.",
"viewer":"Sorry!. This app is under maintenance",
"app":{
"updateAvailable":"Update available",
"newVersionReleased":"A new version of ToolJet has been released.",
"readReleaseNotes":"Read release notes & update",
"skipVersion":"Skip this version"
},
"stripe":"Please wait whle we load the OpenAPI specification for Stripe.",
"openApi":{
"noValidOpenApi":"Valid OpenAPI Spec is not available!.",
"selectHost":"Select a host",
"selectOperation":"Select an operation"
},
"slack":{
"authorize":"Authorize",
"connectToolJetToSlack":"ToolJet can connect to Slack and list users, send messages, etc. Please select appropriate permission scopes.",
"chatWrite":"chat:write",
"listUsersAndSendMessage":"Your ToolJet app will be able to list users and send messages to users & channels.",
"connectSlack":"Connect to Slack"
},
"googleSheets":{
"readOnly":"Read only",
"enableReadAndWrite":"If you want your ToolJet apps to modify your Google sheets, make sure to select read and write access",
"readDataFromSheets":"Your ToolJet apps can only read data from Google sheets",
"readWrite":"Read and write",
"readModifySheets":"Your ToolJet apps can read data from sheets, modify sheets, and more.",
"toGoogleSheets":"to Google Sheets"
},
"profile": {
"profileSettings": "Profile Settings"
},
"loginSignupPage":{
"forgotPassword":"Forgot Password",
"emailAddress":"Email address",
"enterEmail":"Enter email",
"resetPassword":"Reset Password",
"dontHaveAccount":"Don't have account yet?",
"signIn":"Sign in",
"signUp":"Sign up",
"createToolJetAccount":"Create a ToolJet account",
"enterBusinessEmail":"Enter your business email",
"alreadyHaveAnAccount":"Already have an account?",
"password":"Password",
"showPassword":"show password",
"loginTo":"Login to",
"yourAccount":"your account",
"noLoginMethodsEnabled":"No login methods enabled for this workspace",
"emailConfirmLink":"Please check your email for confirmation link",
"newPassword":"New Password",
"passwordConfirmation":"Password Confirmation"
},
"editor": {
"preview": "Preview",
"share":"Share",
"shareModal": {
"makeApplicationPublic":"Make application public ?",
"shareableLink":"Get shareable link for this application",
"copy":"copy",
"embeddableLink":"Get embeddable link for this application",
"manageUsers":"Manage Users"
},
"appVersionManager":{
"version":"Version",
"currentlyReleased":"Currently Released",
"createVersion":"Create Version",
"versionName":"Version Name",
"createVersionFrom":"Create version from",
"save":"Save",
"create":"Create Version",
"editVersion": "Edit Version",
"deleteVersion":"Do you really want to delete this version?",
"enterVersionName":"Enter version name",
"versionAlreadyReleased":"Version already released. Kindly create a new version or switch to a different version to continue making changes."
},
"queries":"Queries",
"inspectComponent":"Please select a component to inspect",
"release":"Release",
"searchQueries":"Search queries",
"createQuery":"Create query",
"queryManager":{
"general":"General",
"advanced":"Advanced",
"preview":"Preview",
"Save":"Save",
"selectDatasource":"Select Datasource",
"addDatasource":"Add datasource",
"dataSourceManager":{
"toast":{
"success": {
"dataSourceAdded": "Datasource Added",
"dataSourceSaved": "Datasource Saved"
},
"error" :{
"noEmptyDsName" :"The name of datasource should not be empty"
}
},
"suggestDataSource":"Suggest Datasource",
"suggestAnIntegration":"Suggest an integration",
"whatLookingFor":"Tell us what you were looking for?",
"noResultFound":"Don't see what you were looking for?",
"suggest":"Suggest",
"addNewDataSource":"Add new datasource",
"whiteListIP":"Please white-list our IP address if the data source is not publicly accessible",
"copied":"Copied",
"copy":"Copy",
"saving":"Saving",
"noResultsFor":"No results for",
"noteTaken":"Thank you, we've taken a note of that!",
"goToAllDatasources":"Go to all Datasource",
"send":"Send"
},
"runQueryOnPageLoad":"Run this query on page load?",
"confirmBeforeQueryRun":"Request confirmation before running query?",
"notificationOnSuccess":"Show notification on success?",
"successMessage":"Success Message",
"queryRanSuccessfully":"Query ran successfully",
"notificationDuration":"Notification duration (s)",
"events":"Events",
"transformation":{
"transformationToolTip":"Transformations can be used to transform the results of queries. All the app variables are accessible from transformers and supports JS libraries such as Lodash & Moment.",
"transformations":"Transformations"
}
},
"inspector":{
"eventManager":{
"event": "Event",
"action": "Action",
"actionOptions": "Action Options",
"message": "Message",
"alertType": "Alert Type",
"url": "URL",
"modal": "Modal",
"text": "Text",
"query": "Query",
"key": "Key",
"value": "Value",
"type": "Type",
"fileName": "File name",
"data": "Data",
"table": "Table",
"pageIndex": "Page index",
"component": "Component",
"addHandler": "+ Add handler",
"addEventHandler": "+ Add event handler",
"emptyMessage": "This {{componentName}} doesn't have any event handlers"
}
}
},
"header":{
"darkModeToggle":{
"activateLightMode" :"Activate light mode",
"activateDarkMode":"Activate dark mode"
},
"languageSelection":{
"changeLanguage":"Change language",
"searchLanguage":"Search language"
},
"notificationCenter":{
"notifications":"Notifications",
"markAllAs":"Mark all as",
"read":"read",
"un":"un",
"youDontHaveany":"You don't have any",
"youAreCaughtUp":"You're all caught up!",
"view":"View"
},
"organization":{
"loadOrganizations":"Load Organizations",
"createWorkspace":"Create workspace",
"workspaceName":"workspace name",
"editWorkspace":"Edit workspace",
"menus": {
"addWorkspace":"Add workspace",
"menusList":{
"manageUsers":"Manage Users",
"manageGroups":"Manage Groups",
"manageSso":"Manage SSO",
"manageEnv":"Manage Environment Variables"
},
"manageUsers":{
"usersAndPermission":"Users & Permissions",
"inviteNewUser":"Invite new user",
"name":"NAME",
"email":"EMAIL",
"status":"STATUS",
"archive":"Archive",
"unarchive":"Unarchive",
"addNewUser":"Add new user",
"emailAddress":"Email address",
"createUser":"Create User",
"enterFirstName":"Enter First Name",
"enterLastName":"Enter Last Name",
"enterEmail":"Enter Email"
},
"manageGroups":{
"permissions":{
"userGroups":"User Groups",
"createNewGroup":"Create new group",
"udpateGroup":"Update group",
"addNewGroup":"Add new group",
"enterName":"Enter Name",
"createGroup":"Create Group",
"name":"Name"
},
"permissionResources":{
"userGroup":"User group",
"apps":"Apps",
"users":"User",
"permissions":"Permissions",
"addAppsToGroup":"Select apps to add to the group",
"name":"name",
"addUsersToGroup":"Select users to add to the group",
"email":"email",
"resource":"resource",
"createUpdateDelete":"Create/Update/Delete",
"folder":"Folder"
}
},
"manageSSO":{
"manageSso":"Manage SSO",
"generalSettings":{
"title":"General Settings",
"enableSignup":"Enable Signup",
"newAccountWillBeCreated":"New account will be created for user's first time SSO sign in",
"allowedDomains":"Allowed domains",
"enterDomains":"Enter Domains",
"supportMultiDomains":"Support multiple domains. Enter domain names separated by comma. example: tooljet.com,tooljet.io,yourorganization.com",
"loginUrl":"Login URL",
"workspaceLogin":"Use this URL to login directly to this workspace",
"allowDefaultSso":"Allow default SSO",
"ssoAuth":"Allow users to authenticate via default SSO. Default SSO configurations can be overridden by workspace level SSO."
},
"google":{
"title":"Google",
"enabled":"Enabled",
"disabled":"Disabled",
"clientId":"Client Id",
"enterClientId":"Enter Client Id",
"redirectUrl":"Redirect URL"
},
"github":{
"title":"Github",
"hostName":"Host Name",
"enterHostName":"Enter Host Name",
"requiredGithub":"Required if GitHub is self hosted",
"clientId":"Client Id",
"enterClientId":"Enter Client Id",
"clientSecret":"Client Secret",
"enterClientSecret":"Enter Client Secret",
"encrypted":"Encrypted",
"redirectUrl":"Redirect URL"
},
"passwordLogin":"Password Login",
"environmentVar" : {
"noEnvConfig":"You haven't configured any environment variables, press the 'Add new variable' button to create one",
"envWillBeDeleted":"Variable will be deleted, do you want to continue?",
"addNewVariable":"Add new variable",
"variableForm":{
"addNewVariable":"Add new variable",
"updatevariable":"Update variable",
"name":"Name",
"value":"Value",
"enterVariableName":"Enter Variable Name",
"enterValue":"Enter Value",
"type":"Type",
"enableEncryption":"Enable encryption",
"addVariable":"Add variable"
},
"variableTable":{
"name":"name",
"value":"value",
"type":"type",
"secret":"secret"
}
}
}
}
},
"profileSettingPage":{
"profileSettings":"Profile Settings",
"firstName":"First name",
"lastName":"Last name",
"enterFirstName":"Enter First Name",
"enterLastName":"Enter Last Name",
"email":"Email",
"avatar":"Avatar",
"update":"Update",
"profile":"Profile",
"changePassword":"Change password",
"currentPassword":"Current password",
"newPassword":"New password",
"confirmNewPassword":"Confirm new password",
"enterCurrentPassword":"Enter current password",
"enterNewPassword":"Enter new password"
},
"profile":"Profile",
"logout":"Logout"
},
"homePage":{
"appCard":{
"changeIcon":"Change Icon",
"addToFolder":"Add to folder",
"move":"Move",
"to":"to",
"selectFolder":"Select folder",
"deleteApp":"Delete app",
"exportApp":"Export app",
"cloneApp":"Clone app",
"launch":"Launch",
"maintenance":"Maintenance",
"noDeployedVersion":"App does not have a deployed version",
"openInAppViewer":"Open in app viewer",
"removeFromFolder":"Remove from folder"
},
"blankPage":{
"welcomeToToolJet":"Welcome to ToolJet!",
"getStartedCreateNewApp":"You can get started by creating a new application or by creating an application using a template in ToolJet Library.",
"importApplication":"Import an application"
},
"foldersSection":{
"allApplications":"All applications",
"folders":"Folders",
"createNewFolder":"+ Create new folder",
"noFolders":"You haven't created any folders. Use folders to organize your apps",
"createFolder":"Create folder",
"updateFolder":"Update folder",
"editFolder":"Edit folder",
"deleteFolder":"Delete folder",
"folderName":"Folder name",
"wishToDeleteFolder":"Are you sure you want to delete the folder? Apps within the folder will not be deleted."
},
"header":{
"createNewApplication":"Create new application",
"import":"Import",
"chooseFromTemplate":"Choose from template"
},
"pagination":{
"showing":"Showing",
"of":"of",
"to":"to"
},
"noApplicationFound":"No Applications found",
"thisFolderIsEmpty":"This folder is empty",
"deleteAppAndData":"The app and the associated data will be permanently deleted, do you want to continue?",
"removeAppFromFolder":"The app will be removed from this folder, do you want to continue?",
"change":"Change",
"templateCard":{
"use":"Use",
"preview":"Preview",
"leadGeneretion":"Lead generetion"
},
"templateLibraryModal":{
"select":"Select template",
"createAppfromTemplate":"Create application from template"
}
},
"confirmationPage":{
"setupAccount":"Set up your account",
"signupWithGoogle":"Sign up with Google",
"signupWithGitHub":"Sign up with GitHub",
"or":"OR",
"firstName":"First name",
"lastName":"Last name",
"company":"Company",
"role":"Role",
"pleaseSelect":"Please select",
"password":"Password",
"confirmPassword":"Confirm Password",
"clickAndAgree":"By clicking the button below, you agree to our",
"termsAndConditions":"Terms and Conditions",
"finishAccountSetup":"Finish account setup",
"acceptInvite":"accept invite",
"accountExists":"Already have an account?",
"and":"and"
},
"onBoarding":{
"finishToolJetInstallation":"Finish ToolJet installation",
"organization":"Organization",
"name":"Name",
"email":"Email",
"receiveUpdatesFromToolJet":"You will receive updates from the ToolJet team ( 1-2 emails every month, we do not spam )",
"finishSetup":"Finish setup",
"skip":"Skip"
},
"redirectSso":{
"upgradingTov1.13.0":"Upgrading to v1.13.0 and above.",
"fromV1.13.0":"From v1.13.0 we have introduced",
"multiWorkspace":"Multi-Workspace",
"singleSignOnConfig":"The Single Sign-On related configurations are moved from environment variables to database. Please refer this",
"link":"Link",
"toConfigureSSO":"to configure SSO.",
"haveGoogleGithubSSo":"If you have Google or GitHub SSO configurations before upgrade and disabled Multi-Workspace, then theSSO configurations will be migrated while upgrade but you have to re-configure the redirect URL in the SSO provider side. Redirect URLs for each SSO are given below.",
"isMultiWorkspaceEnabled":"If you have enabled Multi-Workspace, then the SSO configurations will not be migrated while upgrade so you have to re-configure the SSO under the respective workspace.",
"youHaveEnabled":"You have Enabled",
"setupSsoWorkspace":"Please login with password and you can setup sso using workspace",
"manageSsoMenu":"Manage SSO menu.",
"youHaveDisabled":"You have Disabled",
"configureRedirectUrl":"Please configure redirect url in SSO provider side.",
"google":"Google",
"redirectUrl":"Redirect URL:",
"gitHub":"GitHub"
},
"oAuth2": {
"pleaseWait":"Please wait...",
"authSuccess":"Auth successful, you can close this tab now.",
"authFailed":"Auth failed"
},
"widgetManager": {
"commonlyUsed": "commonly used",
"layouts" : "layouts",
"forms" : "forms",
"intregrations" : "integrations",
"others":"others",
"noResults": "No results found",
"tryAdjustingFilterMessage": "Try adjusting your search or filter to find what you're looking for.",
"clearQuery": "clear query"
},
"widget":{
"common":{
"properties": "Properties",
"events": "Events",
"layout": "Layout",
"styles": "Styles",
"general": "General",
"validation": "Validation",
"documentation": "{{componentMeta}} documentation",
"widgetNameEmptyError": "Widget name cannot be empty",
"componentNameExistsError": "Component name already exists",
"invalidWidgetName": "Invalid widget name. Should be unique and only include letters, numbers and underscore."
},
"commonProperties": {
"visibility": "Visibility",
"disable": "Disable",
"borderRadius": "Border Radius",
"boxShadow": "Box Shadow",
"tooltip": "Tooltip",
"showOnDesktop": "Show on desktop",
"showOnMobile": "Show on mobile",
"showLoadingState": "Show loading state",
"backgroundColor": "Background Color",
"textColor": "Text color",
"loaderColor": "Loader color",
"defaultValue": "Default Value",
"placeholder": "Placeholder",
"label": "Label",
"title": "Title",
"code": "Code",
"data": "Data",
"tableData": "Table data",
"tableColumns": "Table columns",
"searverSideSearch": "Server-side search",
"loadingState": "Loading State",
"serverSidePagination": "Server-side pagination",
"clientSidePagination": "Client-side pagination",
"serverSideSearch": "Server-side search",
"showSearchBox": "Show search box",
"showDownloadButton": "Show download button",
"showFilterButton": "Show filter button",
"showBulkUpdateActions": "Show bulk update actions",
"bulkSelection": "Bulk selection",
"highlightSelectedRow": "Highlight selected row",
"actionButtonRadius": "Action Button Radius",
"tableType": "Table type",
"cellSize": "Cell size",
"setPage": "Set page",
"page": "Page",
"buttonText": "Button Text",
"click": "Click",
"setText": "Set text",
"text": "Text",
"markerColor": "Marker color",
"showAxes": "Show axes",
"showGridLines": "Show grid lines",
"chartType": "Chart type",
"jsonDescription": "Json Description",
"usePlotlyJsonSchema": "Use Plotly JSON schema",
"padding": "Padding",
"hideTitleBar": "Hide title bar",
"hideCloseButton": "Hide close button",
"hideOnEscape": "Hide on escape",
"modalSize": "Modal size",
"open": "Open",
"close": "Close",
"regex": "Regex",
"minLength": "Min length",
"maxLength": "Max length",
"customValidation": "Custom validation",
"clear": "Clear",
"minimumValue": "Minimum value",
"maximumValue": "Maximum value",
"format": "Format",
"enableTimeSelection": "Enable time selection?",
"enableDateSelection": "Enable date selection?",
"disabledDates": "Disabled dates",
"setChecked": "Set checked",
"status": "status",
"defaultStatus": "Default Status",
"checkboxColor": "Checkbox Color",
"optionValues": "Option values",
"optionLabels": "Option labels",
"activeColor": "Active Color",
"selectOption": "Select option",
"option": "Option",
"toggleSwitchColor": "Toggle Switch Color",
"defaultStartDate": "Default start date",
"defaultEndDate": "Default end date",
"textSize": "Text Size",
"alignText": "Align Text",
"url": "URL",
"alternativeText": "Alternative text",
"zoomButton": "Zoom button",
"borderType": "Border type",
"imageFit": "Image fit",
"optionsLoadingState": "Options loading state",
"selectedTextColor": "Selected Text Color",
"select": "Select",
"deselectOption": "Deselect Option",
"clearSelections": "Clear selections",
"enableSelectAllOption": "Enable select All option",
"initialLocation": "Initial location",
"defaultMarkers": "Default markers",
"addNewMarkers": "Add new markers",
"searchForPlaces": "Search for places",
"setLocation": "Set Location",
"latitude": "Latitude",
"longitude": "Longitude",
"numberOfStars": "Number of stars",
"defaultNoOfSelectedStars": "Default no of selected stars",
"enableHalfStar": "Enable half star",
"tooltips": "Tooltips",
"starColor": "Star Color",
"labelColor": "Label Color",
"dividerColor": "Divider Color",
"clearFiles": "Clear Files",
"instructionText": "Instruction Text",
"useDropZone": "Use Drop zone",
"useFilePicker": "Use File Picker",
"pickMultipleFiles": "Pick multiple files",
"maxFileCount": "Max file count",
"acceptFileTypes": "Accept file types",
"maxSizeLimitBytes": "Max size limit (Bytes)",
"minSizeLimitBytes": "Min size limit (Bytes)",
"parseContent": "Parse content",
"fileType": "File type",
"dateFormat": "Date format",
"defaultDate": "Default date",
"events": "Events",
"resources": "Resources",
"defaultView": "Default view",
"startTimeOnWeekAndDayView": "Start time on week and day view",
"endTimeOnWeekAndDayView": "End time on week and day view",
"showToolbar": "Show toolbar",
"showViewSwitcher": "Show view switcher",
"highlightToday": "Highlight today",
"showPopoverWhenEventIsClicked": "Show popover when event is clicked",
"cellSizeInViewsClassifiedByResource": "Cell size in views classified by resource",
"headerDateFormatOnWeekView": "Header date format on week view",
"showLineNumber": "Show Line Number",
"mode": "Mode",
"tabs": "Tabs",
"defaultTab": "Default tab",
"hideTabs": "Hide Tabs",
"highlightColor": "Highlight Color",
"tabWidth": "Tab width",
"setCurrentTab": "Set current tab",
"id": "Id",
"timerType": "Timer type",
"listData": "List data",
"rowHeight": "Row height",
"showBottomBorder": "Show bottom border",
"tags": "Tags",
"numberOfPages": "Number of pages",
"defaultPageIndex": "Default page index",
"progress": "Progress",
"color": "Color",
"strokeWidth": "Stroke Width",
"counterClockwise": "Counter Clockwise",
"circleRatio": "Circle Ratio",
"colour": "Colour",
"size": "Size",
"primaryValueLabel": "Primary value label",
"primaryValue": "Primary value",
"hideSecondaryValue": "Hide secondary value",
"secondaryValueLabel": "Secondary value label",
"secondaryValue": "Secondary value",
"secondarySignDisplay": "Secondary sign display",
"primaryLabelColour": "Primary Label Colour",
"primaryTextColour": "Primary Text Colour",
"secondaryLabelColour": "Secondary Label Colour",
"secondaryTextColour": "Secondary Text Colour",
"min": "Min",
"max": "Max",
"value": "Value",
"twoHandles": "Two handles",
"lineColor": "Line color",
"handleColor": "Handle color",
"trackColor": "Track color",
"timelineData": "Timeline data",
"hideDate": "Hide Date",
"svgData": "Svg data",
"rawHtml": "Raw HTML",
"values": "values",
"labels": "Labels",
"defaultSelected": "Default selected",
"enableMutipleSelection": "Enable mutiple selection",
"selectedTextColour": "Selected text colour",
"selectedBackgroundColor": "Selected background color",
"fileUrl": "File URL",
"scalePageToWidth": "Scale page to width",
"showPageControls": "Show page controls",
"steps": "Steps",
"currentStep": "Current step",
"stepsSelectable": "Steps selectable",
"theme": "Theme",
"columns": "Columns",
"cardData": "Card Data",
"enableAddCard": "Enable Add Card",
"width": "Width",
"minWidth": "Min Width",
"accentColor": "Accent color",
"defaultColor": "Default Color",
"setColor": "Set Color",
"structure": "Structure",
"checkedValues": "Checked Values",
"expandedValues": "Expanded Values"
},
"Table": {
"displayName": "Table",
"description": "Display paginated tabular data",
"columnType": "Column type",
"columnName": "Column name",
"overflow": "Overflow",
"key": "key",
"textColor": "Text color",
"validation": "Validation",
"regex": "Regex",
"minLength": "Min length",
"maxLength": "Max length",
"customRule": "Custom rule",
"values": "Values",
"labels": "Labels",
"cellBgColor": "Cell Background Color",
"dateDisplayformat": "Date Display Format",
"dateParseformat": "Date Parse Format",
"showTime": "show time",
"makeEditable": "make editable",
"buttonText": "Button Text",
"buttonPosition": "Button Position",
"remove": "Remove",
"addButton": "+ Add button",
"addColumn": "+ Add column",
"noActionMessage": "This table doesn't have any action buttons"
},
"Button": {
"displayName": "Button",
"description": "Trigger actions: queries, alerts etc"
},
"Chart": {
"displayName": "Chart",
"description": "Display charts"
},
"Modal" : {
"displayName": "Modal",
"description": "Modal triggered by events"
},
"TextInput" : {
"displayName": "Text Input",
"description": "Text field for forms"
},
"NumberInput" :{
"displayName": "Number Input",
"description": "Number field for forms"
},
"PasswordInput":{
"displayName": "Password Input",
"description": "Password input field for forms"
},
"Datepicker" :{
"displayName": "Date Picker",
"description": "Select a date and time"
},
"Checkbox":{
"displayName": "Checkbox",
"description": "A single checkbox"
},
"Radio-button":{
"displayName": "Radio Button",
"description": "Radio buttons"
},
"ToggleSwitch":{
"displayName": "Toggle Switch",
"description": "Toggle Switch"
},
"Textarea":{
"displayName": "Textarea",
"description": "Text area form field"
},
"DateRangePicker":{
"displayName": "Range Picker",
"description": "Select a date range"
},
"Text":{
"displayName": "Text",
"description": "Display markdown or HTML"
},
"Image":{
"displayName": "Image",
"description": "Display an Image"
},
"Container":{
"displayName": "Container",
"description": "Wrapper for multiple components"
},
"Dropdown":{
"displayName": "Dropdown",
"description": "Select one value from options"
},
"Multiselect":{
"displayName": "Multiselect",
"description": "Select multiple values from options"
},
"RichTextEditor":{
"displayName": "Text Editor",
"description": "Rich text editor"
},
"Map":{
"displayName": "Map",
"description": "Display Google Maps"
},
"QrScanner":{
"displayName": "QR Scanner",
"description": "Scan QR codes and hold its data"
},
"StarRating":{
"displayName": "Rating",
"description": "Star rating"
},
"Divider":{
"displayName": "Divider",
"description": "Separator between components"
},
"FilePicker":{
"displayName": "File Picker",
"description": "File Picker"
},
"Calendar":{
"displayName": "Calendar",
"description": "Calendar"
},
"Iframe":{
"displayName": "Iframe",
"description": "Display an Iframe"
},
"CodeEditor":{
"displayName": "Code Editor",
"description": "Code Editor"
},
"Tabs":{
"displayName": "Tabs",
"description": "Tabs component"
},
"Timer":{
"displayName": "Timer",
"description": "timer"
},
"Listview":{
"displayName": "List View",
"description": "Wrapper for multiple components"
},
"Tags":{
"displayName": "Tags",
"description": "Content can be shown as tags"
},
"Pagination":{
"displayName": "Pagination",
"description": "Pagination "
},
"CircularProgressbar":{
"displayName": "Circular Progressbar",
"description": "Show the progress using circular progressbar"
},
"Spinner":{
"displayName": "Spinner",
"description": "Spinner can be used to display loading status"
},
"Statistics":{
"displayName": "Statistics",
"description": "Statistics can be used to display different statistical information"
},
"RangeSlider":{
"displayName": "Range Slider",
"description": "Can be used to show slider with a range"
},
"Timeline":{
"displayName": "Timeline",
"description": "Visual representation of a sequence of events"
},
"SvgImage":{
"displayName": "Svg Image",
"description": "Svg image"
},
"Html":{
"displayName": "HTML Viewer",
"description": "HTML Viewer"
},
"VerticalDivider":{
"displayName": "Vertical Divider",
"description": "Vertical Separator between components"
},
"CustomComponent":{
"displayName": "Custom Component",
"description": "Add your custom react component"
},
"ButtonGroup":{
"displayName": "Button Group",
"description": "ButtonGroup"
},
"PDF":{
"displayName": "PDF",
"description": "Embed PDF file"
},
"Steps":{
"displayName": "Steps",
"description": "Steps"
},
"KanbanBoard":{
"displayName": "Kanban Board",
"description": "Kanban Board"
},
"ColorPicker":{
"displayName": "Color Picker",
"description": "Color Picker Pallete"
},
"TreeSelect":{
"displayName": "Tree Select",
"description": "Select values from a tree view"
}
},
"leftSidebar":{
"Inspector":{
"text":"Inspector",
"tip" : "Inspector"
},
"Sources" :{
"text":"Sources",
"tip" : "Add or edit datasources",
"dataSources":"Data sources",
"addDataSource":"+ add data source"
},
"Debugger":{
"text":"Debugger",
"tip" : "Debugger",
"errors":"Errors",
"noErrors":"No errors found",
"clear":"clear"
},
"Comments":{
"text":"Comments",
"tip" : "toggle comments",
"commentBody":"There are no comments to display",
"typeComment":"Type your comment here"
},
"Settings":{
"text":"Settings",
"tip" : "Global Settings",
"hideHeader": "Hide header for launched apps",
"maintenanceMode":"Maintenance mode",
"maxWidthOfCanvas":"Max width of canvas",
"maxHeightOfCanvas" :"Max height of canvas",
"backgroundColorOfCanvas":"Background color of canvas"
},
"Back":{
"text":"Back",
"tip" : "Back to Home"
}
}
}

View file

@ -0,0 +1,6 @@
{
"languageList": [
{ "lang": "English", "code": "en", "nativeLang": "English" },
{ "lang": "French", "code": "fr", "nativeLang": "Français" }
]
}

File diff suppressed because it is too large Load diff

View file

@ -21,6 +21,11 @@
"emoji-mart": "^3.0.1",
"fuse.js": "^6.4.6",
"history": "^4.9.0",
"html-loader": "^3.1.0",
"html-webpack-plugin": "^5.3.2",
"i18next": "^21.8.14",
"i18next-browser-languagedetector": "^6.1.4",
"i18next-http-backend": "^1.4.1",
"immer": "^9.0.6",
"immutability-helper": "^3.1.1",
"lodash": "^4.17.21",
@ -50,6 +55,7 @@
"react-google-login": "^5.2.2",
"react-hot-toast": "^2.1.1",
"react-hotkeys-hook": "^3.4.4",
"react-i18next": "^11.18.3",
"react-json-tree": "^0.16.1",
"react-json-view": "^1.21.3",
"react-lazy-load-image-component": "^1.5.1",

View file

@ -1,4 +1,4 @@
import React from 'react';
import React, { Suspense } from 'react';
import config from 'config';
import { BrowserRouter, Route, Redirect } from 'react-router-dom';
import { history } from '@/_helpers';
@ -86,7 +86,7 @@ class App extends React.Component {
}
return (
<>
<Suspense fallback={null}>
<BrowserRouter history={history} basename={window.public_config?.SUB_PATH || '/'}>
<div className={`main-wrapper ${darkMode ? 'theme-dark' : ''}`}>
{updateAvailable && (
@ -261,7 +261,7 @@ class App extends React.Component {
</div>
</BrowserRouter>
<Toast toastOptions={toastOptions} />
</>
</Suspense>
);
}
}

View file

@ -4,8 +4,9 @@ import { toast } from 'react-hot-toast';
import GoogleSSOLoginButton from '@ee/components/LoginPage/GoogleSSOLoginButton';
import GitSSOLoginButton from '@ee/components/LoginPage/GitSSOLoginButton';
import { ShowLoading } from '@/_components';
import { withTranslation } from 'react-i18next';
class ConfirmationPage extends React.Component {
class ConfirmationPageComponent extends React.Component {
constructor(props) {
super(props);
@ -150,30 +151,33 @@ class ConfirmationPage extends React.Component {
) : (
<div className="card-body">
<h2 className="card-title text-center mb-4" data-cy="card-title">
Set up your account
{this.props.t('confirmationPage.setupAccount', 'Set up your account')}
</h2>
{this.state.configs?.enable_sign_up && (
<div className="d-flex flex-column align-items-center separator-bottom">
{this.state.configs?.google?.enabled && (
<GoogleSSOLoginButton
text="Sign up with Google"
text={this.props.t('confirmationPage.signupWithGoogle', 'Sign up with Google')}
configs={this.state.configs?.google?.configs}
configId={this.state.configs?.google?.config_id}
/>
)}
{this.state.configs?.git?.enabled && (
<GitSSOLoginButton text="Sign up with GitHub" configs={this.state.configs?.git?.configs} />
<GitSSOLoginButton
text={this.props.t('confirmationPage.signupWithGitHub', 'Sign up with GitHub')}
configs={this.state.configs?.git?.configs}
/>
)}
<div className="mt-2 separator">
<h2>
<span>OR</span>
<span>{this.props.t('confirmationPage.or', 'OR')}</span>
</h2>
</div>
</div>
)}
<div className="mb-3">
<label className="form-label" data-cy="first-name-label">
First name
{this.props.t('confirmationPage.firstName', 'First name')}
</label>
<div className="input-group input-group-flat">
<input
@ -189,7 +193,7 @@ class ConfirmationPage extends React.Component {
</div>
<div className="mb-3">
<label className="form-label" data-cy="last-name-label">
Last name
{this.props.t('confirmationPage.lastName', 'Last name')}
</label>
<div className="input-group input-group-flat">
<input
@ -205,7 +209,7 @@ class ConfirmationPage extends React.Component {
</div>
<div className="mb-3">
<label className="form-label" data-cy="company-label">
Company
{this.props.t('confirmationPage.company', 'Company')}
</label>
<div className="input-group input-group-flat">
<input
@ -221,7 +225,7 @@ class ConfirmationPage extends React.Component {
</div>
<div className="mb-3">
<div className="form-label" data-cy="role-label">
Role
{this.props.t('confirmationPage.role', 'Role')}
</div>
<select
className="form-select"
@ -231,14 +235,14 @@ class ConfirmationPage extends React.Component {
data-cy="role-options"
>
<option value="" disabled>
Please select
{this.props.t('confirmationPage.pleaseSelect', 'Please select')}
</option>
{roleOptions}
</select>
</div>
<div className="mb-3">
<label className="form-label" data-cy="password-label">
Password
{this.props.t('confirmationPage.password', 'Password')}
</label>
<div className="input-group input-group-flat">
<input
@ -254,7 +258,7 @@ class ConfirmationPage extends React.Component {
</div>
<div className="mb-3">
<label className="form-label" data-cy="confirm-password-label">
Confirm Password
{this.props.t('confirmationPage.confirmPassword', 'Confirm Password')}
</label>
<div className="input-group input-group-flat">
<input
@ -270,8 +274,11 @@ class ConfirmationPage extends React.Component {
</div>
<div className="form-footer">
<p data-cy="terms-and-condition-info">
By clicking the button below, you agree to our{' '}
<a href="https://tooljet.io/terms">Terms and Conditions</a>.
{this.props.t('confirmationPage.clickAndAgree', 'By clicking the button below, you agree to our')}{' '}
<a href="https://tooljet.io/terms">
{this.props.t('confirmationPage.termsAndConditions', 'Terms and Conditions')}
</a>
.
</p>
<button
className={`btn mt-2 btn-primary w-100 ${isLoading ? ' btn-loading' : ''}`}
@ -279,7 +286,7 @@ class ConfirmationPage extends React.Component {
disabled={isLoading}
data-cy="finish-setup-button"
>
Finish account setup
{this.props.t('confirmationPage.finishAccountSetup', 'Finish account setup')}
</button>
</div>
</div>
@ -291,4 +298,4 @@ class ConfirmationPage extends React.Component {
}
}
export { ConfirmationPage };
export const ConfirmationPage = withTranslation()(ConfirmationPageComponent);

View file

@ -4,8 +4,9 @@ import { toast } from 'react-hot-toast';
import GoogleSSOLoginButton from '@ee/components/LoginPage/GoogleSSOLoginButton';
import GitSSOLoginButton from '@ee/components/LoginPage/GitSSOLoginButton';
import { ShowLoading } from '@/_components';
import { withTranslation } from 'react-i18next';
class OrganizationInvitationPage extends React.Component {
class OrganizationInvitationPageComponent extends React.Component {
constructor(props) {
super(props);
@ -101,7 +102,7 @@ class OrganizationInvitationPage extends React.Component {
{!this.single_organization ? (
<>
<h2 className="card-title text-center mb-2" data-cy="card-title">
Already have an account?
{this.props.t('confirmationPage.accountExists', 'Already have an account?')}
</h2>
<div className="mb-3">
<button
@ -110,37 +111,40 @@ class OrganizationInvitationPage extends React.Component {
disabled={isLoading}
data-cy="accept-invite-button"
>
Accept invite
{this.props.t('confirmationPage.acceptInvite', 'Accept invite')}
</button>
</div>
</>
) : (
<>
<h2 className="card-title text-center mb-4" data-cy="card-title">
Set up your account
{this.props.t('confirmationPage.setupAccount', 'Set up your account')}
</h2>
{this.state.configs?.enable_sign_up && (
<div className="d-flex flex-column align-items-center separator-bottom">
{this.state.configs?.google?.enabled && (
<GoogleSSOLoginButton
text="Sign up with Google"
text={this.props.t('confirmationPage.signupWithGoogle', 'Sign up with Google')}
configs={this.state.configs?.google?.configs}
configId={this.state.configs?.google?.config_id}
/>
)}
{this.state.configs?.git?.enabled && (
<GitSSOLoginButton text="Sign up with GitHub" configs={this.state.configs?.git?.configs} />
<GitSSOLoginButton
text={this.props.t('confirmationPage.signupWithGitHub', 'Sign up with GitHub')}
configs={this.state.configs?.git?.configs}
/>
)}
<div className="mt-2 separator">
<h2>
<span>OR</span>
<span>{this.props.t('confirmationPage.or', 'OR')}</span>
</h2>
</div>
</div>
)}
<div className="mb-3">
<label className="form-label" data-cy="password-label">
Password
{this.props.t('confirmationPage.password', 'Password')}
</label>
<div className="input-group input-group-flat">
<input
@ -156,7 +160,7 @@ class OrganizationInvitationPage extends React.Component {
</div>
<div className="mb-3">
<label className="form-label" data-cy="confirm-password-label">
Confirm Password
{this.props.t('confirmationPage.confirmPassword', 'Confirm Password')}
</label>
<div className="input-group input-group-flat">
<input
@ -172,8 +176,14 @@ class OrganizationInvitationPage extends React.Component {
</div>
<div className="form-footer">
<p data-cy="terms-and-condition-info">
By clicking the button below, you agree to our{' '}
<a href="https://tooljet.io/terms">Terms and Conditions</a>.
{this.props.t(
'confirmationPage.clickAndAgree',
'By clicking the button below, you agree to our'
)}{' '}
<a href="https://tooljet.io/terms">
{this.props.t('confirmationPage.termsAndConditions', 'Terms and Conditions')}
</a>
.
</p>
<button
className={`btn mt-2 btn-primary w-100 ${isLoading ? ' btn-loading' : ''}`}
@ -181,7 +191,9 @@ class OrganizationInvitationPage extends React.Component {
disabled={isLoading}
data-cy="finish-setup-button"
>
Finish account setup and accept invite
{this.props.t('confirmationPage.finishAccountSetup', 'Finish account setup')}{' '}
{this.props.t('confirmationPage.and', 'and')}{' '}
{this.props.t('confirmationPage.acceptInvite', 'accept invite')}
</button>
</div>
</>
@ -195,4 +207,4 @@ class OrganizationInvitationPage extends React.Component {
}
}
export { OrganizationInvitationPage };
export const OrganizationInvitationPage = withTranslation()(OrganizationInvitationPageComponent);

View file

@ -5,6 +5,8 @@ import { appVersionService } from '@/_services';
import { Confirm } from './Viewer/Confirm';
import Select from '../_ui/Select';
import defaultStyle from '../_ui/Select/styles';
import { useTranslation } from 'react-i18next';
export const AppVersionsManager = function AppVersionsManager({
appId,
editingVersion,
@ -13,6 +15,7 @@ export const AppVersionsManager = function AppVersionsManager({
showCreateVersionModalPrompt,
closeCreateVersionModalPrompt,
}) {
const { t } = useTranslation();
const [showDropDown, setShowDropDown] = useState(false);
const [showModal, setShowModal] = useState(false);
const [isCreatingVersion, setIsCreatingVersion] = useState(false);
@ -173,7 +176,7 @@ export const AppVersionsManager = function AppVersionsManager({
return (
<div ref={wrapperRef} className="input-group app-version-menu">
<span className="input-group-text app-version-menu-sm">Version</span>
<span className="input-group-text app-version-menu-sm">{t('editor.appVersionManager.version', 'Version')}</span>
<span
className={`app-version-name form-select app-version-menu-sm ${appVersions ? '' : 'disabled'}`}
onClick={() => {
@ -198,8 +201,10 @@ export const AppVersionsManager = function AppVersionsManager({
>
<div className="col-md-4">{version.name}</div>
<div className="released-subtext">
<img src={'assets/images/icons/editor/deploy-rocket.svg'} />
<span className="px-1">Currently Released</span>
<img src={'/assets/images/icons/editor/deploy-rocket.svg'} />
<span className="px-1">
{t('editor.appVersionManager.currentlyReleased', 'Currently Released')}
</span>
</div>
</div>
<div className="dropdown-divider m-0"></div>
@ -271,11 +276,13 @@ export const AppVersionsManager = function AppVersionsManager({
setShowModal(true);
}}
>
<span className="color-primary create-link">Create Version</span>
<span className="color-primary create-link">
{t('editor.appVersionManager.createVersion', 'Create Version')}
</span>
</div>
<Confirm
show={showVersionDeletionConfirmation}
message={'Do you really want to delete this version?'}
message={t('editor.appVersionManager.deleteVersion', 'Do you really want to delete this version?')}
confirmButtonLoading={isDeletingVersion}
onConfirm={(versionId) => deleteAppVersion(versionId)}
queryConfirmationData={deletingVersionId}
@ -297,14 +304,18 @@ export const AppVersionsManager = function AppVersionsManager({
showCreateVersionModalPrompt={showCreateVersionModalPrompt}
/>
</span>
<Modal show={showVersionUpdateModal} closeModal={() => setShowVersionUpdateModal(false)} title="Edit version">
<Modal
show={showVersionUpdateModal}
closeModal={() => setShowVersionUpdateModal(false)}
title={t('editor.appVersionManager.editVersion', 'Edit Version')}
>
<div className="row">
<div className="col modal-main">
<input
type="text"
onChange={(e) => setVersionName(e.target.value)}
className="form-control"
placeholder="version name"
placeholder={t('editor.appVersionManager.versionName', 'Version name')}
disabled={isEditingVersion}
value={versionName}
maxLength={25}
@ -314,10 +325,10 @@ export const AppVersionsManager = function AppVersionsManager({
<div className="row">
<div className="col d-flex modal-footer-btn">
<button className="btn btn-light" onClick={() => setShowVersionUpdateModal(false)}>
Cancel
{t('globals.cancel', 'Cancel')}
</button>
<button className={`btn btn-primary ${isEditingVersion ? 'btn-loading' : ''}`} onClick={editVersionName}>
Save
{t('globals.save', 'Save')}
</button>
</div>
</div>
@ -338,6 +349,7 @@ const CreateVersionModal = function CreateVersionModal({
appVersions,
showCreateVersionModalPrompt,
}) {
const { t } = useTranslation();
const handleKeyPress = (event) => {
if (event.key === 'Enter') {
// eslint-disable-next-line no-undef
@ -379,18 +391,18 @@ const CreateVersionModal = function CreateVersionModal({
<Modal
show={showModal || showCreateVersionModalPrompt}
setShow={setShowModal}
title="Create Version"
title={t('editor.appVersionManager.createVersion', 'Create Version')}
autoFocus={false}
closeModal={() => setShowModal(false)}
>
<div className="mb-3">
<div className="col">
<label className="form-label">Version Name</label>
<label className="form-label">{t('editor.appVersionManager.versionName', 'Version Name')}</label>
<input
type="text"
onChange={(e) => setVersionName(e.target.value)}
className="form-control"
placeholder="Enter version name"
placeholder={t('editor.appVersionManager.enterVersionName', 'Enter version name')}
disabled={isCreatingVersion}
value={versionName}
autoFocus={true}
@ -400,7 +412,7 @@ const CreateVersionModal = function CreateVersionModal({
</div>
<div className="mb-3" style={{ padding: '2rem 0' }}>
<label className="form-label">Create version from</label>
<label className="form-label">{t('editor.appVersionManager.createVersionFrom', 'Create version from')}</label>
<div className="ts-control">
<Select
options={options}
@ -428,8 +440,11 @@ const CreateVersionModal = function CreateVersionModal({
</div>
<div className="col">
<span>
Version already released. Kindly create a new version or switch to a different version to continue
making changes.
{t(
'editor.appVersionManager.versionAlreadyReleased',
`Version already released. Kindly create a new version or switch to a different version to continue
making changes.`
)}
</span>
</div>
</div>
@ -441,13 +456,13 @@ const CreateVersionModal = function CreateVersionModal({
<div className="mb-3">
<div className="col d-flex modal-footer-btn">
<button className="btn btn-light" onClick={() => setShowModal(false)}>
Cancel
{t('globals.cancel', 'Cancel')}
</button>
<button
className={`btn btn-primary ${isCreatingVersion ? 'btn-loading' : ''}`}
onClick={() => createVersion(versionName, createAppVersionFrom)}
>
Create Version
{t('editor.appVersionManager.createVersion', 'Create Version')}
</button>
</div>
</div>

View file

@ -60,6 +60,7 @@ import {
} from './component-properties-resolution';
import _ from 'lodash';
import { EditorContext } from '@/Editor/Context/EditorContextWrapper';
import { useTranslation } from 'react-i18next';
const AllComponents = {
Button,
@ -137,6 +138,7 @@ export const Box = function Box({
dataQueries,
readOnly,
}) {
const { t } = useTranslation();
const backgroundColor = yellow ? 'yellow' : '';
let styles = {
@ -249,7 +251,12 @@ export const Box = function Box({
delay={{ show: 500, hide: 0 }}
trigger={inCanvas && !validatedGeneralProperties.tooltip?.trim() ? null : ['hover', 'focus']}
overlay={(props) =>
renderTooltip({ props, text: inCanvas ? `${validatedGeneralProperties.tooltip}` : `${component.description}` })
renderTooltip({
props,
text: inCanvas
? `${validatedGeneralProperties.tooltip}`
: `${t(`widget.${component.name}.description`, component.description)}`,
})
}
>
<div
@ -314,7 +321,9 @@ export const Box = function Box({
}}
></div>
</center>
<span className="component-title">{component.displayName}</span>
<span className="component-title">
{t(`widget.${component.name}.displayName`, component.displayName)}
</span>
</div>
</div>
)}

View file

@ -5,6 +5,7 @@ import { componentTypes } from '../WidgetManager/components';
import { DataSourceTypes } from '../DataSourceManager/SourceComponents';
import { debounce } from 'lodash';
import Fuse from 'fuse.js';
import { useTranslation } from 'react-i18next';
export function CodeBuilder({ initialValue, onChange, components, dataQueries }) {
const [showDropdown, setShowDropdown] = useState(false);
@ -12,6 +13,7 @@ export function CodeBuilder({ initialValue, onChange, components, dataQueries })
const [currentValue, setCurrentValue] = useState(initialValue);
const [codeMirrorInstance, setCodeMirrorInstance] = useState(null);
const [currentWord, setCurrentWord] = useState('');
const { t } = useTranslation();
function computeCurrentWord(value, _cursorPosition) {
const sliced = value
@ -134,7 +136,7 @@ export function CodeBuilder({ initialValue, onChange, components, dataQueries })
{showDropdown && (
<div className="variables-dropdown">
<div className="card">
<div className="group-header p-2">components</div>
<div className="group-header p-2">{t('globals.components', 'components')}</div>
<div className="group-body p-2">
{Object.keys(components).map((component) => renderComponentVariables(components[component]))}
</div>

View file

@ -29,6 +29,8 @@ import FxButton from './Elements/FxButton';
import { ToolTip } from '../Inspector/Elements/Components/ToolTip';
import { toast } from 'react-hot-toast';
import { EditorContext } from '@/Editor/Context/EditorContextWrapper';
import { camelCase } from 'lodash';
import { useTranslation } from 'react-i18next';
const AllElements = {
Color,
@ -92,6 +94,7 @@ export function CodeHinter({
height: isFocused ? currentHeight : 0,
},
});
const { t } = useTranslation();
const { variablesExposedForPreview } = useContext(EditorContext);
@ -264,7 +267,7 @@ export function CodeHinter({
{paramLabel && (
<div className={`mb-2 field ${options.className}`} data-cy={`${cyLabel}-widget-parameter-label`}>
<ToolTip
label={paramLabel}
label={t(`widget.commonProperties.${camelCase(paramLabel)}`, paramLabel)}
meta={fieldMeta}
labelClass={`form-label ${darkMode && 'color-whitish-darkmode'}`}
/>

View file

@ -1,10 +1,12 @@
import React from 'react';
import FxButton from './FxButton';
import { useTranslation } from 'react-i18next';
export const AlignButtons = ({ value, onChange, forceCodeBox, meta }) => {
function handleOptionChanged(event) {
onChange(event.currentTarget.value);
}
const { t } = useTranslation();
return (
<div className="row fx-container">
@ -29,7 +31,7 @@ export const AlignButtons = ({ value, onChange, forceCodeBox, meta }) => {
/>
</svg>
</div>
<span className="tooltiptext">Left</span>
<span className="tooltiptext">{t('globals.left', 'Left')}</span>
</label>
<label className="radio-img">
@ -50,7 +52,7 @@ export const AlignButtons = ({ value, onChange, forceCodeBox, meta }) => {
/>
</svg>
</div>
<span className="tooltiptext">Center</span>
<span className="tooltiptext">{t('globals.center', 'Center')}</span>
</label>
<label className="radio-img">
@ -71,7 +73,7 @@ export const AlignButtons = ({ value, onChange, forceCodeBox, meta }) => {
/>
</svg>
</div>
<span className="tooltiptext">Right</span>
<span className="tooltiptext">{t('globals.right', 'Right')}</span>
</label>
<label className="radio-img">
@ -93,7 +95,7 @@ export const AlignButtons = ({ value, onChange, forceCodeBox, meta }) => {
/>
</svg>
</div>
<span className="tooltiptext">Justified</span>
<span className="tooltiptext">{t('globals.justified', 'Justified')}</span>
</label>
</div>
</div>

View file

@ -1,14 +1,14 @@
import React from 'react';
import cx from 'classnames';
import { useSpring, animated } from 'react-spring';
import { useSpring, animated, useTransition } from 'react-spring';
import usePopover from '@/_hooks/use-popover';
import OptionsIcon from './icons/options.svg';
// import OptionsSelectedIcon from './icons/options-selected.svg';
import useRouter from '@/_hooks/use-router';
import { commentsService } from '@/_services';
import { useTranslation } from 'react-i18next';
const CommentActions = ({
socket,
@ -22,6 +22,7 @@ const CommentActions = ({
const [open, trigger, content, setOpen] = usePopover(false);
const popoverFadeStyle = useSpring({ opacity: open ? 1 : 0 });
const router = useRouter();
const { t } = useTranslation();
const handleDelete = async () => {
await commentsService.deleteComment(commentId);
@ -58,11 +59,11 @@ const CommentActions = ({
>
<div>
<div className="comment-action" onClick={handleEdit}>
Edit
{t('globals.edit', 'Edit')}
</div>
{/* TODO: Add a popup confirmation on delete */}
<div className="comment-action border-top" onClick={handleDelete}>
Delete
{t('globals.delete', 'Delete')}
</div>
</div>
</animated.div>

View file

@ -1,7 +1,7 @@
import React from 'react';
import cx from 'classnames';
import Spinner from '@/_ui/Spinner';
import { useTranslation } from 'react-i18next';
import { isEmpty } from 'lodash';
import moment from 'moment';
import CommentActions from './CommentActions';
@ -16,6 +16,7 @@ moment.updateLocale('en', {
const CommentBody = ({ socket, thread, isLoading, setEditComment, setEditCommentId, fetchComments }) => {
const bottomRef = React.useRef();
const { t } = useTranslation();
const scrollToBottom = () => {
bottomRef?.current?.scrollIntoView({
@ -33,7 +34,10 @@ const CommentBody = ({ socket, thread, isLoading, setEditComment, setEditComment
}, []);
const getContent = () => {
if (isEmpty(thread)) return <div className="text-center">There are no comments to display</div>;
if (isEmpty(thread))
return (
<div className="text-center">{t('leftSidebar.Comments.commentBody', 'There are no comments to display')}</div>
);
const currentUser = JSON.parse(localStorage.getItem('currentUser'));
return (

View file

@ -6,11 +6,13 @@ import TextareaMentions from '@/_ui/Mentions';
import Button from '@/_ui/Button';
import usePopover from '@/_hooks/use-popover';
import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
function CommentFooter({ users, editComment = '', setMentionedUsers, editCommentId, setEditCommentId, handleSubmit }) {
const [comment, setComment] = React.useState(editComment);
const [loading, setLoading] = React.useState(false);
const [open, trigger, content, setOpen] = usePopover(false);
const { t } = useTranslation();
React.useEffect(() => {
setComment(editComment);
@ -44,7 +46,7 @@ function CommentFooter({ users, editComment = '', setMentionedUsers, editComment
setMentionedUsers={setMentionedUsers}
value={comment}
setValue={setComment}
placeholder="Type your comment here"
placeholder={t('leftSidebar.Comments.typeComment', 'Type your comment here')}
darkMode={darkMode}
/>
</div>
@ -66,7 +68,7 @@ function CommentFooter({ users, editComment = '', setMentionedUsers, editComment
})}
>
<Button loading={loading} disabled={!comment} className={`m2 btn-sm rounded-2`} onClick={handleClick}>
Send
{t('globals.send', 'Send')}
</Button>
</div>
</div>

View file

@ -2,6 +2,7 @@ import React, { useState, useCallback, useEffect } from 'react';
import { GoogleMap, LoadScript, Marker, Autocomplete } from '@react-google-maps/api';
import { resolveReferences, resolveWidgetFieldValue } from '@/_helpers/utils';
import { darkModeStyles } from './styles';
import { useTranslation } from 'react-i18next';
export const Map = function Map({
id,
@ -19,6 +20,7 @@ export const Map = function Map({
}) {
const center = component.definition.properties.initialLocation.value;
const defaultMarkerValue = component.definition.properties.defaultMarkers.value;
const { t } = useTranslation();
let defaultMarkers = [];
try {
@ -165,7 +167,7 @@ export const Map = function Map({
<Autocomplete onPlaceChanged={onPlaceChanged} onLoad={onAutocompleteLoad}>
<input
type="text"
placeholder="Search"
placeholder={t('globals.search', 'Search')}
className={`place-search-input ${darkMode && 'text-light bg-dark dark-theme-placeholder'}`}
/>
</Autocomplete>

View file

@ -1,7 +1,10 @@
import React from 'react';
import SelectSearch from 'react-select-search';
import { useTranslation } from 'react-i18next';
export const CustomSelect = ({ options, value, multiple, onChange }) => {
const { t } = useTranslation();
function renderValue(valueProps) {
if (valueProps) {
return valueProps.value.split(', ').map((value, index) => (
@ -22,7 +25,7 @@ export const CustomSelect = ({ options, value, multiple, onChange }) => {
search={false}
onChange={onChange}
multiple={multiple}
placeholder="Select.."
placeholder={t('globals.select', 'Select') + '...'}
/>
</div>
);

View file

@ -26,6 +26,7 @@ import { Datepicker } from './Datepicker';
import { GlobalFilter } from './GlobalFilter';
var _ = require('lodash');
import { EditorContext } from '@/Editor/Context/EditorContextWrapper';
import { useTranslation } from 'react-i18next';
export function Table({
id,
@ -45,6 +46,8 @@ export function Table({
registerAction,
properties,
}) {
const { t } = useTranslation();
const color =
component.definition.styles.textColor.value !== '#000'
? component.definition.styles.textColor.value
@ -498,7 +501,7 @@ export function Table({
handleCellValueChange(cell.row.index, column.key || column.name, value, cell.row.original);
}}
filterOptions={fuzzySearch}
placeholder="Select.."
placeholder={t('globals.select', 'Select') + '...'}
disabled={!column.isEditable}
/>
<div className={`invalid-feedback ${isValid ? '' : 'd-flex'}`}>{validationError}</div>
@ -512,7 +515,7 @@ export function Table({
printOptions="on-focus"
multiple
search={true}
placeholder="Select.."
placeholder={t('globals.select', 'Select') + '...'}
options={columnOptions.selectOptions}
value={cellValue}
onChange={(value) => {
@ -1133,7 +1136,7 @@ export function Table({
filterColumnChanged(index, value);
}}
filterOptions={fuzzySearch}
placeholder="Select.."
placeholder={t('globals.select', 'Select') + '...'}
/>
</div>
<div className="col" style={{ maxWidth: '180px' }}>
@ -1155,7 +1158,7 @@ export function Table({
filterOperationChanged(index, value);
}}
filterOptions={fuzzySearch}
placeholder="Select.."
placeholder={t('globals.select', 'Select') + '...'}
/>
</div>
<div className="col">

View file

@ -15,8 +15,9 @@ import { CopyToClipboard } from 'react-copy-to-clipboard';
import config from 'config';
import { isEmpty } from 'lodash';
import { Card } from '@/_ui/card';
import { withTranslation, useTranslation } from 'react-i18next';
class DataSourceManager extends React.Component {
class DataSourceManagerComponent extends React.Component {
constructor(props) {
super(props);
@ -132,7 +133,10 @@ class DataSourceManager extends React.Component {
datasourceService.save(selectedDataSource.id, appId, name, parsedOptions).then(() => {
this.setState({ isSaving: false });
this.hideModal();
toast.success('Datasource Saved', { position: 'top-center' });
toast.success(
this.props.t('editor.queryManager.dataSourceManager.toast.success.dataSourceSaved', 'Datasource Saved'),
{ position: 'top-center' }
);
this.props.dataSourcesChanged();
});
} else {
@ -140,12 +144,21 @@ class DataSourceManager extends React.Component {
datasourceService.create(appId, appVersionId, name, kind, parsedOptions).then(() => {
this.setState({ isSaving: false });
this.hideModal();
toast.success('Datasource Added', { position: 'top-center' });
toast.success(
this.props.t('editor.queryManager.dataSourceManager.toast.success.dataSourceAdded', 'Datasource Added'),
{ position: 'top-center' }
);
this.props.dataSourcesChanged();
});
}
} else {
toast.error('The name of datasource should not be empty', { position: 'top-center' });
toast.error(
this.props.t(
'editor.queryManager.dataSourceManager.toast.error.noEmptyDsName',
'The name of datasource should not be empty'
),
{ position: 'top-center' }
);
}
};
@ -221,7 +234,10 @@ class DataSourceManager extends React.Component {
queryString={this.state.queryString}
handleBackToAllDatasources={goBacktoAllDatasources}
darkMode={this.props.darkMode}
placeholder={'Suggest an integration'}
placeholder={this.props.t(
'editor.queryManager.dataSourceManager.suggestAnIntegration',
'Suggest an integration'
)}
/>
</div>
);
@ -244,7 +260,9 @@ class DataSourceManager extends React.Component {
<Tab.Content>
{suggestingDatasources ? (
<div className="suggestion-container">
<h4 className="justify-content-start">Suggest Datasource</h4>
<h4 className="justify-content-start">
{this.props.t('editor.queryManager.dataSourceManager.suggestDataSource', 'Suggest Datasource')}
</h4>
{datasourceSuggestionUI()}
</div>
) : (
@ -276,7 +294,10 @@ class DataSourceManager extends React.Component {
queryString={this.state.queryString}
handleBackToAllDatasources={this.handleBackToAllDatasources}
darkMode={this.props.darkMode}
placeholder={'Tell us what you were looking for?'}
placeholder={this.props.t(
'editor.queryManager.dataSourceManager.whatLookingFor',
'Tell us what you were looking for?'
)}
/>
</div>
)}
@ -349,10 +370,15 @@ class DataSourceManager extends React.Component {
</ListGroup>
<div className="datasource-modal-sidebar-footer">
<p>
<span className="footer-text">Don&apos;t see what you were looking for?</span>
<span className="footer-text">
{this.props.t(
'editor.queryManager.dataSourceManager.noResultFound',
`Don't see what you were looking for?`
)}
</span>
<br />
<span className="link-span" onClick={updateSuggestionState}>
Suggest
{this.props.t('editor.queryManager.dataSourceManager.suggest', 'Suggest')}
</span>
</p>
</div>
@ -544,7 +570,11 @@ class DataSourceManager extends React.Component {
</div>
</div>
)}
{!selectedDataSource && <span className="text-muted">Add new datasource</span>}
{!selectedDataSource && (
<span className="text-muted">
{this.props.t('editor.queryManager.dataSourceManager.addNewDataSource', 'Add new datasource')}
</span>
)}
</Modal.Title>
<span
className={`close-btn mx-4 mt-3 ${this.props.darkMode ? 'dark' : ''}`}
@ -580,12 +610,19 @@ class DataSourceManager extends React.Component {
</svg>
</div>
<div className="col" style={{ maxWidth: '480px' }}>
<p>Please white-list our IP address if the data source is not publicly accessible.</p>
<p>
{this.props.t(
'editor.queryManager.dataSourceManager.whiteListIP',
'Please white-list our IP address if the data source is not publicly accessible.'
)}
</p>
</div>
<div className="col-auto">
{isCopied ? (
<center className="my-2">
<span className="copied">Copied</span>
<span className="copied">
{this.props.t('editor.queryManager.dataSourceManager.copied', 'Copied')}
</span>
</center>
) : (
<CopyToClipboard
@ -607,7 +644,7 @@ class DataSourceManager extends React.Component {
fill="currentColor"
/>
</svg>
Copy
{this.props.t('editor.queryManager.dataSourceManager.copy', 'Copy')}
</button>
</CopyToClipboard>
)}
@ -632,7 +669,7 @@ class DataSourceManager extends React.Component {
target="_blank"
rel="noreferrer"
>
Read documentation
{this.props.t('globals.readDocumentation', 'Read documentation')}
</a>
</small>
</div>
@ -651,7 +688,7 @@ class DataSourceManager extends React.Component {
variant="primary"
onClick={this.createDataSource}
>
{'Save'}
{this.props.t('globals.save', 'Save')}
</Button>
</div>
</Modal.Footer>
@ -666,13 +703,15 @@ class DataSourceManager extends React.Component {
target="_blank"
rel="noreferrer"
>
Read documentation
{this.props.t('globals.readDocumentation', 'Read documentation')}
</a>
</small>
</div>
<div className="col-auto">
<Button className="m-2" disabled={isSaving} variant="primary" onClick={this.createDataSource}>
{isSaving ? 'Saving...' : 'Save'}
{isSaving
? this.props.t('editor.queryManager.dataSourceManager.saving' + '...', 'Saving...')
: this.props.t('globals.save', 'Save')}
</Button>
</div>
</Modal.Footer>
@ -690,6 +729,7 @@ const EmptyStateContainer = ({
darkMode,
placeholder,
}) => {
const { t } = useTranslation();
const [inputValue, set] = React.useState(() => '');
const [status, setStatus] = React.useState(false);
@ -706,17 +746,26 @@ const EmptyStateContainer = ({
return (
<div className="empty">
{queryString && !suggestionUI && <h3>No results for &quot;{queryString} &quot;</h3>}
{queryString && !suggestionUI && (
<h3>
{t(
`editor.queryManager.dataSourceManager.noResultsFor + "${queryString}"`,
`No results for "${queryString}"`
)}
</h3>
)}
<center className={`empty-results ${suggestionUI ? 'suggestionUI-results' : ''}`}>
<img src="assets/images/icons/no-results.svg" width="150" height="150" />
{status ? (
<div>
<p className="text-success mt-2">Thank you, we&apos;ve taken a note of that!</p>
<p className="text-success mt-2">
{t('editor.queryManager.dataSourceManager.noteTaken', `Thank you, we've taken a note of that!`)}
</p>
<button
className={`datasource-modal-button ${darkMode && 'dark-button'}`}
onClick={handleBackToAllDatasources}
>
{'Go to all Datasources'}
{t('editor.queryManager.dataSourceManager.goToAllDatasources', 'Go to all Datasources')}
</button>
</div>
) : (
@ -734,7 +783,7 @@ const EmptyStateContainer = ({
</div>
<div className="col-auto">
<Button className="mt-2" variant="primary" onClick={handleSend}>
{'Send'}
{t('editor.queryManager.dataSourceManager.send', 'Send')}
</Button>
</div>
</div>
@ -746,7 +795,7 @@ const EmptyStateContainer = ({
const SearchBoxContainer = ({ onChange, onClear, queryString, activeDatasourceList }) => {
const [searchText, setSearchText] = React.useState(queryString ?? '');
const { t } = useTranslation();
const handleChange = (e) => {
setSearchText(e.target.value);
onChange(e.target.value, activeDatasourceList);
@ -832,7 +881,7 @@ const SearchBoxContainer = ({ onChange, onClear, queryString, activeDatasourceLi
value={searchText}
onChange={handleChange}
className="form-control"
placeholder="Search"
placeholder={t('globals.search', 'Search')}
autoFocus
/>
</div>
@ -840,4 +889,4 @@ const SearchBoxContainer = ({ onChange, onClear, queryString, activeDatasourceLi
);
};
export { DataSourceManager };
export const DataSourceManager = withTranslation()(DataSourceManagerComponent);

View file

@ -1,11 +1,13 @@
import React, { useEffect, useState } from 'react';
import { toast } from 'react-hot-toast';
import { datasourceService } from '@/_services';
import { useTranslation } from 'react-i18next';
export const TestConnection = ({ kind, options, onConnectionTestFailed, darkMode }) => {
const [isTesting, setTestingStatus] = useState(false);
const [connectionStatus, setConnectionStatus] = useState('unknown');
const [buttonText, setButtonText] = useState('Test Connection');
const { t } = useTranslation();
useEffect(() => {
if (isTesting) {
@ -44,9 +46,13 @@ export const TestConnection = ({ kind, options, onConnectionTestFailed, darkMode
return (
<div>
{connectionStatus === 'failed' && <span className="badge bg-red-lt">could not connect</span>}
{connectionStatus === 'failed' && (
<span className="badge bg-red-lt">{t('globals.noConnection', 'could not connect')}</span>
)}
{connectionStatus === 'success' && <span className="badge bg-green-lt">connection verified</span>}
{connectionStatus === 'success' && (
<span className="badge bg-green-lt">{t('globals.connectionVerifeid', 'connection verified')}</span>
)}
{connectionStatus === 'unknown' && (
<button

View file

@ -62,11 +62,12 @@ import { initEditorWalkThrough } from '@/_helpers/createWalkThrough';
import { EditorContextWrapper } from './Context/EditorContextWrapper';
// eslint-disable-next-line import/no-unresolved
import Selecto from 'react-selecto';
import { withTranslation } from 'react-i18next';
setAutoFreeze(false);
enablePatches();
class Editor extends React.Component {
class EditorComponent extends React.Component {
constructor(props) {
super(props);
@ -1258,7 +1259,7 @@ class Editor extends React.Component {
rel="noreferrer"
data-cy="preview-button"
>
Preview
{this.props.t('editor.preview', 'Preview')}
</Link>
</div>
<div className="nav-item dropdown d-none d-md-flex me-2">
@ -1451,7 +1452,7 @@ class Editor extends React.Component {
<SearchBoxComponent
onChange={this.filterQueries}
callback={this.toggleQuerySearch}
placeholder={'Search queries'}
placeholder={this.props.t('editor.searchQueries', 'Search queries')}
/>
</div>
</div>
@ -1464,7 +1465,7 @@ class Editor extends React.Component {
style={{ fontSize: '14px', marginLeft: ' 6px' }}
className="py-1 px-3 mt-2 text-muted"
>
Queries
{this.props.t('editor.queries', 'Queries')}
</h5>
</div>
@ -1529,7 +1530,7 @@ class Editor extends React.Component {
})
}
>
{'Create query'}
{this.props.t('editor.createQuery', 'Create query')}
</button>
</center>
</div>
@ -1658,7 +1659,9 @@ class Editor extends React.Component {
handleEditorEscapeKeyPress={this.handleEditorEscapeKeyPress}
></Inspector>
) : (
<center className="mt-5 p-2">Please select a component to inspect</center>
<center className="mt-5 p-2">
{this.props.t('editor.inspectComponent', 'Please select a component to inspect')}
</center>
)}
</div>
)}
@ -1687,4 +1690,4 @@ class Editor extends React.Component {
}
}
export { Editor };
export const Editor = withTranslation()(EditorComponent);

View file

@ -1,11 +1,11 @@
import React, { Component } from 'react';
import { withTranslation } from 'react-i18next';
class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
// eslint-disable-next-line no-unused-vars
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
@ -20,11 +20,11 @@ class ErrorBoundary extends Component {
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return this.props.showFallback ? <h2>Something went wrong.</h2> : <div></div>;
return this.props.showFallback ? <h2>{this.props.t('errorBoundary', 'Something went wrong.')}</h2> : <div></div>;
}
return this.props.children;
}
}
export default ErrorBoundary;
export default withTranslation()(ErrorBoundary);

View file

@ -2,12 +2,14 @@ import React, { useState, useEffect } from 'react';
import Select from '@/_ui/Select';
import defaultStyles from '@/_ui/Select/styles';
import { CodeHinter } from '../../CodeBuilder/CodeHinter';
import { useTranslation } from 'react-i18next';
export function GotoApp({ getAllApps, currentState, event, handlerChanged, eventIndex, darkMode }) {
const queryParamChangeHandler = (index, key, value) => {
event.queryParams[index][key] = value;
handlerChanged(eventIndex, 'queryParams', event.queryParams);
};
const { t } = useTranslation();
const addQueryParam = () => {
if (!event.queryParams) {
@ -53,7 +55,7 @@ export function GotoApp({ getAllApps, currentState, event, handlerChanged, event
onChange={(value) => {
handlerChanged(eventIndex, 'slug', value);
}}
placeholder="Select.."
placeholder={t('globals.select', 'Select') + '...'}
styles={styles}
useMenuPortal={false}
className={`${darkMode ? 'select-search-dark' : 'select-search'}`}

View file

@ -2,6 +2,8 @@ import React from 'react';
import Accordion from '@/_ui/Accordion';
import { EventManager } from '../EventManager';
import { renderElement } from '../Utils';
// eslint-disable-next-line import/no-unresolved
import i18next from 'i18next';
export const DefaultComponent = ({ componentMeta, darkMode, ...restProps }) => {
const {
@ -64,7 +66,7 @@ export const baseComponentProperties = (
let items = [];
if (properties.length > 0) {
items.push({
title: 'Properties',
title: `${i18next.t('widget.common.properties', 'Properties')}`,
children: properties.map((property) =>
renderElement(
component,
@ -83,7 +85,7 @@ export const baseComponentProperties = (
if (events.length > 0) {
items.push({
title: 'Events',
title: `${i18next.t('widget.common.events', 'Events')}`,
isOpen: false,
children: (
<EventManager
@ -102,7 +104,7 @@ export const baseComponentProperties = (
if (validations.length > 0) {
items.push({
title: 'Validation',
title: `${i18next.t('widget.common.validation', 'Validation')}`,
children: validations.map((property) =>
renderElement(
component,
@ -120,7 +122,7 @@ export const baseComponentProperties = (
}
items.push({
title: 'General',
title: `${i18next.t('widget.common.general', 'General')}`,
isOpen: false,
children: (
<>
@ -139,7 +141,7 @@ export const baseComponentProperties = (
});
items.push({
title: 'Layout',
title: `${i18next.t('widget.common.layout', 'Layout')}`,
isOpen: false,
children: (
<>

View file

@ -13,8 +13,8 @@ import SelectSearch, { fuzzySearch } from 'react-select-search';
import { v4 as uuidv4 } from 'uuid';
import { EventManager } from '../EventManager';
import { CodeHinter } from '../../CodeBuilder/CodeHinter';
class Table extends React.Component {
import { withTranslation } from 'react-i18next';
class TableComponent extends React.Component {
constructor(props) {
super(props);
@ -164,7 +164,7 @@ class Table extends React.Component {
<Popover id="popover-basic-2" className={`${this.props.darkMode && 'popover-dark-themed theme-dark'} shadow`}>
<Popover.Content>
<div className="field mb-2">
<label className="form-label">Column type</label>
<label className="form-label">{this.props.t('widget.Table.columnType', 'Column type')}</label>
<SelectSearch
className={`${this.props.darkMode ? 'select-search-dark' : 'select-search'}`}
options={[
@ -187,11 +187,11 @@ class Table extends React.Component {
this.onColumnItemChange(index, 'columnType', value);
}}
filterOptions={fuzzySearch}
placeholder="Select.."
placeholder={this.props.t('globals.select', 'Select') + '...'}
/>
</div>
<div className="field mb-2">
<label className="form-label">Column name</label>
<label className="form-label">{this.props.t('widget.Table.columnName', 'Column name')}</label>
<input
type="text"
className="form-control text-field"
@ -204,7 +204,7 @@ class Table extends React.Component {
</div>
{(column.columnType === 'string' || column.columnType === undefined || column.columnType === 'default') && (
<div className="field mb-2">
<label className="form-label">Overflow</label>
<label className="form-label">{this.props.t('widget.Table.overflow', 'Overflow')}</label>
<SelectSearch
options={[
{ name: 'Wrap', value: 'wrap' },
@ -218,12 +218,12 @@ class Table extends React.Component {
this.onColumnItemChange(index, 'textWrap', value);
}}
filterOptions={fuzzySearch}
placeholder="Select.."
placeholder={this.props.t('globals.select', 'Select') + '...'}
/>
</div>
)}
<div className="field mb-2">
<label className="form-label">key</label>
<label className="form-label">{this.props.t('widget.Table.key', 'key')}</label>
<CodeHinter
currentState={this.props.currentState}
initialValue={column.key}
@ -239,7 +239,7 @@ class Table extends React.Component {
{(column.columnType === 'string' || column.columnType === undefined || column.columnType === 'default') && (
<div>
<div className="field mb-2">
<label className="form-label">Text color</label>
<label className="form-label">{this.props.t('widget.Table.textColor', 'Text color')}</label>
<CodeHinter
currentState={this.props.currentState}
initialValue={column.textColor}
@ -255,9 +255,9 @@ class Table extends React.Component {
</div>
{column.isEditable && (
<div>
<div className="hr-text">Validation</div>
<div className="hr-text">{this.props.t('widget.Table.validation', 'Validation')}</div>
<div className="field mb-2">
<label className="form-label">Regex</label>
<label className="form-label">{this.props.t('widget.Table.regex', 'Regex')}</label>
<CodeHinter
currentState={this.props.currentState}
initialValue={column.regex}
@ -270,7 +270,7 @@ class Table extends React.Component {
/>
</div>
<div className="field mb-2">
<label className="form-label">Min length</label>
<label className="form-label">{this.props.t('widget.Table.minLength', 'Min length')}</label>
<CodeHinter
currentState={this.props.currentState}
initialValue={column.minLength}
@ -283,7 +283,7 @@ class Table extends React.Component {
/>
</div>
<div className="field mb-2">
<label className="form-label">Max length</label>
<label className="form-label">{this.props.t('widget.Table.maxLength', 'Max length')}</label>
<CodeHinter
currentState={this.props.currentState}
initialValue={column.maxLength}
@ -296,7 +296,7 @@ class Table extends React.Component {
/>
</div>
<div className="field mb-2">
<label className="form-label">Custom rule</label>
<label className="form-label">{this.props.t('widget.Table.customRule', 'Custom rule')}</label>
<CodeHinter
currentState={this.props.currentState}
initialValue={column.customRule}
@ -352,7 +352,7 @@ class Table extends React.Component {
column.columnType === 'radio') && (
<div>
<div className="field mb-2">
<label className="form-label">Values</label>
<label className="form-label">{this.props.t('widget.Table.values', 'Values')}</label>
<CodeHinter
currentState={this.props.currentState}
initialValue={column.values}
@ -365,7 +365,7 @@ class Table extends React.Component {
/>
</div>
<div className="field mb-2">
<label className="form-label">Labels</label>
<label className="form-label">{this.props.t('widget.Table.labels', 'Labels')}</label>
<CodeHinter
currentState={this.props.currentState}
initialValue={column.labels}
@ -384,9 +384,9 @@ class Table extends React.Component {
<>
{column.isEditable && (
<div>
<div className="hr-text">Validation</div>
<div className="hr-text">{this.props.t('widget.Table.validation', 'Validation')}</div>
<div className="field mb-2">
<label className="form-label">Custom rule</label>
<label className="form-label">{this.props.t('widget.Table.customRule', 'Custom Rule')}</label>
<CodeHinter
currentState={this.props.currentState}
initialValue={column.customRule}
@ -404,7 +404,7 @@ class Table extends React.Component {
)}
<div className="field mb-2">
<label className="form-label">Cell background color</label>
<label className="form-label">{this.props.t('widget.Table.cellBgColor', 'Cell Background Color')}</label>
<CodeHinter
currentState={this.props.currentState}
initialValue={column.cellBackgroundColor ?? 'inherit'}
@ -419,7 +419,9 @@ class Table extends React.Component {
{column.columnType === 'datepicker' && (
<div>
<label className="form-label">Date Display Format</label>
<label className="form-label">
{this.props.t('widget.Table.dateDisplayformat', 'Date Display Format')}
</label>
<div className="field mb-2">
<CodeHinter
currentState={this.props.currentState}
@ -432,7 +434,7 @@ class Table extends React.Component {
componentName={this.getPopoverFieldSource(column.columnType, 'dateFormat')}
/>
</div>
<label className="form-label">Date Parse Format</label>
<label className="form-label">{this.props.t('widget.Table.dateParseformat', 'Date Parse Format')}</label>
<div className="field mb-2">
<input
type="text"
@ -485,7 +487,7 @@ class Table extends React.Component {
}}
checked={column.isTimeChecked}
/>
<span className="form-check-label">show time</span>
<span className="form-check-label">{this.props.t('widget.Table.showTime', 'show time')}</span>
</div>
</div>
</div>
@ -498,7 +500,7 @@ class Table extends React.Component {
onClick={() => this.onColumnItemChange(index, 'isEditable', !column.isEditable)}
checked={column.isEditable}
/>
<span className="form-check-label">make editable</span>
<span className="form-check-label">{this.props.t('widget.Table.makeEditable', 'make editable')}</span>
</div>
</Popover.Content>
</Popover>
@ -518,7 +520,7 @@ class Table extends React.Component {
<Popover id="popover-basic" className={`${this.props.darkMode && 'popover-dark-themed theme-dark'} shadow`}>
<Popover.Content>
<div className="field mb-2">
<label className="form-label">Button Text</label>
<label className="form-label">{this.props.t('widget.Table.buttonText', 'Button Text')}</label>
<input
type="text"
className="form-control text-field"
@ -530,7 +532,7 @@ class Table extends React.Component {
/>
</div>
<div className="field mb-2">
<label className="form-label">Button position</label>
<label className="form-label">{this.props.t('widget.Table.buttonPosition', 'Button Position')}</label>
<SelectSearch
className={`${this.props.darkMode ? 'select-search-dark' : 'select-search'}`}
options={[
@ -576,7 +578,7 @@ class Table extends React.Component {
}}
/>
<button className="btn btn-sm btn-outline-danger mt-2 col" onClick={() => this.removeAction(index)}>
Remove
{this.props.t('widget.Table.remove', 'Remove')}
</button>
</Popover.Content>
</Popover>
@ -717,7 +719,7 @@ class Table extends React.Component {
onClick={this.addNewColumn}
className="btn btn-sm border-0 font-weight-normal padding-2 col-auto color-primary inspector-add-button"
>
+ Add column
{this.props.t('widget.Table.addColumn', '+ Add column')}
</button>
</div>
<SortableList onSortEnd={this.onSortEnd} className="w-100" draggedItemClassName="dragged">
@ -803,14 +805,16 @@ class Table extends React.Component {
onClick={this.addNewAction}
className="btn btn-sm border-0 font-weight-normal padding-2 col-auto color-primary inspector-add-button"
>
+ Add button
{this.props.t('widget.Table.addButton', '+ Add button')}
</button>
</div>
</div>
<div>{actions.value.map((action, index) => this.actionButton(action, index))}</div>
{actions.value.length === 0 && (
<div className="text-center">
<small className="color-disabled">This table doesn&apos;t have any action buttons</small>
<small className="color-disabled">
{this.props.t('widget.Table.noActionMessage', "This table doesn't have any action buttons")}
</small>
</div>
)}
</div>
@ -896,4 +900,4 @@ class Table extends React.Component {
}
}
export { Table };
export const Table = withTranslation()(TableComponent);

View file

@ -1,11 +1,13 @@
import React from 'react';
import { ToolTip } from './Components/ToolTip';
import { useTranslation } from 'react-i18next';
export const AlignButtons = ({ param, definition, onChange, paramType, componentMeta }) => {
const initialValue = definition ? definition.value : '';
const paramMeta = componentMeta[paramType][param.name];
const displayName = paramMeta.displayName || param.name;
const options = paramMeta.options || {};
const { t } = useTranslation();
function handleOptionChanged(event) {
onChange(param, 'value', event.currentTarget.value, paramType);
@ -33,7 +35,7 @@ export const AlignButtons = ({ param, definition, onChange, paramType, component
/>
</svg>
</div>
<span className="tooltiptext">Left</span>
<span className="tooltiptext">{t('globals.left', 'Left')}</span>
</label>
<label className="radio-img">
@ -54,7 +56,7 @@ export const AlignButtons = ({ param, definition, onChange, paramType, component
/>
</svg>
</div>
<span className="tooltiptext">Center</span>
<span className="tooltiptext">{t('globals.center', 'Center')}</span>
</label>
<label className="radio-img">
@ -75,7 +77,7 @@ export const AlignButtons = ({ param, definition, onChange, paramType, component
/>
</svg>
</div>
<span className="tooltiptext">Right</span>
<span className="tooltiptext">{t('globals.right', 'Right')}</span>
</label>
<label className="radio-img">
@ -97,7 +99,7 @@ export const AlignButtons = ({ param, definition, onChange, paramType, component
/>
</svg>
</div>
<span className="tooltiptext">Justified</span>
<span className="tooltiptext">{t('globals.justified', 'Justified')}</span>
</label>
</div>
</div>

View file

@ -1,12 +1,14 @@
import React from 'react';
import { ToolTip } from './Components/ToolTip';
import SelectSearch, { fuzzySearch } from 'react-select-search';
import { useTranslation } from 'react-i18next';
export const Select = ({ param, definition, onChange, paramType, componentMeta }) => {
const paramMeta = componentMeta[paramType][param.name];
const displayName = paramMeta.displayName || param.name;
const options = paramMeta.options;
const value = definition ? definition.value : '';
const { t } = useTranslation();
return (
<div className="field mb-3">
@ -17,7 +19,7 @@ export const Select = ({ param, definition, onChange, paramType, componentMeta }
search={true}
onChange={(newVal) => onChange(param, 'value', newVal, paramType)}
filterOptions={fuzzySearch}
placeholder="Select.."
placeholder={t('globals.select', 'Select') + '...'}
/>
</div>
);

View file

@ -10,6 +10,7 @@ import _ from 'lodash';
import { componentTypes } from '../WidgetManager/components';
import Select from '@/_ui/Select';
import defaultStyles from '@/_ui/Select/styles';
import { useTranslation } from 'react-i18next';
export const EventManager = ({
component,
@ -24,6 +25,7 @@ export const EventManager = ({
popoverPlacement,
}) => {
const [focusedEventIndex, setFocusedEventIndex] = useState(null);
const { t } = useTranslation();
let actionOptions = ActionTypes.map((action) => {
return { name: action.name, value: action.id };
@ -196,7 +198,7 @@ export const EventManager = ({
<Popover.Content>
<div className="row">
<div className="col-3 p-2">
<span data-cy="event-label">Event</span>
<span data-cy="event-label">{t('editor.inspector.eventManager.event', 'Event')}</span>
</div>
<div className="col-9" data-cy="event-selection">
<Select
@ -205,7 +207,7 @@ export const EventManager = ({
value={event.eventId}
search={false}
onChange={(value) => handlerChanged(index, 'eventId', value)}
placeholder="Select.."
placeholder={t('globals.select', 'Select') + '...'}
styles={styles}
useMenuPortal={false}
/>
@ -213,7 +215,7 @@ export const EventManager = ({
</div>
<div className="row mt-3">
<div className="col-3 p-2">
<span data-cy="action-label">Action</span>
<span data-cy="action-label">{t('editor.inspector.eventManager.action', 'Action')}</span>
</div>
<div className="col-9 popover-action-select-search" data-cy="action-selection">
<Select
@ -222,7 +224,7 @@ export const EventManager = ({
value={event.actionId}
search={false}
onChange={(value) => handlerChanged(index, 'actionId', value)}
placeholder="Select.."
placeholder={t('globals.select', 'Select') + '...'}
styles={styles}
useMenuPortal={false}
/>
@ -231,7 +233,7 @@ export const EventManager = ({
{actionLookup[event.actionId].options?.length > 0 && (
<div className="hr-text" data-cy="action-option">
Action options
{t('editor.inspector.eventManager.actionOptions', 'Action options')}
</div>
)}
<div>
@ -239,7 +241,7 @@ export const EventManager = ({
<>
<div className="row">
<div className="col-3 p-2" data-cy="message-label">
Message
{t('editor.inspector.eventManager.message', 'Message')}
</div>
<div className="col-9" data-cy="alert-message-input-field">
<CodeHinter
@ -253,7 +255,7 @@ export const EventManager = ({
</div>
<div className="row mt-3">
<div className="col-3 p-2" data-cy="alert-type-label">
Alert Type
{t('editor.inspector.eventManager.alertType', 'Alert Type')}
</div>
<div className="col-9" data-cy="alert-message-type">
<Select
@ -262,7 +264,7 @@ export const EventManager = ({
value={event.alertType}
search={false}
onChange={(value) => handlerChanged(index, 'alertType', value)}
placeholder="Select.."
placeholder={t('globals.select', 'Select') + '...'}
styles={styles}
useMenuPortal={false}
/>
@ -273,7 +275,7 @@ export const EventManager = ({
{event.actionId === 'open-webpage' && (
<div className="p-1">
<label className="form-label mt-1">URL</label>
<label className="form-label mt-1">{t('editor.inspector.eventManager.url', 'URL')}</label>
<CodeHinter
theme={darkMode ? 'monokai' : 'default'}
currentState={currentState}
@ -297,7 +299,7 @@ export const EventManager = ({
{event.actionId === 'show-modal' && (
<div className="row">
<div className="col-3 p-2">Modal</div>
<div className="col-3 p-2">{t('editor.inspector.eventManager.modal', 'Modal')}</div>
<div className="col-9">
<Select
className={`${darkMode ? 'select-search-dark' : 'select-search'}`}
@ -307,7 +309,7 @@ export const EventManager = ({
onChange={(value) => {
handlerChanged(index, 'modal', value);
}}
placeholder="Select.."
placeholder={t('globals.select', 'Select') + '...'}
styles={styles}
useMenuPortal={false}
/>
@ -317,7 +319,7 @@ export const EventManager = ({
{event.actionId === 'close-modal' && (
<div className="row">
<div className="col-3 p-2">Modal</div>
<div className="col-3 p-2">{t('editor.inspector.eventManager.modal', 'Modal')}</div>
<div className="col-9">
<Select
className={`${darkMode ? 'select-search-dark' : 'select-search'}`}
@ -327,7 +329,7 @@ export const EventManager = ({
onChange={(value) => {
handlerChanged(index, 'modal', value);
}}
placeholder="Select.."
placeholder={t('globals.select', 'Select') + '...'}
styles={styles}
useMenuPortal={false}
/>
@ -337,7 +339,7 @@ export const EventManager = ({
{event.actionId === 'copy-to-clipboard' && (
<div className="p-1">
<label className="form-label mt-1">Text</label>
<label className="form-label mt-1">{t('editor.inspector.eventManager.text', 'Text')}</label>
<CodeHinter
theme={darkMode ? 'monokai' : 'default'}
currentState={currentState}
@ -349,7 +351,7 @@ export const EventManager = ({
{event.actionId === 'run-query' && (
<div className="row">
<div className="col-3 p-2">Query</div>
<div className="col-3 p-2">{t('editor.inspector.eventManager.query', 'Query')}</div>
<div className="col-9">
<Select
className={`${darkMode ? 'select-search-dark' : 'select-search'}`}
@ -363,7 +365,7 @@ export const EventManager = ({
handlerChanged(index, 'queryId', query.id);
handlerChanged(index, 'queryName', query.name);
}}
placeholder="Select.."
placeholder={t('globals.select', 'Select') + '...'}
styles={styles}
useMenuPortal={false}
/>
@ -374,7 +376,7 @@ export const EventManager = ({
{event.actionId === 'set-localstorage-value' && (
<>
<div className="row">
<div className="col-3 p-2">Key</div>
<div className="col-3 p-2">{t('editor.inspector.eventManager.key', 'Key')}</div>
<div className="col-9">
<CodeHinter
theme={darkMode ? 'monokai' : 'default'}
@ -387,7 +389,7 @@ export const EventManager = ({
</div>
</div>
<div className="row mt-3">
<div className="col-3 p-2">Value</div>
<div className="col-3 p-2">{t('editor.inspector.eventManager.value', 'Value')}</div>
<div className="col-9">
<CodeHinter
theme={darkMode ? 'monokai' : 'default'}
@ -404,7 +406,7 @@ export const EventManager = ({
{event.actionId === 'generate-file' && (
<>
<div className="row">
<div className="col-3 p-2">Type</div>
<div className="col-3 p-2">{t('editor.inspector.eventManager.type', 'Type')}</div>
<div className="col-9">
<Select
className={`${darkMode ? 'select-search-dark' : 'select-search'}`}
@ -417,14 +419,14 @@ export const EventManager = ({
onChange={(value) => {
handlerChanged(index, 'fileType', value);
}}
placeholder="Select.."
placeholder={t('globals.select', 'Select') + '...'}
styles={styles}
useMenuPortal={false}
/>
</div>
</div>
<div className="row mt-3">
<div className="col-3 p-2">File name</div>
<div className="col-3 p-2">{t('editor.inspector.eventManager.fileName', 'File name')}</div>
<div className="col-9">
<CodeHinter
theme={darkMode ? 'monokai' : 'default'}
@ -436,7 +438,7 @@ export const EventManager = ({
</div>
</div>
<div className="row mt-3">
<div className="col-3 p-2">Data</div>
<div className="col-3 p-2">{t('editor.inspector.eventManager.data', 'Data')}</div>
<div className="col-9">
<CodeHinter
theme={darkMode ? 'monokai' : 'default'}
@ -452,7 +454,7 @@ export const EventManager = ({
{event.actionId === 'set-table-page' && (
<>
<div className="row">
<div className="col-3 p-2">Table</div>
<div className="col-3 p-2">{t('editor.inspector.eventManager.table', 'Table')}</div>
<div className="col-9">
<Select
className={`${darkMode ? 'select-search-dark' : 'select-search'}`}
@ -462,14 +464,14 @@ export const EventManager = ({
onChange={(value) => {
handlerChanged(index, 'table', value);
}}
placeholder="Select.."
placeholder={t('globals.select', 'Select') + '...'}
styles={styles}
useMenuPortal={false}
/>
</div>
</div>
<div className="row mt-3">
<div className="col-3 p-2">Page index</div>
<div className="col-3 p-2">{t('editor.inspector.eventManager.pageIndex', 'Page index')}</div>
<div className="col-9">
<CodeHinter
theme={darkMode ? 'monokai' : 'default'}
@ -486,7 +488,7 @@ export const EventManager = ({
{event.actionId === 'set-custom-variable' && (
<>
<div className="row">
<div className="col-3 p-2">Key</div>
<div className="col-3 p-2">{t('editor.inspector.eventManager.key', 'Key')}</div>
<div className="col-9">
<CodeHinter
theme={darkMode ? 'monokai' : 'default'}
@ -498,7 +500,7 @@ export const EventManager = ({
</div>
</div>
<div className="row mt-3">
<div className="col-3 p-2">Value</div>
<div className="col-3 p-2">{t('editor.inspector.eventManager.value', 'Value')}</div>
<div className="col-9">
<CodeHinter
theme={darkMode ? 'monokai' : 'default'}
@ -514,7 +516,7 @@ export const EventManager = ({
{event.actionId === 'unset-custom-variable' && (
<>
<div className="row">
<div className="col-3 p-2">Key</div>
<div className="col-3 p-2">{t('editor.inspector.eventManager.key', 'Key')}</div>
<div className="col-9">
<CodeHinter
theme={darkMode ? 'monokai' : 'default'}
@ -531,7 +533,7 @@ export const EventManager = ({
<>
<div className="row">
<div className="col-3 p-1" data-cy="action-options-component-field-label">
Component
{t('editor.inspector.eventManager.component', 'Component')}
</div>
<div className="col-9" data-cy="action-options-component-selection-field">
<Select
@ -543,7 +545,7 @@ export const EventManager = ({
handlerChanged(index, 'componentSpecificActionHandle', '');
handlerChanged(index, 'componentId', value);
}}
placeholder="Select.."
placeholder={t('globals.select', 'Select') + '...'}
styles={styles}
useMenuPortal={false}
/>
@ -551,7 +553,7 @@ export const EventManager = ({
</div>
<div className="row mt-2">
<div className="col-3 p-1" data-cy="action-options-action-field-label">
Action
{t('editor.inspector.eventManager.action', 'Action')}
</div>
<div className="col-9" data-cy="action-options-action-selection-field">
<Select
@ -567,7 +569,7 @@ export const EventManager = ({
getComponentActionDefaultParams(event?.componentId, value)
);
}}
placeholder="Select.."
placeholder={t('globals.select', 'Select') + '...'}
styles={styles}
useMenuPortal={false}
/>
@ -780,12 +782,14 @@ export const EventManager = ({
onClick={addHandler}
data-cy="add-event-handler"
>
+ Add event handler
{t('editor.inspector.eventManager.addEventHandler', '+ Add event handler')}
</button>
</div>
<div className="text-center">
<small className="color-disabled" data-cy="no-event-handler-message">
This {componentName.toLowerCase()} doesn&apos;t have any event handlers
{t('editor.inspector.eventManager.emptyMessage', "This {{componentName}} doesn't have any event handlers", {
componentName: componentName.toLowerCase(),
})}
</small>
</div>
</>
@ -800,7 +804,7 @@ export const EventManager = ({
onClick={addHandler}
data-cy="add-more-event-handler"
>
+ Add handler
{t('editor.inspector.eventManager.addHandler', '+ Add handler')}
</button>
</div>
{renderHandlers(events)}

View file

@ -14,6 +14,7 @@ import { FilePicker } from './Components/FilePicker';
import { CustomComponent } from './Components/CustomComponent';
import useFocus from '@/_hooks/use-focus';
import Accordion from '@/_ui/Accordion';
import { useTranslation } from 'react-i18next';
export const Inspector = ({
selectedComponentId,
@ -41,6 +42,7 @@ export const Inspector = ({
const componentNameRef = useRef(null);
const [newComponentName, setNewComponentName] = useState(component.component.name);
const [inputRef, setInputFocus] = useFocus();
const { t } = useTranslation();
useHotkeys('backspace', () => setWidgetDeleteConfirmation(true));
useHotkeys('escape', () => switchSidebarTab(2));
@ -78,12 +80,12 @@ export const Inspector = ({
function handleComponentNameChange(newName) {
if (component.component.name === newName) return;
if (newName.length === 0) {
toast.error('Widget name cannot be empty');
toast.error(t('widget.common.widgetNameEmptyError', 'Widget name cannot be empty'));
return setInputFocus();
}
if (!validateComponentName(newName)) {
toast.error('Component name already exists');
toast.error(t('widget.common.componentNameExistsError', 'Component name already exists'));
return setInputFocus();
}
@ -92,7 +94,12 @@ export const Inspector = ({
newComponent.component.name = newName;
componentDefinitionChanged(newComponent);
} else {
toast.error('Invalid widget name. Should be unique and only include letters, numbers and underscore.');
toast.error(
t(
'widget.common.invalidWidgetName',
'Invalid widget name. Should be unique and only include letters, numbers and underscore.'
)
);
setInputFocus();
}
}
@ -318,7 +325,7 @@ export const Inspector = ({
const items = [];
items.push({
title: 'General',
title: `${t('widget.common.general', 'General')}`,
isOpen: false,
children: (
<>
@ -359,7 +366,7 @@ export const Inspector = ({
/>
<div ref={tabsRef}>
<Tabs activeKey={key} onSelect={(k) => handleTabSelect(k)} className={`tabs-inspector ${darkMode && 'dark'}`}>
<Tab style={{ marginBottom: 100 }} eventKey="properties" title="Properties">
<Tab style={{ marginBottom: 100 }} eventKey="properties" title={t('widget.common.properties', 'Properties')}>
<div className="header py-1 row">
<div>
<div className="input-icon">
@ -388,7 +395,7 @@ export const Inspector = ({
</div>
{getAccordion(componentMeta.component)}
</Tab>
<Tab eventKey="styles" title="Styles">
<Tab eventKey="styles" title={t('widget.common.styles', 'Styles')}>
<div style={{ marginBottom: '6rem' }}>
<div className="p-3">
{Object.keys(componentMeta.styles).map((style) =>
@ -440,7 +447,9 @@ export const Inspector = ({
rel="noreferrer"
data-cy="widget-documentation-link"
>
<small>{componentMeta.name} documentation</small>
<small>
{t('widget.common.documentation', '{{componentMeta}} documentation', { componentMeta: componentMeta.name })}
</small>
</a>
</div>
</div>

View file

@ -1,9 +1,11 @@
import React, { useState } from 'react';
import SelectSearch, { fuzzySearch } from 'react-select-search';
import Collapse from 'react-bootstrap/Collapse';
import { useTranslation } from 'react-i18next';
export const QuerySelector = ({ param, definition, eventOptionUpdated, dataQueries, extraData, eventMeta }) => {
const [open, setOpen] = useState(false);
const { t } = useTranslation();
function onChange(value) {
const query = dataQueries.find((dataquery) => dataquery.id === value);
@ -42,7 +44,7 @@ export const QuerySelector = ({ param, definition, eventOptionUpdated, dataQueri
onChange(value);
}}
filterOptions={fuzzySearch}
placeholder="Select.."
placeholder={t('globals.select', 'Select') + '...'}
/>
</div>
</div>

View file

@ -10,7 +10,7 @@ import { getSvgIcon } from '@/_helpers/appUtils';
import { datasourceService } from '@/_services';
import { ConfirmDialog } from '@/_components';
import toast from 'react-hot-toast';
import { useTranslation } from 'react-i18next';
export const LeftSidebarDataSources = ({
appId,
editingVersionId,
@ -124,12 +124,13 @@ export const LeftSidebarDataSources = ({
};
const LeftSidebarDataSourcesContainer = ({ renderDataSource, dataSources = [], toggleDataSourceManagerModal }) => {
const { t } = useTranslation();
return (
<div className="card-body">
<div>
<div className="row">
<div className="col">
<h5 className="text-muted">Data sources</h5>
<h5 className="text-muted">{t('leftSidebar.Sources.dataSources', 'Data sources')}</h5>
</div>
<div className="col-auto">
<OverlayTrigger
@ -147,7 +148,7 @@ const LeftSidebarDataSourcesContainer = ({ renderDataSource, dataSources = [], t
<div className="d-flex w-100">
{dataSources.length === 0 ? (
<center onClick={() => toggleDataSourceManagerModal(true)} className="p-2 color-primary cursor-pointer">
+ add data source
{t(`leftSidebar.Sources.addDataSource`, '+ add data source')}
</center>
) : (
<div className="mt-2 w-100">{dataSources?.map((source, idx) => renderDataSource(source, idx))}</div>

View file

@ -4,9 +4,11 @@ import { LeftSidebarItem } from './SidebarItem';
import _ from 'lodash';
import moment from 'moment';
import { SidebarPinnedButton } from './SidebarPinnedButton';
import { useTranslation } from 'react-i18next';
import JSONTreeViewer from '@/_ui/JSONTreeViewer';
export const LeftSidebarDebugger = ({ darkMode, errors, debuggerActions }) => {
const { t } = useTranslation();
const [open, trigger, content, popoverPinned, updatePopoverPinnedState] = usePinnedPopover(false);
const [errorLogs, setErrorLogs] = React.useState([]);
const [unReadErrorCount, setUnReadErrorCount] = React.useState({ read: 0, unread: 0 });
@ -84,7 +86,7 @@ export const LeftSidebarDebugger = ({ darkMode, errors, debuggerActions }) => {
<div className="nav-header">
<ul className="nav nav-tabs d-flex justify-content-between" data-bs-toggle="tabs">
<li className="nav-item">
<a className="nav-link active">Errors</a>
<a className="nav-link active">{t(`leftSidebar.Debugger.errors`, 'Errors')}</a>
</li>
<li className="btn-group">
{errorLogs.length > 0 && (
@ -94,7 +96,7 @@ export const LeftSidebarDebugger = ({ darkMode, errors, debuggerActions }) => {
className="btn btn-light btn-sm m-1 py-1"
aria-label="clear button"
>
<span className="text-muted">clear</span>
<span className="text-muted">{t(`leftSidebar.Debugger.clear`, 'clear')}</span>
</button>
)}
<SidebarPinnedButton
@ -109,7 +111,9 @@ export const LeftSidebarDebugger = ({ darkMode, errors, debuggerActions }) => {
</div>
<div className="card-body">
{errorLogs.length === 0 && <center className="p-2 text-muted">No errors found.</center>}
{errorLogs.length === 0 && (
<center className="p-2 text-muted">{t(`leftSidebar.Debugger.noErrors`, 'No errors found.')}</center>
)}
<div className="tab-content">
{errorLogs.map((error, index) => (

View file

@ -7,6 +7,7 @@ import { LeftSidebarItem } from './SidebarItem';
import FxButton from '../CodeBuilder/Elements/FxButton';
import { CodeHinter } from '../CodeBuilder/CodeHinter';
import { resolveReferences } from '@/_helpers/utils';
import { useTranslation } from 'react-i18next';
export const LeftSidebarGlobalSettings = ({
globalSettings,
@ -16,6 +17,7 @@ export const LeftSidebarGlobalSettings = ({
is_maintenance_on,
currentState,
}) => {
const { t } = useTranslation();
const [open, trigger, content] = usePopover(false);
const { hideHeader, canvasMaxWidth, canvasMaxHeight, canvasBackgroundColor, backgroundFxQuery } = globalSettings;
const [showPicker, setShowPicker] = React.useState(false);
@ -65,7 +67,7 @@ export const LeftSidebarGlobalSettings = ({
<div style={{ marginTop: '1rem' }} className="card-body">
<div>
<div className="d-flex mb-3">
<span>Hide header for launched apps</span>
<span>{t('leftSidebar.Settings.hideHeader', 'Hide header for launched apps')}</span>
<div className="ms-auto form-check form-switch position-relative">
<input
className="form-check-input"
@ -76,7 +78,7 @@ export const LeftSidebarGlobalSettings = ({
</div>
</div>
<div className="d-flex mb-3">
<span>Maintenance mode</span>
<span>{t('leftSidebar.Settings.maintenanceMode', 'Maintenance mode')}</span>
<div className="ms-auto form-check form-switch position-relative">
<input
className="form-check-input"
@ -87,7 +89,7 @@ export const LeftSidebarGlobalSettings = ({
</div>
</div>
<div className="d-flex mb-3">
<span className="w-full m-auto">Max width of canvas</span>
<span className="w-full m-auto">{t('leftSidebar.Settings.maxWidthOfCanvas', 'Max width of canvas')}</span>
<div className="position-relative">
<div className="input-with-icon">
<input
@ -104,7 +106,9 @@ export const LeftSidebarGlobalSettings = ({
</div>
</div>
<div className="d-flex mb-3">
<span className="w-full m-auto">Max height of canvas</span>
<span className="w-full m-auto">
{t('leftSidebar.Settings.maxHeightOfCanvas', 'Max height of canvas')}
</span>
<div className="position-relative">
<div className="input-with-icon">
<input
@ -122,7 +126,9 @@ export const LeftSidebarGlobalSettings = ({
</div>
</div>
<div className="d-flex">
<span className="w-full">Background color of canvas</span>
<span className="w-full">
{t('leftSidebar.Settings.backgroundColorOfCanvas', 'Background color of canvas')}
</span>
<div className="canvas-codehinter-container">
{showPicker && (
<div>

View file

@ -1,6 +1,7 @@
import React from 'react';
import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
import Tooltip from 'react-bootstrap/Tooltip';
import { useTranslation } from 'react-i18next';
export const LeftSidebarItem = ({
tip = '',
@ -13,12 +14,13 @@ export const LeftSidebarItem = ({
count,
...rest
}) => {
const { t } = useTranslation();
return (
<OverlayTrigger
trigger={['click', 'hover', 'focus']}
placement="right"
delay={{ show: 800, hide: 100 }}
overlay={<Tooltip id="button-tooltip">{tip}</Tooltip>}
overlay={<Tooltip id="button-tooltip">{t(`leftSidebar.${text}.tip`, tip)}</Tooltip>}
>
<div>
<div
@ -39,7 +41,7 @@ export const LeftSidebarItem = ({
</div>
)}
{badge && <LeftSidebarItem.Badge count={count} />}
<p>{text && text}</p>
<p>{text && t(`leftSidebar.${text}.text`, text)}</p>
</div>
</div>
</OverlayTrigger>

View file

@ -7,8 +7,10 @@ import { CopyToClipboard } from 'react-copy-to-clipboard';
import Skeleton from 'react-loading-skeleton';
import { debounce } from 'lodash';
import Textarea from '@/_ui/Textarea';
import { withTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
class ManageAppUsers extends React.Component {
class ManageAppUsersComponent extends React.Component {
constructor(props) {
super(props);
@ -133,7 +135,7 @@ class ManageAppUsers extends React.Component {
return (
<div>
<button className="btn font-500 color-primary btn-sm" onClick={() => this.setState({ showModal: true })}>
Share
{this.props.t('editor.share', 'Share')}
</button>
<Modal
@ -148,7 +150,7 @@ class ManageAppUsers extends React.Component {
contentClassName={this.props.darkMode ? 'theme-dark' : ''}
>
<Modal.Header>
<Modal.Title>Share</Modal.Title>
<Modal.Title>{this.props.t('editor.share', 'Share')}</Modal.Title>
<div>
<Button variant={this.props.darkMode ? 'secondary' : 'light'} size="sm" onClick={() => this.hideModal()}>
x
@ -172,12 +174,16 @@ class ManageAppUsers extends React.Component {
checked={this.state.app.is_public}
disabled={this.state.ischangingVisibility}
/>
<span className="form-check-label">Make application public ?</span>
<span className="form-check-label">
{this.props.t('editor.shareModal.makeApplicationPublic', 'Make application public ?')}
</span>
</div>
</div>
<div className="shareable-link mb-3">
<label className="form-label">
<small>Get shareable link for this application</small>
<small>
{this.props.t('editor.shareModal.shareableLink', 'Get shareable link for this application')}
</small>
</label>
<div className="input-group">
<span className="input-group-text">{appLink}</span>
@ -200,7 +206,9 @@ class ManageAppUsers extends React.Component {
</div>
<span className="input-group-text">
<CopyToClipboard text={shareableLink} onCopy={() => toast.success('Link copied to clipboard')}>
<button className="btn btn-secondary btn-sm">Copy</button>
<button className="btn btn-secondary btn-sm">
{this.props.t('editor.shareModal.copy', 'copy')}
</button>
</CopyToClipboard>
</span>
<div className="invalid-feedback">{slugError}</div>
@ -209,7 +217,9 @@ class ManageAppUsers extends React.Component {
<hr />
<div className="shareable-link mb-3">
<label className="form-label">
<small>Get embeddable link for this application</small>
<small>
{this.props.t('editor.shareModal.embeddableLink', 'Get embeddable link for this application')}
</small>
</label>
<div className="input-group">
<Textarea
@ -228,7 +238,9 @@ class ManageAppUsers extends React.Component {
})
}
>
<button className="btn btn-secondary btn-sm">Copy</button>
<button className="btn btn-secondary btn-sm">
{this.props.t('editor.shareModal.copy', 'copy')}
</button>
</CopyToClipboard>
</span>
</div>
@ -311,7 +323,7 @@ class ManageAppUsers extends React.Component {
<Modal.Footer>
<Link to="/users" target="_blank" className="btn color-primary mt-3">
Manage Users
{this.props.t('editor.shareModal.manageUsers', 'Manage Users')}
</Link>
</Modal.Footer>
</Modal>
@ -320,4 +332,4 @@ class ManageAppUsers extends React.Component {
}
}
export { ManageAppUsers };
export const ManageAppUsers = withTranslation()(ManageAppUsersComponent);

View file

@ -3,6 +3,7 @@ import RunjsIcon from '../Icons/runjs.svg';
import AddIcon from '../../../assets/images/icons/add-source.svg';
// eslint-disable-next-line import/no-unresolved
import { allSvgs } from '@tooljet/plugins/client';
import { useTranslation } from 'react-i18next';
function DataSourceLister({
dataSources,
@ -13,7 +14,7 @@ function DataSourceLister({
dataSourceModalHandler,
}) {
const [allSources] = useState([...dataSources, ...staticDataSources]);
const { t } = useTranslation();
const computedStyles = {
background: darkMode ? '#2f3c4c' : 'white',
color: darkMode ? 'white' : '#1f2936',
@ -45,7 +46,7 @@ function DataSourceLister({
})}
<div className="query-datasource-card" style={computedStyles} onClick={dataSourceModalHandler}>
<AddIcon style={{ height: 25, width: 25, marginTop: '-3px' }} />
<p> Add datasource</p>
<p>{t('editor.queryManager.addDatasource', 'Add datasource')}</p>
</div>
</div>
);

View file

@ -1,8 +1,9 @@
import React, { useEffect } from 'react';
import { JSONTree } from 'react-json-tree';
import { Tab, ListGroup, Row } from 'react-bootstrap';
import { useTranslation } from 'react-i18next';
const Preview = ({ previewPanelRef, previewLoading, queryPreviewData, theme, darkMode }) => {
const { t } = useTranslation();
const [key, setKey] = React.useState('raw');
const [isJson, setIsJson] = React.useState(false);
const tabs = ['Json', 'Raw'];
@ -27,7 +28,7 @@ const Preview = ({ previewPanelRef, previewLoading, queryPreviewData, theme, dar
<div>
<div className="row preview-header border-top" ref={previewPanelRef}>
<div className="py-2" style={{ fontWeight: 600 }}>
Preview
{t('editor.preview', 'Preview')}
</div>
</div>
<Tab.Container activeKey={key} onSelect={(k) => setKey(k)} defaultActiveKey="raw">

View file

@ -2,6 +2,7 @@ import React from 'react';
import SelectSearch, { fuzzySearch } from 'react-select-search';
import DOMPurify from 'dompurify';
import { CodeHinter } from '../../CodeBuilder/CodeHinter';
import { withTranslation } from 'react-i18next';
const operationColorMapping = {
get: 'azure',
@ -12,7 +13,7 @@ const operationColorMapping = {
head: 'blue',
};
class Openapi extends React.Component {
class OpenapiComponent extends React.Component {
constructor(props) {
super(props);
const { selectedDataSource } = props;
@ -196,14 +197,16 @@ class Openapi extends React.Component {
return (
<div>
{!spec && <div className="p-3">Valid OpenAPI Spec is not available!.</div>}
{!spec && (
<div className="p-3">{this.props.t('openApi.noValidOpenApi', 'Valid OpenAPI Spec is not available!.')}</div>
)}
{options && spec && (
<div className="mb-3 mt-2">
{baseUrls.length > 0 && (
<div className="row g-2">
<div className="col-12">
<label className="form-label pt-2">Host</label>
<label className="form-label pt-2">{this.props.t('globals.host', 'Host')}</label>
</div>
<div className="col openapi-operation-options">
<SelectSearch
@ -213,14 +216,14 @@ class Openapi extends React.Component {
onChange={(value) => this.changeHost(value)}
filterOptions={fuzzySearch}
renderOption={this.renderHostOptions}
placeholder="Select a host"
placeholder={this.props.t('openApi.selectHost', 'Select a host')}
/>
</div>
</div>
)}
<div className="row g-2">
<div className="col-12">
<label className="form-label pt-2">Operation</label>
<label className="form-label pt-2">{this.props.t('globals.operation', 'Operation')}</label>
</div>
<div className="col openapi-operation-options">
<SelectSearch
@ -230,7 +233,7 @@ class Openapi extends React.Component {
onChange={(value) => this.changeOperation(value)}
filterOptions={fuzzySearch}
renderOption={this.renderOperationOption}
placeholder="Select an operation"
placeholder={this.props.t('openApi.selectOperation', 'Select an operation')}
/>
{selectedOperation && (
@ -250,7 +253,7 @@ class Openapi extends React.Component {
<div className="row mt-2">
{headerParams.length > 0 && (
<div className="mt-2">
<h5 className="text-muted">HEADER</h5>
<h5 className="text-muted">{this.props.t('globals.header', 'HEADER')}</h5>
{headerParams.map((param) => (
<div className="row input-group my-1" key={param.name}>
<div className="col-4 field field-width-268">
@ -286,7 +289,7 @@ class Openapi extends React.Component {
{pathParams.length > 0 && (
<div className="mt-2">
<h5 className="text-muted">PATH</h5>
<h5 className="text-muted">{this.props.t('globals.path', 'PATH')}</h5>
{pathParams.map((param) => (
<div className="row input-group my-1" key={param.name}>
<div className="col-4 field field-width-268">
@ -322,7 +325,7 @@ class Openapi extends React.Component {
{queryParams.length > 0 && (
<div className="mt-2">
<h5 className="text-muted">QUERY</h5>
<h5 className="text-muted">{this.props.t('globals.query', 'QUERY')}</h5>
{queryParams.map((param) => (
<div className="row input-group my-1" key={param.name}>
<div className="col-4 field field-width-268">
@ -358,7 +361,7 @@ class Openapi extends React.Component {
{requestBody?.schema?.properties && (
<div className="mt-2">
<h5 className="text-muted">REQUEST BODY</h5>
<h5 className="text-muted">{this.props.t('globals.requestBody', 'REQUEST BODY')}</h5>
{Object.keys(requestBody.schema.properties).map((param) => (
<div className="row input-group my-1" key={param}>
<div className="col-4 field field-width-268">
@ -398,4 +401,4 @@ class Openapi extends React.Component {
}
}
export { Openapi };
export const Openapi = withTranslation()(OpenapiComponent);

View file

@ -4,6 +4,7 @@ import DOMPurify from 'dompurify';
import Select from '@/_ui/Select';
import { openapiService } from '@/_services';
import { CodeHinter } from '../../CodeBuilder/CodeHinter';
import { withTranslation } from 'react-i18next';
const operationColorMapping = {
get: 'azure',
@ -12,7 +13,7 @@ const operationColorMapping = {
put: 'yellow',
};
class Stripe extends React.Component {
class StripeComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
@ -178,7 +179,7 @@ class Stripe extends React.Component {
{loadingSpec && (
<div className="p-3">
<div className="spinner-border spinner-border-sm text-azure mx-2" role="status"></div>
Please wait whle we load the OpenAPI specification for Stripe.
{this.props.t('stripe', 'Please wait whle we load the OpenAPI specification for Stripe.')}
</div>
)}
@ -186,7 +187,7 @@ class Stripe extends React.Component {
<div className="mb-3 mt-2">
<div className="row g-2">
<div className="col-12">
<label className="form-label pt-2">Operation</label>
<label className="form-label pt-2">{this.props.t('globals.operation', 'Operation')}</label>
</div>
<div className="col stripe-operation-options" style={{ width: '90px', marginTop: 0 }}>
<Select
@ -212,7 +213,7 @@ class Stripe extends React.Component {
<div className="row mt-2">
{pathParams.length > 0 && (
<div className="mt-2">
<h5 className="text-muted">PATH</h5>
<h5 className="text-muted">{this.props.t('globals.path', 'PATH')}</h5>
{pathParams.map((param) => (
<div className="row input-group my-1" key={param.name}>
<div className="col-4 field field-width-268">
@ -258,7 +259,7 @@ class Stripe extends React.Component {
{queryParams.length > 0 && (
<div className="mt-2">
<h5 className="text-muted">QUERY</h5>
<h5 className="text-muted">{this.props.t('globals.query', 'QUERY')}</h5>
{queryParams.map((param) => (
<div className="row input-group my-1" key={param.name}>
<div className="col-4 field field-width-268">
@ -304,7 +305,7 @@ class Stripe extends React.Component {
{requestBody.schema.properties && (
<div className="mt-2">
<h5 className="text-muted">REQUEST BODY</h5>
<h5 className="text-muted">{this.props.t('globals.requestBody', 'REQUEST BODY')}</h5>
{Object.keys(requestBody.schema.properties).map((param) => (
<div className="row input-group my-1" key={param.name}>
<div className="col-4 field field-width-268">
@ -356,4 +357,4 @@ class Stripe extends React.Component {
}
}
export { Stripe };
export const Stripe = withTranslation()(StripeComponent);

View file

@ -15,6 +15,8 @@ import _, { isEmpty, isEqual } from 'lodash';
import { Button, ButtonGroup, Dropdown } from 'react-bootstrap';
// eslint-disable-next-line import/no-unresolved
import { allSvgs } from '@tooljet/plugins/client';
// eslint-disable-next-line import/no-unresolved
import { withTranslation } from 'react-i18next';
const queryNameRegex = new RegExp('^[A-Za-z0-9_-]*$');
@ -23,7 +25,7 @@ const staticDataSources = [
{ kind: 'runjs', id: 'runjs', name: 'Run JavaScript code' },
];
let QueryManager = class QueryManager extends React.Component {
class QueryManagerComponent extends React.Component {
constructor(props) {
super(props);
@ -448,7 +450,7 @@ let QueryManager = class QueryManager extends React.Component {
onClick={() => this.switchCurrentTab(1)}
className={currentTab === 1 ? 'nav-link active' : 'nav-link'}
>
&nbsp; General
&nbsp; {this.props.t('editor.queryManager.general', 'General')}
</a>
</li>
<li className="nav-item">
@ -456,7 +458,7 @@ let QueryManager = class QueryManager extends React.Component {
onClick={() => this.switchCurrentTab(2)}
className={currentTab === 2 ? 'nav-link active' : 'nav-link'}
>
&nbsp; Advanced
&nbsp; {this.props.t('editor.queryManager.advanced', 'Advanced')}
</a>
</li>
</ul>
@ -498,7 +500,7 @@ let QueryManager = class QueryManager extends React.Component {
} ${this.state.selectedDataSource ? '' : 'disabled'}`}
style={{ width: '72px', height: '28px' }}
>
Preview
{this.props.t('editor.queryManager.preview', 'Preview')}
</button>
)}
{selectedDataSource && (addingQuery || editingQuery) && (
@ -524,14 +526,14 @@ let QueryManager = class QueryManager extends React.Component {
this.updateButtonText(dropDownButtonText, false);
}}
>
{dropDownButtonText}
{this.props.t(`editor.queryManager.${dropDownButtonText}`, dropDownButtonText)}
</Dropdown.Item>
<Dropdown.Item
onClick={() => {
this.updateButtonText(`${dropDownButtonText} & Run`, true);
}}
>
{`${dropDownButtonText} & Run`}
{this.props.t(`editor.queryManager.${dropDownButtonText} & Run`, `${dropDownButtonText} & Run`)}
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
@ -580,7 +582,11 @@ let QueryManager = class QueryManager extends React.Component {
</svg>
</p>
)}
{!this.state.isSourceSelected && <label className="form-label col-md-3">Select Datasource</label>}{' '}
{!this.state.isSourceSelected && (
<label className="form-label col-md-3">
{this.props.t('editor.queryManager.selectDatasource', 'Select Datasource')}
</label>
)}{' '}
{this?.state?.selectedDataSource?.kind && (
<div className="header-query-datasource-card-container">
<div
@ -662,7 +668,9 @@ let QueryManager = class QueryManager extends React.Component {
onClick={() => this.toggleOption('runOnPageLoad')}
checked={this.state.options.runOnPageLoad}
/>
<span className="form-check-label">Run this query on page load?</span>
<span className="form-check-label">
{this.props.t('editor.queryManager.runQueryOnPageLoad', 'Run this query on page load?')}
</span>
</div>
<div className="form-check form-switch">
<input
@ -671,7 +679,12 @@ let QueryManager = class QueryManager extends React.Component {
onClick={() => this.toggleOption('requestConfirmation')}
checked={this.state.options.requestConfirmation}
/>
<span className="form-check-label">Request confirmation before running query?</span>
<span className="form-check-label">
{this.props.t(
'editor.queryManager.confirmBeforeQueryRun',
'Request confirmation before running query?'
)}
</span>
</div>
<div className="form-check form-switch">
@ -681,13 +694,17 @@ let QueryManager = class QueryManager extends React.Component {
onClick={() => this.toggleOption('showSuccessNotification')}
checked={this.state.options.showSuccessNotification}
/>
<span className="form-check-label">Show notification on success?</span>
<span className="form-check-label">
{this.props.t('editor.queryManager.notificationOnSuccess', 'Show notification on success?')}
</span>
</div>
{this.state.options.showSuccessNotification && (
<div>
<div className="row mt-3">
<div className="col-auto">
<label className="form-label p-2">Success Message</label>
<label className="form-label p-2">
{this.props.t('editor.queryManager.successMessage', 'Success Message')}
</label>
</div>
<div className="col">
<CodeHinter
@ -696,14 +713,19 @@ let QueryManager = class QueryManager extends React.Component {
height="36px"
theme={this.props.darkMode ? 'monokai' : 'default'}
onChange={(value) => this.optionchanged('successMessage', value)}
placeholder="Query ran successfully"
placeholder={this.props.t(
'editor.queryManager.queryRanSuccessfully',
'Query ran successfully'
)}
/>
</div>
</div>
<div className="row mt-3">
<div className="col-auto">
<label className="form-label p-2">Notification duration (s)</label>
<label className="form-label p-2">
{this.props.t('editor.queryManager.notificationDuration', 'Notification duration (s)')}
</label>
</div>
<div className="col">
<input
@ -719,7 +741,7 @@ let QueryManager = class QueryManager extends React.Component {
</div>
)}
<div className="hr-text hr-text-left">Events</div>
<div className="hr-text hr-text-left">{this.props.t('editor.queryManager.events', 'Events')}</div>
<div className="query-manager-events">
<EventManager
@ -740,7 +762,6 @@ let QueryManager = class QueryManager extends React.Component {
</div>
);
}
};
}
QueryManager = React.memo(QueryManager);
export { QueryManager };
export const QueryManager = withTranslation()(React.memo(QueryManagerComponent));

View file

@ -7,8 +7,10 @@ import 'codemirror/addon/search/match-highlighter';
import 'codemirror/addon/hint/show-hint.css';
import { CodeHinter } from '../CodeBuilder/CodeHinter';
import { Popover, OverlayTrigger } from 'react-bootstrap';
import { useTranslation } from 'react-i18next';
export const Transformation = ({ changeOption, currentState, options, darkMode }) => {
const { t } = useTranslation();
const defaultValue =
options.transformation ||
`// write your code here
@ -17,7 +19,6 @@ return data.filter(row => row.amount > 1000);`;
const [value, setValue] = useState(defaultValue);
const [enableTransformation, setEnableTransformation] = useState(() => options.enableTransformation);
// let suggestions = useMemo(() => getSuggestionKeys(currentState), [currentState.components, currentState.queries]);
function codeChanged(value) {
setValue(() => value);
@ -32,11 +33,13 @@ return data.filter(row => row.amount > 1000);`;
const popover = (
<Popover id="transformation-popover-container">
<p className="transformation-popover">
Transformations can be used to transform the results of queries. All the app variables are accessible from
transformers and supports JS libraries such as Lodash & Moment.
{t(
'editor.queryManager.transformation.transformationToolTip',
'Transformations can be used to transform the results of queries. All the app variables are accessible from transformers and supports JS libraries such as Lodash & Moment.'
)}
<br />
<a href="https://docs.tooljet.io/docs/tutorial/transformations" target="_blank" rel="noreferrer">
Read documentation
{t('globals.readDocumentation', 'Read documentation')}
</a>
.
</p>
@ -64,7 +67,7 @@ return data.filter(row => row.amount > 1000);`;
}}
className="form-check-label mx-1"
>
Transformations
{t('editor.queryManager.transformation.transformations', 'Transformations')}
</span>
</OverlayTrigger>
</div>

View file

@ -1,6 +1,7 @@
import React, { useState } from 'react';
import { appService } from '@/_services';
import { toast } from 'react-hot-toast';
import { useTranslation } from 'react-i18next';
export const ReleaseVersionButton = function DeployVersionButton({
appId,
@ -12,7 +13,7 @@ export const ReleaseVersionButton = function DeployVersionButton({
saveEditingVersion,
}) {
const [isReleasing, setIsReleasing] = useState(false);
const { t } = useTranslation();
const releaseVersion = (editingVersion) => {
setIsReleasing(true);
saveEditingVersion();
@ -41,7 +42,7 @@ export const ReleaseVersionButton = function DeployVersionButton({
className={`btn btn-primary btn-sm ${isVersionReleased ? 'disabled' : ''} ${isReleasing ? 'btn-loading' : ''}`}
onClick={() => releaseVersion(editingVersion)}
>
Release
{t('editor.release', 'Release')}
</button>
</div>
);

View file

@ -19,8 +19,10 @@ import { DarkModeToggle } from '@/_components/DarkModeToggle';
import LogoIcon from './Icons/logo.svg';
import { DataSourceTypes } from './DataSourceManager/SourceComponents';
import { resolveReferences } from '@/_helpers/utils';
import { withTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
class Viewer extends React.Component {
class ViewerComponent extends React.Component {
constructor(props) {
super(props);
@ -231,7 +233,7 @@ class Viewer extends React.Component {
<div className="maintenance_container">
<div className="card">
<div className="card-body" style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<h3>Sorry!. This app is under maintenance</h3>
<h3>{this.props.t('viewer', 'Sorry!. This app is under maintenance')}</h3>
</div>
</div>
</div>
@ -327,4 +329,4 @@ class Viewer extends React.Component {
}
}
export { Viewer };
export const Viewer = withTranslation()(ViewerComponent);

View file

@ -1,9 +1,11 @@
import React, { useState, useEffect } from 'react';
import Modal from 'react-bootstrap/Modal';
import Button from 'react-bootstrap/Button';
import { useTranslation } from 'react-i18next';
export function Confirm({ show, message, onConfirm, onCancel, queryConfirmationData, darkMode }) {
const [showModal, setShow] = useState(show);
const { t } = useTranslation();
useEffect(() => {
setShow(show);
@ -32,10 +34,10 @@ export function Confirm({ show, message, onConfirm, onCancel, queryConfirmationD
<Modal.Body>{message}</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={handleClose}>
Cancel
{t('globals.cancel', 'Cancel')}
</Button>
<Button variant="primary" onClick={handleConfirm}>
Yes
{t('globals.yes', 'Yes')}
</Button>
</Modal.Footer>
</Modal>

View file

@ -2,10 +2,12 @@ import React, { useState } from 'react';
import { DraggableBox } from './DraggableBox';
import Fuse from 'fuse.js';
import { isEmpty } from 'lodash';
import { useTranslation } from 'react-i18next';
export const WidgetManager = function WidgetManager({ componentTypes, zoomLevel, currentLayout, darkMode }) {
const [filteredComponents, setFilteredComponents] = useState(componentTypes);
const [searchQuery, setSearchQuery] = useState('');
const { t } = useTranslation();
function handleSearchQueryChange(e) {
const { value } = e.target;
@ -53,9 +55,12 @@ export const WidgetManager = function WidgetManager({ componentTypes, zoomLevel,
{/* <div class="empty-img">
<img src="./static/illustrations/undraw_printing_invoices_5r4r.svg" height="128" alt="" />
</div> */}
<p className="empty-title">No results found</p>
<p className="empty-title">{t('widgetManager.noResults', 'No results found')}</p>
<p className={`empty-subtitle ${darkMode ? 'text-white-50' : 'text-secondary'}`}>
Try adjusting your search or filter to find what you&apos;re looking for.
{t(
'widgetManager.tryAdjustingFilterMessage',
"Try adjusting your search or filter to find what you're looking for."
)}
</p>
<button
className="btn btn-sm btn-outline-azure mt-3"
@ -64,16 +69,16 @@ export const WidgetManager = function WidgetManager({ componentTypes, zoomLevel,
setSearchQuery('');
}}
>
clear query
{t('widgetManager.clearQuery', 'clear query')}
</button>
</div>
);
}
const commonSection = { title: 'commonly used', items: [] };
const layoutsSection = { title: 'layouts', items: [] };
const formSection = { title: 'forms', items: [] };
const integrationSection = { title: 'integrations', items: [] };
const otherSection = { title: 'others', items: [] };
const commonSection = { title: t('widgetManager.commonlyUsed', 'commonly used'), items: [] };
const layoutsSection = { title: t('widgetManager.layouts', 'layouts'), items: [] };
const formSection = { title: t('widgetManager.forms', 'forms'), items: [] };
const integrationSection = { title: t('widgetManager.integrations', 'integrations'), items: [] };
const otherSection = { title: t('widgetManager.others', 'others'), items: [] };
const allWidgets = [];
const commonItems = ['Table', 'Chart', 'Button', 'Text', 'Datepicker'];
@ -126,7 +131,7 @@ export const WidgetManager = function WidgetManager({ componentTypes, zoomLevel,
<input
type="text"
className={`form-control mt-3 mb-2 ${darkMode && 'dark-theme-placeholder'}`}
placeholder="Search…"
placeholder={t('globals.search', 'Search') + '...'}
value={searchQuery}
onChange={(e) => handleSearchQueryChange(e)}
data-cy="widget-search-box"

View file

@ -3,8 +3,8 @@ import { Link } from 'react-router-dom';
import { toast } from 'react-hot-toast';
import { validateEmail } from '../_helpers/utils';
import { authenticationService } from '@/_services';
class ForgotPassword extends React.Component {
import { withTranslation } from 'react-i18next';
class ForgotPasswordComponent extends React.Component {
constructor(props) {
super(props);
@ -59,15 +59,17 @@ class ForgotPassword extends React.Component {
</div>
<form className="card card-md" action="." method="get" autoComplete="off">
<div className="card-body">
<h2 className="card-title text-center mb-4">Forgot Password</h2>
<h2 className="card-title text-center mb-4">
{this.props.t('loginSignupPage.forgotPassword', 'Forgot Password')}
</h2>
<div className="mb-3">
<label className="form-label">Email address</label>
<label className="form-label">{this.props.t('loginSignupPage.emailAddress', 'Email address')}</label>
<input
onChange={this.handleChange}
name="email"
type="email"
className="form-control"
placeholder="Enter email"
placeholder={this.props.t('loginSignupPage.enterEmail', 'Enter email')}
data-testid="emailField"
/>
</div>
@ -78,15 +80,15 @@ class ForgotPassword extends React.Component {
onClick={this.handleClick}
disabled={isLoading || !this.state.email}
>
Reset Password
{this.props.t('loginSignupPage.resetPassword', 'Reset Password')}
</button>
</div>
</div>
</form>
<div className="text-center text-muted mt-3">
Don&apos;t have account yet? &nbsp;
{this.props.t('loginSignupPage.dontHaveAccount', `Don't have account yet?`)}
<Link to={'/signup'} tabIndex="-1">
Sign up
{this.props.t('loginSignupPage.signUp', `Sign up`)}
</Link>
</div>
</div>
@ -95,4 +97,4 @@ class ForgotPassword extends React.Component {
}
}
export { ForgotPassword };
export const ForgotPassword = withTranslation()(ForgotPasswordComponent);

View file

@ -7,7 +7,7 @@ import useHover from '@/_hooks/useHover';
import configs from './Configs/AppIcon.json';
import { Link } from 'react-router-dom';
import urlJoin from 'url-join';
import { useTranslation } from 'react-i18next';
const { defaultIcon } = configs;
export default function AppCard({
@ -25,6 +25,7 @@ export default function AppCard({
const [hoverRef, isHovered] = useHover();
const [focused, setFocused] = useState(false);
const [isMenuOpen, setMenuOpen] = useState(false);
const { t } = useTranslation();
const onMenuToggle = useCallback(
(status) => {
@ -115,7 +116,7 @@ export default function AppCard({
<ToolTip message="Open in app builder">
<Link to={`/apps/${app.id}`}>
<button type="button" className="btn btn-sm btn-light edit-button" data-cy="edit-button">
Edit
{t('globals.edit', 'Edit')}
</button>
</Link>
</ToolTip>
@ -124,7 +125,9 @@ export default function AppCard({
<div className={`col-${canUpdate ? '6' : '12'} ps-1`}>
<ToolTip
message={
app?.current_version_id === null ? 'App does not have a deployed version' : 'Open in app viewer'
app?.current_version_id === null
? t('homePage.appCard.noDeployedVersion', 'App does not have a deployed version')
: t('homePage.appCard.openInAppViewer', 'Open in app viewer')
}
>
<span>
@ -141,7 +144,9 @@ export default function AppCard({
}}
data-cy="launch-button"
>
{app?.is_maintenance_on ? 'Maintenance' : 'Launch'}
{app?.is_maintenance_on
? t('homePage.appCard.maintenance', 'Maintenance')
: t('homePage.appCard.launch', 'Launch')}
</button>
</span>
</ToolTip>

View file

@ -1,7 +1,9 @@
import React from 'react';
import AppCard from './AppCard';
import { useTranslation } from 'react-i18next';
const AppList = (props) => {
const { t } = useTranslation();
return (
<div style={{ minHeight: '600px' }} className="app-list">
{props.isLoading && (
@ -54,7 +56,7 @@ const AppList = (props) => {
{!props.isLoading && props.meta.total_count === 0 && !(props.currentFolder && props.currentFolder.id) && (
<div>
<span className={`d-block text-center text-body pt-5 ${props.darkMode && 'text-white-50'}`}>
No Applications found
{t('homePage.noApplicationFound', 'No Applications found')}
</span>
</div>
)}
@ -70,7 +72,7 @@ const AppList = (props) => {
className={`d-block text-center text-body ${props.darkMode && 'text-white-50'}`}
data-cy="empty-folder-text"
>
This folder is empty
{t('homePage.thisFolderIsEmpty', 'This folder is empty')}
</span>
</div>
)}

View file

@ -1,6 +1,7 @@
import React from 'react';
import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
import Popover from 'react-bootstrap/Popover';
import { useTranslation } from 'react-i18next';
export const AppMenu = function AppMenu({
deleteApp,
@ -18,6 +19,7 @@ export const AppMenu = function AppMenu({
const closeMenu = () => {
document.body.click();
};
const { t } = useTranslation();
const Field = ({ text, onClick, customClass }) => {
return (
<div className={`field mb-3${customClass ? ` ${customClass}` : ''}`}>
@ -45,19 +47,36 @@ export const AppMenu = function AppMenu({
<Popover id="popover-app-menu" className={darkMode && 'popover-dark-themed'}>
<Popover.Content bsPrefix="popover-body">
<div data-cy="card-options">
{canUpdateApp && <Field text="Change icon" onClick={() => openAppActionModal('change-icon')} />}
{canUpdateApp && (
<Field
text={t('homePage.appCard.changeIcon', 'Change Icon')}
onClick={() => openAppActionModal('change-icon')}
/>
)}
{canCreateApp && (
<>
<Field text="Add to folder" onClick={() => openAppActionModal('add-to-folder')} />
<Field
text={t('homePage.appCard.addToFolder', 'Add to folder')}
onClick={() => openAppActionModal('add-to-folder')}
/>
{currentFolder.id && (
<Field text="Remove from folder" onClick={() => openAppActionModal('remove-app-from-folder')} />
<Field
text={t('homePage.appCard.removeFromFolder', 'Remove from folder')}
onClick={() => openAppActionModal('remove-app-from-folder')}
/>
)}
<Field text="Clone app" onClick={cloneApp} />
<Field text="Export app" onClick={exportApp} />
<Field text={t('homePage.appCard.cloneApp', 'Clone app')} onClick={cloneApp} />
<Field text={t('homePage.appCard.exportApp', 'Export app')} onClick={exportApp} />
</>
)}
{canDeleteApp && <Field text="Delete app" customClass="field__danger" onClick={deleteApp} />}
{canDeleteApp && (
<Field
text={t('homePage.appCard.deleteApp', 'Delete app')}
customClass="field__danger"
onClick={deleteApp}
/>
)}
</div>
</Popover.Content>
</Popover>

View file

@ -1,5 +1,6 @@
import React from 'react';
import TemplateLibraryModal from './TemplateLibraryModal/';
import { useTranslation } from 'react-i18next';
export const BlankPage = function BlankPage({
createApp,
@ -12,6 +13,7 @@ export const BlankPage = function BlankPage({
hideTemplateLibraryModal,
viewTemplateLibraryModal,
}) {
const { t } = useTranslation();
return (
<div>
<div className="page-wrapper">
@ -31,11 +33,13 @@ export const BlankPage = function BlankPage({
style={{ color: darkMode && '#ffffff' }}
data-cy="empty-welcome-header"
>
Welcome to ToolJet!
{t('blankPage.welcomeToToolJet', 'Welcome to ToolJet!')}
</h3>
<p className={`empty-title ${darkMode && 'text-white-50'}`} data-cy="empty-description">
You can get started by creating a new application or by creating an application using a template in
ToolJet Library.
{t(
'blankPage.getStartedCreateNewApp',
'You can get started by creating a new application or by creating an application using a template in ToolJet Library.'
)}
</p>
<div className="empty-action">
<a
@ -43,14 +47,14 @@ export const BlankPage = function BlankPage({
className={`btn btn-primary ${creatingApp ? 'btn-loading' : ''}`}
data-cy="create-new-application"
>
Create new application
{t('homePage.header.createNewApplication', 'Create new application')}
</a>
<a
className={`btn empty-import-button ${isImportingApp ? 'btn-loading' : ''}`}
onChange={handleImportApp}
>
<label style={{ visibility: isImportingApp ? 'hidden' : 'visible' }} data-cy="import-an-application">
Import an application
{t('blankPage.importApplication', 'Import an application')}
<input type="file" ref={fileInput} style={{ display: 'none' }} />
</label>
</a>
@ -60,7 +64,7 @@ export const BlankPage = function BlankPage({
style={{ marginLeft: '24px' }}
data-cy="choose-from-template"
>
Choose from template
{t('homePage.header.chooseFromTemplate', 'Choose from template')}
</a>
</div>
</div>

View file

@ -1,6 +1,7 @@
import React from 'react';
import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
import Popover from 'react-bootstrap/Popover';
import { useTranslation } from 'react-i18next';
export const FolderMenu = function FolderMenu({
deleteFolder,
@ -29,7 +30,7 @@ export const FolderMenu = function FolderMenu({
</div>
);
};
const { t } = useTranslation();
return (
<OverlayTrigger
trigger="click"
@ -40,8 +41,16 @@ export const FolderMenu = function FolderMenu({
<Popover id="popover-app-menu" className={darkMode && 'popover-dark-themed'} data-cy="folder-card">
<Popover.Content bsPrefix="popover-body">
<div>
{canUpdateFolder && <Field text="Edit folder" onClick={editFolder} />}
{canDeleteFolder && <Field text="Delete folder" customClass="field__danger" onClick={deleteFolder} />}
{canUpdateFolder && (
<Field text={t('homePage.foldersSection.editFolder', 'Edit folder')} onClick={editFolder} />
)}
{canDeleteFolder && (
<Field
text={t('homePage.foldersSection.deleteFolder', 'Delete folder')}
customClass="field__danger"
onClick={deleteFolder}
/>
)}
</div>
</Popover.Content>
</Popover>

View file

@ -5,6 +5,7 @@ import Modal from './Modal';
import { FolderMenu } from './FolderMenu';
import useHover from '@/_hooks/useHover';
import { ConfirmDialog } from '@/_components';
import { useTranslation } from 'react-i18next';
export const Folders = function Folders({
folders,
@ -21,7 +22,7 @@ export const Folders = function Folders({
const [isMenuOpen, setMenuOpen] = useState(false);
const [hoverRef, isHovered] = useHover();
const [focused, setFocused] = useState(false);
const { t } = useTranslation();
const onMenuToggle = useCallback(
(status) => {
setMenuOpen(!!status);
@ -141,7 +142,10 @@ export const Folders = function Folders({
<div className="w-100 px-3 pe-lg-4 folder-list">
<ConfirmDialog
show={showDeleteConfirmation}
message={`Are you sure you want to delete the folder? Apps within the folder will not be deleted.`}
message={t(
'homePage.foldersSection.wishToDeleteFolder',
`Are you sure you want to delete the folder? Apps within the folder will not be deleted.`
)}
confirmButtonLoading={isDeleting}
onConfirm={() => executeDeletion()}
onCancel={() => cancelDeleteDialog()}
@ -159,12 +163,12 @@ export const Folders = function Folders({
onClick={() => handleFolderChange({})}
data-cy="all-applications-link"
>
All applications
{t('homePage.foldersSection.allApplications', 'All applications')}
</a>
<hr></hr>
<div className="d-flex justify-content-between mb-3">
<div className="folder-info" data-cy="folder-info">
Folders
{t('homePage.foldersSection.folders', 'Folders')}
</div>
{canCreateFolder && (
<div
@ -175,7 +179,7 @@ export const Folders = function Folders({
}}
data-cy="create-new-folder-button"
>
+ Create new folder
{t('homePage.foldersSection.createNewFolder', '+ Create new folder')}
</div>
)}
</div>
@ -235,14 +239,21 @@ export const Folders = function Folders({
))
: !isLoading && (
<div className="folder-info" data-cy="folder-info-text">
You haven&apos;t created any folders. Use folders to organize your apps
{t(
'homePage.foldersSection.noFolders',
`You haven't created any folders. Use folders to organize your apps`
)}
</div>
)}
<Modal
show={showForm || showUpdateForm}
closeModal={() => (showUpdateForm ? setShowUpdateForm(false) : setShowForm(false))}
title={showUpdateForm ? 'Update Folder' : 'Create folder'}
title={
showUpdateForm
? t('homePage.foldersSection.updateFolder', 'Update Folder')
: t('homePage.foldersSection.createFolder', 'Create folder')
}
>
<div className="row">
<div className="col modal-main">
@ -250,7 +261,7 @@ export const Folders = function Folders({
type="text"
onChange={(e) => setNewFolderName(e.target.value)}
className="form-control"
placeholder="folder name"
placeholder={t('homePage.foldersSection.folderName', 'folder name')}
disabled={isCreating || isUpdating}
value={newFolderName}
maxLength={25}
@ -265,14 +276,16 @@ export const Folders = function Folders({
onClick={() => (showUpdateForm ? setShowUpdateForm(false) : setShowForm(false))}
data-cy="cancel-button"
>
Cancel
{t('globals.cancel', 'Cancel')}
</button>
<button
className={`btn btn-primary ${isCreating || isUpdating ? 'btn-loading' : ''}`}
onClick={showUpdateForm ? executeEditFolder : saveFolder}
data-cy={`${showUpdateForm ? 'update-folder' : 'create-folder'}-button`}
>
{showUpdateForm ? 'Update folder' : 'Create folder'}
{showUpdateForm
? t('homePage.foldersSection.updateFolder', 'Update Folder')
: t('homePage.foldersSection.createFolder', 'Create folder')}
</button>
</div>
</div>

View file

@ -1,6 +1,7 @@
import React from 'react';
import { SearchBox } from '@/_components/SearchBox';
import { Button, ButtonGroup, Dropdown } from 'react-bootstrap';
import { useTranslation } from 'react-i18next';
export default function Header({
folderName,
@ -14,6 +15,7 @@ export default function Header({
fileInput,
darkMode,
}) {
const { t } = useTranslation();
return (
<div className="row">
<div className="col-4">
@ -22,7 +24,7 @@ export default function Header({
</h2>
</div>
<div className="col-8 ms-auto d-print-none d-flex flex-row justify-content-end">
<SearchBox onSubmit={onSearchSubmit} darkMode={darkMode} />
<SearchBox onSubmit={onSearchSubmit} darkMode={darkMode} placeholder={t('globals.search', 'Search')} />
{canCreateApp() && (
<>
{canCreateApp() && (
@ -33,13 +35,15 @@ export default function Header({
data-cy="create-new-app-button"
>
{isImportingApp && <span className="spinner-border spinner-border-sm mx-2" role="status"></span>}
Create new application
{t('homePage.header.createNewApplication', 'Create new application')}
</Button>
<Dropdown.Toggle split className="btn btn-primary d-none d-lg-inline mb-3 " />
<Dropdown.Menu className="import-lg-position">
<Dropdown.Item onClick={showTemplateLibraryModal}>Choose from template</Dropdown.Item>
<Dropdown.Item onClick={showTemplateLibraryModal}>
{t('homePage.header.chooseFromTemplate', 'Choose from template')}
</Dropdown.Item>
<label className="homepage-dropdown-style" onChange={handleImportApp}>
Import
{t('homePage.header.import', 'Import')}
<input type="file" accept=".json" ref={fileInput} style={{ display: 'none' }} />
</label>
</Dropdown.Menu>

View file

@ -11,10 +11,10 @@ import Modal from './Modal';
import SelectSearch from 'react-select-search';
import Fuse from 'fuse.js';
import configs from './Configs/AppIcon.json';
import { withTranslation } from 'react-i18next';
const { iconList, defaultIcon } = configs;
class HomePage extends React.Component {
class HomePageComponent extends React.Component {
constructor(props) {
super(props);
@ -505,7 +505,10 @@ class HomePage extends React.Component {
<div className="wrapper home-page">
<ConfirmDialog
show={showAppDeletionConfirmation}
message={'The app and the associated data will be permanently deleted, do you want to continue?'}
message={this.props.t(
'homePage.deleteAppAndData',
'The app and the associated data will be permanently deleted, do you want to continue?'
)}
confirmButtonLoading={isDeletingApp}
onConfirm={() => this.executeAppDeletion()}
onCancel={() => this.cancelDeleteAppDialog()}
@ -514,7 +517,10 @@ class HomePage extends React.Component {
<ConfirmDialog
show={showRemoveAppFromFolderConfirmation}
message={'The app will be removed from this folder, do you want to continue?'}
message={this.props.t(
'homePage.removeAppFromFolder',
'The app will be removed from this folder, do you want to continue?'
)}
confirmButtonLoading={isDeletingAppFromFolder}
onConfirm={() => this.removeAppFromFolder()}
onCancel={() =>
@ -529,14 +535,14 @@ class HomePage extends React.Component {
<Modal
show={showAddToFolderModal && !!appOperations.selectedApp}
closeModal={() => this.setState({ showAddToFolderModal: false, appOperations: {} })}
title="Add to folder"
title={this.props.t('homePage.appCard.addToFolder', 'Add to folder')}
>
<div className="row">
<div className="col modal-main">
<div className="mb-3" data-cy="move-selected-app-to-text">
<span>Move</span>
<span>{this.props.t('homePage.appCard.move', 'Move')}</span>
<strong>{` "${appOperations?.selectedApp?.name}" `}</strong>
<span>to</span>
<span>{this.props.t('homePage.appCard.to', 'to')}</span>
</div>
<div data-cy="select-folder">
<SelectSearch
@ -552,7 +558,7 @@ class HomePage extends React.Component {
value={appOperations?.selectedFolder}
emptyMessage={this.state.folders === 0 ? 'No folders present' : 'Not found'}
filterOptions={this.customFuzzySearch}
placeholder="Select folder"
placeholder={this.props.t('homePage.appCard.selectFolder', 'Select folder')}
/>
</div>
</div>
@ -564,14 +570,14 @@ class HomePage extends React.Component {
onClick={() => this.setState({ showAddToFolderModal: false, appOperations: {} })}
data-cy="cancel-button"
>
Cancel
{this.props.t('globals.cancel', 'Cancel')}
</button>
<button
className={`btn btn-primary ${appOperations?.isAdding ? 'btn-loading' : ''}`}
onClick={this.addAppToFolder}
data-cy="add-to-folder-button"
>
Add to folder
{this.props.t('homePage.appCard.addToFolder', 'Add to folder')}
</button>
</div>
</div>
@ -580,7 +586,7 @@ class HomePage extends React.Component {
<Modal
show={showChangeIconModal && !!appOperations.selectedApp}
closeModal={() => this.setState({ showChangeIconModal: false, appOperations: {} })}
title="Change Icon"
title={this.props.t('homePage.appCard.changeIcon', 'Change Icon')}
>
<div className="row">
<div className="col modal-main icon-change-modal">
@ -594,14 +600,14 @@ class HomePage extends React.Component {
onClick={() => this.setState({ showChangeIconModal: false, appOperations: {} })}
data-cy="cancel-button"
>
Cancel
{this.props.t('globals.cancel', 'Cancel')}
</button>
<button
className={`btn btn-primary ${appOperations?.isAdding ? 'btn-loading' : ''}`}
onClick={this.changeIcon}
data-cy="change-button"
>
Change
{this.props.t('homePage.change', 'Change')}
</button>
</div>
</div>
@ -697,4 +703,4 @@ class HomePage extends React.Component {
}
}
export { HomePage };
export const HomePage = withTranslation()(HomePageComponent);

View file

@ -1,14 +1,16 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
function TemplateCard() {
const { t } = useTranslation();
return (
<div className="template-card-wrapper">
<div className="template-icon-wrapper"></div>
<div className="template-card-details">
<p className="template-card-title">Lead generetion</p>
<p className="template-card-title">{t('homePage.templateCard.leadGeneration', 'Lead generation')}</p>
<div className="template-action-wrapper">
<p>Use</p>
<p>Preview</p>
<p>{t('homePage.templateCard.use', 'Use')}</p>
<p>{t('homePage.templateCard.preview', 'Preview')}</p>
</div>
</div>
</div>

View file

@ -1,5 +1,6 @@
import React, { useState } from 'react';
import { ListGroup } from 'react-bootstrap';
import { useTranslation } from 'react-i18next';
export default function AppList(props) {
const { apps, selectedApp, selectApp } = props;
@ -31,6 +32,7 @@ export default function AppList(props) {
const SearchBoxContainer = ({ onChange, queryString }) => {
const [searchText, setSearchText] = React.useState(queryString ?? '');
const { t } = useTranslation();
const handleChange = (e) => {
setSearchText(e.target.value);
@ -103,7 +105,13 @@ const SearchBoxContainer = ({ onChange, queryString }) => {
</svg>
</span>
)}
<input type="text" value={searchText} onChange={handleChange} className="form-control" placeholder="Search" />
<input
type="text"
value={searchText}
onChange={handleChange}
className="form-control"
placeholder={t('globals.search', 'Search')}
/>
</div>
</div>
);

View file

@ -7,6 +7,7 @@ import { toast } from 'react-hot-toast';
import _ from 'lodash';
import TemplateDisplay from './TemplateDisplay';
import { useHistory } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
const identifyUniqueCategories = (templates) =>
['all', ...new Set(_.map(templates, 'category'))].map((categoryId) => ({
@ -22,6 +23,7 @@ export default function TemplateLibraryModal(props) {
(app) => selectedCategory.id === 'all' || app.category === selectedCategory.id
);
const [selectedApp, selectApp] = useState(undefined);
const { t } = useTranslation();
useEffect(() => {
selectApp(filteredApps[0]);
@ -76,7 +78,7 @@ export default function TemplateLibraryModal(props) {
centered
>
<Modal.Header>
<Modal.Title>Select template</Modal.Title>
<Modal.Title>{t('homePage.templateLibraryModal.select', 'Select template')}</Modal.Title>
</Modal.Header>
<Modal.Body>
<Container fluid>
@ -106,7 +108,7 @@ export default function TemplateLibraryModal(props) {
>
<div className="d-flex flex-row align-items-center" style={{ height: '100%' }}>
<Button variant="outline-primary" onClick={props.onCloseButtonClick}>
Cancel
{t('globals.cancel', 'Cancel')}
</Button>
<a
href="#"
@ -115,7 +117,7 @@ export default function TemplateLibraryModal(props) {
deployApp();
}}
>
Create application from template
{t('homePage.templateLibraryModal.createAppfromTemplate', 'Create application from template')}
</a>
</div>
</Col>

View file

@ -7,7 +7,8 @@ import GoogleSSOLoginButton from '@ee/components/LoginPage/GoogleSSOLoginButton'
import GitSSOLoginButton from '@ee/components/LoginPage/GitSSOLoginButton';
import { validateEmail } from '../_helpers/utils';
import { ShowLoading } from '@/_components';
class LoginPage extends React.Component {
import { withTranslation } from 'react-i18next';
class LoginPageComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
@ -150,32 +151,42 @@ class LoginPage extends React.Component {
<ShowLoading />
) : (
<div className="card-body">
{!configs && <div className="text-center">No login methods enabled for this workspace</div>}
{!configs && (
<div className="text-center">
{this.props.t(
'loginSignupPage.noLoginMethodsEnabled',
'No login methods enabled for this workspace'
)}
</div>
)}
{configs?.form?.enabled && (
<div>
<h2 className="card-title text-center mb-4" data-cy="login-page-header">
Login to {this.single_organization ? 'your account' : configs?.name || 'your account'}
{this.props.t('loginSignupPage.loginTo', 'Login to')}{' '}
{this.single_organization
? this.props.t('loginSignupPage.yourAccount', 'your account')
: configs?.name || this.props.t('loginSignupPage.yourAccount', 'your account')}
</h2>
<div className="mb-3">
<label className="form-label" data-cy="email-label">
Email address
{this.props.t('loginSignupPage.emailAddress', 'Email address')}
</label>
<input
onChange={this.handleChange}
name="email"
type="email"
className="form-control"
placeholder="Email"
placeholder={this.props.t('loginSignupPage.enterEmail', 'Enter email')}
data-testid="emailField"
data-cy="email-text-field"
/>
</div>
<div className="mb-2">
<label className="form-label" data-cy="password-label">
Password
{this.props.t('loginSignupPage.password', 'Password')}
<span className="form-label-description">
<Link to={'/forgot-password'} tabIndex="-1" data-cy="forgot-password-link">
Forgot password
{this.props.t('loginSignupPage.forgotPassword', 'Forgot Password')}
</Link>
</span>
</label>
@ -185,7 +196,7 @@ class LoginPage extends React.Component {
name="password"
type={this.state.showPassword ? 'text' : 'password'}
className="form-control"
placeholder="Password"
placeholder={this.props.t('loginSignupPage.password', 'Password')}
autoComplete="off"
data-testid="passwordField"
data-cy="password-text-field"
@ -207,7 +218,7 @@ class LoginPage extends React.Component {
htmlFor="check-input"
data-cy="show-password-label"
>
show password
{this.props.t('loginSignupPage.showPassword', 'show password')}
</label>
</div>
</div>
@ -224,7 +235,7 @@ class LoginPage extends React.Component {
onClick={this.authUser}
data-cy="login-button"
>
Sign in
{this.props.t('loginSignupPage.signIn', 'Sign in')}
</button>
)}
{this.state.configs?.google?.enabled && (
@ -240,9 +251,9 @@ class LoginPage extends React.Component {
</form>
{!this.organizationId && configs?.form?.enabled && configs?.form?.enable_sign_up && (
<div className="text-center text-secondary mt-3" data-cy="sign-up-message">
Don&apos;t have account yet? &nbsp;
{this.props.t('loginSignupPage.dontHaveAccount', `Don't have account yet?`)}
<Link to={'/signup'} tabIndex="-1" data-cy="sign-up-link">
Sign up
{this.props.t('loginSignupPage.signUp', `Sign up`)}
</Link>
</div>
)}
@ -257,4 +268,4 @@ class LoginPage extends React.Component {
}
}
export { LoginPage };
export const LoginPage = withTranslation()(LoginPageComponent);

View file

@ -5,8 +5,9 @@ import { groupPermissionService } from '../_services/groupPermission.service';
import { Header } from '@/_components';
import { toast } from 'react-hot-toast';
import { Link } from 'react-router-dom';
import { withTranslation } from 'react-i18next';
class ManageGroupPermissionResources extends React.Component {
class ManageGroupPermissionResourcesComponent extends React.Component {
constructor(props) {
super(props);
@ -322,14 +323,22 @@ class ManageGroupPermissionResources extends React.Component {
{isLoadingGroup ? (
<ol className="breadcrumb" aria-label="breadcrumbs">
<li className="breadcrumb-item">
<a href="#">User groups</a>
<a href="#">
{this.props.t(
'header.organization.menus.manageGroups.permissionResources.userGroup',
'User group'
)}
</a>
</li>
</ol>
) : (
<ol className="breadcrumb" aria-label="breadcrumbs">
<li className="breadcrumb-item">
<Link to="/groups" data-cy="user-groups">
User groups
{this.props.t(
'header.organization.menus.manageGroups.permissionResources.userGroup',
'User group'
)}
</Link>
</li>
<li className="breadcrumb-item">
@ -353,21 +362,24 @@ class ManageGroupPermissionResources extends React.Component {
className={cx('nav-item nav-link', { active: currentTab === 'apps' })}
data-cy="apps-link"
>
Apps
{this.props.t('header.organization.menus.manageGroups.permissionResources.apps', 'Apps')}
</a>
<a
onClick={() => this.setState({ currentTab: 'users' })}
className={cx('nav-item nav-link', { active: currentTab === 'users' })}
data-cy="users-link"
>
Users
{this.props.t('header.organization.menus.manageGroups.permissionResources.users', 'Users')}
</a>
<a
onClick={() => this.setState({ currentTab: 'permissions' })}
className={cx('nav-item nav-link', { active: currentTab === 'permissions' })}
data-cy="permissions-link"
>
Permissions
{this.props.t(
'header.organization.menus.manageGroups.permissionResources.permissions',
'Permissions'
)}
</a>
</nav>
<div className="card-body">
@ -386,7 +398,10 @@ class ManageGroupPermissionResources extends React.Component {
filterOptions={fuzzySearch}
onChange={(value) => this.setSelectedApps(value)}
printOptions="on-focus"
placeholder="Select apps to add to the group"
placeholder={this.props.t(
'header.organization.menus.manageGroups.permissionResources.addAppsToGroup',
'Select apps to add to the group'
)}
/>
</div>
<div className="col-auto">
@ -397,7 +412,7 @@ class ManageGroupPermissionResources extends React.Component {
onClick={() => this.addSelectedAppsToGroup(groupPermission.id, selectedAppIds)}
data-cy="add-button"
>
Add
{this.props.t('globals.add', 'Add')}
</div>
</div>
</div>
@ -407,8 +422,18 @@ class ManageGroupPermissionResources extends React.Component {
<table className="table table-vcenter">
<thead>
<tr>
<th data-cy="name-header">Name</th>
<th data-cy="permissions-header">Permissions</th>
<th data-cy="name-header">
{this.props.t(
'header.organization.menus.manageGroups.permissionResources.name',
'Name'
)}
</th>
<th data-cy="permissions-header">
{this.props.t(
'header.organization.menus.manageGroups.permissionResources.permissions',
'Permissions'
)}
</th>
<th></th>
</tr>
</thead>
@ -443,7 +468,9 @@ class ManageGroupPermissionResources extends React.Component {
disabled={groupPermission.group === 'admin'}
checked={this.canAppGroupPermission(app, groupPermission.id, 'view')}
/>
<span className="form-check-label">View</span>
<span className="form-check-label">
{this.props.t('globals.view', 'view')}
</span>
</label>
<label className="form-check form-check-inline">
<input
@ -455,7 +482,9 @@ class ManageGroupPermissionResources extends React.Component {
disabled={groupPermission.group === 'admin'}
checked={this.canAppGroupPermission(app, groupPermission.id, 'edit')}
/>
<span className="form-check-label">Edit</span>
<span className="form-check-label">
{this.props.t('globals.edit', 'Edit')}
</span>
</label>
</div>
{this.canAppGroupPermission(app, groupPermission.id, 'view') && (
@ -492,7 +521,7 @@ class ManageGroupPermissionResources extends React.Component {
}}
data-cy="delete-link"
>
Delete
{this.props.t('globals.delete', 'Delete')}
</Link>
)}
</td>
@ -519,7 +548,10 @@ class ManageGroupPermissionResources extends React.Component {
value={selectedUserIds}
onChange={(value) => this.setSelectedUsers(value)}
printOptions="on-focus"
placeholder="Select users to add to the group"
placeholder={this.props.t(
'header.organization.menus.manageGroups.permissionResources.addUsersToGroup',
'Select users to add to the group'
)}
/>
</div>
<div className="col-auto">
@ -529,7 +561,7 @@ class ManageGroupPermissionResources extends React.Component {
}`}
onClick={() => this.addSelectedUsersToGroup(groupPermission.id, selectedUserIds)}
>
Add
{this.props.t('globals.add', 'Add')}
</div>
</div>
</div>
@ -539,8 +571,18 @@ class ManageGroupPermissionResources extends React.Component {
<table className="table table-vcenter">
<thead>
<tr>
<th data-cy="name-header">Name</th>
<th data-cy="email-header">Email</th>
<th data-cy="name-header">
{this.props.t(
'header.organization.menus.manageGroups.permissionResources.name',
'name'
)}
</th>
<th data-cy="email-header">
{this.props.t(
'header.organization.menus.manageGroups.permissionResources.email',
'email'
)}
</th>
<th></th>
</tr>
</thead>
@ -572,7 +614,7 @@ class ManageGroupPermissionResources extends React.Component {
this.removeUserFromGroup(groupPermission.id, user.id);
}}
>
Delete
{this.props.t('globals.delete', 'Delete')}
</Link>
)}
</td>
@ -592,8 +634,18 @@ class ManageGroupPermissionResources extends React.Component {
<table className="table table-vcenter">
<thead>
<tr>
<th data-cy="resource-header">Resource</th>
<th data-cy="permissions-header">Permissions</th>
<th data-cy="resource-header">
{this.props.t(
'header.organization.menus.manageGroups.permissionResources.resource',
'Resource'
)}
</th>
<th data-cy="permissions-header">
{this.props.t(
'header.organization.menus.manageGroups.permissionResources.permissions',
'Permissions'
)}
</th>
<th></th>
</tr>
</thead>
@ -615,7 +667,12 @@ class ManageGroupPermissionResources extends React.Component {
) : (
<>
<tr>
<td data-cy="resource-apps">Apps</td>
<td data-cy="resource-apps">
{this.props.t(
'header.organization.menus.manageGroups.permissionResources.apps',
'Apps'
)}
</td>
<td className="text-muted">
<div>
<label className="form-check form-check-inline">
@ -632,7 +689,7 @@ class ManageGroupPermissionResources extends React.Component {
data-cy="app-create-checkbox"
/>
<span className="form-check-label" data-cy="app-create-label">
Create
{this.props.t('globals.create', 'Create')}
</span>
</label>
<label className="form-check form-check-inline">
@ -649,7 +706,7 @@ class ManageGroupPermissionResources extends React.Component {
data-cy="app-delete-checkbox"
/>
<span className="form-check-label" data-cy="app-delete-label">
Delete
{this.props.t('globals.delete', 'Delete')}
</span>
</label>
</div>
@ -658,7 +715,12 @@ class ManageGroupPermissionResources extends React.Component {
</tr>
<tr>
<td data-cy="resource-folders">Folders</td>
<td data-cy="resource-folders">
{this.props.t(
'header.organization.menus.manageGroups.permissionResources.folder',
'Folder'
)}
</td>
<td className="text-muted">
<div>
<label className="form-check form-check-inline">
@ -677,7 +739,10 @@ class ManageGroupPermissionResources extends React.Component {
data-cy="folder-create-checkbox"
/>
<span className="form-check-label" data-cy="folder-create-label">
Create/Update/Delete
{this.props.t(
'header.organization.menus.manageGroups.permissionResources.createUpdateDelete',
'Create/Update/Delete'
)}
</span>
</label>
</div>
@ -685,7 +750,7 @@ class ManageGroupPermissionResources extends React.Component {
<td></td>
</tr>
<tr>
<td>Environment variables</td>
<td>{this.props.t('globals.environmentVar', 'Environment variables')}</td>
<td className="text-muted">
<div>
<label className="form-check form-check-inline">
@ -702,7 +767,12 @@ class ManageGroupPermissionResources extends React.Component {
checked={orgEnvironmentPermission}
disabled={groupPermission.group === 'admin'}
/>
<span className="form-check-label">Create/Update/Delete</span>
<span className="form-check-label">
{this.props.t(
'header.organization.menus.manageGroups.permissionResources.createUpdateDelete',
'Create/Update/Delete'
)}
</span>
</label>
</div>
</td>
@ -726,4 +796,4 @@ class ManageGroupPermissionResources extends React.Component {
}
}
export { ManageGroupPermissionResources };
export const ManageGroupPermissionResources = withTranslation()(ManageGroupPermissionResourcesComponent);

View file

@ -4,8 +4,8 @@ import { groupPermissionService } from '../_services/groupPermission.service';
import { Header, ConfirmDialog } from '@/_components';
import { toast } from 'react-hot-toast';
import { Link } from 'react-router-dom';
class ManageGroupPermissions extends React.Component {
import { withTranslation } from 'react-i18next';
class ManageGroupPermissionsComponent extends React.Component {
constructor(props) {
super(props);
@ -184,7 +184,7 @@ class ManageGroupPermissions extends React.Component {
<div className="col">
<div className="page-pretitle"></div>
<h2 className="page-title" data-cy="user-groups-title">
User Groups
{this.props.t('header.organization.menus.manageGroups.permissions.userGroups', 'User Groups')}
</h2>
</div>
<div className="col-auto ms-auto d-print-none">
@ -194,7 +194,10 @@ class ManageGroupPermissions extends React.Component {
onClick={() => this.setState({ showNewGroupForm: true, isSaveBtnDisabled: true })}
data-cy="create-new-group-button"
>
Create new group
{this.props.t(
'header.organization.menus.manageGroups.permissions.createNewGroup',
'Create new group'
)}
</div>
)}
</div>
@ -208,7 +211,12 @@ class ManageGroupPermissions extends React.Component {
<div className="card">
<div className="card-header">
<h3 className="card-title" data-cy="card-title">
{showGroupNameUpdateForm ? 'Update group' : 'Add new group'}
{showGroupNameUpdateForm
? this.props.t('header.organization.menus.manageGroups.permissions.updateGroup', 'Update group')
: this.props.t(
'header.organization.menus.manageGroups.permissions.addNewGroup',
'Add new group'
)}
</h3>
</div>
<div className="card-body">
@ -229,7 +237,10 @@ class ManageGroupPermissions extends React.Component {
type="text"
required
className="form-control"
placeholder="Enter Name"
placeholder={this.props.t(
'header.organization.menus.manageGroups.permissions.enterName',
'Enter Name'
)}
onChange={(e) => {
this.changeNewGroupName(e.target.value);
}}
@ -253,7 +264,7 @@ class ManageGroupPermissions extends React.Component {
disabled={creatingGroup}
data-cy="cancel-button"
>
Cancel
{this.props.t('globals.cancel', 'Cancel')}
</button>
<button
type="submit"
@ -263,7 +274,12 @@ class ManageGroupPermissions extends React.Component {
disabled={creatingGroup || this.state.isSaveBtnDisabled}
data-cy="create-group-button"
>
{showGroupNameUpdateForm ? 'Save' : 'Create Group'}
{showGroupNameUpdateForm
? this.props.t('globals.save', 'Save')
: this.props.t(
'header.organization.menus.manageGroups.permissions.createGroup',
'Create Group'
)}
</button>
</div>
</form>
@ -278,7 +294,9 @@ class ManageGroupPermissions extends React.Component {
<table data-testid="usersTable" className="table table-vcenter" disabled={true}>
<thead>
<tr>
<th data-cy="table-header">Name</th>
<th data-cy="table-header">
{this.props.t('header.organization.menus.manageGroups.permissions.name', 'Name')}
</th>
<th className="w-1"></th>
<th className="w-1"></th>
</tr>
@ -314,14 +332,14 @@ class ManageGroupPermissions extends React.Component {
{permissionGroup.group !== 'admin' && permissionGroup.group !== 'all_users' && (
<div className="user-group-actions">
<Link onClick={() => this.updateGroupName(permissionGroup)} data-cy="update-link">
Update
{this.props.t('globals.update', 'Update')}
</Link>
<Link
className="text-danger"
onClick={() => this.deleteGroup(permissionGroup.id)}
data-cy="delete-link"
>
Delete
{this.props.t('globals.delete', 'Delete')}
</Link>
</div>
)}
@ -342,4 +360,5 @@ class ManageGroupPermissions extends React.Component {
}
}
export { ManageGroupPermissions };
export const ManageGroupPermissions = withTranslation()(ManageGroupPermissionsComponent);
// export { ManageGroupPermissions };

View file

@ -4,8 +4,9 @@ import { Header } from '@/_components';
import { toast } from 'react-hot-toast';
import { CopyToClipboard } from 'react-copy-to-clipboard';
import ReactTooltip from 'react-tooltip';
import { withTranslation } from 'react-i18next';
import urlJoin from 'url-join';
class ManageOrgUsers extends React.Component {
class ManageOrgUsersComponent extends React.Component {
constructor(props) {
super(props);
@ -188,7 +189,7 @@ class ManageOrgUsers extends React.Component {
<div className="col">
<div className="page-pretitle"></div>
<h2 className="page-title" data-cy="users-page-title">
Users & Permissions
{this.props.t('header.organization.menus.manageUsers.usersAndPermission', 'Users & Permissions')}
</h2>
</div>
<div className="col-auto ms-auto d-print-none">
@ -198,7 +199,7 @@ class ManageOrgUsers extends React.Component {
onClick={() => this.setState({ showNewUserForm: true })}
data-cy="invite-new-user"
>
Invite new user
{this.props.t('header.organization.menus.manageUsers.inviteNewUser', 'Invite new user')}
</div>
)}
</div>
@ -212,7 +213,7 @@ class ManageOrgUsers extends React.Component {
<div className="card">
<div className="card-header">
<h3 className="card-title" data-cy="add-new-user">
Add new user
{this.props.t('header.organization.menus.manageUsers.addNewUser', 'Add new user')}
</h3>
</div>
<div className="card-body">
@ -223,7 +224,10 @@ class ManageOrgUsers extends React.Component {
<input
type="text"
className="form-control"
placeholder="Enter First Name"
placeholder={this.props.t(
'header.organization.menus.manageUsers.enterFirstName',
'Enter First Name'
)}
name="firstName"
onChange={this.changeNewUserOption.bind(this, 'firstName')}
value={this.state.fields['firstName']}
@ -237,7 +241,10 @@ class ManageOrgUsers extends React.Component {
<input
type="text"
className="form-control"
placeholder="Enter Last Name"
placeholder={this.props.t(
'header.organization.menus.manageUsers.enterLastName',
'Enter Last Name'
)}
name="lastName"
onChange={this.changeNewUserOption.bind(this, 'lastName')}
value={this.state.fields['lastName']}
@ -251,14 +258,17 @@ class ManageOrgUsers extends React.Component {
</div>
<div className="form-group mb-3 ">
<label className="form-label" data-cy="email-label">
Email address
{this.props.t('header.organization.menus.manageUsers.emailAddress', 'Email Address')}
</label>
<div>
<input
type="text"
className="form-control"
aria-describedby="emailHelp"
placeholder="Enter email"
placeholder={this.props.t(
'header.organization.menus.manageUsers.enterEmail',
'Enter Email'
)}
name="email"
onChange={this.changeNewUserOption.bind(this, 'email')}
value={this.state.fields['email']}
@ -281,7 +291,7 @@ class ManageOrgUsers extends React.Component {
}
data-cy="cancel-button"
>
Cancel
{this.props.t('globals.cancel', 'Cancel')}
</button>
<button
type="submit"
@ -289,7 +299,7 @@ class ManageOrgUsers extends React.Component {
disabled={creatingUser}
data-cy="create-user-button"
>
Create User
{this.props.t('header.organization.menus.manageUsers.createUser', 'Create User')}
</button>
</div>
</form>
@ -311,9 +321,15 @@ class ManageOrgUsers extends React.Component {
<table data-testid="usersTable" className="table table-vcenter" disabled={true}>
<thead>
<tr>
<th data-cy="name-title">Name</th>
<th data-cy="email-title">Email</th>
<th data-cy="status-title">Status</th>
<th data-cy="name-title">
{this.props.t('header.organization.menus.manageUsers.name', 'Name')}
</th>
<th data-cy="email-title">
{this.props.t('header.organization.menus.manageUsers.email', 'Email')}
</th>
<th data-cy="status-title">
{this.props.t('header.organization.menus.manageUsers.status', 'Status')}
</th>
<th className="w-1"></th>
</tr>
</thead>
@ -420,7 +436,9 @@ class ManageOrgUsers extends React.Component {
}}
data-cy="user-state"
>
{user.status === 'archived' ? 'Unarchive' : 'Archive'}
{user.status === 'archived'
? this.props.t('header.organization.menus.manageUsers.unarchive', 'Unarchive')
: this.props.t('header.organization.menus.manageUsers.archive', 'Archive')}
</button>
</td>
</tr>
@ -439,4 +457,4 @@ class ManageOrgUsers extends React.Component {
}
}
export { ManageOrgUsers };
export const ManageOrgUsers = withTranslation()(ManageOrgUsersComponent);

View file

@ -5,8 +5,8 @@ import { toast } from 'react-hot-toast';
import ReactTooltip from 'react-tooltip';
import VariableForm from './VariableForm';
import VariablesTable from './VariablesTable';
class ManageOrgVars extends React.Component {
import { withTranslation } from 'react-i18next';
class ManageOrgVarsComponent extends React.Component {
constructor(props) {
super(props);
this.currentUser = authenticationService.currentUserValue;
@ -241,7 +241,10 @@ class ManageOrgVars extends React.Component {
<ConfirmDialog
show={this.state.showVariableDeleteConfirmation}
message={'Variable will be deleted, do you want to continue?'}
message={this.props.t(
'header.organization.menus.manageSSO.environmentVar.envWillBeDeleted',
'Variable will be deleted, do you want to continue?'
)}
onConfirm={() => {
this.deleteVariable(this.state.selectedVariableId);
}}
@ -259,7 +262,7 @@ class ManageOrgVars extends React.Component {
<div className="row align-items-center">
<div className="col">
<div className="page-pretitle"></div>
<h2 className="page-title">Environment Variables</h2>
<h2 className="page-title">{this.props.t('globals.environmentVar', 'Environment Variables')}</h2>
</div>
<div className="col-auto ms-auto d-print-none">
{!showVariableForm && this.canCreateVariable() && (
@ -267,7 +270,10 @@ class ManageOrgVars extends React.Component {
className="btn btn-primary"
onClick={() => this.setState({ showVariableForm: true, errors: {} })}
>
Add new variable
{this.props.t(
'header.organization.menus.manageSSO.environmentVar.addNewVariable',
'Add new variable'
)}
</div>
)}
</div>
@ -302,8 +308,10 @@ class ManageOrgVars extends React.Component {
/>
) : (
<span className="no-vars-text">
You haven&apos;t configured any environment variables, press the &apos;<b>Add new variable</b>&apos;
button to create one
{this.props.t(
'header.organization.menus.manageSSO.environmentVar.noEnvConfig',
`You haven't configured any environment variables, press the 'Add new variable' button to create one`
)}
</span>
)}
</>
@ -315,4 +323,4 @@ class ManageOrgVars extends React.Component {
}
}
export { ManageOrgVars };
export const ManageOrgVars = withTranslation()(ManageOrgVarsComponent);

View file

@ -1,7 +1,7 @@
import React from 'react';
import Select from '@/_ui/Select';
export default class VariableForm extends React.Component {
import { withTranslation } from 'react-i18next';
class VariableForm extends React.Component {
constructor(props) {
super(props);
}
@ -11,18 +11,33 @@ export default class VariableForm extends React.Component {
<div className="container-xl">
<div className="card">
<div className="card-header">
<h3 className="card-title">{!this.props.selectedVariableId ? 'Add new variable' : 'Update variable'}</h3>
<h3 className="card-title">
{!this.props.selectedVariableId
? this.props.t(
'header.organization.menus.manageSSO.environmentVar.variableForm.addNewVariable',
'Add new variable'
)
: this.props.t(
'header.organization.menus.manageSSO.environmentVar.variableForm.updatevariable',
'Update variable'
)}
</h3>
</div>
<div className="card-body">
<form onSubmit={this.props.createOrUpdate} noValidate>
<div className="form-group mb-3 ">
<div className="row">
<div className="col">
<label className="form-label">Name</label>
<label className="form-label">
{this.props.t('header.organization.menus.manageSSO.environmentVar.variableForm.name', 'Name')}
</label>
<input
type="text"
className="form-control"
placeholder="Enter Variable Name"
placeholder={this.props.t(
'header.organization.menus.manageSSO.environmentVar.variableForm.enterVariableName',
'Enter Variable Name'
)}
name="variable_name"
onChange={this.props.changeNewVariableOption.bind(this, 'variable_name')}
value={this.props.fields['variable_name']}
@ -30,11 +45,17 @@ export default class VariableForm extends React.Component {
<span className="text-danger">{this.props.errors['variable_name']}</span>
</div>
<div className="col">
<label className="form-label">Value</label>
<label className="form-label">
{' '}
{this.props.t('header.organization.menus.manageSSO.environmentVar.variableForm.value', 'Value')}
</label>
<input
type="text"
className="form-control"
placeholder="Enter Value"
placeholder={this.props.t(
'header.organization.menus.manageSSO.environmentVar.variableForm.enterValue',
'Enter Value'
)}
name="value"
onChange={this.props.changeNewVariableOption.bind(this, 'value')}
value={this.props.fields['value']}
@ -46,7 +67,9 @@ export default class VariableForm extends React.Component {
<div className="form-group mb-3 ">
<div className="row">
<div className="col">
<label className="form-label">Type</label>
<label className="form-label">
{this.props.t('header.organization.menus.manageSSO.environmentVar.variableForm.type', 'Type')}
</label>
{this.props.selectedVariableId ? (
<span>{this.props.fields['variable_type']}</span>
) : (
@ -63,7 +86,12 @@ export default class VariableForm extends React.Component {
)}
</div>
<div className="col">
<label className="form-label">Enable encryption</label>
<label className="form-label">
{this.props.t(
'header.organization.menus.manageSSO.environmentVar.variableForm.enableEncryption',
' Enable encryption'
)}
</label>
<div className="form-check form-switch encryption-input">
<input
className="form-check-input"
@ -84,14 +112,19 @@ export default class VariableForm extends React.Component {
</div>
<div className="form-footer">
<button type="button" className="btn btn-light mr-2" onClick={() => this.props.onCancelBtnClicked()}>
Cancel
{this.props.t('globals.cancel', 'Cancel')}
</button>
<button
type="submit"
className={`btn mx-2 btn-primary ${this.props.addingVar ? 'btn-loading' : ''}`}
disabled={this.props.addingVar}
>
{!this.props.selectedVariableId ? 'Add variable' : 'Save'}
{!this.props.selectedVariableId
? this.props.t(
'header.organization.menus.manageSSO.environmentVar.variableForm.addVariable',
'Add variable'
)
: this.props.t('globals.save', 'Save')}
</button>
</div>
</form>
@ -101,3 +134,4 @@ export default class VariableForm extends React.Component {
);
}
}
export default withTranslation()(VariableForm);

View file

@ -1,6 +1,7 @@
import React from 'react';
import { withTranslation } from 'react-i18next';
export default class VariablesTable extends React.Component {
class VariablesTable extends React.Component {
constructor(props) {
super(props);
@ -25,9 +26,15 @@ export default class VariablesTable extends React.Component {
<table data-testid="variablesTable" className="table table-vcenter" disabled={true}>
<thead>
<tr>
<th>Name</th>
<th>Value</th>
<th>Type</th>
<th>
{this.props.t('header.organization.menus.manageSSO.environmentVar.variableTable.name', 'Name')}
</th>
<th>
{this.props.t('header.organization.menus.manageSSO.environmentVar.variableTable.value', 'Value')}
</th>
<th>
{this.props.t('header.organization.menus.manageSSO.environmentVar.variableTable.type', 'Type')}
</th>
{(this.props.canUpdateVariable || this.props.canDeleteVariable) && <th className="w-1"></th>}
</tr>
</thead>
@ -67,7 +74,12 @@ export default class VariablesTable extends React.Component {
width="12"
height="12"
/>
<span className="text-success mx-2">secret</span>
<span className="text-success mx-2">
{this.props.t(
'header.organization.menus.manageSSO.environmentVar.variableTable.secret',
'secret'
)}
</span>
</small>
) : (
variable.value
@ -132,3 +144,4 @@ export default class VariablesTable extends React.Component {
);
}
}
export default withTranslation()(VariablesTable);

View file

@ -1,9 +1,11 @@
import React, { useState } from 'react';
import { organizationService } from '@/_services';
import { toast } from 'react-hot-toast';
import { useTranslation } from 'react-i18next';
export function Form({ settings, updateData }) {
const [enabled, setEnabled] = useState(settings?.enabled || false);
const { t } = useTranslation();
const changeStatus = () => {
organizationService.editOrganizationConfigs({ type: 'form', enabled: !enabled }).then(
@ -26,9 +28,9 @@ export function Form({ settings, updateData }) {
<div className="card-header">
<div className="d-flex justify-content-between title-with-toggle">
<div className="card-title" data-cy="card-title">
Password Login
{t('header.organization.menus.manageSSO.passwordLogin', 'Password Login')}
<span className={`badge bg-${enabled ? 'green' : 'grey'} ms-1`} data-cy="status-label">
{enabled ? 'Enabled' : 'Disabled'}
{enabled ? t('globals.enabled', 'Enabled') : t('globals.disabled', 'Disabled')}
</span>
</div>
<div>

View file

@ -2,6 +2,7 @@ import { authenticationService, organizationService } from '@/_services';
import React, { useState } from 'react';
import { toast } from 'react-hot-toast';
import { copyToClipboard } from '@/_helpers/appUtils';
import { useTranslation } from 'react-i18next';
export function GeneralSettings({ settings, updateData }) {
const isSingleOrganization = window.public_config?.DISABLE_MULTI_WORKSPACE === 'true';
@ -9,6 +10,7 @@ export function GeneralSettings({ settings, updateData }) {
const [inheritSSO, setInheritSSO] = useState(settings?.inherit_s_s_o || false);
const [domain, setDomain] = useState(settings?.domain || '');
const [isSaving, setSaving] = useState(false);
const { t } = useTranslation();
const reset = () => {
setEnableSignUp(settings?.enable_sign_up || false);
@ -92,7 +94,7 @@ export function GeneralSettings({ settings, updateData }) {
<div className="card">
<div className="card-header">
<div className="card-title" data-cy="card-title">
General Settings
{t('header.organization.menus.manageSSO.generalSettings.title', 'General Settings')}
</div>
</div>
<div className="card-body">
@ -107,12 +109,15 @@ export function GeneralSettings({ settings, updateData }) {
data-cy="form-check-input"
/>
<span className="form-check-label" data-cy="form-check-label">
Enable signup
{t('header.organization.menus.manageSSO.generalSettings.enableSignup', 'Enable signup')}
</span>
</label>
<div className="help-text">
<div data-cy="general-settings-help-text">
New account will be created for user&apos;s first time SSO sign in
{t(
'header.organization.menus.manageSSO.generalSettings.newAccountWillBeCreated',
`New account will be created for user's first time SSO sign in`
)}
</div>
</div>
</div>
@ -128,7 +133,7 @@ export function GeneralSettings({ settings, updateData }) {
data-cy="form-check-input"
/>
<span className="form-check-label" data-cy="form-check-label">
Allow default SSO
{t('header.organization.menus.manageSSO.generalSettings.allowDefaultSso', `Allow default SSO`)}
</span>
</label>
<div className="d-flex tick-cross-info mb-2">
@ -137,21 +142,23 @@ export function GeneralSettings({ settings, updateData }) {
</div>
<div className="help-text mt-1">
<div data-cy="login-help-text">
Allow users to authenticate via default SSO. Default SSO configurations can be overridden by
workspace level SSO.
{t(
'header.organization.menus.manageSSO.generalSettings.ssoAuth',
`Allow users to authenticate via default SSO. Default SSO configurations can be overridden by workspace level SSO.`
)}
</div>
</div>
</div>
)}
<div className="form-group mb-3">
<label className="form-label" data-cy="allowed-domains-label">
Allowed domains
{t('header.organization.menus.manageSSO.generalSettings.allowedDomains', `Allowed domains`)}
</label>
<div>
<input
type="text"
className="form-control"
placeholder="Enter Domains"
placeholder={t('header.organization.menus.manageSSO.generalSettings.enterDomains', `Enter Domains`)}
name="domain"
value={domain}
onChange={(e) => setDomain(e.target.value)}
@ -160,15 +167,17 @@ export function GeneralSettings({ settings, updateData }) {
</div>
<div className="help-text mt-1">
<div data-cy="login-help-text">
Support multiple domains. Enter domain names separated by comma. example:
tooljet.com,tooljet.io,yourorganization.com
{t(
'header.organization.menus.manageSSO.generalSettings.supportMultiDomains',
`Support multiple domains. Enter domain names separated by comma. example: tooljet.com,tooljet.io,yourorganization.com`
)}
</div>
</div>
</div>
{!isSingleOrganization && (
<div className="form-group mb-3">
<label className="form-label" data-cy="login-url-label">
Login URL
{t('header.organization.menus.manageSSO.generalSettings.loginUrl', `Login URL`)}
</label>
<div className="flexer-sso-input form-control">
@ -184,13 +193,18 @@ export function GeneralSettings({ settings, updateData }) {
/>
</div>
<div className="help-text mt-1">
<div data-cy="login-help-text">Use this URL to login directly to this workspace</div>
<div data-cy="login-help-text">
{t(
'header.organization.menus.manageSSO.generalSettings.workspaceLogin',
`Use this URL to login directly to this workspace`
)}
</div>
</div>
</div>
)}
<div className="form-footer">
<button type="button" className="btn btn-light mr-2" onClick={reset} data-cy="cancel-button">
Cancel
{t('globals.cancel', 'Cancel')}
</button>
<button
type="button"
@ -199,7 +213,7 @@ export function GeneralSettings({ settings, updateData }) {
onClick={saveSettings}
data-cy="save-button"
>
Save
{t('globals.save', 'Save')}
</button>
</div>
</form>

View file

@ -2,6 +2,7 @@ import React, { useState } from 'react';
import { organizationService } from '@/_services';
import { toast } from 'react-hot-toast';
import { copyToClipboard } from '@/_helpers/appUtils';
import { useTranslation } from 'react-i18next';
export function Git({ settings, updateData }) {
const [enabled, setEnabled] = useState(settings?.enabled || false);
@ -10,6 +11,7 @@ export function Git({ settings, updateData }) {
const [clientSecret, setClientSecret] = useState(settings?.configs?.client_secret || '');
const [isSaving, setSaving] = useState(false);
const [configId, setConfigId] = useState(settings?.id);
const { t } = useTranslation();
const reset = () => {
setClientId(settings?.configs?.client_id || '');
@ -71,9 +73,9 @@ export function Git({ settings, updateData }) {
<div className="card-header">
<div className="d-flex justify-content-between title-with-toggle">
<div className="card-title" data-cy="card-title">
GitHub
{t('header.organization.menus.manageSSO.github.title', 'Github')}
<span className={`badge bg-${enabled ? 'green' : 'grey'} ms-1`} data-cy="status-label">
{enabled ? 'Enabled' : 'Disabled'}
{enabled ? t('globals.enabled', 'Enabled') : t('globals.disabled', 'Disabled')}
</span>
</div>
<div>
@ -93,31 +95,34 @@ export function Git({ settings, updateData }) {
<form noValidate>
<div className="form-group mb-3">
<label className="form-label" data-cy="host-name-label">
Host Name
{t('header.organization.menus.manageSSO.github.hostName', 'Host Name')}
</label>
<div>
<input
type="text"
className="form-control"
placeholder="Enter Host Name"
placeholder={t('header.organization.menus.manageSSO.github.enterHostName', 'Enter Host Name')}
value={hostName}
onChange={(e) => setHostName(e.target.value)}
data-cy="host-name-input"
/>
</div>
<div className="help-text mt-2">
<div data-cy="general-settings-help-text">Required if GitHub is self hosted</div>
<div data-cy="general-settings-help-text">
{' '}
{t('header.organization.menus.manageSSO.github.requiredGithub', 'Required if GitHub is self hosted')}
</div>
</div>
</div>
<div className="form-group mb-3">
<label className="form-label" data-cy="client-id-label">
Client Id
{t('header.organization.menus.manageSSO.github.clientId', ' Client Id')}
</label>
<div>
<input
type="text"
className="form-control"
placeholder="Enter Client Id"
placeholder={t('header.organization.menus.manageSSO.github.enterClientId', 'Enter Client Id')}
value={clientId}
onChange={(e) => setClientId(e.target.value)}
data-cy="client-id-input"
@ -126,17 +131,17 @@ export function Git({ settings, updateData }) {
</div>
<div className="form-group mb-3">
<label className="form-label" data-cy="client-secret-label">
Client Secret
{t('header.organization.menus.manageSSO.github.clientSecret', 'Client Secret')}
<small className="text-green mx-2" data-cy="encripted-label">
<img className="mx-2 encrypted-icon" src="assets/images/icons/padlock.svg" width="12" height="12" />
Encrypted
{t('header.organization.menus.manageSSO.github.encrypted', 'Encrypted')}
</small>
</label>
<div>
<input
type="text"
className="form-control"
placeholder="Enter Client Secret"
placeholder={t('header.organization.menus.manageSSO.github.enterClientSecret', 'Enter Client Secret')}
value={clientSecret}
onChange={(e) => setClientSecret(e.target.value)}
data-cy="client-secret-input"
@ -146,7 +151,7 @@ export function Git({ settings, updateData }) {
{configId && (
<div className="form-group mb-3">
<label className="form-label" data-cy="redirect-url-label">
Redirect URL
{t('header.organization.menus.manageSSO.github.redirectUrl', 'Redirect URL')}
</label>
<div className="flexer-sso-input form-control">
<p
@ -165,7 +170,7 @@ export function Git({ settings, updateData }) {
)}
<div className="form-footer">
<button type="button" className="btn btn-light mr-2" onClick={reset} data-cy="cancel-button">
Cancel
{t('globals.cancel', 'Cancel')}
</button>
<button
type="button"
@ -174,7 +179,7 @@ export function Git({ settings, updateData }) {
onClick={saveSettings}
data-cy="save-button"
>
Save
{t('globals.save', 'Save')}
</button>
</div>
</form>

View file

@ -2,12 +2,14 @@ import React, { useState } from 'react';
import { organizationService } from '@/_services';
import { toast } from 'react-hot-toast';
import { copyToClipboard } from '@/_helpers/appUtils';
import { useTranslation } from 'react-i18next';
export function Google({ settings, updateData }) {
const [enabled, setEnabled] = useState(settings?.enabled || false);
const [clientId, setClientId] = useState(settings?.configs?.client_id || '');
const [isSaving, setSaving] = useState(false);
const [configId, setConfigId] = useState(settings?.id);
const { t } = useTranslation();
const reset = () => {
setClientId(settings?.configs?.client_id || '');
@ -63,9 +65,11 @@ export function Google({ settings, updateData }) {
<div className="card-header">
<div className="d-flex justify-content-between title-with-toggle">
<div className="card-title" data-cy="card-title">
Google
{t('header.organization.menus.manageSSO.google.title', 'Google')}
<span className={`badge bg-${enabled ? 'green' : 'grey'} ms-1`} data-cy="status-label">
{enabled ? 'Enabled' : 'Disabled'}
{enabled
? t('header.organization.menus.manageSSO.google.enabled', 'Enabled')
: t('header.organization.menus.manageSSO.google.disabled', 'Disabled')}
</span>
</div>
<div>
@ -85,13 +89,13 @@ export function Google({ settings, updateData }) {
<form noValidate>
<div className="form-group mb-3">
<label className="form-label" data-cy="client-id-label">
Client Id
{t('header.organization.menus.manageSSO.google.clientId', 'Client Id')}
</label>
<div>
<input
type="text"
className="form-control"
placeholder="Enter Client Id"
placeholder={t('header.organization.menus.manageSSO.google.enterClientId', 'Enter Client Id')}
value={clientId}
onChange={(e) => setClientId(e.target.value)}
data-cy="client-id-input"
@ -101,7 +105,7 @@ export function Google({ settings, updateData }) {
{configId && (
<div className="form-group mb-3">
<label className="form-label" data-cy="redirect-url-label">
Redirect URL
{t('header.organization.menus.manageSSO.google.redirectUrl', 'Redirect URL')}
</label>
<div className="flexer-sso-input form-control">
<p
@ -120,7 +124,7 @@ export function Google({ settings, updateData }) {
)}
<div className="form-footer">
<button type="button" className="btn btn-light mr-2" onClick={reset} data-cy="cancel-button">
Cancel
{t('globals.cancel', 'Cancel')}
</button>
<button
type="button"
@ -129,7 +133,7 @@ export function Google({ settings, updateData }) {
onClick={saveSettings}
data-cy="save-button"
>
Save
{t('globals.save', 'Save')}
</button>
</div>
</form>

View file

@ -7,6 +7,7 @@ import { Google } from './Google';
import { Loader } from './Loader';
import { Git } from './Git';
import { Form } from './Form';
import { useTranslation } from 'react-i18next';
export function ManageSSO({ switchDarkMode, darkMode }) {
const menuItems = [
@ -15,6 +16,7 @@ export function ManageSSO({ switchDarkMode, darkMode }) {
{ id: 'git', label: 'GitHub' },
{ id: 'form', label: 'Password Login' },
];
const { t } = useTranslation();
const changePage = useCallback(
(page) => {
setCurrentPage(page);
@ -90,7 +92,7 @@ export function ManageSSO({ switchDarkMode, darkMode }) {
<div className="col">
<div className="page-pretitle"></div>
<h2 className="page-title" data-cy="manage-sso-page-title">
Manage SSO
{t('header.organization.menus.manageSSO.manageSso', 'Manage SSO')}
</h2>
</div>
</div>

View file

@ -2,8 +2,8 @@ import React from 'react';
import queryString from 'query-string';
import { datasourceService } from '@/_services';
import { RedirectLoader } from '@/_components';
class Authorize extends React.Component {
import { withTranslation } from 'react-i18next';
class AuthorizeComponent extends React.Component {
constructor(props) {
super(props);
@ -113,4 +113,4 @@ class Authorize extends React.Component {
}
}
export { Authorize };
export const Authorize = withTranslation()(AuthorizeComponent);

View file

@ -1,8 +1,8 @@
import React from 'react';
import { tooljetService } from '@/_services';
import Modal from 'react-bootstrap/Modal';
class OnboardingModal extends React.Component {
import { withTranslation } from 'react-i18next';
class OnboardingModalComponent extends React.Component {
constructor(props) {
super(props);
@ -54,13 +54,15 @@ class OnboardingModal extends React.Component {
className={`${this.props.darkMode && 'dark'} onboarding-modal`}
>
<Modal.Header>
<Modal.Title className="text-center">Finish ToolJet installation</Modal.Title>
<Modal.Title className="text-center">
{this.props.t('onBoarding.finishToolJetInstallation', 'Finish ToolJet installation')}
</Modal.Title>
<br />
</Modal.Header>
<Modal.Body>
<div className="mb-3 mt-2">
<label className="form-label">Organization</label>
<label className="form-label">{this.props.t('onBoarding.organization', 'Organization')}</label>
<div className="input-group input-group-flat">
<input
type="text"
@ -74,7 +76,7 @@ class OnboardingModal extends React.Component {
</div>
<div className="mb-3">
<label className="form-label">Name</label>
<label className="form-label">{this.props.t('onBoarding.name', 'Name')}</label>
<div className="input-group input-group-flat">
<input
type="text"
@ -88,7 +90,7 @@ class OnboardingModal extends React.Component {
</div>
<div className="mb-3">
<label className="form-label">Email</label>
<label className="form-label">{this.props.t('onBoarding.email', 'Email')}</label>
<div className="input-group input-group-flat">
<input
type="text"
@ -100,19 +102,24 @@ class OnboardingModal extends React.Component {
<span className="input-group-text"></span>
</div>
</div>
<small>You will receive updates from the ToolJet team ( 1-2 emails every month, we do not spam )</small>
<small>
{this.props.t(
'onBoarding.receiveUpdatesFromToolJet',
'You will receive updates from the ToolJet team ( 1-2 emails every month, we do not spam )'
)}
</small>
</Modal.Body>
<Modal.Footer>
<div className="row w-100 gx-0">
<div className="col">
<button className={`btn btn-primary`} onClick={this.finishOnboarding}>
Finish setup
{this.props.t('onBoarding.finishSetup', 'Finish setup')}
</button>
</div>
<div className="col-auto">
<a onClick={this.skipOnboard} className="mt-3 text-muted" data-cy="skip-button">
Skip
{this.props.t('onBoarding.skip', 'Skip')}
</a>
</div>
</div>
@ -122,4 +129,4 @@ class OnboardingModal extends React.Component {
}
}
export { OnboardingModal };
export const OnboardingModal = withTranslation()(OnboardingModalComponent);

View file

@ -1,8 +1,10 @@
import React, { useState, useEffect } from 'react';
import { authenticationService } from '@/_services';
import { useTranslation } from 'react-i18next';
export const RedirectSso = function RedirectSso() {
const isSingleOrganization = window.public_config?.DISABLE_MULTI_WORKSPACE === 'true';
const { t } = useTranslation();
const [organization, setOrganization] = useState([]);
const [googlessoEnabled, setGoogleSsoEnabled] = useState(false);
@ -42,32 +44,39 @@ export const RedirectSso = function RedirectSso() {
</div>
<div className="sso-helper-container">
<h2 className="sso-helper-header">
<span className="gg-album"></span>Upgrading to v1.13.0 and above.
<span className="gg-album"></span>
{t('redirectSso.upgradingTov1.13.0', 'Upgrading to v1.13.0 and above.')}
</h2>
<p className="sso-helper-doc">
From v1.13.0 we have introduced
{t('redirectSso.fromV1.13.0', 'From v1.13.0 we have introduced')}
<a style={{ marginLeft: '4px' }} href="https://docs.tooljet.com/docs/tutorial/multiworkspace">
Multi-Workspace
{t('redirectSso.multiWorkspace', 'Multi-Workspace')}
</a>
. The Single Sign-On related configurations are moved from environment variables to database. Please refer
this
.{' '}
{t(
'redirectSso.singleSignOnConfig',
'The Single Sign-On related configurations are moved from environment variables to database. Please refer this'
)}
<a
style={{ marginLeft: '4px', marginRight: '4px' }}
href="https://docs.tooljet.com/docs/category/single-sign-on"
>
Link
{t('redirectSso.link', 'Link')}
</a>
to configure SSO.
{t('redirectSso.toConfigureSSO', 'to configure SSO.')}
<br />
<li>
If you have Google or GitHub SSO configurations before upgrade and disabled Multi-Workspace, then the
SSO configurations will be migrated while upgrade but you have to re-configure the redirect URL in the
SSO provider side. Redirect URLs for each SSO are given below.
{t(
'redirectSso.haveGoogleGithubSSo',
'If you have Google or GitHub SSO configurations before upgrade and disabled Multi-Workspace, then theSSO configurations will be migrated while upgrade but you have to re-configure the redirect URL in the SSO provider side. Redirect URLs for each SSO are given below.1'
)}
<br />
</li>
<li>
If you have enabled Multi-Workspace, then the SSO configurations will not be migrated while upgrade so
you have to re-configure the SSO under the respective workspace.
{t(
'redirectSso.isMultiWorkspaceEnabled',
'If you have enabled Multi-Workspace, then the SSO configurations will not be migrated while upgrade so you have to re-configure the SSO under the respective workspace.1'
)}
</li>
</p>
<div className="sso-content-wrapper">
@ -75,18 +84,21 @@ export const RedirectSso = function RedirectSso() {
<>
<div>
<p className="workspace-status">
You have Enabled
{t('redirectSso.youHaveEnabled', 'You have Enabled')}
<a style={{ marginLeft: '4px' }} href="https://docs.tooljet.com/docs/tutorial/multiworkspace">
Multi-Workspace
{t('redirectSso.multiWorkspace', 'Multi-Workspace')}
</a>
</p>
<p>
Please login with password and you can setup sso using workspace
{t(
'redirectSso.setupSsoWorkspace',
'Please login with password and you can setup sso using workspace'
)}
<a
href="https://docs.tooljet.com/docs/user-authentication/general-settings"
style={{ marginLeft: '4px' }}
>
Manage SSO menu.
{t('redirectSso.manageSsoMenu', 'Manage SSO menu.')}
</a>
</p>
</div>
@ -96,13 +108,10 @@ export const RedirectSso = function RedirectSso() {
<div>
<p className="workspace-status">
{' '}
<span
className="gg-border-all
"
></span>
You have Disabled
<span className="gg-border-all"></span>
{t('redirectSso.youHaveDisabled', 'You have Disabled')}
<a style={{ marginLeft: '4px' }} href="https://docs.tooljet.com/docs/tutorial/multiworkspace">
Multi-Workspace.
{t('redirectSso.multiWorkspace', 'Multi-Workspace')}
</a>
</p>
</div>
@ -113,15 +122,20 @@ export const RedirectSso = function RedirectSso() {
<>
<div>
{googlessoEnabled || gitSsoEnabled ? (
<p>Please configure redirect url in SSO provider side.</p>
<p>
{t('redirectSso.configureRedirectUrl', 'Please configure redirect url in SSO provider side.')}
</p>
) : (
<p>
Please login with password and you can setup sso using workspace
{t(
'redirectSso.setupSsoWorkspace',
'Please login with password and you can setup sso using workspace'
)}
<a
style={{ marginLeft: '4px' }}
href="https://docs.tooljet.com/docs/user-authentication/general-settings"
>
Manage SSO menu.
{t('redirectSso.manageSsoMenu', 'Manage SSO menu.')}
</a>
</p>
)}
@ -129,10 +143,11 @@ export const RedirectSso = function RedirectSso() {
<>
<p className="sso-type">
<span className="">-</span>
Google : <a href="https://docs.tooljet.com/docs/sso/google"> Link</a>
{t('redirectSso.google', 'Google')} :{' '}
<a href="https://docs.tooljet.com/docs/sso/google"> {t('redirectSso.link', 'Link')}</a>
</p>
<div className="flexer">
<span> Redirect URL: </span>
<span> {t('redirectSso.redirectUrl', 'Redirect URL:')} </span>
<p id="google-url">{`${window.public_config?.TOOLJET_HOST}/sso/google/${organization?.google?.config_id}`}</p>
<img
@ -151,11 +166,12 @@ export const RedirectSso = function RedirectSso() {
<>
<p className="sso-type ">
<span className="">-</span>
GitHub : <a href="https://docs.tooljet.com/docs/sso/github"> Link</a>
{t('redirectSso.gitHub', 'GitHub')} :{' '}
<a href="https://docs.tooljet.com/docs/sso/github"> {t('redirectSso.link', 'Link')}</a>
</p>
<div className="flexer">
<span> Redirect URL :</span>
<span> {t('redirectSso.redirectUrl', 'Redirect URL:')}</span>
<p id="git-url">{`${window.public_config?.TOOLJET_HOST}/sso/git/${organization?.git?.config_id}`}</p>
<img

View file

@ -1,8 +1,8 @@
import React from 'react';
import { toast } from 'react-hot-toast';
import { authenticationService } from '@/_services';
class ResetPassword extends React.Component {
import { withTranslation } from 'react-i18next';
class ResetPasswordComponent extends React.Component {
constructor(props) {
super(props);
@ -59,30 +59,34 @@ class ResetPassword extends React.Component {
</div>
<form className="card card-md" action="." method="get" autoComplete="off">
<div className="card-body">
<h2 className="card-title text-center mb-4">Reset Password</h2>
<h2 className="card-title text-center mb-4">
{this.props.t('loginSignupPage.resetPassword', 'Reset Password')}
</h2>
<div className="mb-2">
<label className="form-label">New Password</label>
<label className="form-label">{this.props.t('loginSignupPage.newPassword', 'New Password')}</label>
<div className="input-group input-group-flat">
<input
onChange={this.handleChange}
name="password"
type="password"
className="form-control"
placeholder="Password"
placeholder={this.props.t('loginSignupPage.password', 'Password')}
autoComplete="off"
/>
<span className="input-group-text"></span>
</div>
</div>
<div className="mb-2">
<label className="form-label">Password Confirmation</label>
<label className="form-label">
{this.props.t('loginSignupPage.passwordConfirmation', 'Password Confirmation')}
</label>
<div className="input-group input-group-flat">
<input
onChange={this.handleChange}
name="password_confirmation"
type="password"
className="form-control"
placeholder="Password Confirmation"
placeholder={this.props.t('loginSignupPage.passwordConfirmation', 'Password Confirmation')}
autoComplete="off"
/>
<span className="input-group-text"></span>
@ -93,7 +97,7 @@ class ResetPassword extends React.Component {
className={`btn btn-primary w-100 ${isLoading ? 'btn-loading' : ''}`}
onClick={this.handleClick}
>
Submit
{this.props.t('globals.submit', 'Submit')}
</button>
</div>
</div>
@ -104,4 +108,4 @@ class ResetPassword extends React.Component {
}
}
export { ResetPassword };
export const ResetPassword = withTranslation()(ResetPasswordComponent);

View file

@ -2,6 +2,7 @@ import React from 'react';
import { authenticationService, userService } from '@/_services';
import { Header } from '@/_components';
import { toast } from 'react-hot-toast';
import { useTranslation } from 'react-i18next';
function SettingsPage(props) {
const [firstName, setFirstName] = React.useState(authenticationService.currentUserValue.first_name);
@ -15,6 +16,7 @@ function SettingsPage(props) {
const [passwordChangeInProgress, setPasswordChangeInProgress] = React.useState(false);
const [selectedFile, setSelectedFile] = React.useState(null);
const focusRef = React.useRef(null);
const { t } = useTranslation();
const updateDetails = async () => {
const firstNameMatch = firstName.match(/^ *$/);
@ -118,7 +120,7 @@ function SettingsPage(props) {
<div className="col">
<div className="page-pretitle"></div>
<h2 className="page-title" data-cy="page-title">
Profile Settings
{t('header.profileSettingPage.profileSettings', 'Profile Settings')}
</h2>
</div>
</div>
@ -130,7 +132,7 @@ function SettingsPage(props) {
<div className="card">
<div className="card-header">
<h3 className="card-title" data-cy="card-title-profile">
Profile
{t('header.profileSettingPage.profile', 'Profile')}
</h3>
</div>
<div className="card-body">
@ -138,13 +140,13 @@ function SettingsPage(props) {
<div className="col">
<div className="mb-3">
<label className="form-label" data-cy="first-name-label">
First name{' '}
{t('header.profileSettingPage.firstName', 'First name')}
</label>
<input
type="text"
className="form-control"
name="first-name"
placeholder="Enter first name"
placeholder={t('header.profileSettingPage.enterFirstName', 'Enter first name')}
value={firstName}
onChange={(event) => setFirstName(event.target.value)}
data-cy="first-name-input"
@ -154,13 +156,13 @@ function SettingsPage(props) {
<div className="col">
<div className="mb-3">
<label className="form-label" data-cy="last-name-label">
Last name
{t('header.profileSettingPage.lastName', 'Last name')}
</label>
<input
type="text"
className="form-control"
name="last-name"
placeholder="Enter last name"
placeholder={t('header.profileSettingPage.enterLastName', 'Enter last name')}
value={lastName}
onChange={(event) => setLastName(event.target.value)}
data-cy="last-name-input"
@ -172,7 +174,7 @@ function SettingsPage(props) {
<div className="col">
<div className="mb-3">
<label className="form-label" data-cy="email-label">
Email{' '}
{t('header.profileSettingPage.email', 'Email')}
</label>
<input
type="text"
@ -187,7 +189,7 @@ function SettingsPage(props) {
</div>
<div className="col">
<div className="mb-3">
<div className="form-label">Avatar</div>
<div className="form-label">{t('header.profileSettingPage.avatar', 'Avatar')}</div>
<input
onChange={(e) => {
const file = e.target.files[0];
@ -210,7 +212,7 @@ function SettingsPage(props) {
onClick={updateDetails}
data-cy="update-button"
>
Update
{t('header.profileSettingPage.update', 'Update')}
</button>
{/* An !important style on theme.scss is making the last child of every .card-body color to #c3c3c3!. */}
{/* The div below is a placeholder to prevent it from affecting the button above. */}
@ -221,7 +223,7 @@ function SettingsPage(props) {
<div className="card">
<div className="card-header">
<h3 className="card-title" data-cy="card-title-change-password">
Change password
{t('header.profileSettingPage.changePassword', 'Change password')}
</h3>
</div>
<div className="card-body">
@ -229,13 +231,13 @@ function SettingsPage(props) {
<div className="col">
<div className="mb-3">
<label className="form-label" data-cy="current-password-label">
Current password
{t('header.profileSettingPage.currentPassword', 'Current password')}
</label>
<input
type="password"
className="form-control"
name="last-name"
placeholder="Enter current password"
placeholder={t('header.profileSettingPage.enterCurrentPassword', 'Enter current password')}
value={currentpassword}
onChange={(event) => setCurrentPassword(event.target.value)}
data-cy="current-password-input"
@ -245,13 +247,13 @@ function SettingsPage(props) {
<div className="col">
<div className="mb-3">
<label className="form-label" data-cy="new-password-label">
New password
{t('header.profileSettingPage.newPassword', 'New password')}
</label>
<input
type="password"
className="form-control"
name="last-name"
placeholder="Enter new password"
placeholder={t('header.profileSettingPage.enterNewPassword', 'Enter new password')}
value={newPassword}
onChange={(event) => setNewPassword(event.target.value)}
onKeyPress={newPasswordKeyPressHandler}
@ -263,13 +265,13 @@ function SettingsPage(props) {
<div className="w-50 confirm-input">
<div className="mb-3">
<label className="form-label" data-cy="new-password-label">
Confirm new password
{t('header.profileSettingPage.confirmNewPassword', 'Confirm new password')}
</label>
<input
type="password"
className="form-control"
name="last-name"
placeholder="Confirm new password"
placeholder={t('header.profileSettingPage.confirmNewPassword', 'Confirm new password')}
value={confirmPassword}
ref={focusRef}
onChange={(event) => setConfirmPassword(event.target.value)}
@ -283,7 +285,7 @@ function SettingsPage(props) {
onClick={changePassword}
data-cy="change-password-button"
>
Change password
{t('header.profileSettingPage.changePassword', 'Change password')}
</button>
{/* An !important style on theme.scss is making the last child of every .card-body color to #c3c3c3!. */}
{/* The div below is a placeholder to prevent it from affecting the button above. */}

View file

@ -5,8 +5,9 @@ import { Link } from 'react-router-dom';
import { validateEmail } from '../_helpers/utils';
import GoogleSSOLoginButton from '@ee/components/LoginPage/GoogleSSOLoginButton';
import GitSSOLoginButton from '@ee/components/LoginPage/GitSSOLoginButton';
import { withTranslation } from 'react-i18next';
class SignupPage extends React.Component {
class SignupPageComponent extends React.Component {
constructor(props) {
super(props);
@ -14,6 +15,8 @@ class SignupPage extends React.Component {
isLoading: false,
};
console.log('window.public_config?.SSO_DISABLE_SIGNUPS--- ', window.public_config?.SSO_DISABLE_SIGNUPS != true);
this.ssoConfigs = {
enableSignUp:
window.public_config?.DISABLE_MULTI_WORKSPACE !== 'true' &&
@ -85,7 +88,9 @@ class SignupPage extends React.Component {
<form className="card card-md" action="." method="get" autoComplete="off">
{!signupSuccess && (
<div className="card-body">
<h2 className="card-title text-center mb-4">Create a ToolJet account</h2>
<h2 className="card-title text-center mb-4">
{this.props.t('loginSignupPage.createToolJetAccount', 'Create a ToolJet account')}
</h2>
{this.ssoConfigs.enableSignUp && (
<div className="d-flex flex-column align-items-center separator-bottom">
{this.ssoConfigs.configs?.google?.enabled && (
@ -108,29 +113,33 @@ class SignupPage extends React.Component {
</div>
)}
<div className="mb-3">
<label className="form-label">Email address</label>
<label className="form-label">{this.props.t('loginSignupPage.emailAddress', 'Email address')}</label>
<input
onChange={this.handleChange}
name="email"
type="email"
className="form-control"
placeholder="Enter your business email"
placeholder={this.props.t('loginSignupPage.enterBusinessEmail', 'Enter your business email')}
/>
</div>
<div className="form-footer">
<button className={`btn btn-primary w-100 ${isLoading ? 'btn-loading' : ''}`} onClick={this.signup}>
Sign up
{this.props.t('loginSignupPage.signUp', 'Sign up')}
</button>
</div>
</div>
)}
{signupSuccess && <div className="card-body">Please check your email for confirmation link</div>}
{signupSuccess && (
<div className="card-body">
{this.props.t('loginSignupPage.emailConfirmLink', 'Please check your email for confirmation link')}
</div>
)}
</form>
{!signupSuccess && (
<div className="text-center text-muted mt-3">
Already have an account? &nbsp;
{this.props.t('loginSignupPage.alreadyHaveAnAccount', 'Already have an account?')}
<Link to={'/login'} tabIndex="-1">
Sign in
{this.props.t('loginSignupPage.signIn', 'Sign in')}
</Link>
</div>
)}
@ -140,4 +149,4 @@ class SignupPage extends React.Component {
}
}
export { SignupPage };
export const SignupPage = withTranslation()(SignupPageComponent);

View file

@ -1,9 +1,11 @@
import React, { useState, useEffect } from 'react';
import Modal from 'react-bootstrap/Modal';
import Button from 'react-bootstrap/Button';
import { useTranslation } from 'react-i18next';
export function ConfirmDialog({ show, message, onConfirm, onCancel, confirmButtonLoading, darkMode }) {
const [showModal, setShow] = useState(show);
const { t } = useTranslation();
useEffect(() => {
setShow(show);
@ -32,7 +34,7 @@ export function ConfirmDialog({ show, message, onConfirm, onCancel, confirmButto
<Modal.Body data-cy="modal-message">{message}</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={handleClose} data-cy="cancel-button">
Cancel
{t('globals.cancel', 'Cancel')}
</Button>
<Button
variant="danger"
@ -41,7 +43,7 @@ export function ConfirmDialog({ show, message, onConfirm, onCancel, confirmButto
onClick={handleConfirm}
data-cy="yes-button"
>
Yes
{t('globals.yes', 'Yes')}
</Button>
</Modal.Footer>
</Modal>

View file

@ -2,6 +2,7 @@ import React from 'react';
import { useSpring, animated } from 'react-spring';
import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
import Tooltip from 'react-bootstrap/Tooltip';
import { useTranslation } from 'react-i18next';
export const DarkModeToggle = function DarkModeToggle({
darkMode = false,
@ -11,7 +12,7 @@ export const DarkModeToggle = function DarkModeToggle({
const toggleDarkMode = () => {
switchDarkMode(!darkMode);
};
const { t } = useTranslation();
const properties = {
sun: {
r: 9,
@ -49,7 +50,13 @@ export const DarkModeToggle = function DarkModeToggle({
<OverlayTrigger
placement={tooltipPlacement}
delay={{ show: 250, hide: 400 }}
overlay={<Tooltip id="button-tooltip">{darkMode ? 'Activate light mode' : 'Activate dark mode'}</Tooltip>}
overlay={
<Tooltip id="button-tooltip">
{darkMode
? t('header.darkModeToggle.activateLightMode', 'Activate light mode')
: t('header.darkModeToggle.activateDarkMode', 'Activate dark mode')}
</Tooltip>
}
>
<animated.svg
xmlns="http://www.w3.org/2000/svg"

View file

@ -1,12 +1,14 @@
import React, { useState } from 'react';
import { datasourceService } from '@/_services';
import { toast } from 'react-hot-toast';
import { useTranslation } from 'react-i18next';
import Radio from '@/_ui/Radio';
import Button from '@/_ui/Button';
const Googlesheets = ({ optionchanged, createDataSource, options, isSaving, selectedDataSource }) => {
const [authStatus, setAuthStatus] = useState(null);
const { t } = useTranslation();
function authGoogle() {
const provider = 'googlesheets';
@ -45,22 +47,33 @@ const Googlesheets = ({ optionchanged, createDataSource, options, isSaving, sele
<div className="row">
<div className="col-md-12">
<div className="mb-3">
<div className="form-label">Authorize</div>
<p>If you want your ToolJet apps to modify your Google sheets, make sure to select read and write access</p>
<div className="form-label">{t('globals.authorize', 'Authorize')}</div>
<p>
{t(
'googleSheets.enableReadAndWrite',
'If you want your ToolJet apps to modify your Google sheets, make sure to select read and write access'
)}
</p>
<div>
<Radio
checked={options.access_type?.value === 'read'}
disabled={authStatus === 'waiting_for_token'}
onClick={() => optionchanged('access_type', 'read')}
text="Read only"
helpText="Your ToolJet apps can only read data from Google sheets"
text={t('googleSheets.readOnly', 'Read only')}
helpText={t(
'googleSheets.readDataFromSheets',
'Your ToolJet apps can only read data from Google sheets'
)}
/>
<Radio
checked={options.access_type?.value === 'write'}
disabled={authStatus === 'waiting_for_token'}
onClick={() => optionchanged('access_type', 'write')}
text="Read and write"
helpText="Your ToolJet apps can read data from sheets, modify sheets, and more."
text={t('googleSheets.readWrite', 'Read and write')}
helpText={t(
'googleSheets.readModifySheets',
'Your ToolJet apps can read data from sheets, modify sheets, and more.'
)}
/>
</div>
</div>
@ -75,7 +88,7 @@ const Googlesheets = ({ optionchanged, createDataSource, options, isSaving, sele
disabled={isSaving}
onClick={() => saveDataSource()}
>
{isSaving ? 'Saving...' : 'Save data source'}
{isSaving ? t('globals.saving', 'Saving...') : t('globals.saveDatasource', 'Save data source')}
</Button>
</div>
)}
@ -86,7 +99,8 @@ const Googlesheets = ({ optionchanged, createDataSource, options, isSaving, sele
disabled={isSaving}
onClick={() => authGoogle()}
>
{selectedDataSource.id ? 'Reconnect' : 'Connect'} to Google Sheets
{selectedDataSource.id ? t('globals.reconnect', 'Reconnect') : t('globals.connect', 'Connect')}{' '}
{t('googleSheets.toGoogleSheets', 'to Google Sheets')}
</Button>
)}
</center>

View file

@ -7,6 +7,8 @@ import { DarkModeToggle } from './DarkModeToggle';
import LogoIcon from '../Editor/Icons/logo.svg';
import { Organization } from './Organization';
import { NotificationCenter } from './NotificationCenter';
import { LanguageSelection } from './LanguageSelection';
import { useTranslation } from 'react-i18next';
export const Header = function Header({ switchDarkMode, darkMode }) {
// eslint-disable-next-line no-unused-vars
@ -14,6 +16,7 @@ export const Header = function Header({ switchDarkMode, darkMode }) {
const [avatar, setAvatar] = useState();
const { first_name, last_name, avatar_id, admin } = authenticationService.currentUserValue;
const currentVersion = localStorage.getItem('currentVersion');
const { t } = useTranslation();
useEffect(() => {
setPathName(document.location.pathname);
@ -53,6 +56,9 @@ export const Header = function Header({ switchDarkMode, darkMode }) {
<div className="p-1 m-1 d-flex align-items-center" data-cy="mode-toggle">
<DarkModeToggle switchDarkMode={switchDarkMode} darkMode={darkMode} />
</div>
{/* <div className="p-1 m-1 d-flex align-items-center">
<LanguageSelection darkMode={darkMode} />
</div> */}
{config.COMMENT_FEATURE_ENABLE && (
<div className="p-1 d-flex align-items-center" data-cy="notification-center">
<NotificationCenter />
@ -87,10 +93,10 @@ export const Header = function Header({ switchDarkMode, darkMode }) {
</a>
<div className="dropdown-menu dropdown-menu-end dropdown-menu-arrow end-0" data-cy="dropdown-menu">
<Link data-testid="settingsBtn" to="/settings" className="dropdown-item" data-cy="profile-link">
Profile
{t('header.profile', 'Profile')}
</Link>
<Link data-testid="logoutBtn" to="#" onClick={logout} className="dropdown-item" data-cy="logout-link">
Logout
{t('header.logout', 'Logout')}
</Link>
{currentVersion && (
<Link to="#" className={`dropdown-item pe-none ${darkMode ? 'color-muted-darkmode' : 'color-muted'}`}>

View file

@ -0,0 +1,179 @@
import React, { useState, useEffect, useRef } from 'react';
import Modal from 'react-bootstrap/Modal';
import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
import Tooltip from 'react-bootstrap/Tooltip';
import { SearchBox } from './SearchBox';
import { ListGroup } from 'react-bootstrap';
// eslint-disable-next-line import/no-unresolved
import i18n from 'i18next';
import { isEqual } from 'lodash';
import { useTranslation } from 'react-i18next';
export const LanguageSelection = ({ darkMode = false, tooltipPlacement = 'bottom' }) => {
const [showModal, setShow] = useState(false);
const [selectedLang, setLanguage] = useState({});
const [filteredLang, setFilteredLang] = useState([]);
const languageRef = useRef(null);
const { t } = useTranslation();
useEffect(() => {
const lang = i18n.language || 'en';
(async () => {
languageRef.current = await fetch('/assets/translations/languages.json')
.then((response) => response.json())
.then((data) => data.languageList);
const filteredLanguage = languageRef.current.find((ln) => ln.code === lang);
if (filteredLanguage === undefined) {
setLanguage(languageRef.current.find((ln) => ln.code === 'en'));
} else {
setLanguage(filteredLanguage);
}
setFilteredLang(languageRef.current);
})();
}, []);
const handleClose = () => {
setShow(false);
};
const handleOpen = () => {
setShow(true);
};
const onLanguageSelection = (lang) => {
setLanguage(lang);
i18n.changeLanguage(lang.code);
handleClose();
};
const searchLanguage = (searchText) => {
const lowerCaseSearchText = searchText.toLowerCase();
const filteredLanguages = languageRef.current.filter(
(ln) =>
ln.lang.toLowerCase().startsWith(lowerCaseSearchText) ||
ln.nativeLang.toLowerCase().startsWith(lowerCaseSearchText) ||
ln.code.toLowerCase().startsWith(lowerCaseSearchText)
);
if (!isEqual(filteredLanguages, filteredLang)) {
setFilteredLang(filteredLanguages);
}
};
const renderLanguageList = () => {
return (
<>
{filteredLang.length === 0 ? (
<ListGroup.Item variant="light" className="no-results-item">
No results
</ListGroup.Item>
) : (
<>
<ListGroup.Item key={selectedLang.code} action active onClick={() => onLanguageSelection(selectedLang)}>
<div className="row align-items-center">
<div className="col-auto">
{selectedLang.lang}
<p>{selectedLang.nativeLang}</p>
</div>
<div className="col-auto ms-auto">
<svg
xmlns="http://www.w3.org/2000/svg"
className="icon icon-tabler icon-tabler-check"
width="44"
height="44"
viewBox="0 0 24 24"
strokeWidth="1.5"
stroke="#4d72fa"
fill="none"
strokeLinecap="round"
strokeLinejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path d="M5 12l5 5l10 -10" />
</svg>
</div>
</div>
</ListGroup.Item>
{filteredLang.map((ln) => {
if (ln.code === selectedLang.code) return;
return (
<ListGroup.Item key={ln.code} action onClick={() => onLanguageSelection(ln)}>
<div className="row align-items-center">
<div className="col-auto">
{ln.lang}
<p>{ln.nativeLang}</p>
</div>
</div>
</ListGroup.Item>
);
})}
</>
)}
</>
);
};
const renderModal = () => {
return (
<Modal
show={showModal}
onHide={handleClose}
size="sm"
centered={true}
contentClassName={`lang-selection-modal ${darkMode && 'dark'}`}
>
<Modal.Header>
<Modal.Title>{t('header.languageSelection.changeLanguage', 'Change language')}</Modal.Title>
<span className={`close-btn mx-4 mt-3 ${darkMode ? 'dark' : ''}`} onClick={handleClose}>
<img src="/assets/images/icons/close.svg" width="12" height="12" />
</span>
</Modal.Header>
<Modal.Body>
<div className="lang-list">
<div className="search-box">
<SearchBox
onSubmit={searchLanguage}
width="100%"
placeholder={t('header.languageSelection.searchLanguage', 'Search language')}
/>
</div>
<ListGroup>{renderLanguageList()}</ListGroup>
</div>
</Modal.Body>
</Modal>
);
};
return (
<>
<OverlayTrigger
placement={tooltipPlacement}
delay={{ show: 250, hide: 400 }}
overlay={
<Tooltip id="button-tooltip">{t('header.languageSelection.changeLanguage', 'Change language')}</Tooltip>
}
>
<svg
xmlns="http://www.w3.org/2000/svg"
className="icon icon-tabler icon-tabler-world"
width="24"
height="24"
viewBox="0 0 24 24"
strokeWidth="1.5"
stroke={darkMode ? '#fff' : '#808080'}
fill="none"
strokeLinecap="round"
strokeLinejoin="round"
onClick={handleOpen}
style={{ cursor: 'pointer' }}
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<circle cx="12" cy="12" r="9" />
<line x1="3.6" y1="9" x2="20.4" y2="9" />
<line x1="3.6" y1="15" x2="20.4" y2="15" />
<path d="M11.5 3a17 17 0 0 0 0 18" />
<path d="M12.5 3a17 17 0 0 1 0 18" />
</svg>
</OverlayTrigger>
{showModal && renderModal()}
</>
);
};

View file

@ -3,12 +3,13 @@ import { commentNotificationsService } from '@/_services';
import { Notification } from './Notification';
import { toast } from 'react-hot-toast';
import Spinner from '@/_ui/Spinner';
import { useTranslation } from 'react-i18next';
export const NotificationCenter = () => {
const [loading, setLoading] = React.useState(false);
const [isRead, setIsRead] = React.useState(false);
const [commentNotifications, setCommentNotifications] = React.useState([]);
const { t } = useTranslation();
async function fetchData() {
setLoading(true);
const { data, error } = await commentNotificationsService.findAll(isRead);
@ -70,7 +71,7 @@ export const NotificationCenter = () => {
>
<div className="card">
<div className="card-header">
<h1 className="card-title">Notifications</h1>
<h1 className="card-title">{t('header.notificationCenter.notifications', 'Notifications')}</h1>
{!loading && commentNotifications?.length > 0 && (
<a href="#" onClick={updateAllNotifications} className="text-muted text-decoration-none ms-auto">
Mark all as {isRead && 'un'}read
@ -85,9 +86,17 @@ export const NotificationCenter = () => {
{!loading && commentNotifications.length === 0 && (
<div className="empty">
<div className="empty-img pb-3">🔔</div>
<p className="empty-title mb-1">You&apos;re all caught up!</p>
<p className="empty-title mb-1">
{t('header.notificationCenter.youAreCaughtUp', `You're all caught up!`)}
</p>
<p className="empty-subtitle text-muted">
You don&apos;t have any {!isRead && 'un'}read notifications!
{`${t('header.notificationCenter.youDontHaveany', `You don't have any`)} ${
!isRead && t('header.notificationCenter.un', 'un')
}${t('header.notificationCenter.read', 'read')} ${t(
`header.notificationCenter.notifications`,
'notifications'
).toLowerCase()}!
`}
</p>
</div>
)}
@ -99,7 +108,10 @@ export const NotificationCenter = () => {
</div>
<div className="card-footer text-center margin-auto">
<a href="#" className="text-muted text-decoration-none" onClick={() => setIsRead(!isRead)}>
View {isRead && 'un'}read notifications
{`${t('header.notificationCenter.view', 'View')} ${isRead && t('header.notificationCenter.un', 'un')}${t(
'header.notificationCenter.read',
'read'
)} ${t(`header.notificationCenter.notifications`, 'notifications').toLowerCase()}`}
</a>
</div>
</div>

View file

@ -4,6 +4,7 @@ import { authenticationService, organizationService } from '@/_services';
import Modal from '../HomePage/Modal';
import { toast } from 'react-hot-toast';
import { SearchBox } from './SearchBox';
import { useTranslation } from 'react-i18next';
export const Organization = function Organization({ darkMode }) {
const isSingleOrganization = window.public_config?.DISABLE_MULTI_WORKSPACE === 'true';
@ -17,6 +18,7 @@ export const Organization = function Organization({ darkMode }) {
const [getOrgStatus, setGetOrgStatus] = useState('loading');
const [isListOrganizations, setIsListOrganizations] = useState(false);
const [newOrgName, setNewOrgName] = useState('');
const { t } = useTranslation();
const getAvatar = (organization) => {
if (!organization) return;
@ -210,11 +212,16 @@ export const Organization = function Organization({ darkMode }) {
</svg>
</div>
<div className="back-btn" onClick={() => setIsListOrganizations(false)}>
Back
{t('globals.back', 'Back')}
</div>
</div>
<div className="search-box">
<SearchBox onSubmit={searchOrganizations} debounceDelay={100} width="14rem" />
<SearchBox
onSubmit={searchOrganizations}
debounceDelay={100}
width="14rem"
placeholder={t('globals.search', 'Search')}
/>
</div>
</div>
<div className="org-list">
@ -227,7 +234,7 @@ export const Organization = function Organization({ darkMode }) {
href="#"
className={`btn btn-primary mb-2 ${getOrgStatus === 'loading' ? 'btn-loading' : ''}`}
>
Load Organizations
{t('header.organization.loadOrganizations', 'Load Organizations')}
</a>
</div>
)}
@ -251,7 +258,7 @@ export const Organization = function Organization({ darkMode }) {
{admin && (
<div className="org-edit">
<span onClick={showEditModal} data-cy="edit-workspace-name">
Edit
{t('globals.edit', 'Edit')}
</span>
</div>
)}
@ -282,25 +289,27 @@ export const Organization = function Organization({ darkMode }) {
</div>
{!isSingleOrganization && (
<div className="dropdown-item org-actions">
<div onClick={showCreateModal}>Add workspace</div>
<div onClick={showCreateModal}>{t('header.organization.menus.addWorkspace', 'Add workspace')}</div>
</div>
)}
<div className="dropdown-divider"></div>
{admin && (
<>
<Link data-testid="settingsBtn" to="/users" className="dropdown-item" data-cy="manage-users">
Manage Users
{t('header.organization.menus.menusList.manageUsers', 'Manage Users')}
</Link>
<Link data-tesid="settingsBtn" to="/groups" className="dropdown-item" data-cy="manage-groups">
Manage Groups
{t('header.organization.menus.menusList.manageGroups', 'Manage Groups')}
</Link>
<Link data-tesid="settingsBtn" to="/manage-sso" className="dropdown-item" data-cy="manage-sso">
Manage SSO
{t('header.organization.menus.menusList.manageSso', 'Manage SSO')}
</Link>
</>
)}
<Link data-tesid="settingsBtn" to="/manage-environment-vars" className="dropdown-item">
{admin ? 'Manage Environment Variables' : 'Environment Variables'}
{admin
? t('header.organization.menus.menusList.manageEnv', 'Manage Environment Variables')
: t('globals.environmentVar', 'Environment Variables')}
</Link>
</div>
);
@ -322,14 +331,18 @@ export const Organization = function Organization({ darkMode }) {
</div>
)}
</div>
<Modal show={showCreateOrg} closeModal={() => setShowCreateOrg(false)} title="Create workspace">
<Modal
show={showCreateOrg}
closeModal={() => setShowCreateOrg(false)}
title={t('header.organization.createWorkspace', 'Create workspace')}
>
<div className="row">
<div className="col modal-main">
<input
type="text"
onChange={(e) => setNewOrgName(e.target.value)}
className="form-control"
placeholder="workspace name"
placeholder={t('header.organization.workspaceName', 'workspace name')}
disabled={isCreating}
maxLength={25}
/>
@ -338,26 +351,30 @@ export const Organization = function Organization({ darkMode }) {
<div className="row">
<div className="col d-flex modal-footer-btn">
<button className="btn btn-light" onClick={() => setShowCreateOrg(false)}>
Cancel
{t('globals.cancel', 'Cancel')}
</button>
<button
disabled={isCreating}
className={`btn btn-primary ${isCreating ? 'btn-loading' : ''}`}
onClick={createOrganization}
>
Create workspace
{t('header.organization.createWorkspace', 'Create workspace')}
</button>
</div>
</div>
</Modal>
<Modal show={showEditOrg} closeModal={() => setShowEditOrg(false)} title="Edit workspace">
<Modal
show={showEditOrg}
closeModal={() => setShowEditOrg(false)}
title={t('header.organization.editWorkspace', 'Edit workspace')}
>
<div className="row">
<div className="col modal-main">
<input
type="text"
onChange={(e) => setNewOrgName(e.target.value)}
className="form-control"
placeholder="workspace name"
placeholder={t('header.organization.workspaceName', 'workspace name')}
disabled={isCreating}
value={newOrgName}
maxLength={25}
@ -367,10 +384,10 @@ export const Organization = function Organization({ darkMode }) {
<div className="row">
<div className="col d-flex modal-footer-btn">
<button className="btn btn-light" onClick={() => setShowEditOrg(false)}>
Cancel
{t('globals.cancel', 'Cancel')}
</button>
<button className={`btn btn-primary ${isCreating ? 'btn-loading' : ''}`} onClick={editOrganization}>
Save
{t('globals.save', 'Save')}
</button>
</div>
</div>

View file

@ -1,6 +1,8 @@
import React, { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
export const Pagination = function Pagination({ currentPage, count, pageChanged, itemsPerPage = 10, darkMode }) {
const { t } = useTranslation();
const totalPages = useMemo(() => {
return Math.floor((count - 1) / itemsPerPage) + 1;
}, [count, itemsPerPage]);
@ -54,7 +56,9 @@ export const Pagination = function Pagination({ currentPage, count, pageChanged,
return (
<div className={`card-footer d-flex align-items-center px-1 ${darkMode ? ' bg-transparent' : ''}`}>
<p className={`m-0 ${darkMode ? 'text-light' : 'text-muted'}`}>
Showing <span>{startingAppCount()}</span> to <span>{endingAppCount()}</span> of <span>{count}</span>
{t('homePage.pagination.showing', 'Showing')} <span>{startingAppCount()}</span>{' '}
{t('homePage.pagination.to', 'to')} <span>{endingAppCount()}</span> {t('homePage.pagination.of', 'of')}{' '}
<span>{count}</span>
</p>
<ul className="pagination m-0 ms-auto">
<li className={`page-item ${currentPage === 1 ? 'disabled' : ''}`}>

View file

@ -2,7 +2,13 @@ import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import useDebounce from '@/_hooks/useDebounce';
export function SearchBox({ width = '200px', onSubmit, debounceDelay = 300, darkMode = false }) {
export function SearchBox({
width = '200px',
onSubmit,
debounceDelay = 300,
darkMode = false,
placeholder = 'Search',
}) {
const [searchText, setSearchText] = useState('');
const debouncedSearchTerm = useDebounce(searchText, debounceDelay);
const [isFocused, setFocussed] = useState(false);
@ -48,7 +54,7 @@ export function SearchBox({ width = '200px', onSubmit, debounceDelay = 300, dark
value={searchText}
onChange={handleChange}
className={`form-control ${darkMode && 'dark-theme-placeholder'}`}
placeholder="Search"
placeholder={placeholder}
onFocus={() => setFocussed(true)}
onBlur={() => setFocussed(false)}
data-cy="home-page-search-bar"

View file

@ -1,10 +1,12 @@
import React, { useState } from 'react';
import { datasourceService } from '@/_services';
import { useTranslation } from 'react-i18next';
import Button from '@/_ui/Button';
const Slack = ({ optionchanged, createDataSource, options, isSaving, selectedDataSource }) => {
const [authStatus, setAuthStatus] = useState(null);
const { t } = useTranslation();
function authGoogle() {
const provider = 'slack';
@ -38,10 +40,12 @@ const Slack = ({ optionchanged, createDataSource, options, isSaving, selectedDat
<div className="row">
<div className="col-md-12">
<div className="mb-3">
<div className="form-label">Authorize</div>
<div className="form-label">{t('slack.authorize', 'Authorize')}</div>
<p>
ToolJet can connect to Slack and list users, send messages, etc. Please select appropriate permission
scopes.
{t(
'slack.connectToolJetToSlack',
'ToolJet can connect to Slack and list users, send messages, etc. Please select appropriate permission scopes.'
)}
</p>
<div>
<label className="form-check mt-3">
@ -53,9 +57,12 @@ const Slack = ({ optionchanged, createDataSource, options, isSaving, selectedDat
disabled={authStatus === 'waiting_for_token'}
/>
<span className="form-check-label">
chat:write <br />
{t('slack.chatWrite', 'chat:write')} <br />
<small className="text-muted">
Your ToolJet app will be able to list users and send messages to users & channels.
{t(
'slack.listUsersAndSendMessage',
'Your ToolJet app will be able to list users and send messages to users & channels.'
)}
</small>
</span>
</label>
@ -72,7 +79,7 @@ const Slack = ({ optionchanged, createDataSource, options, isSaving, selectedDat
disabled={isSaving}
onClick={() => saveDataSource()}
>
{isSaving ? 'Saving...' : 'Save data source'}
{isSaving ? t('globals.saving', 'Saving...') : t('globals.saveDatasource', 'Save data source')}
</Button>
</div>
)}
@ -83,7 +90,7 @@ const Slack = ({ optionchanged, createDataSource, options, isSaving, selectedDat
disabled={isSaving}
onClick={() => authGoogle()}
>
Connect to Slack
{t('slack.connectSlack', 'Connect to Slack')}
</Button>
)}
</center>

View file

@ -6118,4 +6118,220 @@ input.hide-input-arrows{
color: #C8C6C6;
}
}
}
}
// Language Selection Modal
.lang-selection-modal {
font-weight: 500;
.list-group{
padding: 1rem 1.5rem;
padding-top: 0;
overflow-y: scroll;
height: calc(100% - 68px);
}
.list-group-item {
border: 0;
p {
margin-bottom: 0px;
margin-top: 2px;
}
}
.list-group-item.active {
background-color: #edf1ff;
color: #4d72fa;
font-weight: 600;
margin-top: 0px;
}
.modal-body{
height: 50vh;
padding: 0;
}
.lang-list {
height: 100%;
.search-box{
position: relative;
margin: 1rem 1.5rem;
}
input {
border-radius: 5px !important;
}
.input-icon {
display: flex;
}
.input-icon {
.search-icon {
display: block;
position: absolute;
left: 0;
margin-right: 0.5rem;
}
.clear-icon {
cursor: pointer;
display: block;
position: absolute;
right: 0;
margin-right: 0.5rem;
}
}
.list-group-item.active {
color: $primary;
}
}
}
.lang-selection-modal.dark {
.modal-header {
border-color: #232e3c !important;
}
.modal-body,
.modal-footer,
.modal-header,
.modal-content {
color: white;
background-color: #2b394a;
}
.list-group-item {
color: white;
border: 0;
}
.list-group-item:hover {
background-color: #232e3c;
}
.list-group-item.active {
background-color: #4d72fa;
color: white;
font-weight: 600;
}
.no-results-item {
background-color: #2b394a;
color: white;
}
input {
background-color: #2b394a;
border-color: #232e3c;
color: white;
}
}
// Language Selection Modal
.lang-selection-modal {
font-weight: 500;
.list-group{
padding: 1rem 1.5rem;
padding-top: 0;
overflow-y: scroll;
height: calc(100% - 68px);
}
.list-group-item {
border: 0;
p {
margin-bottom: 0px;
margin-top: 2px;
}
}
.list-group-item.active {
background-color: #edf1ff;
color: #4d72fa;
font-weight: 600;
margin-top: 0px;
}
.modal-body{
height: 50vh;
padding: 0;
}
.lang-list {
height: 100%;
.search-box{
position: relative;
margin: 1rem 1.5rem;
}
input {
border-radius: 5px !important;
}
.input-icon {
display: flex;
}
.input-icon {
.search-icon {
display: block;
position: absolute;
left: 0;
margin-right: 0.5rem;
}
.clear-icon {
cursor: pointer;
display: block;
position: absolute;
right: 0;
margin-right: 0.5rem;
}
}
.list-group-item.active {
color: $primary;
}
}
}
.lang-selection-modal.dark {
.modal-header {
border-color: #232e3c !important;
}
.modal-body,
.modal-footer,
.modal-header,
.modal-content {
color: white;
background-color: #2b394a;
}
.list-group-item {
color: white;
border: 0;
}
.list-group-item:hover {
background-color: #232e3c;
}
.list-group-item.active {
background-color: #4d72fa;
color: white;
font-weight: 600;
}
.no-results-item {
background-color: #2b394a;
color: white;
}
input {
background-color: #2b394a;
border-color: #232e3c;
color: white;
}
}

View file

@ -1,8 +1,10 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
export const SearchBox = ({ onChange, ...restProps }) => {
const { callback, placeholder } = restProps;
const [searchText, setSearchText] = React.useState('');
const { t } = useTranslation();
const handleChange = (e) => {
setSearchText(e.target.value);
@ -94,7 +96,7 @@ export const SearchBox = ({ onChange, ...restProps }) => {
value={searchText}
onChange={handleChange}
className="form-control animate-width-change"
placeholder={placeholder ?? 'Search'}
placeholder={placeholder ?? t(`globals.search`, 'Search')}
/>
</div>
</div>

20
frontend/src/i18n.js Normal file
View file

@ -0,0 +1,20 @@
// eslint-disable-next-line import/no-unresolved
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import Backend from 'i18next-http-backend';
i18n
.use(Backend)
.use(LanguageDetector)
.use(initReactI18next)
.init({
load: 'languageOnly',
fallbackLng: 'en',
backend: {
loadPath: `/assets/translations/{{lng}}.json`,
},
});
export default i18n;

View file

@ -5,6 +5,7 @@ import { Integrations } from '@sentry/tracing';
import { createBrowserHistory } from 'history';
import { appService } from '@/_services';
import { App } from './App';
import './i18n';
const AppWithProfiler = Sentry.withProfiler(App);

6189
package-lock.json generated

File diff suppressed because it is too large Load diff