DEVELOPMENT.md
March 18, 2026 ยท View on GitHub
Install Prerequisites
- Java 21
- Docker
- Node.js 18 (or higher)
- Intellij Idea (optional)
Clone this repo
git clone --depth 1 git@github.com:tolgee/tolgee-platform.git
git config blame.ignoreRevsFile .git-blame-ignore-revs
Run the stack
- Run backend
- With the prepared Idea run configuration
Backend localhost - With command line:
./gradlew server-app:bootRun --args='--spring.profiles.active=dev'
- With the prepared Idea run configuration
- Run frontend
- With the prepared Idea run configuration
Frontend localhost - With command line:
npm --prefix library ci cd webapp && npm ci && npm run start
- With the prepared Idea run configuration
- Open your browser and go to http://localhost:3000.
Testing
The backend of Tolgee is tested with unit and integration tests.
Backend testing
To run backend tests, you can run Gradle test task
./gradlew test
Or you can select any integration test the code and run it via Idea CE or Idea Ultimate. It should just work out of the box.
E2E testing
Follow the steps in the E2E readme.
Backend configuration
To configure Tolgee, create an empty file backend/app/src/main/resources/application-dev.yaml.
In this file, you can override default configuration properties.
Here are some useful settings for localhost development:
spring:
jpa:
show-sql: true
tolgee:
front-end-url: http://localhost:3000
file-storage-url: http://localhost:8080
authentication: # to see "Server administration", "Organization settings" etc.
enabled: true
initial-username: admin
initial-password: 123123
You can check application-e2e.yaml for further inspiration.
To learn more about externalized configuration in Spring boot, read the docs.
Since we set the active profile to dev, Spring uses the application-dev.yaml configuration file.
Writing emails
Please refer to email/HACKING.md.
Updating the database changelog
Tolgee uses Liquibase to handle the database migration. The migrations are run on every app startup. To update the changelog, run:
./gradlew diffChangeLog
Troubleshooting updating the changelog
If you misspell the command and run diffChangelog, it will find the command, but it would fail, since liquibase changed the command name in the past. We have enhanced the diffChangeLog (with capital L) command, so you have to run that.
Sometimes, Gradle cannot find a docker command to start the database instance to generate the changelog against. This happens due to some issue with setting the paths for Gradle daemon. Running the command without daemon fixes the issue:
./gradlew diffChangeLog --no-daemon
Static analysis
For the frontend, there are npm tasks prettier and eslint, which you should run before every commit.
Otherwise, the "Frontend static check" workflow will fail.
You can also use prettier plugins for VS Code, Idea, or WebStorm.
To fix prettier issues and check everything is fine, run these commands:
cd webapp
npm run prettier
npm run tsc
npm run eslint
On the backend, there is Gradle task ktlintFormat, which helps you to format Kotlin code.
./gradlew ktlintFormat
On IntelliJ, you can install the Ktlint plugin to get the editor to format the code correctly.
Using current translations from the Tolgee app
This is an optional step for contributors with access to the Tolgee project at app.tolgee.io.
Create a file webapp/.env.development.local with following content.
Don't forget to supply it with your API key generated in the Tolgee app:
VITE_APP_TOLGEE_API_URL=https://app.tolgee.io
VITE_APP_TOLGEE_API_KEY=your-tolgee-api-key
Troubleshooting
Command not found when executing gradle tasks on MacOS
When running E2e Tests from Idea on Mac, you encounter fails due to command not found.
Apparentrly this happens because IDEA starts the gradle daemon with wrong path.
The only workaround I currently found is killing the gradle daemon and running IDEA from terminal
pkill -f '.*GradleDaemon.*'
open -a 'IntelliJ IDEA Ultimate'
This way, IDEA is started with correct environment from zsh or bash and so the Gradle Daemon is started correctly.
If you don't like this solution (I don't like it too), you can start looking for better solution. This thread is a good starting point: https://discuss.gradle.org/t/exec-execute-in-gradle-doesnt-use-path/25598/3
MCP Server
Tolgee includes an MCP (Model Context Protocol) server at /mcp/developer that lets AI coding assistants manage translations directly.
Connecting with Claude Code
claude mcp add --transport http tolgee http://localhost:8080/mcp/developer \
--header "X-API-Key: tgpat_<your_personal_access_token>"
Replace tgpat_<your_personal_access_token> with a Personal Access Token generated in your Tolgee account settings. When connecting to a deployed instance, replace http://localhost:8080 with that instance's URL.
Once connected, your AI assistant can use Tolgee tools such as search_keys, create_key, set_translation, list_languages, machine_translate, and others.
Logging business events
To monitor business activities in the Tolgee platform, we use PostHog for event tracking. There are three ways to log business events:
1. Automatic logging with ActivityHolder
When an activity is stored with a modifying endpoint on the backend, the event is automatically logged. Developers can optionally provide additional metadata using the businessEventData property in ActivityHolder.
Usually, you don't need to provide the data, but If you really need to, you can do it this way.
// Example: Adding business event data to an activity
@Component
class YourService(
private val activityHolder: ActivityHolder
) {
fun performAction() {
// Set the activity type
activityHolder.activity = ActivityType.YOUR_ACTIVITY_TYPE
// Add business event metadata
activityHolder.businessEventData["key1"] = "value1"
activityHolder.businessEventData["key2"] = "value2"
// Perform your action...
}
}
2. Manual logging from backend code
For cases where you need to log events that aren't tied to an activity, you can use the BusinessEventPublisher directly:
@Component
class YourService(
private val businessEventPublisher: BusinessEventPublisher
) {
fun logCustomEvent() {
businessEventPublisher.publish(
OnBusinessEventToCaptureEvent(
eventName = "YOUR_CUSTOM_EVENT",
data = mapOf("key1" to "value1", "key2" to "value2"),
// Optional fields:
projectId = 123,
organizationId = 456
)
)
}
// You can also publish events that should only be sent once in a specific time period
fun logRareEvent() {
businessEventPublisher.publishOnceInTime(
OnBusinessEventToCaptureEvent(
eventName = "rare_event",
data = mapOf("key1" to "value1")
),
onceIn = Duration.ofHours(24) // Only log once per day
)
}
}
3. Logging from frontend code
For logging events from the frontend, use the provided React hooks:
// Example 1: Using useReportEvent hook for event-triggered reporting
import { useReportEvent } from 'tg.hooks/useReportEvent';
function ExampleComponent() {
// Get the report function
const reportEvent = useReportEvent();
// Use it in an event handler
function handleButtonClick() {
reportEvent('button_clicked', { buttonName: 'submit', page: 'settings' });
// Rest of your click handler logic...
}
}
// Example 2: Using useReportOnce hook for reporting on component mount
import { useReportOnce } from 'tg.hooks/useReportEvent';
function AnotherComponent() {
// This will automatically report the event once when the component mounts
useReportOnce('page_viewed', { pageName: 'settings' });
// Rest of your component...
}
These frontend hooks send events through the backend API, ensuring they aren't blocked by ad blockers.