Execute Lokalise primary workflow: Upload source files and manage translation keys. Use when uploading translation files, creating/updating keys, or managing source strings in Lokalise projects. Trigger with phrases like "lokalise upload", "lokalise push keys", "lokalise source strings", "add translations to lokalise".
Scanned 9/3/2026
Install to Claude Code
npx -y skills add Dicklesworthstone/pi_agent_rust --skill lokalise-core-workflow-a --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Lokalise Core Workflow A?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/dicklesworthstone-lokalise-core-workflow-a)More formats (shields.io, HTML) on the badges page.
---
name: lokalise-core-workflow-a
description: |
Execute Lokalise primary workflow: Upload source files and manage translation keys.
Use when uploading translation files, creating/updating keys,
or managing source strings in Lokalise projects.
Trigger with phrases like "lokalise upload", "lokalise push keys",
"lokalise source strings", "add translations to lokalise".
allowed-tools: Read, Write, Edit, Bash(lokalise2:*), Bash(npm:*), Grep
version: 1.0.0
license: MIT
author: Jeremy Longshore <jeremy@intentsolutions.io>
---
# Lokalise Core Workflow A: Upload & Key Management
## Overview
Primary workflow for uploading source translation files and managing keys in Lokalise.
## Prerequisites
- Completed `lokalise-install-auth` setup
- Lokalise project created
- Source translation file (JSON, XLIFF, PO, etc.)
## Instructions
### Step 1: Upload Source File via CLI
```bash
# Basic file upload
lokalise2 \
--token "$LOKALISE_API_TOKEN" \
--project-id "$LOKALISE_PROJECT_ID" \
file upload \
--file "./locales/en.json" \
--lang-iso en \
--replace-modified \
--distinguish-by-file \
--poll \
--poll-timeout 120s
# Upload with key tagging
lokalise2 \
--token "$LOKALISE_API_TOKEN" \
--project-id "$LOKALISE_PROJECT_ID" \
file upload \
--file "./locales/en.json" \
--lang-iso en \
--tags "web,v2.0" \
--tag-inserted-keys \
--tag-updated-keys \
--poll
```
### Step 2: Upload via SDK (Async Process)
```typescript
import { LokaliseApi } from "@lokalise/node-api";
import fs from "fs";
import path from "path";
const lokaliseApi = new LokaliseApi({
apiKey: process.env.LOKALISE_API_TOKEN!,
});
async function uploadFile(projectId: string, filePath: string, langIso: string) {
// Read and base64 encode the file
const fileContent = fs.readFileSync(filePath);
const base64Content = fileContent.toString("base64");
const fileName = path.basename(filePath);
// Start async upload process
const process = await lokaliseApi.files().upload(projectId, {
data: base64Content,
filename: fileName,
lang_iso: langIso,
replace_modified: true,
convert_placeholders: true,
detect_icu_plurals: true,
tags: ["uploaded-via-sdk"],
});
console.log(`Upload started: Process ID ${process.process_id}`);
// Poll for completion
return pollUploadProcess(projectId, process.process_id);
}
async function pollUploadProcess(projectId: string, processId: string) {
const maxAttempts = 60;
const delayMs = 2000;
for (let i = 0; i < maxAttempts; i++) {
const status = await lokaliseApi.queuedProcesses().get(processId, {
project_id: projectId,
});
console.log(`Status: ${status.status} (${status.progress}%)`);
if (status.status === "finished") {
console.log("Upload completed successfully!");
return status;
}
if (status.status === "failed" || status.status === "cancelled") {
throw new Error(`Upload failed: ${status.status}`);
}
await new Promise(r => setTimeout(r, delayMs));
}
throw new Error("Upload timed out");
}
```
### Step 3: Create Keys Programmatically
```typescript
async function createKeys(projectId: string) {
const keys = await lokaliseApi.keys().create({
project_id: projectId,
keys: [
{
key_name: "common.buttons.submit",
description: "Submit button text",
platforms: ["web", "ios", "android"],
tags: ["buttons", "common"],
translations: [
{ language_iso: "en", translation: "Submit" },
{ language_iso: "es", translation: "Enviar" },
],
},
{
key_name: "common.buttons.cancel",
description: "Cancel button text",
platforms: ["web", "ios", "android"],
tags: ["buttons", "common"],
translations: [
{ language_iso: "en", translation: "Cancel" },
],
},
{
key_name: "errors.network",
description: "Network error message",
platforms: ["web"],
is_plural: false,
tags: ["errors"],
translations: [
{ language_iso: "en", translation: "Network error. Please try again." },
],
},
],
});
console.log(`Created ${keys.items.length} keys`);
return keys;
}
```
### Step 4: Update Existing Keys
```typescript
async function updateKey(projectId: string, keyId: number) {
const updated = await lokaliseApi.keys().update(keyId, {
project_id: projectId,
description: "Updated description",
tags: ["updated", "v2"],
is_archived: false,
});
console.log(`Updated key: ${updated.key_name.web}`);
return updated;
}
async function bulkUpdateKeys(projectId: string, keyIds: number[], updates: object) {
const result = await lokaliseApi.keys().bulk_update({
project_id: projectId,
keys: keyIds.map(id => ({
key_id: id,
...updates,
})),
});
console.log(`Updated ${result.items.length} keys`);
return result;
}
```
### Step 5: Manage Key Tags
```typescript
async function tagKeys(projectId: string, keyIds: number[], tags: string[]) {
// Add tags to keys
const result = await lokaliseApi.keys().bulk_update({
project_id: projectId,
keys: keyIds.map(id => ({
key_id: id,
tags,
})),
});
return result;
}
async function getKeysByTag(projectId: string, tag: string) {
const keys = await lokaliseApi.keys().list({
project_id: projectId,
filter_tags: tag,
limit: 500,
});
return keys.items;
}
```
## Output
- Source file uploaded to Lokalise
- Keys created with translations
- Keys tagged for organization
- Bulk operations completed
## Error Handling
| Error | Cause | Solution |
|-------|-------|----------|
| `400 Invalid file format` | Unsupported format | Check supported formats |
| `400 Key already exists` | Duplicate key name | Use replace_modified or unique names |
| `413 Payload too large` | File too big | Split into smaller files |
| `429 Rate limited` | Too many requests | Use --poll flag, wait between uploads |
## Examples
### Supported File Formats
```typescript
// Lokalise supports many formats
const supportedFormats = [
"json", // JSON (nested or flat)
"xliff", // XLIFF 1.2 and 2.0
"po", // Gettext PO/POT
"strings", // iOS .strings
"xml", // Android XML
"properties", // Java properties
"yaml", // YAML
"csv", // CSV
"xlsx", // Excel
"resx", // .NET RESX
"arb", // Flutter ARB
];
```
### Upload with Cleanup Options
```bash
lokalise2 file upload \
--token "$LOKALISE_API_TOKEN" \
--project-id "$LOKALISE_PROJECT_ID" \
--file "./locales/en.json" \
--lang-iso en \
--cleanup-mode # Remove keys not in file
--replace-modified # Update existing translations
--fill-empty # Copy base to empty translations
--poll
```
### Batch File Upload
```typescript
async function uploadAllLocales(projectId: string, localesDir: string) {
const files = fs.readdirSync(localesDir)
.filter(f => f.endsWith(".json"));
for (const file of files) {
const langIso = path.basename(file, ".json");
const filePath = path.join(localesDir, file);
console.log(`Uploading ${file}...`);
await uploadFile(projectId, filePath, langIso);
// Respect rate limits
await new Promise(r => setTimeout(r, 200));
}
}
```
## Resources
- [File Upload API](https://developers.lokalise.com/reference/upload-a-file)
- [Keys API](https://developers.lokalise.com/reference/create-keys)
- [Supported Formats](https://docs.lokalise.com/en/articles/1400492-uploading-translation-files)
## Next Steps
For downloading translations, see `lokalise-core-workflow-b`.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!