güncellendi
Bir sohbet API'si üzerinden Çince↔İngilizce çeviri ve yerelleştirme
Bir sohbet modeli ile UI dizelerini ve belgeleri çevirmek demo yapmak kolaydır ancak üretimi zordur. Başarısızlıklar sıradandır: yeniden adlandırılmış bir yer tutucu, sözlük teriminin üç farklı şekilde çevrilmesi, metin içinde sarılmış bir JSON yanıtı. Bu kılavuz çeviriyi her aşamada kontrolleri olan bir boru hattı olarak ele alır ve OpenAI uyumlu sohbet tamamlama uç noktasını kullanır.
Bir istemde değil, aşamalarda düşünün
Üretim bir çeviri işi dört aşamadan oluşur: dizeleri hazırlayın, modele çağrı yapın, sonucu doğrulayın ve geri birleştirin. Kalite sorunlarının çoğu doğrulama aşamasını atlamaktan kaynaklanır. Bir yerelleştirme mühendisi yer tutucu denetleyicisini çalıştırmadan bir çeviri dosyasını asla göndermez ve aynı disiplin çevirmen bir model olduğunda da geçerlidir.
Uç nokta https://api.chinesellmapi.com/v1/chat/completions, model kimliği uncensored, Bearer kimlik doğrulamasıdır. Her iki yönü de (İngilizceden Çinceye ve Çince'den İngilizceye) işler; aşağıdaki örnekler, Basit Çince'ye giren İngilizce kaynak dizgelerini kullanır. Ters yön için sistem isteminde yönü değiştirin.
Paylaşım halinde olan 100.000 tokenlık pencereyi, varsayılan max_tokens değeri olan 2.048'i (uzun belgeler için artırın, 32.000'e kadar) ve 8 MB istek gövdesi sınırını göz önünde bulundurun. Bunların hiçbiri UI dizelerinde sorun yaratmaz, ancak tüm belgeler için hepsi önemlidir.
Sözlük enjeksiyonu
Marka adları, ürün isimleri ve yasal olarak hassas terimler tek bir sabit gösterime ihtiyaç duyar. En ucuz mekanizma, kaynak terimi ve gerekli hedefi listeleyen bir sözlük bloğu içeren sistem istemidir. Kuralı açıkça belirtin: kaynak teriminin her çekiminde sözlüğü kelimesi kelimesine kullanın. Sözlüğü kısa tutun, çünkü her satır her çağrıda girdi olarak faturalandırılır; birkaç düzine girdi normaldir, binlerce girdi değildir.
import os
from openai import OpenAI
client = OpenAI(base_url="https://api.chinesellmapi.com/v1", api_key=os.environ["API_KEY"])
GLOSSARY = {
"workspace": "工作区",
"pull request": "合并请求",
"seat": "席位",
"billing cycle": "计费周期",
}
def build_system(glossary, target="Simplified Chinese"):
rows = "\n".join(f"- {src} => {dst}" for src, dst in glossary.items())
return (
f"You are a software localization translator. Translate English UI strings into {target}.\n"
"Rules:\n"
"1. Use the glossary below verbatim whenever the source term appears, in any inflection.\n"
"2. Copy placeholders such as {name}, %s, %d and {{count}} exactly, character for character.\n"
"3. Copy HTML tags and attributes exactly; translate only the text between tags.\n"
"4. Output the translation only, with no notes.\n\n"
"Glossary:\n" + rows
)
def translate(text):
r = client.chat.completions.create(
model="uncensored",
messages=[
{"role": "system", "content": build_system(GLOSSARY)},
{"role": "user", "content": text},
],
temperature=0.2,
max_tokens=400,
)
return r.choices[0].message.content.strip()
print(translate("Invite {name} to the workspace before the next billing cycle."))
Sözlüğünüz büyükse, her istek için filtreleyin: Sadece toplu işteki kaynak terimle eşleşen girdileri dahil edin. Küçük harfe dönüştürülmüş metin üzerinde basit bir alt dizge testi, ilk sürüm için yeterlidir ve istemi küçük tutar. Sıcaklığı yaklaşık 0,2 olarak ayarlayın; yaratıcı varyasyonlar kullanıcı arayüzü metinlerinde bir hatadır.
Hedef dil için stil rehberini de önceden belirleyin: resmi veya samimi hitap, Çince karakterler ile gömülü Latin kelimeler veya sayılar arasında boşluk bırakılıp bırakılmayacağı ve hangi noktalama kümesinin kullanılacağı. Bu kararları aynı sistem istemine yerleştirin, böylece her toplu iş bunları miras alır.
Yer tutucuları ve biçimlendirmeyi korumak
Yer tutucular öngörülebilir şekillerde bozulur: model değişken adını çevirir, sona eklenen %s karakterini atar veya süslü parantezler içinde boşluk ekler. İstem kuralları bu hataları azaltır ancak ortadan kaldıramaz, bu nedenle kodda doğrulayın. Yer tutucuları ve etiketleri kaynak ve hedef dizgelerden düzenli ifade ile ayırın, ardından bunları sıralı listeler olarak karşılaştırın. Sıra diller arasında yasal olarak değişebileceğinden, diziler yerine çoklu kümeler olarak karşılaştırın.
import re
PLACEHOLDER = re.compile(r"\{\{?\w+\}?\}|%[sd]|</?[a-zA-Z][^>]*>")
def placeholders_ok(source: str, target: str) -> bool:
# Same multiset of placeholders and tags, regardless of order.
return sorted(PLACEHOLDER.findall(source)) == sorted(PLACEHOLDER.findall(target))
src = 'You have <b>{count}</b> unread messages in %s.'
bad = '你在 %s 中有 <b>{数量}</b> 条未读消息。'
good = '你在 %s 中有 <b>{count}</b> 条未读消息。'
print(placeholders_ok(src, bad), placeholders_ok(src, good)) # False True
Kontrol başarısız olursa, hata veren çifti isteme geri alıntılayarak bir kez yeniden deneyin, örneğin önceki çıktının yer tutucuyu değiştirdiğini ve düzeltilmesi gerektiğini söyleyen bir kullanıcı mesajı. Hâlâ başarısız olursa, döngüye girmek yerine dizeyi insan incelemesi için işaretleyin. Zengin metin ekstra özen gerektirir: metin düğümlerini çevirmeyi ve biçimlendirmeyi kendiniz yeniden oluşturmayı tercih edin, çünkü bu, etiket hasarını yapısal olarak imkansız kılar.
Talimatlar aracılığıyla yapılandırılmış çıktı
Bir istekte dizeleri toplu hale getirmek makine tarafından okunabilir bir yanıt gerektirir. API standart sohbet tamamlama alanlarını alır; burada yapılandırılmış çıktı şema zorlayıcı bir anahtardan ziyade net talimatlardan gelir, bu nedenle sözleşmeyi isteme yazın, sonuçları yeniden hizalamanıza olanak tanımak için kimlikler dahil edin ve savunmacı bir şekilde ayrıştırın. Modeller bazen JSON'u kod parantezlerinin içine alır veya dostça bir cümle ekler, bu nedenle ayrıştırmadan önce parantezleri temizleyin ve bir ayrıştırma hatasını yeniden denenebilir bir olay olarak ele alın.
import json
import os
import re
from openai import OpenAI
client = OpenAI(base_url="https://api.chinesellmapi.com/v1", api_key=os.environ["API_KEY"])
SYSTEM = (
"Translate each item from English to Simplified Chinese. "
"Reply with a JSON object only, no code fences, no commentary. "
'Shape: {"items": [{"id": <number>, "zh": "<translation>"}]}. '
"Keep the same ids, keep every placeholder and HTML tag unchanged."
)
def translate_batch(strings):
payload = [{"id": i, "en": s} for i, s in enumerate(strings)]
r = client.chat.completions.create(
model="uncensored",
messages=[
{"role": "system", "content": SYSTEM},
{"role": "user", "content": json.dumps(payload, ensure_ascii=False)},
],
temperature=0.2,
max_tokens=2000,
)
raw = r.choices[0].message.content.strip()
raw = re.sub(r"^```(?:json)?|```$", "", raw, flags=re.M).strip() # tolerate stray fences
data = json.loads(raw)
by_id = {item["id"]: item["zh"] for item in data["items"]}
return [by_id[i] for i in range(len(strings))]
print(translate_batch(["Save changes", "Delete {count} files?", "Welcome back, <b>{name}</b>"]))
Ayrıştırmadan sonra her zaman doğrulayın: kimlikler eşleşmeli, sayı eşleşmeli ve her öğe önceki bölümdeki yer tutucu kontrolünden geçmelidir. İstek başına elli dize kısa UI metni için makul bir başlangıç topluluğudur; doğrulama geçiş oranı yüksek kaldıkça yalnızca artırın.
Hız limitinin altında toplu çeviri
Her anahtar dakikada 300 isteğe kadar sınırlıdır. 50.000 dize içeren bir yerelleştirme işi, isteğe başına 20 dize ile 2.500 istek eder; eşit hızda ilerlerseniz bu işlem on dakikadan kısa sürer. Aşağıdaki betik, çalışan çağrılar için bir semafor ve kilit tabanlı bir hız denetleyici birleştirir ve 429 ile 503 hatalarında geri çekilir. Bir doğrulama hatasının aksine, bu iki hata geçicidir: 429 yavaşlamanız gerektiğini, upstream_busy ile birlikte gelen 503 ise birkaç saniye içinde yeniden denemeniz gerektiğini gösterir. 402 ise bakiyenin tükendiğini, bunun da hiçbir yeniden deneme ile düzeltilemeyeceğini gösterir.
import asyncio
import os
import time
from openai import AsyncOpenAI, APIStatusError
client = AsyncOpenAI(
base_url="https://api.chinesellmapi.com/v1",
api_key=os.environ["API_KEY"],
max_retries=0,
timeout=90,
)
RATE = 240 / 60 # requests per second, below the 300/min cap
gate = asyncio.Lock()
next_slot = 0.0
slots = asyncio.Semaphore(6)
async def pace():
global next_slot
async with gate:
now = time.monotonic()
if next_slot > now:
await asyncio.sleep(next_slot - now)
next_slot = max(now, next_slot) + 1 / RATE
async def call(batch, attempt=0):
async with slots:
await pace()
try:
r = await client.chat.completions.create(
model="uncensored",
messages=[
{"role": "system", "content": "Translate to Simplified Chinese. Keep placeholders unchanged. One line per input line."},
{"role": "user", "content": "\n".join(batch)},
],
max_tokens=1500,
temperature=0.2,
)
return r.choices[0].message.content.splitlines()
except APIStatusError as e:
if e.status_code in (429, 503) and attempt < 4:
await asyncio.sleep(2 ** attempt + 1) # 1s, 3s, 5s, 9s
return await call(batch, attempt + 1)
raise
async def run(all_strings, size=20):
chunks = [all_strings[i:i + size] for i in range(0, len(all_strings), size)]
results = await asyncio.gather(*(call(c) for c in chunks))
return [line for part in results for line in part]
if __name__ == "__main__":
strings = [f"Item {n} was updated by {{user}}" for n in range(60)]
out = asyncio.run(run(strings))
print(len(out), out[0])
Bu sürüm kısalık için satırlara böler, bu tek satırlı dizeler için iyidir; çok satırlı her şey için önceki bölümün JSON varyantını kullanın, böylece bir dizenin içindeki satır sonları dize sınırları ile karıştırılamaz.
Ters yön: Çince kaynak metin
Çince'yi İngilizce'ye çevirmek kendi başarısızlık modlarına sahiptir. Çince özneleri ve çoğulları serbestçe bırakır, bu nedenle model bunları tahmin etmelidir; ona bağlam verin. "已发送" gibi bir dize "Sent", "Has been sent" veya "You sent it" olabilir; bu, bir düğme, bir durum rozeti veya bir bildirim etiketlediğine bağlıdır. Düzeltme, her dizenin yanına bir bağlam alanı eklemektir; geliştiricileriniz tarafından sağlanır ve JSON toplu işinde iletilir: dizenin göründüğü yer, maksimum uzunluğu ve bir etiket mi yoksa bir cümle mi olduğu.
Uzunluk sınırlarını açıkça ölçün. İngilizce UI metni genellikle Çince orijinalinden daha uzundur ve tersi Çince hedefler için doğrudur; her karakter tam genişlik olduğu için karakter olarak daha kısa ancak ekranda daha geniş olma eğilimindedir. Bir düğme veya tablo başlığı katı bir sınıra sahipse, istemde bir karakter bütçesi belirtin ve yanıt geldiğinde kodda doğrulayın.
İsimleri ve yerleri içeren Çince kaynak belgeler için, ton işaretleri olmadan Hanyu Pinyin gibi bir romanizasyon kuralını önceden belirtin ve tekrarlayan isimleri sözlüğe ekleyin. Bu olmadan, aynı kişi tek bir belge içinde iki farklı yazımla görünebilir ki bu, bir gözden geçirenin ilk fark edeceği tutarsızlıktır.
Birleştirmeden önce örnekleme ve inceleme
Otomatik kontroller yapısal hataları yakalar, yanlış çevirileri değil. Hafif bir insan adımı ekleyin: her toplu işin sabit bir yüzdesini örneğin yirmi dizenin birini ve yeniden deneme gerektiren her dizeyi örnekleme yapın ve bunları iki dilli bir gözden geçirene gönderin. Gözden geçirenin her toplu iş için düzenleme oranını izleyin. Eğer yükselirse, istemi sıkılaştırın, toplu iş boyutunu küçültün veya düzeltilen terimler için sözlük girdileri ekleyin.
İstemi, sözlük sürümünü ve toplu iş ayarlarını her çıktı dosyasının yanında tutun. Bir terim sözlükte değiştiğinde, tüm korpusu yeniden çalıştırmak yerine yalnızca onu içeren dizeleri yeniden çevirebilirsiniz. Kaynak dize artı sözlük sürümünün basit bir içerik karması bir önbellek anahtarı olarak çalışır ve değişmeyen dizelerin bir sonraki çalıştırmada hiçbir maliyeti olmadığı anlamına gelir.
Son olarak, yer tutucuların iç içe olduğu, çoğul formların, gömülü HTML'nin ve uzun bileşik isimlerin olduğu zorlu dizelerin bir regresyon kümesini tutun. İstemi her değiştirdiğinizde çalıştırın ve değişikliği yayınlamadan önce çıktıları yan yana karşılaştırın.
Bir yerelleştirme çalıştırması için maliyet tahmini
Açık varsayımlar: 30.000 kaynak dizgi, her biri 12 İngilizce kelime, 20'lik toplu işlemlerle çevriliyor. Her dizgi için yaklaşık 20 giriş token'ı ve istek başına sabit sistem istemi için 300 token gerekir; her dizgi için yaklaşık 35 çıkış token'ı. Bu, 1.500 istek, yaklaşık 1,05 milyon giriş token'ı ve yaklaşık 1,05 milyon çıkış token'ı anlamına gelir. Giriş token'ı başına 0,25 $ ve çıkış token'ı başına 1,00 $ ile toplam maliyet yaklaşık 0,26 $ + 1,05 $ = 1,31 $'dir. Bu büyüklük tahmini olarak kabul edin ve varsayımları kendi usage günlüklerinizdeki verilerle değiştirin.
Deneme kredisi yedi gün boyunca 0,50 $'dır; bu, birkaç bin dizgi örneklemesi üzerinde bir boru hattını doğrulamak için yeterlidir. Betik kontrolü ve UTF-8 işleme için app quickstart'a devam edin veya güncel oranlar için pricing page'e bakın.
Sorular ve cevaplar
API her iki yönde de çeviri yapabilir mi?
Evet. Çince'den İngilizce'ye ve İngilizce'den Çince'ye çeviri aynı sohbet uç noktası üzerinden çalışır; yönlendirmeyi sistem isteminde seçersiniz.
Modelin {name} gibi yer tutucuları değiştirmesini nasıl engellerim?
Kuralı istemde belirtin, ardından kaynak ve hedef arasındaki yer tutucu listelerini karşılaştırarak kodda doğrulayın ve eşleşmezse yeniden deneyin veya eşleşmezliği işaretleyin.
Bir JSON modu anahtarı var mı?
Yapılandırılmış çıktı, talimatlar aracılığıyla elde edilir. İstemde tam şekli belirtin, rastgele kod parantezlerini temizleyin, ardından sonucu ayrıştırın ve doğrulayın.
Büyük bir toplu işi ne kadar hızlı çalıştırabilirim?
Her anahtar dakikada 300 istek yapmanıza olanak tanır. İstekleri eşit aralıklarla gönderin ve 429 veya 503 yanıtlarında bekleyin.
Anahtarınız tek bir formun uzağında
Bir hesap oluşturun, anahtarı kopyalayın, temel URL'yi değiştirin. Kurulum bu kadar basit.