fix: Replace angle bracket placeholders with curly braces in docs to fix crowdin's tags mismatch errors (#15751)

This commit is contained in:
Abdul Rahman
2025-11-11 02:20:48 +05:30
committed by GitHub
parent 06b8ea7c36
commit 194a579a03
11 changed files with 216 additions and 39 deletions
+2
View File
@@ -210,6 +210,8 @@ export default [
'unused-imports/no-unused-vars': 'off',
// Enforce JSX tags on separate lines to prevent Crowdin translation issues
'@nx/workspace-mdx-component-newlines': 'error',
// Disallow angle bracket placeholders to prevent Crowdin translation errors
'@nx/workspace-no-angle-bracket-placeholders': 'error',
},
},
];
@@ -20,7 +20,7 @@ Twentys API uses API keys for authentication. Every request to protected endp
* **Auth Header:** Once you have an API key, include it in the `Authorization` header of your HTTP requests. Use the Bearer token scheme. For example:
```
Authorization: Bearer YOUR_API_KEY
```
```
Replace `YOUR_API_KEY` with the key you obtained. This header must be present on **all API requests**. If the token is missing or invalid, the API will respond with an authentication error (HTTP 401 Unauthorized).
@@ -28,13 +28,13 @@ Twentys API uses API keys for authentication. Every request to protected endp
All resources can be accessed and via REST or GraphQL.
* **Cloud:** `https://api.twenty.com/` or your custom domain / sub-domain
* **Self-Hosted Instances:** If you are running Twenty on your own server, use your own domain in place of `api.twenty.com` (for example, `https://<your-domain>/rest/`).
* **Self-Hosted Instances:** If you are running Twenty on your own server, use your own domain in place of `api.twenty.com` (for example, `https://{your-domain}/rest/`).
Endpoints are grouped into two categories: **Core API** and **Metadata API**. The **Core API** deals with primary CRM data (e.g. people, companies, notes, tasks), while the **Metadata API** covers configuration data (like custom fields or object definitions). Most integrations will primarily use the Core API.
### Core API
Accessed on `/rest/` or `/graphql/`.
The **Core API** serves as a unified interface for managing core CRM entities (people, companies, notes, tasks) and their relationships, offering **both REST and GraphQL** interaction models.
The **Core API** serves as a unified interface for managing core CRM entities (people, companies, notes, tasks) and their relationships, offering **both REST and GraphQL** interaction models.
### Metadata API
Accessed on `/rest/metadata/` or `/metadata/`.
@@ -46,4 +46,4 @@ The Metadata API endpoints allow you to retrieve information about your schema a
* `GET /rest/metadata/objects/{objectName}` Get metadata for a specific object (e.g., `people`, `companies`).
* `GET /rest/metadata/picklists` Retrieve picklist (dropdown) field options defined in the CRM.
Typically, the metadata endpoints are used to understand the structure of data (for dynamic integrations or form-building) rather than to manage actual records. They are read-only in most cases. Authentication is required for these as well (use your API key).
Typically, the metadata endpoints are used to understand the structure of data (for dynamic integrations or form-building) rather than to manage actual records. They are read-only in most cases. Authentication is required for these as well (use your API key).
@@ -9,7 +9,7 @@ image: /images/user-guide/kanban-views/kanban.png
## Useful commands
These commands should be executed from packages/twenty-server folder.
From any other folder you can run `npx nx <command> twenty-server` (or `npx nx run twenty-server:<command>`).
From any other folder you can run `npx nx {command} twenty-server` (or `npx nx run twenty-server:{command}`).
### First time setup
@@ -72,11 +72,11 @@ Make sure to back up any data you want to keep before running this command.
## Tech Stack
Twenty primarily uses NestJS for the backend.
Twenty primarily uses NestJS for the backend.
Prisma was the first ORM we used. But in order to allow users to create custom fields and custom objects, a lower-level made more sense as we need to have fine-grained control. The project now uses TypeORM.
Prisma was the first ORM we used. But in order to allow users to create custom fields and custom objects, a lower-level made more sense as we need to have fine-grained control. The project now uses TypeORM.
Here's what the tech stack now looks like.
Here's what the tech stack now looks like.
**Core**
@@ -98,6 +98,6 @@ Here's what the tech stack now looks like.
- [ESLint](https://eslint.org/)
**Development**
- [AWS EKS](https://aws.amazon.com/eks/)
- [AWS EKS](https://aws.amazon.com/eks/)
@@ -49,10 +49,9 @@ git config --global user.email "youremail@domain.com"
```
3. Install nvm, node.js and yarn
<Warning>
Use `nvm` to install the correct `node` version. The `.nvmrc` ensures all contributors use the same version.
</Warning>
```bash
@@ -81,8 +81,8 @@ IS_CONFIG_VARIABLES_IN_DB_ENABLED=false
1. Go to [Credentials](https://console.cloud.google.com/apis/credentials)
2. Create OAuth 2.0 Client ID
3. Add these redirect URIs:
- `https://<your-domain>/auth/google/redirect` (for SSO)
- `https://<your-domain>/auth/google-apis/get-access-token` (for integrations)
- `https://{your-domain}/auth/google/redirect` (for SSO)
- `https://{your-domain}/auth/google-apis/get-access-token` (for integrations)
### Configure in Twenty
@@ -91,10 +91,10 @@ IS_CONFIG_VARIABLES_IN_DB_ENABLED=false
3. Set these variables:
- `MESSAGING_PROVIDER_GMAIL_ENABLED=true`
- `CALENDAR_PROVIDER_GOOGLE_ENABLED=true`
- `AUTH_GOOGLE_CLIENT_ID=<client-id>`
- `AUTH_GOOGLE_CLIENT_SECRET=<client-secret>`
- `AUTH_GOOGLE_CALLBACK_URL=https://<your-domain>/auth/google/redirect`
- `AUTH_GOOGLE_APIS_CALLBACK_URL=https://<your-domain>/auth/google-apis/get-access-token`
- `AUTH_GOOGLE_CLIENT_ID={client-id}`
- `AUTH_GOOGLE_CLIENT_SECRET={client-secret}`
- `AUTH_GOOGLE_CALLBACK_URL=https://{your-domain}/auth/google/redirect`
- `AUTH_GOOGLE_APIS_CALLBACK_URL=https://{your-domain}/auth/google-apis/get-access-token`
<Warning>
**Environment-only mode:** If you set `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false`, add these variables to your `.env` file instead.
@@ -140,8 +140,8 @@ Note: "Mail.ReadWrite" and "Mail.Send" are only mandatory if you want to send em
### Authorized redirect URIs
You need to add the following redirect URIs to your project:
- `https://<your-domain>/auth/microsoft/redirect` if you want to use Microsoft SSO
- `https://<your-domain>/auth/microsoft-apis/get-access-token`
- `https://{your-domain}/auth/microsoft/redirect` if you want to use Microsoft SSO
- `https://{your-domain}/auth/microsoft-apis/get-access-token`
### Configure in Twenty
@@ -151,10 +151,10 @@ You need to add the following redirect URIs to your project:
- `MESSAGING_PROVIDER_MICROSOFT_ENABLED=true`
- `CALENDAR_PROVIDER_MICROSOFT_ENABLED=true`
- `AUTH_MICROSOFT_ENABLED=true`
- `AUTH_MICROSOFT_CLIENT_ID=<client-id>`
- `AUTH_MICROSOFT_CLIENT_SECRET=<client-secret>`
- `AUTH_MICROSOFT_CALLBACK_URL=https://<your-domain>/auth/microsoft/redirect`
- `AUTH_MICROSOFT_APIS_CALLBACK_URL=https://<your-domain>/auth/microsoft-apis/get-access-token`
- `AUTH_MICROSOFT_CLIENT_ID={client-id}`
- `AUTH_MICROSOFT_CLIENT_SECRET={client-secret}`
- `AUTH_MICROSOFT_CALLBACK_URL=https://{your-domain}/auth/microsoft/redirect`
- `AUTH_MICROSOFT_APIS_CALLBACK_URL=https://{your-domain}/auth/microsoft-apis/get-access-token`
<Warning>
**Environment-only mode:** If you set `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false`, add these variables to your `.env` file instead.
@@ -8,9 +8,9 @@ image: /images/user-guide/notes/notes_header.png
## General guidelines
**Always make sure to back up your database before starting the upgrade process** by running `docker exec -it <db_container_name_or_id> pg_dumpall -U <postgres_user> > databases_backup.sql`.
**Always make sure to back up your database before starting the upgrade process** by running `docker exec -it {db_container_name_or_id} pg_dumpall -U {postgres_user} > databases_backup.sql`.
To restore backup, run `cat databases_backup.sql | docker exec -i <db_container_name_or_id> psql -U <postgres_user>`.
To restore backup, run `cat databases_backup.sql | docker exec -i {db_container_name_or_id} psql -U {postgres_user}`.
If you used Docker Compose, follow these steps:
@@ -156,7 +156,7 @@ If you want to migrate your database to the new postgres:16 image, please follow
1. Dump your database from the old postgres-spilo container
```
docker exec -it twenty-db-1 sh
docker exec -it twenty-db-1 sh
pg_dump -U {YOUR_POSTGRES_USER} -d {YOUR_POSTGRES_DB} > databases_backup.sql
exit
docker cp twenty-db-1:/home/postgres/databases_backup.sql .
@@ -249,7 +249,7 @@ The `yarn command:prod upgrade-0.34` takes care of the data migration of all wor
- Removed: `FRONT_BASE_URL`
- Added: `FRONT_DOMAIN`, `FRONT_PROTOCOL`, `FRONT_PORT`
We have updated the way we handle the frontend URL.
We have updated the way we handle the frontend URL.
You can now set the frontend URL using the `FRONT_DOMAIN`, `FRONT_PROTOCOL` and `FRONT_PORT` variables.
If FRONT_DOMAIN is not set, the frontend URL will fall back to `SERVER_URL`.
@@ -321,7 +321,7 @@ The `yarn command:prod upgrade-0.31` takes care of the data migration of all wor
Upgrade your Twenty instance to use v0.30.0 image
**Breaking change**:
**Breaking change**:
To enhance performances, Twenty now requires redis cache to be configured. We have updated our [docker-compose.yml](https://raw.githubusercontent.com/twentyhq/twenty/main/packages/twenty-docker/docker-compose.yml) to reflect this.
Make sure to update your configuration and to update your environment variables accordingly:
```
@@ -330,7 +330,7 @@ REDIS_PORT={your-redis-port}
CACHE_STORAGE_TYPE=redis
```
**Schema and data migration**:
**Schema and data migration**:
```
yarn database:migrate:prod
yarn command:prod upgrade-0.30
+2 -1
View File
@@ -7,7 +7,8 @@
"scripts": {
"dev": "mintlify dev",
"build": "mintlify build",
"fix-links": "bash scripts/fix-translated-links.sh"
"fix-links": "bash scripts/fix-translated-links.sh",
"lint": "eslint **/*.mdx --max-warnings=0"
},
"dependencies": {
"mintlify": "latest"
@@ -24,6 +24,7 @@ On the left side, from the top to the bottom, youll be able to:
<Warning>
Please note that our API documentation is accessible under the Settings section and not the User Guide.
</Warning>
- Have direct access to your **Favourites views**. Favourites are unique for each user.
- Switch between different objects
- **Create automations** using workflows
@@ -42,19 +42,20 @@ Use the CSV import via the Command Menu `Cmd + K` (Mac) or `Ctrl + K` (Windows)
### Data Formatting Issues
- **Email addresses** - remove duplicates (People object requirement)
- **Domain** - remove duplicates (Companies object requirement)
<Warning>
Please note that domain URLs created by the synchronization with your mailbox and calendar have the following format ```https://domain.com```
</Warning>
- **Date formats** - ensure consistent formatting (YYYY-MM-DD) or edit this format under Settings → Experience
- **Phone numbers** - use international format (+1234567890)
- **Phone numbers** - use international format (+1234567890)
### Relationship Mapping
To import relations between records using the csv import function, you can use the following fields
- **Use Twenty IDs** for complex relationships
- **Use email addresses** to link People records
- **Use domain names** to link Company records
- **Use any other field you set as unique**, which can be done in the Data Model section.
- **Use any other field you set as unique**, which can be done in the Data Model section.
Read our [import-export data guide](/user-guide/getting-started/import-export-data) for detailed instructions on creating relationships during CSV import.
## Professional Help
@@ -64,7 +65,7 @@ Read our [import-export data guide](/user-guide/getting-started/import-export-da
Discover our [implementation services](/user-guide/getting-started/implementation-services).
## Migrating from Self-Hosted to Cloud
## Migrating from Self-Hosted to Cloud
If you're moving from Twenty self-hosted to Twenty Cloud:
1. **Export your data** from your self-hosted instance
@@ -73,11 +74,11 @@ If you're moving from Twenty self-hosted to Twenty Cloud:
## Post-Migration Checklist
<input type="checkbox" /> All data imported successfully
<input type="checkbox" /> Custom fields working correctly
<input type="checkbox" /> User permissions configured
<input type="checkbox" /> Email/calendar sync connected
<input type="checkbox" /> Critical workflows recreated and tested
<input type="checkbox" /> Team trained on new system
<input type="checkbox" /> All data imported successfully
<input type="checkbox" /> Custom fields working correctly
<input type="checkbox" /> User permissions configured
<input type="checkbox" /> Email/calendar sync connected
<input type="checkbox" /> Critical workflows recreated and tested
<input type="checkbox" /> Team trained on new system
+5
View File
@@ -30,6 +30,10 @@ import {
rule as mdxComponentNewlines,
RULE_NAME as mdxComponentNewlinesName,
} from './rules/mdx-component-newlines';
import {
rule as noAngleBracketPlaceholders,
RULE_NAME as noAngleBracketPlaceholdersName,
} from './rules/no-angle-bracket-placeholders';
import {
rule as noHardcodedColors,
RULE_NAME as noHardcodedColorsName,
@@ -108,5 +112,6 @@ module.exports = {
[restApiMethodsShouldBeGuardedName]: restApiMethodsShouldBeGuarded,
[graphqlResolversShouldBeGuardedName]: graphqlResolversShouldBeGuarded,
[mdxComponentNewlinesName]: mdxComponentNewlines,
[noAngleBracketPlaceholdersName]: noAngleBracketPlaceholders,
},
};
@@ -0,0 +1,168 @@
import type { Rule } from 'eslint';
export const RULE_NAME = 'no-angle-bracket-placeholders';
const ALLOWED_TAGS = [
'img',
'div',
'span',
'p',
'a',
'b',
'strong',
'em',
'i',
'code',
'pre',
'br',
'hr',
'ul',
'ol',
'li',
'h1',
'h2',
'h3',
'h4',
'h5',
'h6',
'input',
'button',
'summary',
'details',
// MDX/Documentation components
'Frame',
'Warning',
'Tip',
'Info',
'Note',
'Card',
'Step',
'Tab',
'Tabs',
'ArticleTab',
'ArticleTabs',
'Accordion',
'AccordionGroup',
'Router',
'BrowserRouter',
'RecoilRoot',
];
export const rule: Rule.RuleModule = {
meta: {
type: 'problem',
docs: {
description:
'Disallow angle bracket placeholders in MDX files to prevent Crowdin translation errors. Use curly braces {placeholder} instead.',
recommended: true,
},
messages: {
noAngleBracketPlaceholder:
'Avoid angle bracket placeholders like "<{{name}}>" in documentation. Use curly braces "{{{name}}}" instead to prevent Crowdin translation errors.',
},
schema: [],
fixable: 'code',
},
create: (context) => {
const sourceCode = context.sourceCode || context.getSourceCode();
const filename = context.filename || context.getFilename();
if (!filename.endsWith('.mdx')) {
return {};
}
return {
Program: (node) => {
const text = sourceCode.getText();
// check for URL patterns with angle brackets (e.g., https://<your-domain>/)
const urlPlaceholderRegex = /(https?:\/\/)<([a-z][a-z0-9_-]+)>/g;
let urlMatch;
const foundPositions = new Set();
while ((urlMatch = urlPlaceholderRegex.exec(text)) !== null) {
const tagName = urlMatch[2];
const startPos = urlMatch.index + urlMatch[1].length; // Start after http(s)://
const endPos = startPos + tagName.length + 2; // +2 for < and >
foundPositions.add(startPos);
// Check if we're inside a code block (between backticks)
const beforeText = text.substring(0, startPos);
const backticksBefore = (beforeText.match(/`/g) || []).length;
const insideCodeBlock = backticksBefore % 2 !== 0;
context.report({
node: node as any,
loc: {
start: sourceCode.getLocFromIndex(startPos),
end: sourceCode.getLocFromIndex(endPos),
},
messageId: 'noAngleBracketPlaceholder',
data: {
name: tagName,
},
fix: insideCodeBlock
? (fixer) => {
return fixer.replaceTextRange(
[startPos, endPos],
`{${tagName}}`,
);
}
: undefined,
});
}
const placeholderRegex = /<([a-z][a-z0-9_-]+)>/g;
let match;
while ((match = placeholderRegex.exec(text)) !== null) {
const tagName = match[1];
const startPos = match.index;
if (foundPositions.has(startPos)) {
continue;
}
if (ALLOWED_TAGS.includes(tagName)) {
continue;
}
const beforeMatch = text.charAt(match.index - 1);
if (beforeMatch === '/') {
continue;
}
const endPos = startPos + match[0].length;
// Check if we're inside a code block (between backticks)
const beforeText = text.substring(0, match.index);
const backticksBefore = (beforeText.match(/`/g) || []).length;
const insideCodeBlock = backticksBefore % 2 !== 0;
context.report({
node: node as any,
loc: {
start: sourceCode.getLocFromIndex(startPos),
end: sourceCode.getLocFromIndex(endPos),
},
messageId: 'noAngleBracketPlaceholder',
data: {
name: tagName,
},
fix: insideCodeBlock
? (fixer) => {
return fixer.replaceTextRange(
[startPos, endPos],
`{${tagName}}`,
);
}
: undefined,
});
}
},
};
},
};