Skip to main content

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 via LINGO_TRACKER_PORT environment variable)
  • Content-Type: application/json
  • Response Format: JSON
  • CORS: Enabled with wildcard origin (*) in development
  • Swagger Documentation: Available at http://localhost:3030/api when 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 successfully
  • 500 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 successfully
  • 400 Bad Request: Invalid request body or collection already exists
  • 500 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 successfully
  • 400 Bad Request: Invalid request body or collection not found
  • 500 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 successfully
  • 400 Bad Request: Collection not found or deletion error
  • 500 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 backfilled
  • 400 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 purged
  • 400 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 (check entriesCreated to see how many were new)
  • 400 Bad Request: Invalid request body, validation error, or empty resource array
  • 404 Not Found: Collection not found
  • 500 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 verified
  • stale: Base value changed, translation is out of sync
  • verified: 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.okapps/common/buttons/ folder with ok as 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 (check entriesDeleted and errors for details)
  • 400 Bad Request: Invalid request (missing or empty keys array)
  • 404 Not Found: Collection not found
  • 500 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) and errors (array of failures)
  • A 200 OK response 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 error
  • 404 Not Found: Collection or resource not found
  • 500 Internal Server Error: Unexpected error

Notes:

  • Updating baseValue will mark all non-base translations as stale (unless they are also updated in the same request).
  • Updating a locale value will set its status to translated and update its checksums.
  • If no changes are detected (values match existing), updated will be false.

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 (check movedCount and errors for details)
  • 400 Bad Request: Invalid request body
  • 404 Not Found: Collection not found
  • 500 Internal Server Error: Unexpected error during move

Notes:

  • Wildcard moves (source: "prefix.*") will move all matching resources to the destination prefix.
  • 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): When true and path is set, returns all nested resources flattened into the resources array

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 successfully
  • 202 Accepted: Cache is indexing — retry shortly
  • 404 Not Found: Collection or path not found
  • 500 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 retrieved
  • 404 Not Found: Collection not found
  • 500 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 completed
  • 404 Not Found: Collection not found
  • 500 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 completed
  • 404 Not Found: Collection or resource not found
  • 422 Unprocessable Entity: Auto-translation not enabled for this collection
  • 502 Bad Gateway: Translation provider error
  • 500 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 progress
  • 400 Bad Request: Invalid or missing locale
  • 422 Unprocessable Entity: Auto-translation not enabled for this collection
  • 404 Not Found: Collection not found
  • 500 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 collection
  • jobId (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: pendingrunningcompleted / 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 returned
  • 404 Not Found: Collection or job not found
  • 500 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 name
  • 404 Not Found: Collection not found
  • 500 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 (check deleted and error fields)
  • 400 Bad Request: Invalid folder path
  • 404 Not Found: Collection not found
  • 500 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 (check movedCount and errors for details)
  • 400 Bad Request: Invalid paths or validation error
  • 404 Not Found: Collection not found
  • 500 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:

  1. new - Newly created, needs translation
  2. translated - Translation provided, needs review
  3. verified - Translation reviewed and approved
  4. stale - 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 entriesDeleted and errors in 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.json files alongside resource_entries.json

File Structure

Resources are stored as:

translations-folder/
locale/
namespace/
category/
resource_entries.json
tracker_meta.json

Where:

  • Key apps.common.buttons.ok becomes folder path apps/common/buttons/
  • Entry key ok is stored in resource_entries.json
  • Metadata (checksums, status) stored in tracker_meta.json