rebase localization onto latest main
Agent-Logs-Url: https://github.com/wasi-master/13ft/sessions/85227ed8-0556-4ae4-a47b-92bfed6cfe67 Co-authored-by: wasi-master <63045920+wasi-master@users.noreply.github.com>
This commit is contained in:
co-authored by
wasi-master
parent
6e50c9c182
commit
1cefe387ab
@@ -168,7 +168,7 @@ WantedBy=multi-user.target
|
|||||||
|
|
||||||
## Localisation
|
## Localisation
|
||||||
|
|
||||||
The UI strings can be localised by setting the `LOCALE` environment variable before starting the app. Locale files live in `/home/runner/work/13ft/13ft/app/locales/<locale>.json`.
|
The UI strings can be localised by setting the `LOCALE` environment variable before starting the app. Locale files live in `app/locales/<locale>.json`.
|
||||||
|
|
||||||
Built-in locales:
|
Built-in locales:
|
||||||
|
|
||||||
@@ -179,14 +179,14 @@ Built-in locales:
|
|||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# from /home/runner/work/13ft/13ft/app
|
# from app
|
||||||
LOCALE=de python portable.py
|
LOCALE=de python portable.py
|
||||||
|
|
||||||
# or with gunicorn
|
# or with gunicorn
|
||||||
LOCALE=fr python -m gunicorn 'portable:app'
|
LOCALE=fr python -m gunicorn 'portable:app'
|
||||||
```
|
```
|
||||||
|
|
||||||
To add a new locale, copy `/home/runner/work/13ft/13ft/app/locales/en.json` to a new file such as `/home/runner/work/13ft/13ft/app/locales/es.json` and translate the values.
|
To add a new locale, copy `app/locales/en.json` to a new file such as `app/locales/es.json` and translate the values.
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
+2
-2
@@ -22,6 +22,6 @@
|
|||||||
"challenge_error": "Die Website hat eine Anti-Bot-Prüfung (Cloudflare oder ähnlich) ausgeliefert und es wurde kein brauchbarer Schnappschuss auf archive.org oder archive.today gefunden. Diese Website blockiert aktiv automatisierte Anfragen.",
|
"challenge_error": "Die Website hat eine Anti-Bot-Prüfung (Cloudflare oder ähnlich) ausgeliefert und es wurde kein brauchbarer Schnappschuss auf archive.org oder archive.today gefunden. Diese Website blockiert aktiv automatisierte Anfragen.",
|
||||||
"timeout_error": "Die Website hat zu lange zum Antworten gebraucht (30s-Timeout). Bitte versuchen Sie es später erneut.",
|
"timeout_error": "Die Website hat zu lange zum Antworten gebraucht (30s-Timeout). Bitte versuchen Sie es später erneut.",
|
||||||
"connection_error": "Die Verbindung zur Website konnte nicht hergestellt werden. Prüfen Sie die URL und versuchen Sie es erneut.",
|
"connection_error": "Die Verbindung zur Website konnte nicht hergestellt werden. Prüfen Sie die URL und versuchen Sie es erneut.",
|
||||||
"request_error": "Seite konnte nicht geladen werden: {error}",
|
"request_error": "Die Seite konnte nicht geladen werden.",
|
||||||
"unexpected_error": "Unerwarteter Fehler: {error}"
|
"unexpected_error": "Unerwarteter Fehler beim Laden der Seite."
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -22,6 +22,6 @@
|
|||||||
"challenge_error": "The site served an anti-bot challenge (Cloudflare or similar) and no usable snapshot was found on archive.org or archive.today. This site is actively blocking automated requests.",
|
"challenge_error": "The site served an anti-bot challenge (Cloudflare or similar) and no usable snapshot was found on archive.org or archive.today. This site is actively blocking automated requests.",
|
||||||
"timeout_error": "The website took too long to respond (30s timeout). Try again later.",
|
"timeout_error": "The website took too long to respond (30s timeout). Try again later.",
|
||||||
"connection_error": "Could not connect to the website. Check the URL and try again.",
|
"connection_error": "Could not connect to the website. Check the URL and try again.",
|
||||||
"request_error": "Failed to fetch the page: {error}",
|
"request_error": "Failed to fetch the page.",
|
||||||
"unexpected_error": "Unexpected error: {error}"
|
"unexpected_error": "Unexpected error while fetching the page."
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -22,6 +22,6 @@
|
|||||||
"challenge_error": "Le site a présenté une protection anti-bot (Cloudflare ou similaire) et aucun instantané exploitable n'a été trouvé sur archive.org ou archive.today. Ce site bloque activement les requêtes automatisées.",
|
"challenge_error": "Le site a présenté une protection anti-bot (Cloudflare ou similaire) et aucun instantané exploitable n'a été trouvé sur archive.org ou archive.today. Ce site bloque activement les requêtes automatisées.",
|
||||||
"timeout_error": "Le site web a mis trop de temps à répondre (délai de 30 s). Veuillez réessayer plus tard.",
|
"timeout_error": "Le site web a mis trop de temps à répondre (délai de 30 s). Veuillez réessayer plus tard.",
|
||||||
"connection_error": "Impossible de se connecter au site web. Vérifiez l'URL et réessayez.",
|
"connection_error": "Impossible de se connecter au site web. Vérifiez l'URL et réessayez.",
|
||||||
"request_error": "Échec de récupération de la page : {error}",
|
"request_error": "Impossible de récupérer la page.",
|
||||||
"unexpected_error": "Erreur inattendue : {error}"
|
"unexpected_error": "Erreur inattendue lors du chargement de la page."
|
||||||
}
|
}
|
||||||
|
|||||||
+46
-16
@@ -6,6 +6,7 @@ import requests
|
|||||||
import uuid
|
import uuid
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
from functools import lru_cache
|
||||||
from urllib.parse import urljoin, urlparse, quote
|
from urllib.parse import urljoin, urlparse, quote
|
||||||
from flask import request, Response
|
from flask import request, Response
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
@@ -44,16 +45,20 @@ _DEFAULT_STRINGS = {
|
|||||||
),
|
),
|
||||||
"timeout_error": "The website took too long to respond (30s timeout). Try again later.",
|
"timeout_error": "The website took too long to respond (30s timeout). Try again later.",
|
||||||
"connection_error": "Could not connect to the website. Check the URL and try again.",
|
"connection_error": "Could not connect to the website. Check the URL and try again.",
|
||||||
"request_error": "Failed to fetch the page: {error}",
|
"request_error": "Failed to fetch the page.",
|
||||||
"unexpected_error": "Unexpected error: {error}",
|
"unexpected_error": "Unexpected error while fetching the page.",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def load_strings():
|
def get_locale():
|
||||||
locale = os.environ.get("LOCALE", "en")
|
locale = os.environ.get("LOCALE", "en")
|
||||||
if not re.match(r"^[a-zA-Z0-9_-]+$", locale):
|
if not re.match(r"^[a-zA-Z0-9_-]+$", locale):
|
||||||
locale = "en"
|
locale = "en"
|
||||||
|
return locale
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=None)
|
||||||
|
def load_strings_for_locale(locale):
|
||||||
strings = dict(_DEFAULT_STRINGS)
|
strings = dict(_DEFAULT_STRINGS)
|
||||||
locale_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "locales")
|
locale_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "locales")
|
||||||
locale_file = os.path.join(locale_dir, f"{locale}.json")
|
locale_file = os.path.join(locale_dir, f"{locale}.json")
|
||||||
@@ -63,10 +68,17 @@ def load_strings():
|
|||||||
with open(locale_file, "r", encoding="utf-8") as file:
|
with open(locale_file, "r", encoding="utf-8") as file:
|
||||||
strings.update(json.load(file))
|
strings.update(json.load(file))
|
||||||
except (FileNotFoundError, json.JSONDecodeError, OSError):
|
except (FileNotFoundError, json.JSONDecodeError, OSError):
|
||||||
|
strings = dict(_DEFAULT_STRINGS)
|
||||||
locale = "en"
|
locale = "en"
|
||||||
|
|
||||||
strings["lang"] = locale
|
strings["lang"] = locale
|
||||||
return strings
|
return strings
|
||||||
|
|
||||||
|
|
||||||
|
def load_strings():
|
||||||
|
return dict(load_strings_for_locale(get_locale()))
|
||||||
|
|
||||||
|
|
||||||
googlebot_headers = {
|
googlebot_headers = {
|
||||||
"User-Agent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.119 Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
|
"User-Agent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.119 Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
|
||||||
}
|
}
|
||||||
@@ -74,6 +86,12 @@ googlebot_headers = {
|
|||||||
jobs = {}
|
jobs = {}
|
||||||
jobs_lock = threading.Lock()
|
jobs_lock = threading.Lock()
|
||||||
|
|
||||||
|
|
||||||
|
class UserFacingError(Exception):
|
||||||
|
def __init__(self, user_message):
|
||||||
|
super().__init__(user_message)
|
||||||
|
self.user_message = user_message
|
||||||
|
|
||||||
html = """
|
html = """
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="{{ lang }}">
|
<html lang="{{ lang }}">
|
||||||
@@ -692,7 +710,7 @@ def bypass_paywall(url, strings, job_id=None):
|
|||||||
msg = strings["medium_challenge_error"]
|
msg = strings["medium_challenge_error"]
|
||||||
else:
|
else:
|
||||||
msg = strings["challenge_error"]
|
msg = strings["challenge_error"]
|
||||||
raise RuntimeError(msg)
|
raise UserFacingError(msg)
|
||||||
|
|
||||||
set_step(job_id, 'process')
|
set_step(job_id, 'process')
|
||||||
result = add_base_tag(html_text, final_url)
|
result = add_base_tag(html_text, final_url)
|
||||||
@@ -717,15 +735,15 @@ def fetch_worker(job_id, url, strings):
|
|||||||
jobs[job_id]['step'] = 'error'
|
jobs[job_id]['step'] = 'error'
|
||||||
except requests.exceptions.RequestException as e:
|
except requests.exceptions.RequestException as e:
|
||||||
with jobs_lock:
|
with jobs_lock:
|
||||||
jobs[job_id]['error'] = strings["request_error"].format(error=e)
|
jobs[job_id]['error'] = strings["request_error"]
|
||||||
jobs[job_id]['step'] = 'error'
|
jobs[job_id]['step'] = 'error'
|
||||||
except RuntimeError as e:
|
except UserFacingError as error:
|
||||||
with jobs_lock:
|
with jobs_lock:
|
||||||
jobs[job_id]['error'] = str(e)
|
jobs[job_id]['error'] = error.user_message
|
||||||
jobs[job_id]['step'] = 'error'
|
jobs[job_id]['step'] = 'error'
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
with jobs_lock:
|
with jobs_lock:
|
||||||
jobs[job_id]['error'] = strings["unexpected_error"].format(error=e)
|
jobs[job_id]['error'] = strings["unexpected_error"]
|
||||||
jobs[job_id]['step'] = 'error'
|
jobs[job_id]['step'] = 'error'
|
||||||
|
|
||||||
|
|
||||||
@@ -782,10 +800,16 @@ def show_article():
|
|||||||
strings = load_strings()
|
strings = load_strings()
|
||||||
try:
|
try:
|
||||||
return bypass_paywall(link, strings)
|
return bypass_paywall(link, strings)
|
||||||
except requests.exceptions.RequestException as e:
|
except requests.exceptions.Timeout:
|
||||||
return strings["request_error"].format(error=e), 400
|
return strings["timeout_error"], 400
|
||||||
except Exception as e:
|
except requests.exceptions.ConnectionError:
|
||||||
raise e
|
return strings["connection_error"], 400
|
||||||
|
except requests.exceptions.RequestException:
|
||||||
|
return strings["request_error"], 400
|
||||||
|
except UserFacingError as error:
|
||||||
|
return error.user_message, 400
|
||||||
|
except Exception:
|
||||||
|
return strings["unexpected_error"], 500
|
||||||
|
|
||||||
|
|
||||||
@app.route("/", defaults={"path": ""})
|
@app.route("/", defaults={"path": ""})
|
||||||
@@ -798,10 +822,16 @@ def get_article(path):
|
|||||||
actual_url = "https://" + parts[4].lstrip("/")
|
actual_url = "https://" + parts[4].lstrip("/")
|
||||||
try:
|
try:
|
||||||
return bypass_paywall(actual_url, strings)
|
return bypass_paywall(actual_url, strings)
|
||||||
except requests.exceptions.RequestException as e:
|
except requests.exceptions.Timeout:
|
||||||
return strings["request_error"].format(error=e), 400
|
return strings["timeout_error"], 400
|
||||||
except Exception as e:
|
except requests.exceptions.ConnectionError:
|
||||||
raise e
|
return strings["connection_error"], 400
|
||||||
|
except requests.exceptions.RequestException:
|
||||||
|
return strings["request_error"], 400
|
||||||
|
except UserFacingError as error:
|
||||||
|
return error.user_message, 400
|
||||||
|
except Exception:
|
||||||
|
return strings["unexpected_error"], 500
|
||||||
else:
|
else:
|
||||||
return strings["invalid_url"], 400
|
return strings["invalid_url"], 400
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user