User Interface
You can specify your own UI by specifying the uiPath
property (and optionally fileName
) or by using the React JSON schema forms.
Only the Project Integration apps use the crowdin-simple-integration
component provided by the Crowdin UI Kit by default.
React JSON Schema Forms
For all modules that have UI (except projectIntegration
), you can use React JSON Schema forms as frontend.
To achieve this, you can specify the formSchema
property in the module definition:
configuration.projectMenu = { formSchema: { "title": "A registration form", "description": "A simple form example.", "type": "object", "required": [ "firstName", "lastName" ], "properties": { "firstName": { "type": "string", "title": "First name", "default": "Chuck" }, "lastName": { "type": "string", "title": "Last name" } } }, formUiSchema: { "ui:submitButtonOptions": { "submitText": "Confirm Details" }, "lastName": { "ui:help": "Hint: Choose cool lastname!" } }};
By default, form data will be stored in app metadata. To retrieve data you can use this code:
crowdinApp.getMetadata(`${organizationId}-${projectId}`);
To customize this behavior you can provide formGetDataUrl
and formPostDataUrl
properties that will contain URL to custom actions in your app. This actions should support GET request for fetching data (formGetDataUrl
) and POST request to save new data (formPostDataUrl
).
Response structure for GET request example:
res.status(200).send({ formData: { firstName: 'First Name' }}).end();
Also, you can provide the following properties:
formSchema
- new schema to replace active schema;formUiSchema
- new uiSchema to replace current;message
- custom message that will be displayed in toasts;redirect
- url for redirect (useful for files download).
Custom HTML Field
You can add a piece of custom HTML to your form. To do this, you will need:
-
Add field with type
string
to the form with additionaluiSchema
properties:const exampleConfig = {formSchema: {bio: {type: 'string'}},formUiSchema: {bio: {'ui:widget': 'htmlWidget'}}}; -
Provide form value using one of options -
ui:options
content orformData
(formData
value have higher priority):const exampleConfig = {formSchema: {bio: {type: 'string',},},formUiSchema: {bio: {'ui:widget': 'htmlWidget',},},formData: {bio: '<h2>HTML formatted description</h2>',},};
Custom Widgets
You can extend the functionality of your form by using custom widgets. Below are two examples of custom widgets that can be used to enhance your forms: crowdinFilesWidget
and monacoEditorWidget
.
Crowdin Files Widget
The crowdinFilesWidget
widget, part of the Crowdin UI Kit, allows users to select files. To use the crowdinFilesWidget
, add a property to your formSchema
with the type string
and provide the custom widget in the formUiSchema
as follows:
formSchema: { properties: { files: { title: 'Crowdin Files', type: 'string', }, },},formUiSchema: { files: { "ui:widget": 'crowdinFilesWidget', },}
Monaco Editor Widget
The monacoEditorWidget
allows you to embed a powerful code editor into your form. It is useful for scenarios where users need to provide or modify code snippets. The widget is based on the Monaco Editor, which powers Visual Studio Code, and supports syntax highlighting, auto-completion, and other advanced editor features.
formSchema: { properties: { code: { type: 'string', language: 'javascript', height: 500, minimap: true, lineNumbers: 'on', }, },},formUiSchema: { code: { "ui:widget": 'monacoEditorWidget', "ui:help": 'Provide your custom JavaScript code here', },}
You can specify various editor options such as language
, height
, lineNumbers
, and minimap
visibility or provide custom options
with different settings. You can find the full list of available options in the Monaco Editor documentation. For example:
options: { minimap: { enabled: true, side: 'left', }, lineNumbers: 'on',}
Manipulate Form from JavaScript
You may want to manipulate the form from JavaScript. For example, you may want to change the form schema or uiSchema
from a custom code widget.
To do this, you can use the global renderForm
function and the formSchema
, formUiSchema
and formData
variables.
const updatedSchema = { ...window.currentFormSchema, properties: { ...window.currentFormSchema.properties, lastName: { type: 'string', title: 'New title for last name field', default: 'Jon Doe' } }};
window.renderForm(window.currentFormData, updatedSchema, window.currentFormUiSchema);
Listening to Data Change Events
You can also listen for form data changes:
function setInputValue (target, value) { const setter = Object.getOwnPropertyDescriptor(target, "value").set const prototype = Object.getPrototypeOf(target) const prototypeValueSetter = Object.getOwnPropertyDescriptor(prototype, 'value').set if (setter && setter !== prototypeValueSetter) { prototypeValueSetter.call(target, value) } else { setter.call(target, value) } const event = new Event("input", {bubbles: true}) target.dispatchEvent(event);}
document.addEventListener('formDataUpdated', (event) => { if (event?.detail?.type === '1') { setInputValue(document.getElementById('root_selector'), 'value'); }});
Masking Credentials
The maskKey
is designed to mask a given string, revealing only the last three characters while replacing the preceding characters with a masking symbol (by default, an asterisk *).
const crowdinModule = require("@crowdin/app-project-module");
// ...
crowdinModule.maskKey('ABCD-ABCD-ABCD'); // Output: "***********BCD"
You should mask input values if you use form to store credentials, api keys, passwords etc. To achieve this add middlewares before functions used to display and store form data. Also use password widget in form schema.
Example:
const moduleConfig = { formSchema: { title: "Service Setup Form", type: "object", required: ["key"], properties: { key: { type: "string", title: "API Key" } } }, formUiSchema: { key: { "ui:widget": "password" } }, formPostDataUrl: '/form', // temp field, will be removed maskPasswords: true};
const crowdinModule = require("@crowdin/app-project-module");
// ...
app.post('/save-form-data', crowdinModule.postRequestCredentialsMasker(settingsForm), async (req, res) => { const { client, context } = await crowdinApp.establishCrowdinConnection(req.query.jwtToken);
const formData = req.body.data;});
// if your application uses a non-default action to retrieve data, you should also mask it.app.get('/get-form-data', crowdinModule.getRequestCredentialsMasker({ moduleConfig: crowdinModule }), async (req, res) => { const { client, context } = await crowdinApp.establishCrowdinConnection(req.query.jwtToken);
const data = (await storage.getStorage().getMetadata(id)) || {};
return res.send({ formData: data });});
With such setup API key will be masked for front-end. You can use it in your code as usual.
Skipping Toast After Form Submit
To skip default toast you can return message: ''
, or message: null
in response.
req.send({ message: null });
Theming
For applications using the UI Kit, the theme will be automatically selected based on the theme chosen in Crowdin. Additionally, for those using React JSON Schema, the light or dark theme will be automatically set.
Manual Theme Selection for Other Applications
For all other cases, you can use the following App JS method:
AP.getTheme(function (theme) { if (theme === 'dark') { // set dark styles } else { // set light styles }});
You can also retrieve the list of CSS variables and use them for custom styling:
AP.getCssVariables(style => { // apply custom styling});
Alternatively, you can use one of the predefined CSS classes:
crdn-text:primarycrdn-text:titlecrdn-text:bodycrdn-text:mutedcrdn-text:disabledcrdn-text:infocrdn-text:successcrdn-text:warningcrdn-text:danger
crdn-bg:lvl-0crdn-bg:lvl-1crdn-bg:lvl-2crdn-bg:lvl-2:05crdn-bg:lvl-3crdn-bg:primarycrdn-bg:successcrdn-bg:warningcrdn-bg:danger
crdn-border:primarycrdn-border:dangercrdn-border:infocrdn-border:successcrdn-border:warning