LingoTracker API Reference
Overview
The LingoTracker API provides REST endpoints for managing translation resources, collections, and configuration.
- Base URL:
http://localhost:3030/api - Default Port:
3030(configurable viaLINGO_TRACKER_PORTenvironment variable) - Content-Type:
application/json - Response Format: JSON
- CORS: Enabled with wildcard origin (
*) in development - Swagger Documentation: Available at
http://localhost:3030/apiwhen the server is running
Authentication
Currently, the API does not require authentication. It is meant to run on your machine when you need it.
Health Check
Get Health Status
Returns the health status of the API server.
Endpoint: GET /api/health
Response:
{
"status": "all is good"
}
Status Codes:
200 OK: Server is healthy
Example:
curl http://localhost:3030/api/health
Configuration API
Get Configuration
Retrieves the current LingoTracker configuration including global settings and all collections.
Endpoint: GET /api/config
Response:
interface LingoTrackerConfigDto {
exportFolder: string;
importFolder: string;
baseLocale: string;
locales: string[];
collections: Record<string, LingoTrackerCollectionDto>;
}
interface LingoTrackerCollectionDto {
translationsFolder: string;
exportFolder?: string;
importFolder?: string;
baseLocale?: string;
locales?: string[];
}
Example Response:
{
"exportFolder": "./exports",
"importFolder": "./imports",
"baseLocale": "en",
"locales": ["en", "es", "fr", "de"],
"collections": {
"web-app": {
"translationsFolder": "./apps/web/src/assets/i18n",
"baseLocale": "en",
"locales": ["en", "es", "fr"]
},
"mobile-app": {
"translationsFolder": "./apps/mobile/src/i18n",
"baseLocale": "en",
"locales": ["en", "es"]
}
}
}
Status Codes:
200 OK: Configuration retrieved successfully500 Internal Server Error: Configuration file could not be read
Example:
curl http://localhost:3030/api/config
Collections API
Create Collection
Creates a new translation collection with specified configuration.
Endpoint: POST /api/collections
Request Body:
interface CreateCollectionDto {
name: string;
collection: LingoTrackerCollectionDto;
}
interface LingoTrackerCollectionDto {
translationsFolder: string;
exportFolder?: string;
importFolder?: string;
baseLocale?: string;
locales?: string[];
}
Example Request:
{
"name": "admin-portal",
"collection": {
"translationsFolder": "./apps/admin/src/i18n",
"baseLocale": "en",
"locales": ["en", "es", "fr", "de"]
}
}
Response:
{
"message": "Collection 'admin-portal' added successfully"
}
Status Codes:
201 Created: Collection created successfully400 Bad Request: Invalid request body or collection already exists500 Internal Server Error: Unexpected error during collection creation
Example:
curl -X POST http://localhost:3030/api/collections \
-H "Content-Type: application/json" \
-d '{
"name": "admin-portal",
"collection": {
"translationsFolder": "./apps/admin/src/i18n",
"baseLocale": "en",
"locales": ["en", "es", "fr"]
}
}'
Error Example (Collection Already Exists):
curl -X POST http://localhost:3030/api/collections \
-H "Content-Type: application/json" \
-d '{
"name": "web-app",
"collection": {
"translationsFolder": "./apps/web/src/i18n"
}
}'
Response:
{
"statusCode": 400,
"message": "Collection 'web-app' already exists"
}
Update Collection
Updates or renames an existing collection.
Endpoint: PUT /api/collections/:collectionName
Path Parameters:
collectionName(string, required): The name of the collection to update (URL-encoded if necessary)
Request Body:
interface UpdateCollectionDto {
/** Optional new name to rename the collection */
name?: string;
/** Updated collection configuration */
collection: LingoTrackerCollectionDto;
}
Response:
{
"message": "Collection 'admin-portal' updated successfully"
}
Status Codes:
200 OK: Collection updated successfully400 Bad Request: Invalid request body or collection not found500 Internal Server Error: Unexpected error during update
Example:
curl -X PUT http://localhost:3030/api/collections/admin-portal \
-H "Content-Type: application/json" \
-d '{
"name": "admin-portal-v2",
"collection": {
"translationsFolder": "./apps/admin/src/i18n",
"baseLocale": "en",
"locales": ["en", "es", "fr", "de"]
}
}'
Delete Collection
Deletes a collection by name.
Endpoint: DELETE /api/collections/:collectionName
Path Parameters:
collectionName(string, required): The name of the collection to delete (URL-encoded if necessary)
Response:
{
"message": "Collection \"admin-portal\" deleted successfully"
}
Status Codes:
200 OK: Collection deleted successfully400 Bad Request: Collection not found or deletion error500 Internal Server Error: Unexpected error during deletion
Example:
curl -X DELETE http://localhost:3030/api/collections/admin-portal
Example with URL-Encoded Name:
# For a collection named "my collection" (with space)
curl -X DELETE http://localhost:3030/api/collections/my%20collection
Error Example (Collection Not Found):
curl -X DELETE http://localhost:3030/api/collections/non-existent
Response:
{
"statusCode": 400,
"message": "Collection 'non-existent' not found"
}
Locales API
Manage locales within a specific collection.
Add Locale
Adds a locale to a collection and backfills all existing resources with the new locale (value set to the base value, status: "new").
Endpoint: POST /api/collections/:collectionName/locales
Path Parameters:
collectionName(string, required): The name of the collection (URL-encoded if necessary)
Request Body:
{
"locale": "de"
}
Response:
{
"message": "Locale \"de\" added to collection \"main\" successfully",
"entriesBackfilled": 42,
"filesUpdated": 8
}
Status Codes:
200 OK: Locale added and resources backfilled400 Bad Request: Invalid locale format, locale already exists, cannot modify base locale, or collection not found
Example:
curl -X POST http://localhost:3030/api/collections/main/locales \
-H 'Content-Type: application/json' \
-d '{"locale": "de"}'
Remove Locale
Removes a locale from a collection and purges all translation data for that locale from resource files.
Endpoint: DELETE /api/collections/:collectionName/locales/:locale
Path Parameters:
collectionName(string, required): The name of the collection (URL-encoded if necessary)locale(string, required): The locale to remove (URL-encoded if necessary)
Response:
{
"message": "Locale \"de\" removed from collection \"main\" successfully",
"entriesPurged": 42,
"filesUpdated": 8
}
Status Codes:
200 OK: Locale removed and data purged400 Bad Request: Invalid locale format, locale not found in collection, cannot modify base locale, or collection not found
Example:
curl -X DELETE http://localhost:3030/api/collections/main/locales/de
Notes:
- Translation data for the removed locale is permanently deleted from files (recoverable via git)
- Removing the last non-base locale is allowed
- Cannot remove the base locale
Resources API
All resource endpoints are scoped to a specific collection.
Add Resource(s)
Creates one or more translation resource entries within a collection. Supports both single resource and bulk operations.
Endpoint: POST /api/collections/:collectionName/resources
Path Parameters:
collectionName(string, required): The name of the collection (URL-encoded if necessary)
Request Body (Single Resource):
interface CreateResourceDto {
/** Dot-delimited key, e.g., "apps.common.buttons.ok" */
key: string;
/** Base locale value (the source text) */
baseValue: string;
/** Optional context for translators */
comment?: string;
/** Optional tags (stored as array) */
tags?: string[];
/** Optional target folder to override part of the path */
targetFolder?: string;
/** Base locale (defaults to collection's baseLocale) */
baseLocale?: string;
/** Localized translations with locale, value, and status */
translations?: Array<{
locale: string;
value: string;
status: 'new' | 'translated' | 'stale' | 'verified';
}>;
}
Request Body (Multiple Resources):
type CreateResourcesRequest = CreateResourceDto[];
Example Request (Single Resource with Manual Translations):
{
"key": "apps.common.buttons.ok",
"baseValue": "OK",
"comment": "Standard confirmation button",
"tags": ["ui", "button"],
"translations": [
{
"locale": "es",
"value": "Aceptar",
"status": "translated"
},
{
"locale": "fr",
"value": "D'accord",
"status": "verified"
},
{
"locale": "de",
"value": "OK",
"status": "new"
}
]
}
Example Request (Single Resource with Auto-Generated Translations):
If translations is not provided, the system will automatically create entries for all configured locales with the base value and status "new".
{
"key": "apps.common.buttons.cancel",
"baseValue": "Cancel",
"comment": "Standard cancel button"
}
Example Request (Bulk Creation):
[
{
"key": "apps.common.buttons.save",
"baseValue": "Save",
"comment": "Save button"
},
{
"key": "apps.common.buttons.delete",
"baseValue": "Delete",
"comment": "Delete button",
"tags": ["dangerous"]
},
{
"key": "apps.common.messages.success",
"baseValue": "Operation completed successfully",
"translations": [
{
"locale": "es",
"value": "Operación completada con éxito",
"status": "verified"
}
]
}
]
Response:
interface CreateResourceResponseDto {
/** Number of entries that were created (count of new resources) */
entriesCreated: number;
/** Whether at least one resource entry was newly created */
created: boolean;
/**
* Locales skipped during auto-translation (ICU message format not supported).
* Only present when auto-translation is enabled and some locales were skipped.
*/
skippedLocales?: string[];
}
Example Response (Bulk Creation):
{
"entriesCreated": 3,
"created": true
}
Status Codes:
201 Created: Resources processed successfully (checkentriesCreatedto see how many were new)400 Bad Request: Invalid request body, validation error, or empty resource array404 Not Found: Collection not found500 Internal Server Error: Unexpected error during resource creation
Translation Status Values:
new: Not yet translated (default for auto-generated translations)translated: Has translation but not verifiedstale: Base value changed, translation is out of syncverified: Reviewed and approved
Key Format:
- Keys must be dot-delimited (e.g.,
apps.common.buttons.ok) - Keys are decomposed into folder paths:
apps.common.buttons.ok→apps/common/buttons/folder withokas the entry key - The file structure is created automatically based on the key
Examples:
Single Resource with Default Translations:
curl -X POST http://localhost:3030/api/collections/web-app/resources \
-H "Content-Type: application/json" \
-d '{
"key": "apps.common.buttons.ok",
"baseValue": "OK",
"comment": "Confirmation button"
}'
Single Resource with Specific Translations:
curl -X POST http://localhost:3030/api/collections/web-app/resources \
-H "Content-Type: application/json" \
-d '{
"key": "apps.common.buttons.cancel",
"baseValue": "Cancel",
"comment": "Cancel button",
"tags": ["ui", "button"],
"translations": [
{
"locale": "es",
"value": "Cancelar",
"status": "verified"
},
{
"locale": "fr",
"value": "Annuler",
"status": "translated"
}
]
}'
Bulk Resource Creation:
curl -X POST http://localhost:3030/api/collections/web-app/resources \
-H "Content-Type: application/json" \
-d '[
{
"key": "apps.common.buttons.save",
"baseValue": "Save"
},
{
"key": "apps.common.buttons.delete",
"baseValue": "Delete",
"tags": ["dangerous"]
},
{
"key": "apps.common.messages.success",
"baseValue": "Success!"
}
]'
Error Examples:
Collection Not Found:
curl -X POST http://localhost:3030/api/collections/non-existent/resources \
-H "Content-Type: application/json" \
-d '{"key": "test.key", "baseValue": "Test"}'
Response:
{
"statusCode": 404,
"message": "Collection \"non-existent\" not found"
}
Invalid Key Format:
curl -X POST http://localhost:3030/api/collections/web-app/resources \
-H "Content-Type: application/json" \
-d '{
"key": "invalid key with spaces",
"baseValue": "Test"
}'
Response:
{
"statusCode": 400,
"message": "Validation error for resource: Invalid key format"
}
Empty Request:
curl -X POST http://localhost:3030/api/collections/web-app/resources \
-H "Content-Type: application/json" \
-d '[]'
Response:
{
"statusCode": 400,
"message": "At least one resource is required"
}
Delete Resource(s)
Deletes one or more translation resource entries from a collection. This is a best-effort operation: if some keys fail to delete, the operation continues and returns details about failures.
Endpoint: DELETE /api/collections/:collectionName/resources
Path Parameters:
collectionName(string, required): The name of the collection (URL-encoded if necessary)
Request Body:
interface DeleteResourceDto {
/** Array of full dot-delimited keys to delete */
keys: string[];
}
Example Request (Single Key):
{
"keys": ["apps.common.buttons.ok"]
}
Example Request (Multiple Keys):
{
"keys": [
"apps.common.buttons.ok",
"apps.common.buttons.cancel",
"apps.common.buttons.save",
"apps.common.messages.success"
]
}
Response:
interface DeleteResourceResponseDto {
/** Number of resource entries successfully deleted */
entriesDeleted: number;
/** Optional array of errors for entries that failed to delete */
errors?: Array<{
/** The key that failed to delete */
key: string;
/** Error message describing why the deletion failed */
error: string;
}>;
}
Example Response (All Successful):
{
"entriesDeleted": 4,
"errors": []
}
Example Response (Partial Success):
{
"entriesDeleted": 2,
"errors": [
{
"key": "apps.common.buttons.nonexistent",
"error": "Resource key not found"
},
{
"key": "apps.invalid",
"error": "Invalid key format"
}
]
}
Status Codes:
200 OK: Deletion attempted (checkentriesDeletedanderrorsfor details)400 Bad Request: Invalid request (missing or emptykeysarray)404 Not Found: Collection not found500 Internal Server Error: Unexpected error during deletion
Best-Effort Behavior:
The delete operation uses a best-effort approach:
- If some keys are valid and others are invalid, valid keys will be deleted
- If some keys exist and others don't, existing keys will be deleted
- The response includes
entriesDeleted(count of successful deletions) anderrors(array of failures) - A
200 OKresponse doesn't guarantee all deletions succeeded - always check the response body
Examples:
Delete Single Resource:
curl -X DELETE http://localhost:3030/api/collections/web-app/resources \
-H "Content-Type: application/json" \
-d '{
"keys": ["apps.common.buttons.ok"]
}'
Delete Multiple Resources:
curl -X DELETE http://localhost:3030/api/collections/web-app/resources \
-H "Content-Type: application/json" \
-d '{
"keys": [
"apps.common.buttons.save",
"apps.common.buttons.delete",
"apps.common.buttons.cancel"
]
}'
Error Examples:
Collection Not Found:
curl -X DELETE http://localhost:3030/api/collections/non-existent/resources \
-H "Content-Type: application/json" \
-d '{"keys": ["test.key"]}'
Response:
{
"statusCode": 404,
"message": "Collection \"non-existent\" not found"
}
Empty Keys Array:
curl -X DELETE http://localhost:3030/api/collections/web-app/resources \
-H "Content-Type: application/json" \
-d '{"keys": []}'
Response:
{
"statusCode": 400,
"message": "Invalid request: keys array is required and must not be empty"
}
Missing Keys Field:
curl -X DELETE http://localhost:3030/api/collections/web-app/resources \
-H "Content-Type: application/json" \
-d '{}'
Response:
{
"statusCode": 400,
"message": "Invalid request: keys array is required and must not be empty"
}
Partial Failure (Some Keys Don't Exist):
curl -X DELETE http://localhost:3030/api/collections/web-app/resources \
-H "Content-Type: application/json" \
-d '{
"keys": [
"apps.common.buttons.ok",
"apps.common.nonexistent.key",
"apps.common.buttons.cancel"
]
}'
Response:
{
"entriesDeleted": 2,
"errors": [
{
"key": "apps.common.nonexistent.key",
"error": "Resource key not found in resource_entries.json"
}
]
}
Edit Resource
Modifies an existing translation resource. Supports updating base value, comments, tags, and locale-specific translations.
Endpoint: PATCH /api/collections/:collectionName/resources
Path Parameters:
collectionName(string, required): The name of the collection (URL-encoded if necessary)
Request Body:
interface UpdateResourceDto {
/** The key of the resource to update */
key: string;
/** Optional new base value */
baseValue?: string;
/** Optional new comment */
comment?: string;
/** Optional new tags (replaces existing tags) */
tags?: string[];
/** Optional target folder override */
targetFolder?: string;
/** Optional map of locale updates */
locales?: Record<string, {
value: string;
status: 'new' | 'translated' | 'stale' | 'verified';
}>;
}
Example Request:
{
"key": "apps.common.buttons.save",
"baseValue": "Save Item",
"comment": "Main save button",
"tags": ["ui", "primary"],
"locales": {
"fr-ca": { "value": "Enregistrer l'article" }
}
}
Response:
interface UpdateResourceResponseDto {
/** The resolved key of the updated resource */
resolvedKey: string;
/** Whether any changes were actually made */
updated: boolean;
/** Optional message (e.g., "No changes detected") */
message?: string;
/** The updated resource data (present when updated is true) */
resource?: ResourceSummaryDto;
/**
* Locales skipped during auto-translation (ICU message format not supported).
* Only present when auto-translation is enabled and some locales were skipped.
*/
skippedLocales?: string[];
}
Example Response:
{
"resolvedKey": "apps.common.buttons.save",
"updated": true
}
Status Codes:
200 OK: Resource updated successfully (or no changes detected)400 Bad Request: Invalid request body or validation error404 Not Found: Collection or resource not found500 Internal Server Error: Unexpected error
Notes:
- Updating
baseValuewill mark all non-base translations asstale(unless they are also updated in the same request). - Updating a locale value will set its status to
translatedand update its checksums. - If no changes are detected (values match existing),
updatedwill befalse.
Move Resource(s)
Moves or renames translation resources within a collection. Supports single resource moves and wildcard pattern moves.
Endpoint: POST /api/collections/:collectionName/resources/move
Path Parameters:
collectionName(string, required): The name of the collection (URL-encoded if necessary)
Request Body:
interface MoveResourceDto {
moves: MoveOperationDto[];
}
interface MoveOperationDto {
/** Source key or pattern (e.g., "common.buttons.ok" or "common.buttons.*") */
source: string;
/** Destination key (e.g., "common.actions.ok" or "common.actions") */
destination: string;
/** Whether to overwrite the destination if it exists */
override?: boolean;
/** Optional destination collection name (defaults to source collection) */
toCollection?: string;
}
Example Request:
{
"moves": [
{
"source": "common.buttons.ok",
"destination": "common.actions.ok"
},
{
"source": "common.buttons.cancel",
"destination": "common.actions.cancel",
"override": true
},
{
"source": "common.buttons.save",
"destination": "common.buttons.save",
"toCollection": "admin-portal"
}
]
}
Response:
interface MoveResourceResponseDto {
/** Total number of resources moved */
movedCount: number;
/** List of warnings (e.g., destination exists and override was false) */
warnings: string[];
/** List of errors */
errors: string[];
}
Example Response:
{
"movedCount": 2,
"warnings": [],
"errors": []
}
Status Codes:
201 Created: Move operation completed (checkmovedCountanderrorsfor details)400 Bad Request: Invalid request body404 Not Found: Collection not found500 Internal Server Error: Unexpected error during move
Notes:
- Wildcard moves (
source: "prefix.*") will move all matching resources to thedestinationprefix. - The operation is atomic per move entry but not transactional across all moves.
Get Resource Tree
Returns the resource tree for a collection, optionally scoped to a sub-path. The tree is built from an in-memory cache — the first request triggers background indexing and returns a 202 Accepted with a status response until the cache is ready.
Endpoint: GET /api/collections/:collectionName/resources/tree
Path Parameters:
collectionName(string, required): The name of the collection
Query Parameters:
path(string, optional): Dot-delimited folder path to scope the tree (e.g.,apps.common)includeNested(boolean, optional): Whentrueandpathis set, returns all nested resources flattened into theresourcesarray
Response (cache ready — 200 OK):
interface ResourceTreeDto {
/** Current folder path (dot-delimited, empty for root) */
path: string;
/** Resources in this folder */
resources: ResourceSummaryDto[];
/** Child folders */
children: FolderNodeDto[];
}
interface ResourceSummaryDto {
key: string;
translations: Record<string, string>;
status: Record<string, 'new' | 'translated' | 'stale' | 'verified' | undefined>;
comment?: string;
tags?: string[];
}
interface FolderNodeDto {
name: string;
fullPath: string;
loaded: boolean;
tree?: ResourceTreeDto;
}
Response (cache not ready — 202 Accepted):
interface TreeStatusResponseDto {
status: 'not-ready' | 'indexing';
message: string;
}
Status Codes:
200 OK: Tree returned successfully202 Accepted: Cache is indexing — retry shortly404 Not Found: Collection or path not found500 Internal Server Error: Unexpected error
Example:
# Full tree
curl http://localhost:3030/api/collections/web-app/resources/tree
# Scoped subtree
curl "http://localhost:3030/api/collections/web-app/resources/tree?path=apps.common"
# Scoped subtree with all nested resources
curl "http://localhost:3030/api/collections/web-app/resources/tree?path=apps.common&includeNested=true"
Get Cache Status
Returns the current indexing status of a collection's resource cache.
Endpoint: GET /api/collections/:collectionName/resources/cache/status
Response:
interface CacheStatusDto {
status: 'not-started' | 'indexing' | 'ready' | 'error';
collectionName?: string;
/** ISO date string — present when status is 'ready' */
indexedAt?: string;
/** Error message — present when status is 'error' */
error?: string;
/** Collection statistics — present when status is 'ready' */
stats?: {
totalKeys: number;
localeCount: number;
};
}
Example Response:
{
"status": "ready",
"collectionName": "web-app",
"indexedAt": "2026-04-09T12:00:00.000Z",
"stats": {
"totalKeys": 342,
"localeCount": 4
}
}
Status Codes:
200 OK: Status retrieved404 Not Found: Collection not found500 Internal Server Error: Unexpected error
Example:
curl http://localhost:3030/api/collections/web-app/resources/cache/status
Search Translations
Searches resources by key or translation value within a collection. Uses the in-memory cache when available, otherwise falls back to disk.
Endpoint: GET /api/collections/:collectionName/resources/search
Query Parameters:
query(string, required): Search string (case-insensitive, matches keys and values)maxResults(number, optional): Maximum results to return. Default:100, max:500
Response:
interface SearchResultsDto {
query: string;
results: SearchResultDto[];
totalFound: number;
/** True when results were capped by maxResults */
limited: boolean;
}
interface SearchResultDto extends ResourceSummaryDto {
matchType: 'exact-key' | 'partial-key' | 'exact-value' | 'partial-value';
/** Locales where value match was found */
matchedLocales?: string[];
}
Example Response:
{
"query": "cancel",
"results": [
{
"key": "apps.common.buttons.cancel",
"translations": { "en": "Cancel", "es": "Cancelar", "fr": "Annuler" },
"status": { "en": null, "es": "verified", "fr": "translated" },
"matchType": "partial-key"
}
],
"totalFound": 1,
"limited": false
}
Status Codes:
200 OK: Search completed404 Not Found: Collection not found500 Internal Server Error: Unexpected error
Example:
curl "http://localhost:3030/api/collections/web-app/resources/search?query=cancel&maxResults=50"
Translate Resource
Triggers auto-translation for a resource using the collection's configured translation provider. Returns an error if auto-translation is not enabled for the collection.
Endpoint: POST /api/collections/:collectionName/resources/translate
Request Body:
interface TranslateResourceDto {
/** Full dot-delimited key of the resource to translate */
key: string;
}
Response:
interface TranslateResourceResponseDto {
/** The resource with translated values applied */
resource: ResourceSummaryDto;
/** Number of locales successfully translated */
translatedCount: number;
/** Locales skipped because the base value uses ICU message format */
skippedLocales: string[];
}
Status Codes:
201 Created: Translation completed404 Not Found: Collection or resource not found422 Unprocessable Entity: Auto-translation not enabled for this collection502 Bad Gateway: Translation provider error500 Internal Server Error: Unexpected error
Example:
curl -X POST http://localhost:3030/api/collections/web-app/resources/translate \
-H "Content-Type: application/json" \
-d '{"key": "apps.common.buttons.cancel"}'
Start Bulk Locale Translation Job
Starts an async job that translates all new and stale resources in a collection for a single target locale. Returns immediately with a jobId that can be polled for progress. Requires auto-translation to be enabled for the collection.
Endpoint: POST /api/collections/:collectionName/resources/translate-locale
Path Parameters:
collectionName(string, required): The name of the collection
Request Body:
{
"locale": "fr"
}
Response (202 Accepted):
interface TranslateLocaleJobDto {
jobId: string;
collectionName: string;
targetLocale: string;
status: 'pending' | 'running' | 'completed' | 'failed';
totalResources: number;
translatedCount: number;
failedCount: number;
skippedCount: number;
failures?: Array<{ key: string; error: string }>;
skippedKeys?: string[];
startedAt?: string;
completedAt?: string;
}
Example Response:
{
"jobId": "uuid-here",
"collectionName": "playground",
"targetLocale": "fr",
"status": "pending",
"totalResources": 0,
"translatedCount": 0,
"failedCount": 0,
"skippedCount": 0
}
Status Codes:
202 Accepted: Job started — poll the GET endpoint for progress400 Bad Request: Invalid or missing locale422 Unprocessable Entity: Auto-translation not enabled for this collection404 Not Found: Collection not found500 Internal Server Error: Unexpected error
Example:
curl -X POST http://localhost:3030/api/collections/playground/resources/translate-locale \
-H 'Content-Type: application/json' \
-d '{"locale":"fr"}'
Get Bulk Locale Translation Job Status
Returns the current status and progress of a bulk locale translation job. Poll this endpoint every 2–5 seconds until status is completed or failed.
Endpoint: GET /api/collections/:collectionName/resources/translate-locale/:jobId
Path Parameters:
collectionName(string, required): The name of the collectionjobId(string, required): The job ID returned by the POST endpoint
Response:
TranslateLocaleJobDto with current status. See the POST endpoint above for the full type definition.
Status progression: pending → running → completed / failed
Example Response (completed):
{
"jobId": "uuid-here",
"collectionName": "playground",
"targetLocale": "fr",
"status": "completed",
"totalResources": 48,
"translatedCount": 45,
"failedCount": 0,
"skippedCount": 3,
"failures": [],
"skippedKeys": ["forms.upload.acceptedFormats"],
"startedAt": "2026-04-11T12:00:00.000Z",
"completedAt": "2026-04-11T12:00:15.000Z"
}
Status Codes:
200 OK: Job status returned404 Not Found: Collection or job not found500 Internal Server Error: Unexpected error
Example:
curl http://localhost:3030/api/collections/playground/resources/translate-locale/uuid-here
Folders API
All folder endpoints are scoped to a specific collection.
Create Folder
Creates a new folder node in the resource hierarchy.
Endpoint: POST /api/collections/:collectionName/folders
Request Body:
interface CreateFolderDto {
/** Single-segment folder name (alphanumeric, dashes, underscores) */
folderName: string;
/** Optional dot-delimited parent path (e.g., "apps.common"). Root if omitted. */
parentPath?: string;
}
Response:
interface CreateFolderResponseDto {
/** Filesystem path of the created folder */
folderPath: string;
/** False if the folder already existed */
created: boolean;
/** The new folder node for inserting into the tree */
folder: FolderNodeDto;
}
Status Codes:
201 Created: Folder created (or already existed)400 Bad Request: Invalid folder name404 Not Found: Collection not found500 Internal Server Error: Unexpected error
Example:
curl -X POST http://localhost:3030/api/collections/web-app/folders \
-H "Content-Type: application/json" \
-d '{"folderName": "navigation", "parentPath": "apps.common"}'
Delete Folder
Deletes a folder and all resources within it.
Endpoint: DELETE /api/collections/:collectionName/folders
Request Body:
interface DeleteFolderDto {
/** Dot-delimited folder path to delete (e.g., "apps.common.buttons") */
folderPath: string;
}
Response:
interface DeleteFolderResponseDto {
deleted: boolean;
folderPath: string;
/** Number of resource entries deleted with the folder */
resourcesDeleted: number;
error?: string;
}
Status Codes:
200 OK: Deletion attempted (checkdeletedanderrorfields)400 Bad Request: Invalid folder path404 Not Found: Collection not found500 Internal Server Error: Unexpected error
Example:
curl -X DELETE http://localhost:3030/api/collections/web-app/folders \
-H "Content-Type: application/json" \
-d '{"folderPath": "apps.common.buttons"}'
Move Folder
Moves a folder (and all its resources) to a new location. Supports cross-collection moves.
Endpoint: POST /api/collections/:collectionName/folders/move
Request Body:
interface MoveFolderDto {
/** Dot-delimited source folder path (e.g., "apps.common.buttons") */
sourceFolderPath: string;
/** Dot-delimited destination folder path (e.g., "apps.shared") */
destinationFolderPath: string;
/** If true, overwrite existing resources at destination. Default: false */
override?: boolean;
/** Optional destination collection name for cross-collection moves */
toCollection?: string;
/**
* When true, the source folder is nested under the destination as a child.
* Default: true
*/
nestUnderDestination?: boolean;
}
Response:
interface MoveFolderResponseDto {
movedCount: number;
foldersDeleted: number;
warnings: string[];
errors: string[];
}
Status Codes:
201 Created: Move completed (checkmovedCountanderrorsfor details)400 Bad Request: Invalid paths or validation error404 Not Found: Collection not found500 Internal Server Error: Unexpected error
Example:
curl -X POST http://localhost:3030/api/collections/web-app/folders/move \
-H "Content-Type: application/json" \
-d '{
"sourceFolderPath": "apps.common.buttons",
"destinationFolderPath": "apps.shared",
"nestUnderDestination": true
}'
Error Handling
All error responses follow a consistent format:
interface ErrorResponse {
statusCode: number;
message: string;
error?: string; // Optional error type
}
Common HTTP Status Codes
- 200 OK: Request succeeded
- 400 Bad Request: Invalid request body, missing required fields, or validation error
- 404 Not Found: Resource or collection not found
- 500 Internal Server Error: Unexpected server error
Error Response Examples
Validation Error (400):
{
"statusCode": 400,
"message": "Validation error for resource: Invalid key format"
}
Not Found (404):
{
"statusCode": 404,
"message": "Collection \"unknown-collection\" not found"
}
Server Error (500):
{
"statusCode": 500,
"message": "Error creating resources"
}
Complete Workflow Examples
Example 1: Create a Collection and Add Resources
# Step 1: Create a new collection
curl -X POST http://localhost:3030/api/collections \
-H "Content-Type: application/json" \
-d '{
"name": "my-app",
"collection": {
"translationsFolder": "./src/i18n",
"baseLocale": "en",
"locales": ["en", "es", "fr"]
}
}'
# Response:
# {
# "message": "Collection 'my-app' added successfully"
# }
# Step 2: Add resources to the collection
curl -X POST http://localhost:3030/api/collections/my-app/resources \
-H "Content-Type: application/json" \
-d '[
{
"key": "app.welcome.title",
"baseValue": "Welcome",
"comment": "Main welcome message"
},
{
"key": "app.welcome.subtitle",
"baseValue": "Start your journey here",
"comment": "Welcome subtitle"
}
]'
# Response:
# {
# "entriesCreated": 2,
# "created": true
# }
# Step 3: Verify the configuration
curl http://localhost:3030/api/config
# The response will include "my-app" in the collections
Example 2: Add Translations and Update Status
# Add a resource with verified translations
curl -X POST http://localhost:3030/api/collections/my-app/resources \
-H "Content-Type: application/json" \
-d '{
"key": "app.buttons.submit",
"baseValue": "Submit",
"comment": "Form submit button",
"tags": ["form", "button"],
"translations": [
{
"locale": "es",
"value": "Enviar",
"status": "verified"
},
{
"locale": "fr",
"value": "Soumettre",
"status": "verified"
}
]
}'
# Response:
# {
# "entriesCreated": 1,
# "created": true
# }
Example 3: Bulk Operations with Cleanup
# Add multiple resources
curl -X POST http://localhost:3030/api/collections/my-app/resources \
-H "Content-Type: application/json" \
-d '[
{
"key": "temp.test1",
"baseValue": "Test 1"
},
{
"key": "temp.test2",
"baseValue": "Test 2"
},
{
"key": "temp.test3",
"baseValue": "Test 3"
}
]'
# Response:
# {
# "entriesCreated": 3,
# "created": true
# }
# Clean up - delete the temporary resources
curl -X DELETE http://localhost:3030/api/collections/my-app/resources \
-H "Content-Type: application/json" \
-d '{
"keys": ["temp.test1", "temp.test2", "temp.test3"]
}'
# Response:
# {
# "entriesDeleted": 3,
# "errors": []
# }
Example 4: Error Handling in Bulk Operations
# Attempt to delete a mix of existing and non-existing keys
curl -X DELETE http://localhost:3030/api/collections/my-app/resources \
-H "Content-Type: application/json" \
-d '{
"keys": [
"app.welcome.title",
"app.nonexistent.key",
"app.welcome.subtitle",
"another.nonexistent.key"
]
}'
# Response (partial success):
# {
# "entriesDeleted": 2,
# "errors": [
# {
# "key": "app.nonexistent.key",
# "error": "Resource key not found"
# },
# {
# "key": "another.nonexistent.key",
# "error": "Resource key not found"
# }
# ]
# }
Notes and Best Practices
Resource Keys
- Use dot-delimited format:
namespace.category.subcategory.item - Keys are case-sensitive
- Avoid special characters except dots and underscores
- Keys should be descriptive and hierarchical
Translation Status Lifecycle
The typical status progression is:
new- Newly created, needs translationtranslated- Translation provided, needs reviewverified- Translation reviewed and approvedstale- Source text changed, translation needs updating (automatically set by checksum comparison)
Bulk Operations
- The resources API supports both single and array payloads for creation
- Bulk creation processes all resources and returns the total count of created entries
- Individual resource errors during bulk creation are handled gracefully (partial success)
- Delete operations always use an array of keys and support partial success
Best-Effort Deletes
- Delete operations continue even if some keys fail
- Always check both
entriesDeletedanderrorsin the response - Non-existent keys are reported in errors but don't stop deletion of valid keys
- This allows for idempotent cleanup scripts
Checksums and Stale Detection
The system automatically:
- Computes MD5 checksums for base values and translations
- Detects when source text changes (marks translations as
stale) - Maintains metadata in
tracker_meta.jsonfiles alongsideresource_entries.json
File Structure
Resources are stored as:
translations-folder/
locale/
namespace/
category/
resource_entries.json
tracker_meta.json
Where:
- Key
apps.common.buttons.okbecomes folder pathapps/common/buttons/ - Entry key
okis stored inresource_entries.json - Metadata (checksums, status) stored in
tracker_meta.json