Add On-Demand Translation for Comments on Game Profiles, User Profiles, etc... #851

Open
opened 2026-07-28 17:31:44 -05:00 by Codex · 0 comments
Member

Summary

Add on-demand English translation for non-English user-generated Moments and comments on game pages.

The idea was triggered by a non-English comment on:

https://myvideogamelist.com/game/8/hitman-blood-money

The first version should show a small Translate action beneath eligible Moments and comments. Original content remains visible by default. When a signed-in user clicks Translate, the browser asks the MyVideoGameList backend for an English translation, displays the translated text beneath the original, and shows the detected source language when available.

Example behavior

Original Russian comment remains visible.

Very cool game. At first I did not want to play it, but once I started, I could not stop.

Translated from Russian · Hide translation

Users should be able to hide and show an already loaded translation again without triggering another API request.

Initial scope

  • Moments on game pages.
  • Comments on game pages.
  • English as the only target language.
  • DeepL as the initial translation provider.

Out of scope for the first version:

  • Translation into languages other than English.
  • Automatic translation without user interaction.
  • Translating every existing database record.
  • Translation of private messages.
  • Browser-side calls directly to DeepL.
  • Automatic fallback between providers.
  • User-selectable translation providers.
  • Real-time translation while composing content.

Provider abstraction

Use DeepL first, but keep provider-specific code isolated behind an interface so providers such as LibreTranslate, OpenAI, Google Cloud Translation, or Kagi can be added later.

Suggested interface:

interface TranslationProviderInterface
{
    public function translate(
        string $text,
        string $targetLanguage
    ): TranslationResult;
}

TranslationResult should include:

  • Translated text.
  • Detected source language.
  • Provider name.

Use DeepL automatic source-language detection. Store the detected language code with the cached translation and map known codes to readable names, such as RU to Russian, JA to Japanese, and DE to German.

If the source language cannot be determined, display Translated to English.

Do not make a separate language-detection request just to decide whether the Translate button should be displayed. For the first version, it is acceptable to show Translate on all eligible content and let the backend determine whether translation is necessary.

Configuration

Suggested environment variables:

TRANSLATION_ENABLED=true
TRANSLATION_PROVIDER=deepl
TRANSLATION_TARGET_LANGUAGE=EN
DEEPL_API_KEY=

The DeepL API key must remain server-side and must never be exposed to browser JavaScript.

Backend endpoint

Create a protected backend translation endpoint.

Suggested request:

POST /api/translations
{
  "content_type": "comment",
  "content_id": 123,
  "target_language": "EN"
}

The browser should send only the content type and database ID. The backend must load the authoritative source content from the database.

The endpoint must not accept arbitrary text from the browser and forward it to the translation provider, because that would allow abuse as a general-purpose translation proxy.

A successful response should include:

  • Translated text.
  • Source language.
  • Target language.
  • Provider.
  • Whether the result came from cache.

Translation cache

Cache translations in the database so the same unchanged content is not translated repeatedly.

Suggested table: content_translations

Fields:

  • id
  • content_type
  • content_id
  • source_language
  • target_language
  • translated_text
  • provider
  • original_text_hash
  • created_at
  • updated_at

Add a unique constraint for content type, content ID, and target language.

Before calling the provider:

  1. Load the current source content.
  2. Generate a hash of the original text.
  3. Look for an existing translation with the same content type, content ID, target language, and original text hash.
  4. Return the cached translation when available.
  5. Call the provider only when there is no valid cached translation.

If a Moment or comment is edited, the old cached translation must no longer be treated as valid.

Abuse and security protections

  • Require authentication, at least initially.
  • Add per-user and/or per-IP rate limiting.
  • Enforce a maximum content length.
  • Verify authorization for private or restricted content.
  • Use CSRF protection where applicable.
  • Escape and sanitize translated output.
  • Set a timeout for provider requests.
  • Handle provider failures gracefully.
  • Never expose API keys or raw provider responses.
  • Do not send unrelated user information to the provider.

Preserve paragraph breaks where possible. Treat provider output as untrusted text. Do not render provider-returned HTML directly.

Error handling

Handle these states safely:

  • Translation provider unavailable.
  • API quota exhausted.
  • Rate limit exceeded.
  • Content deleted or not found.
  • Content already in English.
  • Translation disabled.
  • Unsupported language.
  • Provider timeout.

Use a generic user-facing message such as:

Translation is temporarily unavailable.

Do not expose stack traces, API responses, credentials, or internal error details.

Logging and metrics

Log translation failures through the application's existing logging system.

Include:

  • Content type.
  • Content ID.
  • Provider.
  • Target language.
  • HTTP status.
  • Whether the request used the cache.

Do not log API keys, authorization headers, or full private content unnecessarily.

