Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 144 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Repository Overview

This is the PHP SDK for the Smartling Translation API. It provides a PHP client library for interacting with Smartling's translation management platform, allowing developers to upload files for translation, download translated content, manage translation jobs, and access other Smartling API features.

## Development Commands

### Install Dependencies
```bash
composer install
```

### Run All Tests
Tests require Smartling API credentials as environment variables:
```bash
account_uid=<account_uid> project_id=<project_id> user_id=<user_id> user_key=<user_key> ./vendor/bin/phpunit
```

### Run Specific Test Suites
```bash
# Unit tests only
./vendor/bin/phpunit --testsuite unit

# Functional tests only (requires API credentials)
account_uid=<account_uid> project_id=<project_id> user_id=<user_id> user_key=<user_key> ./vendor/bin/phpunit --testsuite functional
```

### Run Individual Test Files
```bash
./vendor/bin/phpunit tests/unit/SomeTest.php
```

## Architecture

### Core Components

**BaseApiAbstract** (`src/BaseApiAbstract.php`)
- Abstract base class for all API clients
- Handles HTTP communication via Guzzle
- Manages authentication via `AuthApiInterface`
- Implements automatic token refresh on 401 errors
- Provides logging support via PSR-3 `LoggerInterface`
- Standardizes error handling and response processing
- All API classes extend this base

**Authentication Flow**
- `AuthTokenProvider` (`src/AuthApi/`) implements `AuthApiInterface`
- Manages OAuth access tokens and refresh tokens with automatic expiration handling
- Token refresh happens automatically when expired or on 401 responses
- All API clients receive an auth provider instance and use it to authenticate requests

**API Client Pattern**
Each Smartling API endpoint has a dedicated API class:
- `FileApi` - File upload, download, and management
- `JobsApi` - Translation job management
- `BatchApi` / `BatchApiV2` - Batch operations
- `ContextApi` - Visual context and screenshots
- `ProjectApi` - Project information
- `AuditLogApi` - Audit logs
- `ProgressTrackerApi` - Translation progress tracking
- `TranslationRequestsApi` - Translation request management
- `DistributedLockServiceApi` - Distributed locking

**Parameter Objects**
- Located in `*/Params/` directories under each API module
- Implement `ParameterInterface` or extend `BaseParameters`
- Provide type-safe parameter building for API methods
- Example: `UploadFileParameters`, `CreateJobParameters`, `DownloadFileParameters`

### Directory Structure

```
src/
├── BaseApiAbstract.php # Base class for all API clients
├── AuthApi/ # Authentication provider
├── File/ # File API (upload/download)
├── Jobs/ # Jobs API
├── Batch/ # Batch operations (v1 and v2)
├── Context/ # Visual context API
├── Project/ # Project information API
├── AuditLog/ # Audit logging API
├── ProgressTracker/ # Progress tracking API
├── TranslationRequests/ # Translation requests API
├── DistributedLockService/ # Distributed lock service
├── Parameters/ # Base parameter classes
└── Exceptions/ # Custom exceptions

tests/
├── unit/ # Unit tests (no API calls)
└── functional/ # Functional tests (require API credentials)

examples/ # Usage examples for each API
```

### API Client Instantiation Pattern

All API clients follow this factory pattern:
```php
$authProvider = AuthTokenProvider::create($userIdentifier, $userSecretKey);
$api = SomeApi::create($authProvider, $projectId, $logger);
```

Each API class:
1. Defines its own `ENDPOINT_URL` constant
2. Provides a static `create()` factory method
3. Initializes an HTTP client via `BaseApiAbstract::initializeHttpClient()`
4. Sets the auth provider via `setAuth()`

### Error Handling

- `SmartlingApiException` is thrown for all API errors
- Errors contain structured error information from the API response
- 401 errors trigger automatic token refresh and retry
- Sensitive data (tokens, credentials) is redacted from logs

### Logging

- All API clients accept an optional PSR-3 `LoggerInterface`
- Requests and responses are logged for debugging
- Sensitive data is automatically redacted in logs

## PHP Version Support

- PHP 7.3+ and PHP 8.x
- Uses PSR-4 autoloading
- Requires Guzzle 6.x or 7.x for HTTP client
- Requires PSR Log 1.x or 3.x for logging

## Testing Strategy

- **Unit tests** (`tests/unit/`) - Test logic without making actual API calls
- **Functional tests** (`tests/functional/`) - Integration tests that call the real Smartling API
- Functional tests require valid Smartling credentials passed as environment variables
- Test resources are stored in `tests/resources/`

## Examples

The `examples/` directory contains working examples for each API:
- Run with: `php file-example.php --project-id=XXX --user-id=XXX --secret-key=XXX`
- Each example demonstrates common operations for that API
- Examples are self-contained and include inline documentation
187 changes: 187 additions & 0 deletions examples/file-translations-example.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
<?php

/**
* File Translations API Example
*
* This example demonstrates how to use the File Translations API to:
* 1. Upload a file for machine translation
* 2. Initiate translation to multiple target locales
* 3. Poll translation progress
* 4. Download translated files
* 5. Download all translations as ZIP
* 6. Cancel a translation (optional)
*
* Usage:
* php file-translations-example.php --account-uid=XXX --user-id=XXX --secret-key=XXX [--file-path=path/to/file.json]
*/

require_once '../vendor/autoload.php';

use Smartling\AuthApi\AuthTokenProvider;
use Smartling\FileTranslations\FileTranslationsApi;
use Smartling\FileTranslations\Params\TranslateFileParameters;

// Parse command line arguments
$options = getopt('', [
'account-uid:',
'user-id:',
'secret-key:',
'file-path::',
]);

