Skip to content

Commit 8d8d189

Browse files
Merge pull request #45 from vbossica/azure_open_ai
Add Azure OpenAI as provider
2 parents 04e74b3 + a469064 commit 8d8d189

File tree

9 files changed

+197
-36
lines changed

9 files changed

+197
-36
lines changed

README.md

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@
44
![PyPI](https://img.shields.io/pypi/v/gpt-po-translator?label=gpt-po-translator)
55
![Downloads](https://pepy.tech/badge/gpt-po-translator)
66

7-
A robust tool for translating gettext (.po) files using AI models from multiple providers (OpenAI, Anthropic / Claude, and DeepSeek). It supports both bulk and individual translations, handles fuzzy entries, and can infer target languages based on folder structures. Available as a Python package and Docker container with support for Python 3.8-3.12.
7+
A robust tool for translating gettext (.po) files using AI models from multiple providers (OpenAI, Azure OpenAI, Anthropic / Claude, and DeepSeek). It supports both bulk and individual translations, handles fuzzy entries, and can infer target languages based on folder structures. Available as a Python package and Docker container with support for Python 3.8-3.12.
88

99
## What is GPT-PO Translator?
1010

1111
This tool helps you translate gettext (.po) files using AI models. It's perfect for developers who need to localize their applications quickly and accurately.
1212

1313
### Key Features
1414

15-
- **Multiple AI providers** - OpenAI, Anthropic/Claude, and DeepSeek
15+
- **Multiple AI providers** - OpenAI, Azure OpenAI, Anthropic/Claude, and DeepSeek
1616
- **Flexible translation modes** - Bulk or entry-by-entry processing
1717
- **Smart language handling** - Auto-detects target languages from folder structure
1818
- **Production-ready** - Includes retry logic, validation, and detailed logging
@@ -29,6 +29,8 @@ pip install gpt-po-translator
2929

3030
### Basic Usage
3131

32+
To translate the `po` files for the German and French languages found in the `locales` folder, using OpenAI:
33+
3234
```bash
3335
# Set up your API key
3436
export OPENAI_API_KEY='your_api_key_here'
@@ -77,6 +79,7 @@ export OPENAI_API_KEY='your_api_key_here'
7779
# Or for other providers:
7880
export ANTHROPIC_API_KEY='your_api_key_here'
7981
export DEEPSEEK_API_KEY='your_api_key_here'
82+
export AZURE_OPENAI_API_KEY='your_api_key_here'
8083
```
8184

8285
### Option 2: Command Line
@@ -110,8 +113,14 @@ gpt-po-translator --provider anthropic --folder ./locales --lang de
110113
# Use DeepSeek models
111114
gpt-po-translator --provider deepseek --folder ./locales --lang de
112115

113-
# List available models
116+
# List available models for openai
114117
gpt-po-translator --provider openai --list-models
118+
119+
# List available models for azure openai
120+
gpt-po-translator --provider azure_openai \
121+
--azure-openai-endpoint https://<deployment>.cognitiveservices.azure.com/ \
122+
--azure-openai-api-version <api_version> \
123+
--list-models
115124
```
116125

117126
## Command Reference

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ requires = ["setuptools>=61.0", "wheel", "setuptools_scm>=6.2"]
33
build-backend = "setuptools.build_meta"
44

55
[tool.setuptools]
6-
# Use find with namespaces=True to handle nested packages
6+
# Use find with namespaces=True to handle nested packages
77
packages = { find = { exclude = ["*.__pycache__", "*.__pycache__.*"], namespaces = true } }
88

99
[tool.setuptools_scm]

python_gpt_po/main.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def initialize_provider(args: Namespace) -> tuple[ProviderClients, ModelProvider
4949

5050
# Initialize provider clients
5151
provider_clients = ProviderClients()
52-
provider_clients.initialize_clients(api_keys)
52+
provider_clients.initialize_clients(args, api_keys)
5353

5454
# Get provider from arguments or auto-select
5555
provider = get_provider_from_args(args)
@@ -117,7 +117,8 @@ def get_appropriate_model(
117117

118118
# Fall back to default model if no models could be retrieved
119119
default_model = model_manager.get_default_model(provider)
120-
logging.warning("No available models found from API; defaulting to %s", default_model)
120+
logging.warning("No available models found from API; defaulting to %s",
121+
default_model)
121122
return default_model
122123

123124

@@ -145,7 +146,8 @@ def process_translations(config: TranslationConfig, folder: str,
145146
sys.exit(1)
146147

147148
# Start processing files
148-
logging.info("Starting translation with %s using model %s", config.provider.value, config.model)
149+
logging.info("Starting translation with %s using model %s in folder %s",
150+
config.provider.value, config.model, folder)
149151
translation_service.scan_and_process_po_files(folder, languages, detail_languages)
150152
logging.info("Translation completed successfully")
151153

python_gpt_po/models/enums.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@ class ModelProvider(Enum):
1010
OPENAI = "openai"
1111
ANTHROPIC = "anthropic"
1212
DEEPSEEK = "deepseek"
13+
AZURE_OPENAI = "azure_openai"
1314

1415

1516
ModelProviderList = [
1617
ModelProvider.OPENAI.value,
1718
ModelProvider.ANTHROPIC.value,
18-
ModelProvider.DEEPSEEK.value
19+
ModelProvider.DEEPSEEK.value,
20+
ModelProvider.AZURE_OPENAI.value
1921
]

python_gpt_po/models/provider_clients.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22
Client classes for different AI providers.
33
"""
44

5+
import os
6+
from argparse import Namespace
57
from typing import Dict
68

79
from anthropic import Anthropic
8-
from openai import OpenAI
10+
from openai import AzureOpenAI, OpenAI
911

1012
from .enums import ModelProvider
1113

@@ -15,11 +17,12 @@ class ProviderClients:
1517

1618
def __init__(self):
1719
self.openai_client = None
20+
self.azure_openai_client = None
1821
self.anthropic_client = None
1922
self.deepseek_api_key = None
2023
self.deepseek_base_url = "https://api.deepseek.com/v1"
2124

22-
def initialize_clients(self, api_keys: Dict[str, str]):
25+
def initialize_clients(self, args: Namespace, api_keys: Dict[str, str]):
2326
"""Initialize API clients for all providers with available keys.
2427
2528
Args:
@@ -28,6 +31,21 @@ def initialize_clients(self, api_keys: Dict[str, str]):
2831
if api_keys.get(ModelProvider.OPENAI.value):
2932
self.openai_client = OpenAI(api_key=api_keys[ModelProvider.OPENAI.value])
3033

34+
if api_keys.get(ModelProvider.AZURE_OPENAI.value):
35+
endpoint = args.azure_openai_endpoint or os.getenv("AZURE_OPENAI_ENDPOINT")
36+
if not endpoint:
37+
raise ValueError("Missing Azure OpenAI endpoint.")
38+
39+
api_version = args.azure_openai_api_version or os.getenv("AZURE_OPENAI_API_VERSION")
40+
if not api_version:
41+
raise ValueError("Missing Azure OpenAI API version.")
42+
43+
self.azure_openai_client = AzureOpenAI(
44+
azure_endpoint=endpoint,
45+
api_key=api_keys[ModelProvider.AZURE_OPENAI.value],
46+
api_version=api_version
47+
)
48+
3149
if api_keys.get(ModelProvider.ANTHROPIC.value):
3250
self.anthropic_client = Anthropic(api_key=api_keys[ModelProvider.ANTHROPIC.value])
3351

python_gpt_po/services/model_manager.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,24 @@ def get_available_models(provider_clients: ProviderClients, provider: ModelProvi
7171
else:
7272
logging.error("DeepSeek API key not set")
7373

74+
elif provider == ModelProvider.AZURE_OPENAI:
75+
return ModelManager._get_azure_openai_models(provider_clients)
76+
7477
except Exception as e:
7578
logging.error("Error fetching models from %s: %s", provider.value, str(e))
7679

7780
return models
7881

82+
@staticmethod
83+
def _get_azure_openai_models(provider_clients: ProviderClients) -> List[str]:
84+
"""Retrieve models from Azure OpenAI."""
85+
if provider_clients.azure_openai_client:
86+
response = provider_clients.azure_openai_client.models.list()
87+
return [model.id for model in response.data]
88+
89+
logging.error("Azure OpenAI client not initialized")
90+
return []
91+
7992
@staticmethod
8093
def validate_model(provider_clients: ProviderClients, provider: ModelProvider, model: str) -> bool:
8194
"""
@@ -109,9 +122,10 @@ def get_default_model(provider: ModelProvider) -> str:
109122
default_models = {
110123
ModelProvider.OPENAI: "gpt-4o-mini",
111124
ModelProvider.ANTHROPIC: "claude-3-5-haiku-latest",
112-
ModelProvider.DEEPSEEK: "deepseek-chat"
125+
ModelProvider.DEEPSEEK: "deepseek-chat",
126+
ModelProvider.AZURE_OPENAI: "gpt-35-turbo",
113127
}
114-
return default_models.get(provider)
128+
return default_models.get(provider, "")
115129

116130
@staticmethod
117131
def verify_model_capabilities(

python_gpt_po/services/translation_service.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,19 @@ def _get_deepseek_response(self, content: str) -> str:
8383
response.raise_for_status()
8484
return response.json()["choices"][0]["message"]["content"].strip()
8585

86+
def _get_azure_openai_response(self, content: str) -> str:
87+
"""Get response from OpenAI API."""
88+
if not self.config.provider_clients.azure_openai_client:
89+
raise ValueError("OpenAI client not initialized")
90+
91+
message = {"role": "user", "content": content}
92+
completion = self.config.provider_clients.azure_openai_client.chat.completions.create(
93+
model=self.config.model,
94+
max_tokens=4000,
95+
messages=[message]
96+
)
97+
return completion.choices[0].message.content.strip()
98+
8699
def validate_provider_connection(self) -> bool:
87100
"""Validates the connection to the selected provider by making a test API call."""
88101
provider = self.config.provider
@@ -231,6 +244,8 @@ def _get_provider_response(self, content: str) -> str:
231244
return self._get_anthropic_response(content)
232245
if provider == ModelProvider.DEEPSEEK:
233246
return self._get_deepseek_response(content)
247+
if provider == ModelProvider.AZURE_OPENAI:
248+
return self._get_azure_openai_response(content)
234249
return ""
235250

236251
def _process_bulk_response(self, response_text: str, original_texts: List[str]) -> List[str]:

0 commit comments

Comments
 (0)