Track basic usage metrics if practical:

  • Characters translated.
  • Provider requests.
  • Cache hits.
  • Cache misses.
  • Translation failures.

This matters because the initial DeepL Developer allowance is a one-time pool of 1 million characters.

Accessibility

  • Use a real button element.
  • Make the action keyboard accessible.
  • Use a clear accessible label.
  • Expose loading state with aria-busy or an equivalent.
  • Announce dynamically inserted translations where appropriate.
  • Do not rely on the globe icon alone.

Suggested implementation order

  1. Add translation configuration.
  2. Add the provider interface and translation result object.
  3. Implement the DeepL provider.
  4. Add the translation cache table and model or repository.
  5. Add the protected backend endpoint.
  6. Add validation and rate limiting.
  7. Add the Translate UI to Moments and comments.
  8. Add show and hide behavior.
  9. Add logging and usage metrics.
  10. Add automated tests.

Tests

Provider layer:

  • Successful DeepL translation.
  • Source-language detection.
  • Authentication headers.
  • Timeout behavior.
  • Provider error responses.
  • Malformed provider responses.

Caching:

  • First request calls the provider.
  • Second request returns the cached translation.
  • Editing source content invalidates the cache.
  • Different target languages create separate cache entries.
  • Provider is not called on a valid cache hit.

Endpoint:

  • Valid comment translation.
  • Valid Moment translation.
  • Missing content.
  • Unauthorized or private content.
  • Arbitrary text cannot be submitted.
  • Rate-limit enforcement.
  • Maximum-length enforcement.
  • Translation disabled.
  • Provider failure returns a safe error.

Frontend:

  • Translate button appears for eligible content.
  • Loading state is shown.
  • Translation is inserted successfully.
  • Translation can be hidden and shown again.
  • Showing an already loaded translation does not make another request.
  • Errors are displayed without removing the original content.

Acceptance criteria

  • A signed-in user can click Translate beneath an eligible Moment or comment.
  • The original content remains visible and unchanged.
  • The English translation appears beneath the original.
  • The detected source language is shown when available.
  • The translation can be hidden and shown again.
  • Repeat requests for unchanged content use the database cache.
  • Editing original content invalidates the previous cached translation.
  • The browser never receives the DeepL API key.
  • The endpoint cannot translate arbitrary user-supplied text.
  • Requests are rate-limited.
  • Translation can be disabled through configuration.
  • Provider failures do not break the page or hide the original content.
  • DeepL-specific code is isolated behind a provider interface.
  • Automated tests cover the provider, cache, endpoint, and primary UI behavior.