if (!isset($options['account-uid']) || !isset($options['user-id']) || !isset($options['secret-key'])) {
echo "Usage: php file-translations-example.php --account-uid=XXX --user-id=XXX --secret-key=XXX [--file-path=path/to/file.json]\n";
exit(1);
}

$accountUid = $options['account-uid'];
$userId = $options['user-id'];
$secretKey = $options['secret-key'];
$filePath = $options['file-path'] ?? __DIR__ . '/../tests/resources/test-fts.json';

// Verify file exists
if (!file_exists($filePath)) {
echo "Error: File not found: {$filePath}\n";
exit(1);
}

try {
echo "=== File Translations API Example ===\n\n";

// Step 1: Initialize API client
echo "1. Initializing API client...\n";
$authProvider = AuthTokenProvider::create($userId, $secretKey);
$api = FileTranslationsApi::create($authProvider, $accountUid);
echo " ✓ API client initialized\n\n";

// Step 2: Upload file
echo "2. Uploading file: {$filePath}\n";
$fileName = basename($filePath);
$fileType = pathinfo($filePath, PATHINFO_EXTENSION);

$uploadResult = $api->uploadFile($filePath, $fileName, $fileType);
$fileUid = $uploadResult['fileUid'];
echo " ✓ File uploaded successfully\n";
echo " File UID: {$fileUid}\n\n";

// Step 3: Initiate translation
echo "3. Initiating translation to Spanish, French, and German...\n";
$translateParams = new TranslateFileParameters();
$translateParams
->setSourceLocaleId('en')
->setTargetLocaleIds(['es', 'fr', 'de']);

$translateResult = $api->translateFile($fileUid, $translateParams);
$mtUid = $translateResult['mtUid'];
echo " ✓ Translation initiated\n";
echo " MT UID: {$mtUid}\n\n";

// Step 4: Poll translation progress
echo "4. Polling translation progress...\n";
$maxAttempts = 60;
$pollInterval = 5; // seconds
$completed = false;

for ($i = 0; $i < $maxAttempts; $i++) {
$progress = $api->getTranslationProgress($fileUid, $mtUid);
$status = $progress['state'];

echo " Status: {$status}";

if (isset($progress['completedLocales'])) {
$completedCount = count($progress['completedLocales']);
$totalCount = count($translateParams->exportToArray()['targetLocaleIds']);
echo " ({$completedCount}/{$totalCount} locales completed)";
}

echo "\n";

if ($status === 'COMPLETED') {
$completed = true;
echo " ✓ Translation completed!\n\n";
break;
} elseif ($status === 'FAILED') {
echo " ✗ Translation failed\n";
print_r($progress);
exit(1);
} elseif ($status === 'CANCELLED') {
echo " ✗ Translation was cancelled\n";
exit(1);
}

if ($i < $maxAttempts - 1) {
sleep($pollInterval);
}
}

if (!$completed) {
echo " ⚠ Translation not completed within expected time. Continuing anyway...\n\n";
}

// Step 5: Download translated files
echo "5. Downloading translated files...\n";
$targetLocales = ['es', 'fr', 'de'];

foreach ($targetLocales as $locale) {
try {
$translatedContent = $api->downloadTranslatedFile($fileUid, $mtUid, $locale);
$outputPath = "/tmp/translated-{$locale}-{$fileName}";
file_put_contents($outputPath, $translatedContent);
echo " ✓ Downloaded {$locale}: {$outputPath}\n";

// Show first 100 chars of content
$preview = substr($translatedContent, 0, 100);
if (strlen($translatedContent) > 100) {
$preview .= '...';
}
echo " Preview: {$preview}\n";
} catch (Exception $e) {
echo " ✗ Failed to download {$locale}: {$e->getMessage()}\n";
}
}
echo "\n";

// Step 6: Download all translations as ZIP
echo "6. Downloading all translations as ZIP...\n";
try {
$zipContent = $api->downloadAllTranslationsZip($fileUid, $mtUid);
$zipPath = "/tmp/all-translations-{$fileUid}.zip";
file_put_contents($zipPath, $zipContent);
echo " ✓ Downloaded ZIP: {$zipPath}\n";
echo " Size: " . strlen($zipContent) . " bytes\n\n";
} catch (Exception $e) {
echo " ✗ Failed to download ZIP: {$e->getMessage()}\n\n";
}

// Optional: Demonstrate cancellation with a new translation
echo "7. (Optional) Demonstrating translation cancellation...\n";
echo " Uploading another file...\n";
$uploadResult2 = $api->uploadFile($filePath, "cancel-demo-{$fileName}", $fileType);
$fileUid2 = $uploadResult2['fileUid'];

echo " Starting translation to many locales...\n";
$translateParams2 = new TranslateFileParameters();
$translateParams2
->setSourceLocaleId('en')
->setTargetLocaleIds(['es', 'fr', 'de', 'it', 'pt', 'ja', 'zh', 'ru']);

$translateResult2 = $api->translateFile($fileUid2, $translateParams2);
$mtUid2 = $translateResult2['mtUid'];

echo " Cancelling translation...\n";
$api->cancelFileTranslation($fileUid2, $mtUid2);
echo " ✓ Cancellation request sent\n";

sleep(2);
$progress = $api->getTranslationProgress($fileUid2, $mtUid2);
echo " Final status: {$progress['state']}\n\n";

echo "=== Example completed successfully ===\n";

} catch (Exception $e) {
echo "\nError: {$e->getMessage()}\n";
if (method_exists($e, 'getTraceAsString')) {
echo $e->getTraceAsString() . "\n";
}
exit(1);
}
Loading