fix: Google Cloud API translation key environment and fallback logic
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 2s
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 2s
This commit is contained in:
@@ -105,6 +105,7 @@ services:
|
|||||||
- STRIPE_PRO_PRICE_ID=${STRIPE_PRO_PRICE_ID:-}
|
- STRIPE_PRO_PRICE_ID=${STRIPE_PRO_PRICE_ID:-}
|
||||||
- STRIPE_BUSINESS_PRICE_ID=${STRIPE_BUSINESS_PRICE_ID:-}
|
- STRIPE_BUSINESS_PRICE_ID=${STRIPE_BUSINESS_PRICE_ID:-}
|
||||||
- GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID:-}
|
- GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID:-}
|
||||||
|
- GOOGLE_CLOUD_API_KEY=${GOOGLE_CLOUD_API_KEY:-}
|
||||||
volumes:
|
volumes:
|
||||||
- uploads_data:/app/uploads
|
- uploads_data:/app/uploads
|
||||||
- outputs_data:/app/outputs
|
- outputs_data:/app/outputs
|
||||||
|
|||||||
@@ -836,6 +836,47 @@ def _estimate_pages(file_path: Path, file_extension: str) -> int:
|
|||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
class _GoogleCloudWithFallback:
|
||||||
|
"""Tries Google Cloud API first, falls back to deep_translator on error.
|
||||||
|
|
||||||
|
This avoids a hard crash when the Cloud API key is invalid, quota is
|
||||||
|
exceeded, or the network is unreachable. The legacy GoogleTranslator
|
||||||
|
(deep_translator, free, no key) is used as a best-effort fallback.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, cloud_adapter, legacy_provider):
|
||||||
|
self.cloud = cloud_adapter
|
||||||
|
self.legacy = legacy_provider
|
||||||
|
self.provider_name = "google_cloud_with_fallback"
|
||||||
|
|
||||||
|
def translate(self, text, target_language, source_language="auto"):
|
||||||
|
try:
|
||||||
|
return self.cloud.translate(text, target_language, source_language)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(
|
||||||
|
"google_cloud_failed_fallback_to_legacy",
|
||||||
|
error=str(e)[:200],
|
||||||
|
)
|
||||||
|
return self.legacy.translate(text, target_language, source_language)
|
||||||
|
|
||||||
|
def translate_batch(
|
||||||
|
self, texts, target_language, source_language="auto", **kwargs
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
return self.cloud.translate_batch(
|
||||||
|
texts, target_language, source_language
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(
|
||||||
|
"google_cloud_batch_failed_fallback_to_legacy",
|
||||||
|
error=str(e)[:200],
|
||||||
|
text_count=len(texts),
|
||||||
|
)
|
||||||
|
return self.legacy.translate_batch(
|
||||||
|
texts, target_language, source_language
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def _run_translation_job(
|
async def _run_translation_job(
|
||||||
job_id: str,
|
job_id: str,
|
||||||
input_path: Path,
|
input_path: Path,
|
||||||
@@ -953,6 +994,9 @@ async def _run_translation_job(
|
|||||||
|
|
||||||
# "google" (default classic mode): use Google Cloud API key if available,
|
# "google" (default classic mode): use Google Cloud API key if available,
|
||||||
# otherwise fall back to deep_translator (legacy, no key).
|
# otherwise fall back to deep_translator (legacy, no key).
|
||||||
|
# When the Cloud API key IS present, wrap it in a fallback so that
|
||||||
|
# quota / network errors don't kill the job — deep_translator is tried
|
||||||
|
# as a best-effort second chance.
|
||||||
if _p == "google":
|
if _p == "google":
|
||||||
gc_key = _cfg(
|
gc_key = _cfg(
|
||||||
getattr(_admin_cfg.google_cloud, "api_key", None),
|
getattr(_admin_cfg.google_cloud, "api_key", None),
|
||||||
@@ -960,8 +1004,13 @@ async def _run_translation_job(
|
|||||||
)
|
)
|
||||||
if gc_key:
|
if gc_key:
|
||||||
from services.providers.google_cloud_provider import LegacyGoogleCloudAdapter
|
from services.providers.google_cloud_provider import LegacyGoogleCloudAdapter
|
||||||
translation_provider = LegacyGoogleCloudAdapter(gc_key)
|
from services.translation_service import GoogleTranslationProvider
|
||||||
logger.info("google_provider_using_cloud_api", job_id=job_id)
|
cloud_adapter = LegacyGoogleCloudAdapter(gc_key)
|
||||||
|
legacy_fallback = GoogleTranslationProvider()
|
||||||
|
translation_provider = _GoogleCloudWithFallback(
|
||||||
|
cloud_adapter, legacy_fallback
|
||||||
|
)
|
||||||
|
logger.info("google_provider_using_cloud_api_with_fallback", job_id=job_id)
|
||||||
else:
|
else:
|
||||||
logger.info("google_provider_no_cloud_key_using_legacy", job_id=job_id)
|
logger.info("google_provider_no_cloud_key_using_legacy", job_id=job_id)
|
||||||
|
|
||||||
@@ -1142,7 +1191,7 @@ async def _run_translation_job(
|
|||||||
if attempted > 0:
|
if attempted > 0:
|
||||||
ratio = changed / attempted
|
ratio = changed / attempted
|
||||||
logger.info(f"Job {job_id}: translation stats — {changed}/{attempted} texts changed ({ratio:.0%})")
|
logger.info(f"Job {job_id}: translation stats — {changed}/{attempted} texts changed ({ratio:.0%})")
|
||||||
if ratio < 0.15 and changed == 0:
|
if changed == 0:
|
||||||
error_msg = (
|
error_msg = (
|
||||||
f"Translation failed: 0 out of {attempted} texts were translated. "
|
f"Translation failed: 0 out of {attempted} texts were translated. "
|
||||||
f"The provider ({provider}) may be unavailable or misconfigured. "
|
f"The provider ({provider}) may be unavailable or misconfigured. "
|
||||||
@@ -1151,6 +1200,12 @@ async def _run_translation_job(
|
|||||||
logger.error(f"Job {job_id}: {error_msg}")
|
logger.error(f"Job {job_id}: {error_msg}")
|
||||||
tracker.set_error(error_msg)
|
tracker.set_error(error_msg)
|
||||||
return
|
return
|
||||||
|
elif ratio < 0.05:
|
||||||
|
# Very suspicious — likely partial failure, warn but don't block
|
||||||
|
logger.warning(
|
||||||
|
f"Job {job_id}: suspiciously low translation rate: "
|
||||||
|
f"{changed}/{attempted} ({ratio:.1%})"
|
||||||
|
)
|
||||||
|
|
||||||
if user_id:
|
if user_id:
|
||||||
await tier_quota_service.increment_on_success(user_id)
|
await tier_quota_service.increment_on_success(user_id)
|
||||||
|
|||||||
Reference in New Issue
Block a user