## Summary Add on-demand English translation for non-English user-generated Moments and comments on game pages. The idea was triggered by a non-English comment on: https://myvideogamelist.com/game/8/hitman-blood-money The first version should show a small `Translate` action beneath eligible Moments and comments. Original content remains visible by default. When a signed-in user clicks `Translate`, the browser asks the MyVideoGameList backend for an English translation, displays the translated text beneath the original, and shows the detected source language when available. ## Example behavior Original Russian comment remains visible. > Very cool game. At first I did not want to play it, but once I started, I could not stop. `Translated from Russian · Hide translation` Users should be able to hide and show an already loaded translation again without triggering another API request. ## Initial scope - Moments on game pages. - Comments on game pages. - English as the only target language. - DeepL as the initial translation provider. Out of scope for the first version: - Translation into languages other than English. - Automatic translation without user interaction. - Translating every existing database record. - Translation of private messages. - Browser-side calls directly to DeepL. - Automatic fallback between providers. - User-selectable translation providers. - Real-time translation while composing content. ## Provider abstraction Use DeepL first, but keep provider-specific code isolated behind an interface so providers such as LibreTranslate, OpenAI, Google Cloud Translation, or Kagi can be added later. Suggested interface: ```php interface TranslationProviderInterface { public function translate( string $text, string $targetLanguage ): TranslationResult; } ``` `TranslationResult` should include: - Translated text. - Detected source language. - Provider name. Use DeepL automatic source-language detection. Store the detected language code with the cached translation and map known codes to readable names, such as `RU` to `Russian`, `JA` to `Japanese`, and `DE` to `German`. If the source language cannot be determined, display `Translated to English`. Do not make a separate language-detection request just to decide whether the `Translate` button should be displayed. For the first version, it is acceptable to show `Translate` on all eligible content and let the backend determine whether translation is necessary. ## Configuration Suggested environment variables: ```env TRANSLATION_ENABLED=true TRANSLATION_PROVIDER=deepl TRANSLATION_TARGET_LANGUAGE=EN DEEPL_API_KEY= ``` The DeepL API key must remain server-side and must never be exposed to browser JavaScript. ## Backend endpoint Create a protected backend translation endpoint. Suggested request: ```http POST /api/translations ``` ```json { "content_type": "comment", "content_id": 123, "target_language": "EN" } ``` The browser should send only the content type and database ID. The backend must load the authoritative source content from the database. The endpoint must not accept arbitrary text from the browser and forward it to the translation provider, because that would allow abuse as a general-purpose translation proxy. A successful response should include: - Translated text. - Source language. - Target language. - Provider. - Whether the result came from cache. ## Translation cache Cache translations in the database so the same unchanged content is not translated repeatedly. Suggested table: `content_translations` Fields: - `id` - `content_type` - `content_id` - `source_language` - `target_language` - `translated_text` - `provider` - `original_text_hash` - `created_at` - `updated_at` Add a unique constraint for content type, content ID, and target language. Before calling the provider: 1. Load the current source content. 2. Generate a hash of the original text. 3. Look for an existing translation with the same content type, content ID, target language, and original text hash. 4. Return the cached translation when available. 5. Call the provider only when there is no valid cached translation. If a Moment or comment is edited, the old cached translation must no longer be treated as valid. ## Abuse and security protections - Require authentication, at least initially. - Add per-user and/or per-IP rate limiting. - Enforce a maximum content length. - Verify authorization for private or restricted content. - Use CSRF protection where applicable. - Escape and sanitize translated output. - Set a timeout for provider requests. - Handle provider failures gracefully. - Never expose API keys or raw provider responses. - Do not send unrelated user information to the provider. Preserve paragraph breaks where possible. Treat provider output as untrusted text. Do not render provider-returned HTML directly. ## Error handling Handle these states safely: - Translation provider unavailable. - API quota exhausted. - Rate limit exceeded. - Content deleted or not found. - Content already in English. - Translation disabled. - Unsupported language. - Provider timeout. Use a generic user-facing message such as: > Translation is temporarily unavailable. Do not expose stack traces, API responses, credentials, or internal error details. ## Logging and metrics Log translation failures through the application's existing logging system. Include: - Content type. - Content ID. - Provider. - Target language. - HTTP status. - Whether the request used the cache. Do not log API keys, authorization headers, or full private content unnecessarily. Track basic usage metrics if practical: - Characters translated. - Provider requests. - Cache hits. - Cache misses. - Translation failures. This matters because the initial DeepL Developer allowance is a one-time pool of 1 million characters. ## Accessibility - Use a real button element. - Make the action keyboard accessible. - Use a clear accessible label. - Expose loading state with `aria-busy` or an equivalent. - Announce dynamically inserted translations where appropriate. - Do not rely on the globe icon alone. ## Suggested implementation order 1. Add translation configuration. 2. Add the provider interface and translation result object. 3. Implement the DeepL provider. 4. Add the translation cache table and model or repository. 5. Add the protected backend endpoint. 6. Add validation and rate limiting. 7. Add the `Translate` UI to Moments and comments. 8. Add show and hide behavior. 9. Add logging and usage metrics. 10. Add automated tests. ## Tests Provider layer: - Successful DeepL translation. - Source-language detection. - Authentication headers. - Timeout behavior. - Provider error responses. - Malformed provider responses. Caching: - First request calls the provider. - Second request returns the cached translation. - Editing source content invalidates the cache. - Different target languages create separate cache entries. - Provider is not called on a valid cache hit. Endpoint: - Valid comment translation. - Valid Moment translation. - Missing content. - Unauthorized or private content. - Arbitrary text cannot be submitted. - Rate-limit enforcement. - Maximum-length enforcement. - Translation disabled. - Provider failure returns a safe error. Frontend: - Translate button appears for eligible content. - Loading state is shown. - Translation is inserted successfully. - Translation can be hidden and shown again. - Showing an already loaded translation does not make another request. - Errors are displayed without removing the original content. ## Acceptance criteria - A signed-in user can click `Translate` beneath an eligible Moment or comment. - The original content remains visible and unchanged. - The English translation appears beneath the original. - The detected source language is shown when available. - The translation can be hidden and shown again. - Repeat requests for unchanged content use the database cache. - Editing original content invalidates the previous cached translation. - The browser never receives the DeepL API key. - The endpoint cannot translate arbitrary user-supplied text. - Requests are rate-limited. - Translation can be disabled through configuration. - Provider failures do not break the page or hide the original content. - DeepL-specific code is isolated behind a provider interface. - Automated tests cover the provider, cache, endpoint, and primary UI behavior.
jimmyb changed title from Add on-demand translation for Moments and comments to Add On-Demand Translation for Comments on Game Profiles, User Profiles, etc... 2026-07-28 17:33:01 -05:00
Sign in to join this conversation.
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
MyVideoGameList/myvideogamelist.com#851
No description provided.