[PR #17399] [MERGED] i18n: German 100% translated #128071

Closed
opened 2026-05-21 10:26:41 -05:00 by GiteaMirror · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/open-webui/open-webui/pull/17399
Author: @Ithanil
Created: 9/12/2025
Status: Merged
Merged: 9/12/2025
Merged by: @tjbck

Base: devHead: german_i18n


📝 Commits (1)

  • c9e69fb 100% german translation (~ 250 strings added/changed)

📊 Changes

1 file changed (+249 additions, -249 deletions)

View changed files

📝 src/lib/i18n/locales/de-DE/translation.json (+249 -249)

📄 Description

Pull Request Checklist

Note to first-time contributors: Please open a discussion post in Discussions and describe your changes before submitting a pull request.

Before submitting, make sure you've checked the following:

  • Target branch: Please verify that the pull request targets the dev branch.
  • Description: Provide a concise description of the changes made in this pull request.
  • Changelog: Ensure a changelog entry following the format of Keep a Changelog is added at the bottom of the PR description.
  • Documentation: Have you updated relevant documentation Open WebUI Docs, or other documentation sources?
  • Dependencies: Are there any new dependencies? Have you updated the dependency versions in the documentation?
  • Testing: Have you written and run sufficient tests to validate the changes?
  • Code review: Have you performed a self-review of your code, addressing any coding standard issues and ensuring adherence to the project's coding standards?
  • Prefix: To clearly categorize this pull request, prefix the pull request title using one of the following:
    • BREAKING CHANGE: Significant changes that may affect compatibility
    • build: Changes that affect the build system or external dependencies
    • ci: Changes to our continuous integration processes or workflows
    • chore: Refactor, cleanup, or other non-functional code changes
    • docs: Documentation update or addition
    • feat: Introduces a new feature or enhancement to the codebase
    • fix: Bug fix or error correction
    • i18n: Internationalization or localization changes
    • perf: Performance improvement
    • refactor: Code restructuring for better maintainability, readability, or scalability
    • style: Changes that do not affect the meaning of the code (white space, formatting, missing semi-colons, etc.)
    • test: Adding missing tests or correcting existing tests
    • WIP: Work in progress, a temporary label for incomplete or ongoing work

Changelog Entry

Description

Pre-translated all missing strings using Qwen3 235B 2507 with the following script:

#!/usr/bin/env python3
import json
import argparse
import requests
import time
import sys
from collections import OrderedDict

def load_json_ordered(file_path):
    """Load JSON while preserving key order using OrderedDict"""
    with open(file_path, 'r', encoding='utf-8') as f:
        return json.load(f, object_pairs_hook=OrderedDict)

def save_json_ordered(file_path, data, indent):
    """Save JSON with preserved order and configurable indentation"""
    # Handle indent conversion: try int first, then use as string
    try:
        indent_value = int(indent)
    except (ValueError, TypeError):
        indent_value = indent
    
    with open(file_path, 'w', encoding='utf-8') as f:
        json.dump(data, f, indent=indent_value, ensure_ascii=False)

def get_context(data, current_key, nctx):
    """Collect surrounding context with translations (nctx per side)"""
    keys = list(data.keys())
    idx = keys.index(current_key)
    context_pre = []
    context_post = []
    
    # Collect preceding context (up to nctx non-empty entries)
    i = idx - 1
    count = 0
    preceding = []
    while i >= 0 and count < nctx:
        k = keys[i]
        if data[k]:
            preceding.append((k, data[k]))
            count += 1
        i -= 1
    context_pre = list(reversed(preceding))  # Reverse to maintain file order
    
    # Collect following context (up to nctx non-empty entries)
    i = idx + 1
    count = 0
    while i < len(keys) and count < nctx:
        k = keys[i]
        if data[k]:
            context_post.append((k, data[k]))
            count += 1
        i += 1
    
    return context_pre, context_post

def translate_string(api_url, api_key, model, lang, source_str, context, retry):
    """Translate a string using LLM with retry logic"""
    # Format context examples
    context_str = "\n".join([
        f'"{k}" : "{v}"' 
        for k, v in context
    ])
    
    # Prepare translation request
    system_msg = f"You are a professional translator. Translate English strings to {lang}. Only output the translation without any additional text, quotes, or explanations. Do NOT change date formats, quotation marks or decimal separators."
    user_msg = (
        f"Here are translation examples:\n\n{context_str}\n\n"
        f"Translate this string to {lang}:\n\"{source_str}\""
    )
    
    headers = {
        "Content-Type": "application/json",
        "Authorization": f"Bearer {api_key}"
    }
    payload = {
        "model": model,
        "messages": [
            {"role": "system", "content": system_msg},
            {"role": "user", "content": user_msg}
        ],
        "max_tokens": 1000
    }
    
    # Construct full endpoint URL
    endpoint = f"{api_url.rstrip('/')}/chat/completions"
    
    # Retry logic
    for attempt in range(retry + 1):
        try:
            response = requests.post(
                endpoint, 
                headers=headers, 
                json=payload, 
                timeout=30
            )
            response.raise_for_status()
            result = response.json()
            
            # Extract translation
            translation = result["choices"][0]["message"]["content"].strip()
            if not translation:
                raise ValueError("Empty translation response")
                
            return translation
            
        except (requests.exceptions.RequestException, 
                KeyError, 
                IndexError, 
                ValueError) as e:
            if attempt < retry:
                wait = 2 ** attempt
                print(f"  Retry {attempt+1}/{retry} in {wait}s: {str(e)}", file=sys.stderr)
                time.sleep(wait)
            else:
                raise RuntimeError(f"Translation failed after {retry} retries") from e

def main():
    parser = argparse.ArgumentParser(description='Translate empty locale strings using LLM')
    parser.add_argument('json_file', type=str, help='Locale JSON file to process')
    parser.add_argument('--api-url', required=True, help='LLM API base URL (endpoint will be api-url/chat/completions)')
    parser.add_argument('--api-key', required=True, help='API authentication key')
    parser.add_argument('--model', required=True, help='LLM model name')
    parser.add_argument('--lang', required=True, help='Target language (e.g., "German")')
    parser.add_argument('--nctx', type=int, default=5, help='Number of context strings on EACH side (preceding/following) (default: 5)')
    parser.add_argument('--retry', type=int, default=3, help='Retry attempts per string (default: 3)')
    parser.add_argument('--indent', default='4', help='JSON indentation (integer for spaces, or string like "\\t") (default: 4)')
    args = parser.parse_args()

    # Load locale data with preserved order
    try:
        data = load_json_ordered(args.json_file)
    except Exception as e:
        print(f"Error loading JSON file: {e}", file=sys.stderr)
        sys.exit(1)

    # Track failed keys to prevent infinite loops
    failed_keys = set()
    processed = 0
    total_empty = sum(1 for v in data.values() if v == "")

    print(f"Found {total_empty} untranslated strings. Starting translation...")
    
    while True:
        # Find next untranslated string (skipping previously failed)
        current_key = None
        for key, value in data.items():
            if value == "" and key not in failed_keys:
                current_key = key
                break
        
        if not current_key:
            break  # No more untranslated strings
        
        print(f"\nTranslating: '{current_key}'")
        context_pre, context_post = get_context(data, current_key, args.nctx)
        
        # Show context for debugging
        if context_pre:
            for (k, v) in context_pre:
                print(f"    '{k}': '{v}'")
        print(f"  > '{current_key}': ''")
        if context_post:
            for (k, v) in context_post:
                print(f"    '{k}': '{v}'")
        
        try:
            translation = translate_string(
                api_url=args.api_url,
                api_key=args.api_key,
                model=args.model,
                lang=args.lang,
                source_str=current_key,
                context=(context_pre + context_post),
                retry=args.retry
            )
            data[current_key] = translation
            save_json_ordered(args.json_file, data, args.indent)
            processed += 1
            print(f"  ✅ Success: '{translation}'")
            
        except Exception as e:
            print(f"  ❌ Failed: {str(e)}", file=sys.stderr)
            failed_keys.add(current_key)
    
    # Final status
    remaining = total_empty - processed
    print(f"\nTranslation complete. Processed: {processed}, Remaining: {remaining}")
    if failed_keys:
        print("Failed keys (check logs for details):")
        for key in failed_keys:
            print(f"  - {key}")

if __name__ == "__main__":
    main()

Thoroughly curated the result, including a few select minor changes to existing translations. Every single string has been reviewed and should be acceptable at a minimum.

Not every string has been inspected for looks/fit in actual deployment, given the high volume of translated strings.

Contributor License Agreement

By submitting this pull request, I confirm that I have read and fully agree to the Contributor License Agreement (CLA), and I am providing my contributions under its terms.


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/open-webui/open-webui/pull/17399 **Author:** [@Ithanil](https://github.com/Ithanil) **Created:** 9/12/2025 **Status:** ✅ Merged **Merged:** 9/12/2025 **Merged by:** [@tjbck](https://github.com/tjbck) **Base:** `dev` ← **Head:** `german_i18n` --- ### 📝 Commits (1) - [`c9e69fb`](https://github.com/open-webui/open-webui/commit/c9e69fb2f4d19bc39b9060f0a3b9afdf10eeff36) 100% german translation (~ 250 strings added/changed) ### 📊 Changes **1 file changed** (+249 additions, -249 deletions) <details> <summary>View changed files</summary> 📝 `src/lib/i18n/locales/de-DE/translation.json` (+249 -249) </details> ### 📄 Description # Pull Request Checklist ### Note to first-time contributors: Please open a discussion post in [Discussions](https://github.com/open-webui/open-webui/discussions) and describe your changes before submitting a pull request. **Before submitting, make sure you've checked the following:** - [x] **Target branch:** Please verify that the pull request targets the `dev` branch. - [x] **Description:** Provide a concise description of the changes made in this pull request. - [x] **Changelog:** Ensure a changelog entry following the format of [Keep a Changelog](https://keepachangelog.com/) is added at the bottom of the PR description. - [x] **Documentation:** Have you updated relevant documentation [Open WebUI Docs](https://github.com/open-webui/docs), or other documentation sources? - [x] **Dependencies:** Are there any new dependencies? Have you updated the dependency versions in the documentation? - [x] **Testing:** Have you written and run sufficient tests to validate the changes? - [x] **Code review:** Have you performed a self-review of your code, addressing any coding standard issues and ensuring adherence to the project's coding standards? - [x] **Prefix:** To clearly categorize this pull request, prefix the pull request title using one of the following: - **BREAKING CHANGE**: Significant changes that may affect compatibility - **build**: Changes that affect the build system or external dependencies - **ci**: Changes to our continuous integration processes or workflows - **chore**: Refactor, cleanup, or other non-functional code changes - **docs**: Documentation update or addition - **feat**: Introduces a new feature or enhancement to the codebase - **fix**: Bug fix or error correction - **i18n**: Internationalization or localization changes - **perf**: Performance improvement - **refactor**: Code restructuring for better maintainability, readability, or scalability - **style**: Changes that do not affect the meaning of the code (white space, formatting, missing semi-colons, etc.) - **test**: Adding missing tests or correcting existing tests - **WIP**: Work in progress, a temporary label for incomplete or ongoing work # Changelog Entry ### Description Pre-translated all missing strings using Qwen3 235B 2507 with the following script: ``` #!/usr/bin/env python3 import json import argparse import requests import time import sys from collections import OrderedDict def load_json_ordered(file_path): """Load JSON while preserving key order using OrderedDict""" with open(file_path, 'r', encoding='utf-8') as f: return json.load(f, object_pairs_hook=OrderedDict) def save_json_ordered(file_path, data, indent): """Save JSON with preserved order and configurable indentation""" # Handle indent conversion: try int first, then use as string try: indent_value = int(indent) except (ValueError, TypeError): indent_value = indent with open(file_path, 'w', encoding='utf-8') as f: json.dump(data, f, indent=indent_value, ensure_ascii=False) def get_context(data, current_key, nctx): """Collect surrounding context with translations (nctx per side)""" keys = list(data.keys()) idx = keys.index(current_key) context_pre = [] context_post = [] # Collect preceding context (up to nctx non-empty entries) i = idx - 1 count = 0 preceding = [] while i >= 0 and count < nctx: k = keys[i] if data[k]: preceding.append((k, data[k])) count += 1 i -= 1 context_pre = list(reversed(preceding)) # Reverse to maintain file order # Collect following context (up to nctx non-empty entries) i = idx + 1 count = 0 while i < len(keys) and count < nctx: k = keys[i] if data[k]: context_post.append((k, data[k])) count += 1 i += 1 return context_pre, context_post def translate_string(api_url, api_key, model, lang, source_str, context, retry): """Translate a string using LLM with retry logic""" # Format context examples context_str = "\n".join([ f'"{k}" : "{v}"' for k, v in context ]) # Prepare translation request system_msg = f"You are a professional translator. Translate English strings to {lang}. Only output the translation without any additional text, quotes, or explanations. Do NOT change date formats, quotation marks or decimal separators." user_msg = ( f"Here are translation examples:\n\n{context_str}\n\n" f"Translate this string to {lang}:\n\"{source_str}\"" ) headers = { "Content-Type": "application/json", "Authorization": f"Bearer {api_key}" } payload = { "model": model, "messages": [ {"role": "system", "content": system_msg}, {"role": "user", "content": user_msg} ], "max_tokens": 1000 } # Construct full endpoint URL endpoint = f"{api_url.rstrip('/')}/chat/completions" # Retry logic for attempt in range(retry + 1): try: response = requests.post( endpoint, headers=headers, json=payload, timeout=30 ) response.raise_for_status() result = response.json() # Extract translation translation = result["choices"][0]["message"]["content"].strip() if not translation: raise ValueError("Empty translation response") return translation except (requests.exceptions.RequestException, KeyError, IndexError, ValueError) as e: if attempt < retry: wait = 2 ** attempt print(f" Retry {attempt+1}/{retry} in {wait}s: {str(e)}", file=sys.stderr) time.sleep(wait) else: raise RuntimeError(f"Translation failed after {retry} retries") from e def main(): parser = argparse.ArgumentParser(description='Translate empty locale strings using LLM') parser.add_argument('json_file', type=str, help='Locale JSON file to process') parser.add_argument('--api-url', required=True, help='LLM API base URL (endpoint will be api-url/chat/completions)') parser.add_argument('--api-key', required=True, help='API authentication key') parser.add_argument('--model', required=True, help='LLM model name') parser.add_argument('--lang', required=True, help='Target language (e.g., "German")') parser.add_argument('--nctx', type=int, default=5, help='Number of context strings on EACH side (preceding/following) (default: 5)') parser.add_argument('--retry', type=int, default=3, help='Retry attempts per string (default: 3)') parser.add_argument('--indent', default='4', help='JSON indentation (integer for spaces, or string like "\\t") (default: 4)') args = parser.parse_args() # Load locale data with preserved order try: data = load_json_ordered(args.json_file) except Exception as e: print(f"Error loading JSON file: {e}", file=sys.stderr) sys.exit(1) # Track failed keys to prevent infinite loops failed_keys = set() processed = 0 total_empty = sum(1 for v in data.values() if v == "") print(f"Found {total_empty} untranslated strings. Starting translation...") while True: # Find next untranslated string (skipping previously failed) current_key = None for key, value in data.items(): if value == "" and key not in failed_keys: current_key = key break if not current_key: break # No more untranslated strings print(f"\nTranslating: '{current_key}'") context_pre, context_post = get_context(data, current_key, args.nctx) # Show context for debugging if context_pre: for (k, v) in context_pre: print(f" '{k}': '{v}'") print(f" > '{current_key}': ''") if context_post: for (k, v) in context_post: print(f" '{k}': '{v}'") try: translation = translate_string( api_url=args.api_url, api_key=args.api_key, model=args.model, lang=args.lang, source_str=current_key, context=(context_pre + context_post), retry=args.retry ) data[current_key] = translation save_json_ordered(args.json_file, data, args.indent) processed += 1 print(f" ✅ Success: '{translation}'") except Exception as e: print(f" ❌ Failed: {str(e)}", file=sys.stderr) failed_keys.add(current_key) # Final status remaining = total_empty - processed print(f"\nTranslation complete. Processed: {processed}, Remaining: {remaining}") if failed_keys: print("Failed keys (check logs for details):") for key in failed_keys: print(f" - {key}") if __name__ == "__main__": main() ``` **Thoroughly** curated the result, including a few select minor changes to existing translations. Every single string has been reviewed and should be *acceptable* at a minimum. Not every string has been inspected for looks/fit in actual deployment, given the high volume of translated strings. ### Contributor License Agreement By submitting this pull request, I confirm that I have read and fully agree to the [Contributor License Agreement (CLA)](https://github.com/open-webui/open-webui/blob/main/CONTRIBUTOR_LICENSE_AGREEMENT), and I am providing my contributions under its terms. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
GiteaMirror added the pull-request label 2026-05-21 10:26:41 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/open-webui#128071