mirror of
https://github.com/kevinveenbirkenbach/homepage.veen.world.git
synced 2026-09-10 21:26:48 +00:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f76794df78 | |||
| c6667f3b05 | |||
| b8891fe6fd | |||
| 2c7f0a23d1 | |||
| 330881260c | |||
| 857c470c9e | |||
| 793e2de899 | |||
| f830153cf4 | |||
| 308741346e | |||
| d7149b83e9 | |||
| e00ff9437c | |||
| d115fc99b2 | |||
| e96e8684e6 | |||
| 747ce379cc | |||
| ef1c8ff09a | |||
| 0c67f999f6 | |||
| efabbd3b2e | |||
| 2a35b2910a | |||
| a42930a699 |
2
.dockerignore
Normal file
2
.dockerignore
Normal file
@@ -0,0 +1,2 @@
|
||||
app/node_modules/
|
||||
app/static/vendor/
|
||||
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@@ -70,11 +70,11 @@ jobs:
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
if: steps.semver.outputs.found == 'true'
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
||||
|
||||
- name: Login to GHCR
|
||||
if: steps.semver.outputs.found == 'true'
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
@@ -82,7 +82,7 @@ jobs:
|
||||
|
||||
- name: Build and publish image
|
||||
if: steps.semver.outputs.found == 'true'
|
||||
uses: docker/build-push-action@v6
|
||||
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
|
||||
55
.github/workflows/lint.yml
vendored
55
.github/workflows/lint.yml
vendored
@@ -19,6 +19,61 @@ jobs:
|
||||
- name: Run actionlint
|
||||
run: docker run --rm -v "$PWD:/repo" -w /repo rhysd/actionlint:latest
|
||||
|
||||
lint-yaml:
|
||||
name: Lint YAML
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Install lint dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install ".[dev]"
|
||||
|
||||
- name: Run yamllint
|
||||
run: yamllint --strict .
|
||||
|
||||
lint-js:
|
||||
name: Lint JavaScript
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
cache: npm
|
||||
cache-dependency-path: app/package.json
|
||||
|
||||
- name: Install Node dependencies
|
||||
working-directory: app
|
||||
run: npm install
|
||||
|
||||
- name: Run eslint
|
||||
working-directory: app
|
||||
run: npx eslint .
|
||||
|
||||
lint-shell:
|
||||
name: Lint shell scripts
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Run shellcheck
|
||||
run: docker run --rm -v "$PWD:/mnt" -w /mnt koalaman/shellcheck:stable scripts/*.sh
|
||||
|
||||
lint-python:
|
||||
name: Lint Python
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
7
.github/workflows/tests.yml
vendored
7
.github/workflows/tests.yml
vendored
@@ -21,6 +21,11 @@ jobs:
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Install lint test dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install --ignore-installed .
|
||||
|
||||
- name: Run lint test suite
|
||||
run: python -m unittest discover -s tests/lint -t .
|
||||
|
||||
@@ -185,7 +190,7 @@ jobs:
|
||||
xvfb
|
||||
|
||||
- name: Run Cypress tests
|
||||
uses: cypress-io/github-action@v6
|
||||
uses: cypress-io/github-action@f790eee7a50d9505912f50c2095510be7de06aa7 # v6.10.9
|
||||
with:
|
||||
working-directory: app
|
||||
install: false
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,12 +1,15 @@
|
||||
app/config.yaml
|
||||
app/i18n/content/
|
||||
*__pycache__*
|
||||
app/static/cache/*
|
||||
.env
|
||||
app/cypress/screenshots/*
|
||||
.ruff_cache/
|
||||
.venv/
|
||||
app/node_modules/
|
||||
app/static/vendor/
|
||||
hadolint-results.sarif
|
||||
build/
|
||||
*.egg-info/
|
||||
app/core.*
|
||||
.mcp.json
|
||||
|
||||
23
.yamllint
Normal file
23
.yamllint
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
# Correctness only. No `extends: default`, so nothing but the rules below runs:
|
||||
# the repository predates this linter and its cosmetic findings (indentation,
|
||||
# line length, trailing spaces) would be noise nobody acts on.
|
||||
#
|
||||
# key-duplicates is the rule that earns its keep: PyYAML keeps the last of two
|
||||
# identical keys without complaining, so a duplicated entry in a translation
|
||||
# catalogue silently drops a translation.
|
||||
ignore: |
|
||||
.git/
|
||||
.venv/
|
||||
venv/
|
||||
node_modules/
|
||||
app/node_modules/
|
||||
app/static/vendor/
|
||||
build/
|
||||
.ruff_cache/
|
||||
|
||||
rules:
|
||||
key-duplicates: enable
|
||||
octal-values:
|
||||
forbid-implicit-octal: true
|
||||
forbid-explicit-octal: true
|
||||
26
CHANGELOG.md
26
CHANGELOG.md
@@ -1,3 +1,29 @@
|
||||
# Changelog
|
||||
|
||||
## [2.1.2] - 2026-09-10
|
||||
|
||||
* CI: lint tests install the project first, so the image publishes again
|
||||
* Tooling: make test runs every local suite in .venv and keeps going
|
||||
* Image: multi-stage build ships only runtime assets, no node or dev packages
|
||||
* Security: CodeQL findings fixed; iframe links only open configured origins
|
||||
* i18n: interface strings translated for all 184 languages, test-enforced
|
||||
|
||||
## [2.1.1] - 2026-09-10
|
||||
|
||||
* Accessibility: header icons are *aria-hidden*, links read by label alone
|
||||
* Test coverage: unit test requires *aria-hidden* on every header icon
|
||||
|
||||
## [2.1.0] - 2026-08-22
|
||||
|
||||
* Multilingual site: every ISO 639-1 language has its own URL, */* follows the visitor's browser language, and a switcher in the navbar lists all 184 in their own script
|
||||
* Right-to-left layouts: Arabic, Hebrew, Persian, Urdu and six more mirror the page automatically
|
||||
* Machine translation: *make i18n* fills your card and menu texts from a LibreTranslate instance, *make i18n-ui* the interface strings; hand-corrected entries are never overwritten
|
||||
* Brand protection: list your product names in *app/i18n/keep.txt* and they stay untranslated in every language
|
||||
* Search engines: each page declares a canonical URL and an *hreflang* alternate per language
|
||||
* Security: configuration and translations are HTML-escaped, script URLs are stripped from links, images and the iframe view — set *TRUSTED_HOSTS* in *.env* to pin your public hostname
|
||||
* Reliability: a slow NASA APOD lookup no longer takes the page down
|
||||
* Developer tooling: *make test-e2e* runs Cypress directly instead of through act, and YAML, JavaScript and shell now have linters in CI
|
||||
|
||||
## [2.0.0] - 2026-05-18
|
||||
|
||||
* * Asset resolution: new probe-first resolver tries a HEAD request and embeds reachable image URLs directly via a new external_url field, falling back to the cache-download path only when the probe fails; broken /static/https://... URLs no longer appear when the source cannot be downloaded
|
||||
|
||||
22
Dockerfile
22
Dockerfile
@@ -1,12 +1,16 @@
|
||||
FROM python:3.12-slim
|
||||
FROM node:22-slim AS assets
|
||||
|
||||
WORKDIR /app
|
||||
COPY app/package.json ./
|
||||
COPY app/scripts ./scripts
|
||||
RUN npm install --omit=dev --no-audit --no-fund
|
||||
|
||||
FROM python:3.12-slim AS base
|
||||
|
||||
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
FLASK_HOST=0.0.0.0
|
||||
|
||||
# hadolint ignore=DL3008
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends nodejs npm && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /tmp/build
|
||||
|
||||
COPY pyproject.toml README.md main.py ./
|
||||
@@ -15,6 +19,14 @@ RUN python -m pip install --no-cache-dir .
|
||||
|
||||
WORKDIR /app
|
||||
COPY app/ .
|
||||
RUN npm install --prefix /app
|
||||
|
||||
CMD ["python", "app.py"]
|
||||
|
||||
FROM base AS dev
|
||||
|
||||
# hadolint ignore=DL3008
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends nodejs npm && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
FROM base AS runtime
|
||||
|
||||
COPY --from=assets /app/static/vendor ./static/vendor
|
||||
|
||||
64
Makefile
64
Makefile
@@ -7,6 +7,8 @@ endif
|
||||
|
||||
PYTHON ?= python3
|
||||
ACT ?= act
|
||||
TEST_VENV := $(CURDIR)/.venv
|
||||
TEST_PYTHON ?= $(TEST_VENV)/bin/python
|
||||
|
||||
# Bootstrap the local .env from the checked-in env.example template.
|
||||
# Idempotent: leaves an existing .env untouched.
|
||||
@@ -78,6 +80,8 @@ run-dev: env config config
|
||||
-p "$$PORT:$$PORT" \
|
||||
--name portfolio \
|
||||
-v "$(PWD)/app/:/app" \
|
||||
-e PORT="$$PORT" \
|
||||
-e TRUSTED_HOSTS="$$TRUSTED_HOSTS" \
|
||||
-e FLASK_APP=app.py \
|
||||
-e FLASK_ENV=development \
|
||||
"$$IMAGE_NAME"
|
||||
@@ -89,6 +93,8 @@ run-prod: env config config
|
||||
docker run -d \
|
||||
-p "$$PORT:$$PORT" \
|
||||
--name portfolio \
|
||||
-e PORT="$$PORT" \
|
||||
-e TRUSTED_HOSTS="$$TRUSTED_HOSTS" \
|
||||
"$$IMAGE_NAME"
|
||||
|
||||
.PHONY: logs
|
||||
@@ -132,6 +138,23 @@ install-dev:
|
||||
# Install runtime and developer dependencies from pyproject.toml.
|
||||
$(PYTHON) -m pip install -e ".[dev]"
|
||||
|
||||
.PHONY: i18n
|
||||
i18n: env config
|
||||
# Fill missing content translations in app/i18n/content/ via LibreTranslate.
|
||||
@$(call _require_env,LIBRETRANSLATE_URL); \
|
||||
$(PYTHON) utils/i18n_sync.py \
|
||||
--url "$$LIBRETRANSLATE_URL" \
|
||||
--api-key "$$LIBRETRANSLATE_API_KEY"
|
||||
|
||||
.PHONY: i18n-ui
|
||||
i18n-ui: env
|
||||
# Fill missing interface translations in app/i18n/ui/ via LibreTranslate.
|
||||
@$(call _require_env,LIBRETRANSLATE_URL); \
|
||||
$(PYTHON) utils/i18n_sync.py \
|
||||
--catalog ui \
|
||||
--url "$$LIBRETRANSLATE_URL" \
|
||||
--api-key "$$LIBRETRANSLATE_API_KEY"
|
||||
|
||||
.PHONY: lint-actions
|
||||
lint-actions:
|
||||
# Lint GitHub Actions workflows.
|
||||
@@ -148,8 +171,23 @@ lint-docker:
|
||||
# Lint the Dockerfile.
|
||||
docker run --rm -i hadolint/hadolint < Dockerfile
|
||||
|
||||
.PHONY: lint-yaml
|
||||
lint-yaml: install-dev
|
||||
# Lint YAML for duplicate keys and ambiguous scalars (see .yamllint).
|
||||
$(PYTHON) -m yamllint --strict .
|
||||
|
||||
.PHONY: lint-js
|
||||
lint-js: node-deps
|
||||
# Lint the browser and Cypress JavaScript.
|
||||
cd app && env -u ELECTRON_RUN_AS_NODE npx eslint .
|
||||
|
||||
.PHONY: lint-shell
|
||||
lint-shell:
|
||||
# Lint the shell scripts.
|
||||
docker run --rm -v "$$PWD:/mnt" -w /mnt koalaman/shellcheck:stable scripts/*.sh
|
||||
|
||||
.PHONY: test-lint
|
||||
test-lint:
|
||||
test-lint: install
|
||||
# Run lint guardrail tests.
|
||||
$(PYTHON) -m unittest discover -s tests/lint -t .
|
||||
|
||||
@@ -169,7 +207,7 @@ test-security: install
|
||||
$(PYTHON) -m unittest discover -s tests/security -t .
|
||||
|
||||
.PHONY: lint
|
||||
lint: lint-actions lint-python lint-docker test-lint
|
||||
lint: lint-actions lint-python lint-yaml lint-js lint-docker lint-shell test-lint
|
||||
# Run the full lint suite.
|
||||
|
||||
.PHONY: security
|
||||
@@ -179,9 +217,20 @@ security: install-dev test-security
|
||||
$(PYTHON) utils/export_runtime_requirements.py > /tmp/portfolio-runtime-requirements.txt
|
||||
$(PYTHON) -m pip_audit -r /tmp/portfolio-runtime-requirements.txt
|
||||
|
||||
.PHONY: node-deps
|
||||
node-deps:
|
||||
# Install the Cypress binary and the browser vendor assets into app/.
|
||||
cd app && npm install
|
||||
|
||||
.PHONY: test-e2e
|
||||
test-e2e:
|
||||
# Run Cypress end-to-end tests via act (stop portfolio container to free port first).
|
||||
test-e2e: env config node-deps
|
||||
# Run Cypress against a locally started Flask app — no act, no runner image.
|
||||
@$(call _require_env,PORT); \
|
||||
PORT="$$PORT" PYTHON="$(PYTHON)" scripts/run-e2e.sh
|
||||
|
||||
.PHONY: test-e2e-act
|
||||
test-e2e-act:
|
||||
# Run the CI end-to-end job through act (stop portfolio container to free port first).
|
||||
-docker stop portfolio 2>/dev/null || true
|
||||
$(ACT) workflow_dispatch -W .github/workflows/tests.yml -j e2e
|
||||
-docker start portfolio 2>/dev/null || true
|
||||
@@ -205,5 +254,8 @@ ci: lint security test-unit test-integration test-e2e
|
||||
# Run the local CI suite.
|
||||
|
||||
.PHONY: test
|
||||
test: ci
|
||||
# Run the full validation suite.
|
||||
test:
|
||||
# Run every local suite in a project virtualenv, since PEP 668 refuses pip on
|
||||
# a system Python, and keep going so each failing suite reports.
|
||||
@[ -x "$(TEST_PYTHON)" ] || python3 -m venv "$(TEST_VENV)"
|
||||
$(MAKE) --keep-going ci PYTHON="$(TEST_PYTHON)"
|
||||
|
||||
65
README.md
65
README.md
@@ -22,6 +22,8 @@ A lightweight, Docker-powered portfolio/landing-page generator—fully customiza
|
||||
Auto-cache assets for lightning-fast loading.
|
||||
- **Responsive Design**
|
||||
Built on Bootstrap; looks great on desktop, tablet & mobile.
|
||||
- **184 Languages**
|
||||
Every ISO 639-1 code, browser-negotiated, RTL-aware, with machine translation for your own content.
|
||||
- **YAML-Driven**
|
||||
All content & structure defined in a simple `config.yaml`.
|
||||
- **CLI Control**
|
||||
@@ -139,12 +141,75 @@ company:
|
||||
|
||||
---
|
||||
|
||||
## 🌍 Languages
|
||||
|
||||
Every ISO 639-1 language — all 184 two-letter codes — has a URL, a display
|
||||
name in its own script and a writing direction. The interface ships translated
|
||||
for 29 of them; the rest fall back to English string by string until a
|
||||
catalogue is filled. `/` serves the best match for the visitor's
|
||||
`Accept-Language` header, `/<code>/` forces one, and a switcher in the navbar
|
||||
lists them all. The ten right-to-left languages get `dir="rtl"` and Bootstrap's RTL
|
||||
stylesheet automatically.
|
||||
|
||||
Translations live in two catalogues, both keyed by the English source string:
|
||||
|
||||
| Path | Tracked | Holds |
|
||||
| --- | --- | --- |
|
||||
| `app/i18n/ui/<code>.yaml` | yes | Interface strings. Shipped for 29 languages; English is the source and has no file. |
|
||||
| `app/i18n/content/<code>.yaml` | no | Your `config.yaml` prose, generated per deployment. |
|
||||
|
||||
A string with no catalogue entry falls back to English, so a half-filled
|
||||
catalogue degrades instead of breaking.
|
||||
|
||||
Fill the content catalogues from a [LibreTranslate](https://libretranslate.com/)
|
||||
instance — set `LIBRETRANSLATE_URL` in `.env`, then:
|
||||
|
||||
```bash
|
||||
make i18n
|
||||
```
|
||||
|
||||
This fills the interface strings of the languages that ship no catalogue as
|
||||
well. Existing entries are never overwritten, and a string the shipped
|
||||
catalogue already covers is never requested, so corrections you make by hand
|
||||
survive later runs.
|
||||
|
||||
`name`, `title`, `description`, `text`, `warning`, `info` and `subtitel` are
|
||||
translated; `url`, `link_text`, `identifier` and icon classes never are.
|
||||
|
||||
A machine cannot tell the menu label "Pictures" from the brand "Mastodon", so
|
||||
list the brands in `app/i18n/keep.txt`, one per line — they are then stored as
|
||||
themselves in every language and cost no request:
|
||||
|
||||
```
|
||||
# Strings utils/i18n_sync.py stores as themselves instead of translating.
|
||||
Mastodon
|
||||
Nextcloud
|
||||
freelancermap.de
|
||||
```
|
||||
|
||||
Add one-off entries with `--keep Foo Bar`, or point somewhere else with
|
||||
`--keep-file`. A protected string never replaces an entry you already wrote by
|
||||
hand.
|
||||
|
||||
---
|
||||
|
||||
## 🚢 Production Deployment
|
||||
|
||||
* Use a reverse proxy (NGINX/Apache).
|
||||
* Secure with SSL/TLS.
|
||||
* Swap to a production database if needed.
|
||||
|
||||
Because every page carries a canonical URL and 184 `hreflang` alternates, two
|
||||
details of the proxy setup now matter:
|
||||
|
||||
* **Set `TRUSTED_HOSTS`** in `.env` to your public hostname(s), comma-separated.
|
||||
Left empty, the app reflects whatever `Host` header arrives into its canonical,
|
||||
`hreflang` and redirect URLs — so a shared cache in front of it can be made to
|
||||
store a redirect pointing somewhere else.
|
||||
* **Have the proxy send `X-Forwarded-Proto`.** Without it the app cannot know TLS
|
||||
terminated upstream and every canonical URL claims `http://`. `X-Forwarded-Host`
|
||||
is deliberately *not* trusted; set `Host` to the public name instead.
|
||||
|
||||
---
|
||||
|
||||
## 📜 License
|
||||
|
||||
102
app/app.py
102
app/app.py
@@ -3,10 +3,12 @@ import os
|
||||
|
||||
import requests
|
||||
import yaml
|
||||
from flask import Flask, current_app, render_template, url_for
|
||||
from flask import Flask, current_app, make_response, render_template, request, url_for
|
||||
from markupsafe import Markup
|
||||
from werkzeug.middleware.proxy_fix import ProxyFix
|
||||
|
||||
try:
|
||||
from app.utils import i18n
|
||||
from app.utils.asset_resolver import asset_src, resolve_asset_cache
|
||||
from app.utils.cache_manager import CacheManager
|
||||
from app.utils.compute_card_classes import compute_card_classes
|
||||
@@ -17,12 +19,15 @@ except ImportError: # pragma: no cover - supports running from the app/ directo
|
||||
from utils.compute_card_classes import compute_card_classes
|
||||
from utils.configuration_resolver import ConfigurationResolver
|
||||
|
||||
from utils import i18n
|
||||
|
||||
TRANSLATED_SECTIONS = ("cards", "company", "navigation", "platform")
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
|
||||
FLASK_ENV = os.getenv("FLASK_ENV", "production")
|
||||
FLASK_HOST = os.getenv("FLASK_HOST", "127.0.0.1")
|
||||
FLASK_PORT = int(os.getenv("FLASK_PORT", os.getenv("PORT", 5000)))
|
||||
print(f"Starting app on {FLASK_HOST}:{FLASK_PORT}, FLASK_ENV={FLASK_ENV}")
|
||||
|
||||
# Initialize the CacheManager
|
||||
cache_manager = CacheManager()
|
||||
@@ -42,6 +47,8 @@ def load_config(app):
|
||||
resolver = ConfigurationResolver(config)
|
||||
resolver.resolve_links()
|
||||
app.config.update(resolver.get_config())
|
||||
app.config["TRANSLATED_CONFIG"] = {}
|
||||
i18n.clear_catalogs()
|
||||
|
||||
|
||||
def cache_icons_and_logos(app):
|
||||
@@ -60,6 +67,23 @@ def cache_icons_and_logos(app):
|
||||
# Initialize Flask app
|
||||
app = Flask(__name__)
|
||||
|
||||
app.jinja_options = {**app.jinja_options, "autoescape": True}
|
||||
|
||||
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=0, x_proto=1)
|
||||
|
||||
|
||||
def trusted_hosts(raw):
|
||||
"""Parse a comma-separated host list, or None when nothing is configured.
|
||||
|
||||
Args:
|
||||
raw: the ``TRUSTED_HOSTS`` value, possibly empty.
|
||||
"""
|
||||
hosts = [host.strip() for host in raw.split(",") if host.strip()]
|
||||
return hosts or None
|
||||
|
||||
|
||||
app.config["TRUSTED_HOSTS"] = trusted_hosts(os.getenv("TRUSTED_HOSTS", ""))
|
||||
|
||||
# Load configuration and cache assets on startup
|
||||
load_config(app)
|
||||
cache_icons_and_logos(app)
|
||||
@@ -91,37 +115,81 @@ def reload_config_in_dev():
|
||||
cache_icons_and_logos(app)
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
"""Render the main index page."""
|
||||
cards = app.config["cards"]
|
||||
lg_classes, md_classes = compute_card_classes(cards)
|
||||
apod_bg = None
|
||||
def translated_config(lang):
|
||||
"""Return the configuration sections translated into ``lang``, memoized.
|
||||
|
||||
The memo is dropped by ``load_config``, so a development reload picks up
|
||||
edited content on the next request.
|
||||
"""
|
||||
memo = app.config["TRANSLATED_CONFIG"]
|
||||
if lang not in memo:
|
||||
source = {section: app.config[section] for section in TRANSLATED_SECTIONS}
|
||||
memo[lang] = i18n.translate_tree(source, lang)
|
||||
return memo[lang]
|
||||
|
||||
|
||||
def apod_background():
|
||||
"""Return today's NASA APOD image URL, or None when unavailable."""
|
||||
api_key = app.config.get("NASA_API_KEY")
|
||||
if api_key:
|
||||
if not api_key:
|
||||
return None
|
||||
|
||||
try:
|
||||
resp = requests.get(
|
||||
"https://api.nasa.gov/planetary/apod",
|
||||
params={"api_key": api_key},
|
||||
timeout=10,
|
||||
)
|
||||
if resp.ok:
|
||||
data = resp.json()
|
||||
if data.get("media_type") == "image":
|
||||
apod_bg = data.get("url")
|
||||
except requests.RequestException:
|
||||
logging.warning("APOD lookup failed", exc_info=True)
|
||||
return None
|
||||
|
||||
if not resp.ok:
|
||||
return None
|
||||
|
||||
data = resp.json()
|
||||
return data.get("url") if data.get("media_type") == "image" else None
|
||||
|
||||
|
||||
def render_index(lang):
|
||||
"""Render the index page in ``lang``."""
|
||||
config = translated_config(lang)
|
||||
cards = config["cards"]
|
||||
lg_classes, md_classes = compute_card_classes(cards)
|
||||
|
||||
return render_template(
|
||||
"pages/index.html.j2",
|
||||
cards=cards,
|
||||
company=app.config["company"],
|
||||
navigation=app.config["navigation"],
|
||||
platform=app.config["platform"],
|
||||
company=config["company"],
|
||||
navigation=config["navigation"],
|
||||
platform=config["platform"],
|
||||
lg_classes=lg_classes,
|
||||
md_classes=md_classes,
|
||||
apod_bg=apod_bg,
|
||||
apod_bg=apod_background(),
|
||||
lang=lang,
|
||||
lang_dir=i18n.direction(lang),
|
||||
languages=i18n.LANGUAGES,
|
||||
ui_strings=i18n.ui_strings(lang),
|
||||
t=lambda source: i18n.catalog(lang).get(source, source),
|
||||
)
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
"""Render the index page in the language the browser asks for."""
|
||||
response = make_response(render_index(i18n.negotiate(request.accept_languages)))
|
||||
response.headers["Vary"] = "Accept-Language"
|
||||
return response
|
||||
|
||||
|
||||
@app.route(f"/<any({','.join(i18n.LANGUAGES)}):lang>/")
|
||||
def localized_index(lang):
|
||||
"""Render the index page in an explicitly requested language."""
|
||||
return render_index(lang)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(f"Starting app on {FLASK_HOST}:{FLASK_PORT}, FLASK_ENV={FLASK_ENV}")
|
||||
app.run(
|
||||
debug=(FLASK_ENV == "development"),
|
||||
host=FLASK_HOST,
|
||||
|
||||
243
app/cypress/e2e/i18n.spec.js
Normal file
243
app/cypress/e2e/i18n.spec.js
Normal file
@@ -0,0 +1,243 @@
|
||||
// cypress/e2e/i18n.spec.js
|
||||
|
||||
const GERMAN_BROWSER = { headers: { 'Accept-Language': 'de-DE,de;q=0.9,en;q=0.8' } };
|
||||
|
||||
describe('Language negotiation', () => {
|
||||
it('serves English to an English browser', () => {
|
||||
cy.visit('/', { headers: { 'Accept-Language': 'en-US,en;q=0.9' } });
|
||||
cy.get('html').should('have.attr', 'lang', 'en');
|
||||
cy.get('footer.footer a.iframe-link').should('contain.text', 'Imprint');
|
||||
});
|
||||
|
||||
it('serves German to a German browser', () => {
|
||||
cy.visit('/', GERMAN_BROWSER);
|
||||
cy.get('html').should('have.attr', 'lang', 'de');
|
||||
cy.get('footer.footer a.iframe-link').should('contain.text', 'Impressum');
|
||||
});
|
||||
|
||||
it('falls back to English for an unsupported browser language', () => {
|
||||
cy.visit('/', { headers: { 'Accept-Language': 'xx-XX' } });
|
||||
cy.get('html').should('have.attr', 'lang', 'en');
|
||||
});
|
||||
|
||||
it('lets the URL prefix override the browser language', () => {
|
||||
cy.visit('/fr/', GERMAN_BROWSER);
|
||||
cy.get('html').should('have.attr', 'lang', 'fr');
|
||||
cy.get('footer.footer a.iframe-link').should('contain.text', 'Mentions légales');
|
||||
});
|
||||
|
||||
it('rejects an unsupported language code', () => {
|
||||
cy.request({ url: '/xx/', failOnStatusCode: false })
|
||||
.its('status')
|
||||
.should('eq', 404);
|
||||
});
|
||||
|
||||
it('does not turn unrelated single-segment paths into redirects', () => {
|
||||
['/robots.txt', '/favicon.ico', '/sitemap.xml'].forEach(path => {
|
||||
cy.request({ url: path, failOnStatusCode: false, followRedirect: false })
|
||||
.its('status')
|
||||
.should('eq', 404);
|
||||
});
|
||||
});
|
||||
|
||||
it('marks the negotiated route as varying by language', () => {
|
||||
cy.request('/')
|
||||
.its('headers.vary')
|
||||
.should('contain', 'Accept-Language');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Language switcher', () => {
|
||||
beforeEach(() => {
|
||||
cy.viewport(1280, 720);
|
||||
cy.visit('/en/');
|
||||
});
|
||||
|
||||
it('names the active language and offers every ISO 639-1 code', () => {
|
||||
cy.get('#navbarDropdownLanguage')
|
||||
.should('have.attr', 'data-bs-toggle', 'dropdown')
|
||||
.and('contain.text', 'English');
|
||||
|
||||
cy.get('#navbarDropdownLanguage')
|
||||
.parent('.nav-item')
|
||||
.find('> .dropdown-menu a.dropdown-item')
|
||||
.should('have.length', 184);
|
||||
});
|
||||
|
||||
it('marks the active language', () => {
|
||||
cy.get('#navbarDropdownLanguage').click();
|
||||
cy.get('.dropdown-menu a.dropdown-item.active[hreflang="en"]').should('exist');
|
||||
});
|
||||
|
||||
it('navigates to the chosen language', () => {
|
||||
cy.get('#navbarDropdownLanguage').click();
|
||||
cy.get('.dropdown-menu a.dropdown-item[hreflang="de"]')
|
||||
.should('have.text', 'Deutsch')
|
||||
.click();
|
||||
|
||||
cy.url().should('match', /\/de\/$/);
|
||||
cy.get('html').should('have.attr', 'lang', 'de');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Translated interface strings', () => {
|
||||
it('translates the strings rendered by the templates', () => {
|
||||
cy.visit('/de/');
|
||||
cy.get('#dynamicCopyButton').should('have.text', 'Kopieren');
|
||||
cy.get('#dynamicChildrenSection h6').should('have.text', 'Optionen:');
|
||||
cy.get('#dynamicAlternativesSection h6').should('have.text', 'Alternativen:');
|
||||
cy.get('.modal-footer button').should('have.text', 'Schließen');
|
||||
});
|
||||
|
||||
it('exposes the catalogue to client-side code', () => {
|
||||
cy.visit('/de/');
|
||||
cy.window().its('I18N').should('deep.include', {
|
||||
Open: 'Öffnen',
|
||||
'Open Link': 'Link öffnen',
|
||||
'Identifier copied to clipboard!': 'Kennung in die Zwischenablage kopiert!',
|
||||
});
|
||||
});
|
||||
|
||||
it('leaves the catalogue untranslated in the source language', () => {
|
||||
cy.visit('/en/');
|
||||
cy.window().its('I18N').should('deep.include', { Open: 'Open' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('Strings translated by modal.js', () => {
|
||||
const item = {
|
||||
name: 'Test Item',
|
||||
identifier: 'ABC123',
|
||||
icon: { class: 'fa fa-test' },
|
||||
alternatives: [
|
||||
{ name: 'Alt One', identifier: 'ALT1', icon: { class: 'fa fa-alt1' } },
|
||||
],
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
cy.visit('/de/');
|
||||
cy.window().then(win => {
|
||||
cy.stub(win.navigator.clipboard, 'writeText').resolves();
|
||||
cy.stub(win, 'alert');
|
||||
});
|
||||
});
|
||||
|
||||
it('translates the button of a list entry', () => {
|
||||
cy.window().invoke('openDynamicPopup', item);
|
||||
cy.get('#dynamicAlternativesList button').should('have.text', 'Öffnen');
|
||||
});
|
||||
|
||||
it('translates the link label when the entry has no description', () => {
|
||||
cy.window().invoke('openDynamicPopup', {
|
||||
...item,
|
||||
url: 'https://example.com',
|
||||
description: null,
|
||||
});
|
||||
cy.get('#dynamicModalLinkHref').should('have.text', 'Link öffnen');
|
||||
});
|
||||
|
||||
it('translates the clipboard confirmation', () => {
|
||||
cy.window().invoke('openDynamicPopup', item);
|
||||
cy.get('#dynamicCopyButton').click();
|
||||
cy.window()
|
||||
.its('alert')
|
||||
.should('have.been.calledWith', 'Kennung in die Zwischenablage kopiert!');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Right-to-left languages', () => {
|
||||
it('flips the document and loads the RTL stylesheet', () => {
|
||||
cy.visit('/ar/');
|
||||
cy.get('html').should('have.attr', 'dir', 'rtl');
|
||||
cy.get('link[href*="bootstrap.rtl.min.css"]').should('exist');
|
||||
cy.get('link[href*="vendor/bootstrap/css/bootstrap.min.css"]').should('not.exist');
|
||||
cy.get('body').should('have.css', 'direction', 'rtl');
|
||||
});
|
||||
|
||||
it('keeps left-to-right languages on the default stylesheet', () => {
|
||||
cy.visit('/en/');
|
||||
cy.get('html').should('have.attr', 'dir', 'ltr');
|
||||
cy.get('link[href*="bootstrap.rtl.min.css"]').should('not.exist');
|
||||
});
|
||||
|
||||
it('actually serves the RTL stylesheet it links to', () => {
|
||||
cy.visit('/ar/');
|
||||
cy.get('link[href*="bootstrap.rtl.min.css"]')
|
||||
.should('have.attr', 'href')
|
||||
.then(href => {
|
||||
cy.request(href).its('status').should('eq', 200);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Translated interface details', () => {
|
||||
it('translates the strings only a screen reader sees', () => {
|
||||
cy.visit('/de/');
|
||||
|
||||
cy.get('#dynamicModal .btn-close').should('have.attr', 'aria-label', 'Schließen');
|
||||
});
|
||||
|
||||
it('translates the alert headings', () => {
|
||||
cy.visit('/fr/');
|
||||
|
||||
cy.get('#dynamicModalWarning h5').should('contain.text', 'Avertissement');
|
||||
cy.get('#dynamicModalInfo h5').should('contain.text', 'Informations');
|
||||
});
|
||||
|
||||
it('translates the language switcher tooltip', () => {
|
||||
cy.visit('/de/');
|
||||
|
||||
cy.get('#navbarDropdownLanguage').should('have.attr', 'title', 'Sprache');
|
||||
});
|
||||
|
||||
it('tags each switcher entry with its own language', () => {
|
||||
cy.visit('/en/');
|
||||
|
||||
cy.get('.dropdown-menu a.dropdown-item[hreflang="ja"]').should(
|
||||
'have.attr',
|
||||
'lang',
|
||||
'ja',
|
||||
);
|
||||
});
|
||||
|
||||
it('scrolls inside the language menu instead of past the page', () => {
|
||||
cy.viewport(1280, 720);
|
||||
cy.visit('/en/');
|
||||
cy.get('#navbarDropdownLanguage').click();
|
||||
|
||||
cy.get('.dropdown-menu.language-menu').should($menu => {
|
||||
const menu = $menu[0];
|
||||
expect(menu.scrollHeight, 'taller than it shows').to.be.greaterThan(
|
||||
menu.clientHeight,
|
||||
);
|
||||
expect(menu.getBoundingClientRect().height).to.be.lessThan(720);
|
||||
expect(getComputedStyle(menu).overflowY).to.eq('auto');
|
||||
});
|
||||
});
|
||||
|
||||
it('offers the switcher in the header only', () => {
|
||||
cy.viewport(1280, 720);
|
||||
cy.visit('/en/');
|
||||
|
||||
cy.get('#navbarNavheader #navbarDropdownLanguage').should('exist');
|
||||
cy.get('#navbarNavfooter #navbarDropdownLanguage').should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Search engine metadata', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/de/');
|
||||
});
|
||||
|
||||
it('declares an alternate for every language plus a default', () => {
|
||||
cy.get('link[rel="alternate"][hreflang]').should('have.length', 185);
|
||||
cy.get('link[rel="alternate"][hreflang="x-default"]').should('exist');
|
||||
cy.get('link[rel="alternate"][hreflang="ja"]')
|
||||
.should('have.attr', 'href')
|
||||
.and('match', /\/ja\/$/);
|
||||
});
|
||||
|
||||
it('points the canonical URL at the language actually served', () => {
|
||||
cy.get('link[rel="canonical"]').should('have.attr', 'href').and('match', /\/de\/$/);
|
||||
});
|
||||
});
|
||||
285
app/cypress/e2e/injection.spec.js
Normal file
285
app/cypress/e2e/injection.spec.js
Normal file
@@ -0,0 +1,285 @@
|
||||
// cypress/e2e/injection.spec.js
|
||||
|
||||
describe('Untrusted content in the modal', () => {
|
||||
const base = {
|
||||
name: 'Test Item',
|
||||
identifier: 'ABC123',
|
||||
icon: { class: 'fa fa-test' },
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
cy.visit('/');
|
||||
cy.window().then(win => {
|
||||
cy.stub(win.navigator.clipboard, 'writeText').resolves();
|
||||
cy.stub(win, 'alert');
|
||||
});
|
||||
});
|
||||
|
||||
function open(item = {}) {
|
||||
cy.window().invoke('openDynamicPopup', { ...base, ...item });
|
||||
}
|
||||
|
||||
describe('markdown rendered into innerHTML', () => {
|
||||
it('strips a plain script URL', () => {
|
||||
open({
|
||||
warning: '[click me](javascript:window.__xss = true)',
|
||||
info: '',
|
||||
});
|
||||
|
||||
cy.get('#dynamicModalWarningText').find('a').should('not.exist');
|
||||
cy.get('#dynamicModalWarningText').should('contain.text', 'click me');
|
||||
cy.get('#dynamicModalInfoText').find('img').should('not.exist');
|
||||
cy.window().should('not.have.property', '__xss');
|
||||
});
|
||||
|
||||
it('strips a script URL hidden behind character references', () => {
|
||||
open({
|
||||
warning:
|
||||
'[a](javascript:window.__xss=1) [b](javascript:window.__xss=1)',
|
||||
info: '[c](java	script:window.__xss=1) [d](java
script:window.__xss=1)',
|
||||
});
|
||||
|
||||
cy.get('#dynamicModalWarningText').find('a').should('not.exist');
|
||||
cy.get('#dynamicModalInfoText').find('a').should('not.exist');
|
||||
cy.window().should('not.have.property', '__xss');
|
||||
});
|
||||
|
||||
it('strips a script URL written as a reference-style link', () => {
|
||||
open({
|
||||
warning: '[click me][ref]\n\n[ref]: javascript:window.__xss=1',
|
||||
});
|
||||
|
||||
cy.get('#dynamicModalWarningText').find('a').should('not.exist');
|
||||
cy.window().should('not.have.property', '__xss');
|
||||
});
|
||||
|
||||
it('neutralises raw HTML', () => {
|
||||
open({
|
||||
warning: '<img src=x onerror="window.__xss = true">',
|
||||
info: '<a href="javascript:window.__xss = true">x</a>',
|
||||
});
|
||||
|
||||
cy.get('#dynamicModalWarningText').find('img').should('not.exist');
|
||||
cy.get('#dynamicModalWarningText').should('contain.text', 'onerror');
|
||||
cy.get('#dynamicModalInfoText').find('a').should('not.exist');
|
||||
cy.window().should('not.have.property', '__xss');
|
||||
});
|
||||
|
||||
it('neutralises raw HTML used as the text of a stripped link', () => {
|
||||
open({ warning: '[<img src=x onerror="window.__xss = true">](javascript:bad)' });
|
||||
|
||||
cy.get('#dynamicModalWarningText').find('img').should('not.exist');
|
||||
cy.window().should('not.have.property', '__xss');
|
||||
});
|
||||
|
||||
it('keeps a relative link', () => {
|
||||
open({ warning: 'See [the notes](/#anchor)' });
|
||||
|
||||
cy.get('#dynamicModalWarningText')
|
||||
.find('a')
|
||||
.should('have.attr', 'href', '/#anchor');
|
||||
});
|
||||
|
||||
it('keeps the text of a link it strips', () => {
|
||||
open({ warning: '[read this](javascript:window.__xss=1)' });
|
||||
|
||||
cy.get('#dynamicModalWarningText').should('contain.text', 'read this');
|
||||
cy.window().should('not.have.property', '__xss');
|
||||
});
|
||||
|
||||
it('keeps ordinary markdown', () => {
|
||||
open({ warning: 'See [Matrix](https://matrix.org/) and **mind** this' });
|
||||
|
||||
cy.get('#dynamicModalWarningText')
|
||||
.find('a')
|
||||
.should('have.attr', 'href', 'https://matrix.org/');
|
||||
cy.get('#dynamicModalWarningText').find('strong').should('have.text', 'mind');
|
||||
});
|
||||
});
|
||||
|
||||
describe('values interpolated outside markdown', () => {
|
||||
it('does not treat an interface string as markup', () => {
|
||||
cy.window().then(win => {
|
||||
win.I18N.Open = '<img src=x onerror="window.__xss = true">';
|
||||
});
|
||||
open({
|
||||
alternatives: [
|
||||
{ name: 'Alt', identifier: 'A', icon: { class: 'fa-alt' } },
|
||||
],
|
||||
});
|
||||
|
||||
cy.get('#dynamicAlternativesList').find('img').should('not.exist');
|
||||
cy.get('#dynamicAlternativesList').should('contain.text', 'onerror');
|
||||
cy.window().should('not.have.property', '__xss');
|
||||
});
|
||||
|
||||
it('falls back to the English source when a string is missing', () => {
|
||||
cy.window().then(win => {
|
||||
delete win.I18N;
|
||||
});
|
||||
open({
|
||||
alternatives: [
|
||||
{ name: 'Alt', identifier: 'A', icon: { class: 'fa-alt' } },
|
||||
],
|
||||
});
|
||||
|
||||
cy.get('#dynamicAlternativesList button').should('have.text', 'Open');
|
||||
});
|
||||
|
||||
it('renders no placeholder for a missing name', () => {
|
||||
open({ name: undefined });
|
||||
|
||||
cy.get('#dynamicModalLabel').should('not.contain.text', 'undefined');
|
||||
});
|
||||
|
||||
it('does not treat the name or the icon class as markup', () => {
|
||||
open({
|
||||
name: '<img src=x onerror="window.__xss = true">',
|
||||
icon: { class: 'fa" onmouseover="window.__xss = true' },
|
||||
alternatives: [
|
||||
{
|
||||
name: '<img src=y onerror="window.__xss = true">',
|
||||
identifier: 'ALT1',
|
||||
icon: { class: 'fa-alt' },
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
cy.get('#dynamicModalLabel').find('img').should('not.exist');
|
||||
cy.get('#dynamicModalLabel').should('contain.text', 'onerror');
|
||||
cy.get('#dynamicAlternativesList').find('img').should('not.exist');
|
||||
cy.get('#dynamicAlternativesList').should('contain.text', 'onerror');
|
||||
cy.window().should('not.have.property', '__xss');
|
||||
});
|
||||
});
|
||||
|
||||
describe('the link the modal offers', () => {
|
||||
it('drops a URL that uses an unsafe scheme', () => {
|
||||
open({ url: 'javascript:window.__xss = true', description: 'Bad' });
|
||||
|
||||
cy.get('#dynamicModalLinkHref').should('not.have.attr', 'href');
|
||||
cy.get('#dynamicModalLinkHref').should('have.text', 'Bad');
|
||||
cy.window().should('not.have.property', '__xss');
|
||||
});
|
||||
|
||||
it('keeps an ordinary URL', () => {
|
||||
open({ url: 'https://example.com', description: 'Good' });
|
||||
|
||||
cy.get('#dynamicModalLinkHref').should(
|
||||
'have.attr',
|
||||
'href',
|
||||
'https://example.com',
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps a URL that carries surrounding whitespace', () => {
|
||||
open({ url: ' https://example.com ', description: 'Good' });
|
||||
|
||||
cy.get('#dynamicModalLinkHref').should('have.attr', 'href');
|
||||
});
|
||||
|
||||
it('keeps a mailto URL', () => {
|
||||
open({ url: 'mailto:kevin@veen.world', description: 'Write' });
|
||||
|
||||
cy.get('#dynamicModalLinkHref').should(
|
||||
'have.attr',
|
||||
'href',
|
||||
'mailto:kevin@veen.world',
|
||||
);
|
||||
});
|
||||
|
||||
it('restores the link after a popup whose URL was dropped', () => {
|
||||
open({ url: 'javascript:window.__xss = true', description: 'Bad' });
|
||||
cy.get('#dynamicModalLinkHref').should('not.have.attr', 'href');
|
||||
|
||||
open({ url: 'https://example.com', description: 'Good' });
|
||||
cy.get('#dynamicModalLinkHref').should(
|
||||
'have.attr',
|
||||
'href',
|
||||
'https://example.com',
|
||||
);
|
||||
});
|
||||
|
||||
it('does not let one popup iframe handler outlive it', () => {
|
||||
open({ url: 'https://a.test/', description: 'A', iframe: true });
|
||||
cy.get('#dynamicModalLinkHref').should('have.class', 'iframe');
|
||||
|
||||
open({ url: 'https://b.test/', description: 'B' });
|
||||
|
||||
cy.get('#dynamicModalLinkHref').should('not.have.class', 'iframe');
|
||||
cy.get('#dynamicModalLinkHref').should($anchor => {
|
||||
expect($anchor[0].onclick, 'stale click handler').to.equal(null);
|
||||
});
|
||||
});
|
||||
|
||||
it('opens the current popup URL, not an earlier one', () => {
|
||||
open({ url: 'https://a.test/', description: 'A', iframe: true });
|
||||
open({ url: 'https://b.test/', description: 'B', iframe: true });
|
||||
|
||||
cy.get('#dynamicModalLinkHref').click();
|
||||
|
||||
cy.get('#main')
|
||||
.find('iframe', { timeout: 4000 })
|
||||
.should('have.attr', 'src', 'https://b.test/');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Untrusted content reaching the iframe', () => {
|
||||
const AFTER_THE_FADE = 3000;
|
||||
|
||||
it('refuses to open a script URL handed over by the modal', () => {
|
||||
cy.visit('/');
|
||||
cy.window().invoke('openDynamicPopup', {
|
||||
name: 'Bad',
|
||||
icon: { class: 'fa fa-test' },
|
||||
url: 'javascript:window.__xss = true',
|
||||
description: 'Watch',
|
||||
iframe: true,
|
||||
});
|
||||
|
||||
cy.get('#dynamicModalLinkHref').click({ force: true });
|
||||
|
||||
cy.wait(AFTER_THE_FADE);
|
||||
cy.get('#main').find('iframe').should('not.exist');
|
||||
cy.window().should('not.have.property', '__xss');
|
||||
});
|
||||
|
||||
it('refuses a script URL supplied through the query string', () => {
|
||||
cy.visit('/?iframe=javascript:window.__xss%20%3D%20true');
|
||||
|
||||
cy.wait(AFTER_THE_FADE);
|
||||
cy.get('#main').find('iframe').should('not.exist');
|
||||
cy.window().should('not.have.property', '__xss');
|
||||
});
|
||||
|
||||
it('still opens a configured iframe target from the query string', () => {
|
||||
cy.visit('/');
|
||||
cy.get('a.iframe-link').first().invoke('prop', 'href').then((href) => {
|
||||
cy.visit(`/?iframe=${encodeURIComponent(href)}`);
|
||||
|
||||
cy.get('#main')
|
||||
.find('iframe', { timeout: AFTER_THE_FADE })
|
||||
.should('have.attr', 'src', href);
|
||||
});
|
||||
});
|
||||
|
||||
it('refuses a foreign origin supplied through the query string', () => {
|
||||
cy.visit('/?iframe=https://example.com/');
|
||||
|
||||
cy.wait(AFTER_THE_FADE);
|
||||
cy.get('#main').find('iframe').should('not.exist');
|
||||
});
|
||||
|
||||
it('does not open a foreign query-string origin in a new tab', () => {
|
||||
cy.visit('/?iframe=https://example.com/', {
|
||||
onBeforeLoad(win) {
|
||||
cy.stub(win, 'open').as('open');
|
||||
cy.stub(win, 'alert');
|
||||
},
|
||||
});
|
||||
|
||||
cy.window().then((win) => win.openIframeInNewTab());
|
||||
cy.get('@open').should('not.have.been.called');
|
||||
});
|
||||
});
|
||||
78
app/eslint.config.js
Normal file
78
app/eslint.config.js
Normal file
@@ -0,0 +1,78 @@
|
||||
'use strict';
|
||||
/**
|
||||
* Correctness only — eslint's recommended set, no stylistic rules.
|
||||
*
|
||||
* The browser scripts are plain <script> tags sharing one global scope: each
|
||||
* file declares some functions and calls others declared elsewhere. That is why
|
||||
* they are listed as globals and why no-redeclare is off — the declaring file
|
||||
* would otherwise be reported for defining its own function.
|
||||
*/
|
||||
const js = require('@eslint/js');
|
||||
const globals = require('globals');
|
||||
|
||||
// Vendored libraries plus the functions static/js files call across each other.
|
||||
// Keep this to names that really cross a file boundary. Every superfluous
|
||||
// entry is a permanent no-undef blind spot for a typo of that name.
|
||||
const SHARED = {
|
||||
bootstrap: 'readonly',
|
||||
marked: 'readonly',
|
||||
$: 'readonly',
|
||||
jQuery: 'readonly',
|
||||
openDynamicPopup: 'readonly',
|
||||
closeAllModals: 'readonly',
|
||||
isSafeUrl: 'readonly',
|
||||
openIframe: 'readonly',
|
||||
enterFullscreen: 'readonly',
|
||||
exitFullscreen: 'readonly',
|
||||
setFullWidth: 'readonly',
|
||||
initFullWidthFromUrl: 'readonly',
|
||||
adjustScrollContainerHeight: 'readonly',
|
||||
updateCustomScrollbar: 'readonly',
|
||||
};
|
||||
|
||||
module.exports = [
|
||||
{ ignores: ['node_modules/**', 'static/vendor/**', 'cypress/screenshots/**'] },
|
||||
{
|
||||
files: ['static/js/**/*.js'],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2022,
|
||||
sourceType: 'script',
|
||||
globals: { ...globals.browser, ...SHARED },
|
||||
},
|
||||
rules: {
|
||||
...js.configs.recommended.rules,
|
||||
'no-redeclare': 'off',
|
||||
// vars: 'local' — a top-level function here is the API other files and
|
||||
// the templates call, so only unused locals are a defect.
|
||||
'no-unused-vars': [
|
||||
'error',
|
||||
{ vars: 'local', args: 'none', caughtErrors: 'none' },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['cypress/**/*.js'],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2022,
|
||||
sourceType: 'script',
|
||||
globals: {
|
||||
...globals.browser,
|
||||
...globals.mocha,
|
||||
cy: 'readonly',
|
||||
Cypress: 'readonly',
|
||||
expect: 'readonly',
|
||||
assert: 'readonly',
|
||||
},
|
||||
},
|
||||
rules: js.configs.recommended.rules,
|
||||
},
|
||||
{
|
||||
files: ['scripts/**/*.js', 'cypress.config.js', 'eslint.config.js'],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2022,
|
||||
sourceType: 'commonjs',
|
||||
globals: globals.node,
|
||||
},
|
||||
rules: js.configs.recommended.rules,
|
||||
},
|
||||
];
|
||||
46
app/i18n/keep.txt
Normal file
46
app/i18n/keep.txt
Normal file
@@ -0,0 +1,46 @@
|
||||
# Strings utils/i18n_sync.py stores as themselves instead of translating.
|
||||
# One per line; blank lines and lines starting with # are ignored.
|
||||
|
||||
Baserow
|
||||
Big Blue Button
|
||||
Bluesky
|
||||
Buy me a Coffee
|
||||
Discourse
|
||||
Duolingo
|
||||
Eversports
|
||||
Facebook
|
||||
Friendica
|
||||
Garmin
|
||||
Gitea
|
||||
GitHub
|
||||
GitHub Sponsors
|
||||
Infinito.Nexus
|
||||
Instagram
|
||||
Keycloak
|
||||
LDAP
|
||||
LinkedIn
|
||||
Mailu
|
||||
Mastodon
|
||||
Matomo
|
||||
Matrix
|
||||
Nextcloud
|
||||
Open Project
|
||||
Patreon
|
||||
PayPal
|
||||
Peertube
|
||||
Pixelfed
|
||||
Signal
|
||||
Snipe IT
|
||||
Spotify
|
||||
Taiga
|
||||
Telegram
|
||||
Twitter
|
||||
WhatsApp
|
||||
XING
|
||||
YouTube
|
||||
Yourls
|
||||
Zoom
|
||||
freelancermap.de
|
||||
malt
|
||||
phpMyAdmin
|
||||
upwork.com
|
||||
11
app/i18n/ui/aa.yaml
Normal file
11
app/i18n/ui/aa.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Tan doorit
|
||||
Close: Alif
|
||||
Copy: Garraab
|
||||
Identifier copied to clipboard!: Astooti tixi rakaakayih culte!
|
||||
Imprint: Qaanuunih warsa
|
||||
Information: Warsa
|
||||
Language: Af
|
||||
Open: Fak
|
||||
Open Link: Tixi fak
|
||||
Options: Doorit
|
||||
Warning: Kassiisa
|
||||
11
app/i18n/ui/ab.yaml
Normal file
11
app/i18n/ui/ab.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Альтернативақәа
|
||||
Close: Аркра
|
||||
Copy: Акопиа ахыхра
|
||||
Identifier copied to clipboard!: Аидентификатор абуфер ахь иқәыргылоуп!
|
||||
Imprint: Азакәантә информациа
|
||||
Information: Аинформациа
|
||||
Language: Абызшәа
|
||||
Open: Аартра
|
||||
Open Link: Азхьарԥш аартра
|
||||
Options: Апараметрқәа
|
||||
Warning: Агәаҽанижьра
|
||||
11
app/i18n/ui/ae.yaml
Normal file
11
app/i18n/ui/ae.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Aniiā
|
||||
Close: Pairi.dāraiia
|
||||
Copy: Paitiš.kərənu
|
||||
Identifier copied to clipboard!: Nāman paitiš.kərətəm!
|
||||
Imprint: Dātō.xvarənah
|
||||
Information: Vaēdiia
|
||||
Language: Hizuuā
|
||||
Open: Vīuuāraiia
|
||||
Open Link: Paθō vīuuāraiia
|
||||
Options: Vərəθra
|
||||
Warning: Aiβi.saŋha
|
||||
11
app/i18n/ui/af.yaml
Normal file
11
app/i18n/ui/af.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Alternatiewe
|
||||
Close: Maak toe
|
||||
Copy: Kopieer
|
||||
Identifier copied to clipboard!: Identifiseerder na knipbord gekopieer!
|
||||
Imprint: Regskennisgewing
|
||||
Information: Inligting
|
||||
Language: Taal
|
||||
Open: Maak oop
|
||||
Open Link: Maak skakel oop
|
||||
Options: Opsies
|
||||
Warning: Waarskuwing
|
||||
11
app/i18n/ui/ak.yaml
Normal file
11
app/i18n/ui/ak.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Nneɛma foforɔ
|
||||
Close: To mu
|
||||
Copy: Twa
|
||||
Identifier copied to clipboard!: Wɔatwa nsɛnkyerɛnneɛ no akɔ clipboard so!
|
||||
Imprint: Mmara ho nsɛm
|
||||
Information: Nsɛm
|
||||
Language: Kasa
|
||||
Open: Bue
|
||||
Open Link: Bue link no
|
||||
Options: Nhyehyɛe
|
||||
Warning: Kɔkɔbɔ
|
||||
11
app/i18n/ui/am.yaml
Normal file
11
app/i18n/ui/am.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: አማራጮች
|
||||
Close: ዝጋ
|
||||
Copy: ቅዳ
|
||||
Identifier copied to clipboard!: መለያው ወደ ቅንጥብ ሰሌዳ ተቀድቷል!
|
||||
Imprint: ሕጋዊ መረጃ
|
||||
Information: መረጃ
|
||||
Language: ቋንቋ
|
||||
Open: ክፈት
|
||||
Open Link: ማገናኛውን ክፈት
|
||||
Options: ምርጫዎች
|
||||
Warning: ማስጠንቀቂያ
|
||||
11
app/i18n/ui/an.yaml
Normal file
11
app/i18n/ui/an.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Alternativas
|
||||
Close: Zarrar
|
||||
Copy: Copiar
|
||||
Identifier copied to clipboard!: Identificador copiau en o portafuellas!
|
||||
Imprint: Aviso legal
|
||||
Information: Información
|
||||
Language: Idioma
|
||||
Open: Ubrir
|
||||
Open Link: Ubrir o vinclo
|
||||
Options: Opcions
|
||||
Warning: Alvertencia
|
||||
11
app/i18n/ui/ar.yaml
Normal file
11
app/i18n/ui/ar.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: بدائل
|
||||
Close: إغلاق
|
||||
Copy: نسخ
|
||||
Identifier copied to clipboard!: تم نسخ المعرّف إلى الحافظة!
|
||||
Imprint: معلومات قانونية
|
||||
Information: معلومات
|
||||
Language: اللغة
|
||||
Open: فتح
|
||||
Open Link: فتح الرابط
|
||||
Options: خيارات
|
||||
Warning: تحذير
|
||||
11
app/i18n/ui/as.yaml
Normal file
11
app/i18n/ui/as.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: বিকল্পসমূহ
|
||||
Close: বন্ধ কৰক
|
||||
Copy: কপি কৰক
|
||||
Identifier copied to clipboard!: চিনাক্তকাৰী ক্লিপবোৰ্ডলৈ কপি কৰা হ'ল!
|
||||
Imprint: আইনী তথ্য
|
||||
Information: তথ্য
|
||||
Language: ভাষা
|
||||
Open: খোলক
|
||||
Open Link: লিংক খোলক
|
||||
Options: বিকল্প
|
||||
Warning: সতৰ্কবাণী
|
||||
11
app/i18n/ui/av.yaml
Normal file
11
app/i18n/ui/av.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Альтернативаби
|
||||
Close: Къазе
|
||||
Copy: Копия гьабе
|
||||
Identifier copied to clipboard!: Идентификатор буфералде копия гьабуна!
|
||||
Imprint: Закон баян
|
||||
Information: Хабар
|
||||
Language: МацӀ
|
||||
Open: Рагье
|
||||
Open Link: Ссылка рагье
|
||||
Options: Параметрал
|
||||
Warning: ТӀаде кӀвар
|
||||
11
app/i18n/ui/ay.yaml
Normal file
11
app/i18n/ui/ay.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Yaqha ajlliwinaka
|
||||
Close: Jist'antaña
|
||||
Copy: Qillqaña
|
||||
Identifier copied to clipboard!: Uñt'ayiri clipboard ukar qillqatawa!
|
||||
Imprint: Kamachi yatiyawi
|
||||
Information: Yatiyawi
|
||||
Language: Aru
|
||||
Open: Jist'araña
|
||||
Open Link: Link jist'araña
|
||||
Options: Ajlliwinaka
|
||||
Warning: Iwxt'awi
|
||||
11
app/i18n/ui/az.yaml
Normal file
11
app/i18n/ui/az.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Alternativlər
|
||||
Close: Bağla
|
||||
Copy: Kopyala
|
||||
Identifier copied to clipboard!: İdentifikator mübadilə buferinə kopyalandı!
|
||||
Imprint: Hüquqi məlumat
|
||||
Information: Məlumat
|
||||
Language: Dil
|
||||
Open: Aç
|
||||
Open Link: Keçidi aç
|
||||
Options: Seçimlər
|
||||
Warning: Xəbərdarlıq
|
||||
11
app/i18n/ui/ba.yaml
Normal file
11
app/i18n/ui/ba.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Альтернативалар
|
||||
Close: Ябырға
|
||||
Copy: Күсерергә
|
||||
Identifier copied to clipboard!: Идентификатор алмашыу буферына күсерелде!
|
||||
Imprint: Хоҡуҡи мәғлүмәт
|
||||
Information: Мәғлүмәт
|
||||
Language: Тел
|
||||
Open: Асырға
|
||||
Open Link: Һылтанманы асырға
|
||||
Options: Параметрҙар
|
||||
Warning: Иҫкәртеү
|
||||
11
app/i18n/ui/be.yaml
Normal file
11
app/i18n/ui/be.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Альтэрнатывы
|
||||
Close: Закрыць
|
||||
Copy: Капіяваць
|
||||
Identifier copied to clipboard!: Ідэнтыфікатар скапіяваны ў буфер абмену!
|
||||
Imprint: Прававая інфармацыя
|
||||
Information: Інфармацыя
|
||||
Language: Мова
|
||||
Open: Адкрыць
|
||||
Open Link: Адкрыць спасылку
|
||||
Options: Параметры
|
||||
Warning: Папярэджанне
|
||||
11
app/i18n/ui/bg.yaml
Normal file
11
app/i18n/ui/bg.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Алтернативи
|
||||
Close: Затвори
|
||||
Copy: Копирай
|
||||
Identifier copied to clipboard!: Идентификаторът е копиран в клипборда!
|
||||
Imprint: Правна информация
|
||||
Information: Информация
|
||||
Language: Език
|
||||
Open: Отвори
|
||||
Open Link: Отвори връзката
|
||||
Options: Опции
|
||||
Warning: Предупреждение
|
||||
11
app/i18n/ui/bi.yaml
Normal file
11
app/i18n/ui/bi.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Narafala jois
|
||||
Close: Klosem
|
||||
Copy: Kopi
|
||||
Identifier copied to clipboard!: Namba blong aedentifikesen i kopi finis!
|
||||
Imprint: Loa infomesen
|
||||
Information: Infomesen
|
||||
Language: Lanwis
|
||||
Open: Openem
|
||||
Open Link: Openem link
|
||||
Options: Ol jois
|
||||
Warning: Woning
|
||||
11
app/i18n/ui/bm.yaml
Normal file
11
app/i18n/ui/bm.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Sugandili wɛrɛw
|
||||
Close: A datugu
|
||||
Copy: A kopi
|
||||
Identifier copied to clipboard!: Tɔgɔ taamasiyɛn kopilen don!
|
||||
Imprint: Sariya kunnafoni
|
||||
Information: Kunnafoni
|
||||
Language: Kan
|
||||
Open: A dayɛlɛ
|
||||
Open Link: Jɛɲɔgɔnya dayɛlɛ
|
||||
Options: Sugandiliw
|
||||
Warning: Lasɔmini
|
||||
11
app/i18n/ui/bn.yaml
Normal file
11
app/i18n/ui/bn.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: অন্যান্য বিকল্প
|
||||
Close: বন্ধ করুন
|
||||
Copy: অনুলিপি
|
||||
Identifier copied to clipboard!: শনাক্তকারী ক্লিপবোর্ডে অনুলিপি করা হয়েছে!
|
||||
Imprint: আইনি তথ্য
|
||||
Information: তথ্য
|
||||
Language: ভাষা
|
||||
Open: খুলুন
|
||||
Open Link: লিঙ্ক খুলুন
|
||||
Options: বিকল্প
|
||||
Warning: সতর্কতা
|
||||
11
app/i18n/ui/bo.yaml
Normal file
11
app/i18n/ui/bo.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: གདམ་ག་གཞན།
|
||||
Close: ཁ་རྒྱག
|
||||
Copy: འདྲ་བཤུས།
|
||||
Identifier copied to clipboard!: ངོས་འཛིན་རྟགས་སྦྱར་སྡེར་དུ་འདྲ་བཤུས་བྱས་ཟིན།
|
||||
Imprint: ཁྲིམས་ཀྱི་གསལ་བསྒྲགས།
|
||||
Information: ཆ་འཕྲིན།
|
||||
Language: སྐད་ཡིག
|
||||
Open: ཁ་ཕྱེ།
|
||||
Open Link: སྦྲེལ་ཐག་ཁ་ཕྱེ།
|
||||
Options: གདམ་ག
|
||||
Warning: ཉེན་བརྡ།
|
||||
11
app/i18n/ui/br.yaml
Normal file
11
app/i18n/ui/br.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Dibaboù all
|
||||
Close: Serriñ
|
||||
Copy: Eilañ
|
||||
Identifier copied to clipboard!: Eilet eo bet an anaouder er golver!
|
||||
Imprint: Titouroù lezennel
|
||||
Information: Titouroù
|
||||
Language: Yezh
|
||||
Open: Digeriñ
|
||||
Open Link: Digeriñ al liamm
|
||||
Options: Dibarzhioù
|
||||
Warning: Diwall
|
||||
11
app/i18n/ui/bs.yaml
Normal file
11
app/i18n/ui/bs.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Alternative
|
||||
Close: Zatvori
|
||||
Copy: Kopiraj
|
||||
Identifier copied to clipboard!: Identifikator je kopiran u međuspremnik!
|
||||
Imprint: Pravne informacije
|
||||
Information: Informacije
|
||||
Language: Jezik
|
||||
Open: Otvori
|
||||
Open Link: Otvori link
|
||||
Options: Opcije
|
||||
Warning: Upozorenje
|
||||
11
app/i18n/ui/ca.yaml
Normal file
11
app/i18n/ui/ca.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Alternatives
|
||||
Close: Tanca
|
||||
Copy: Copia
|
||||
Identifier copied to clipboard!: S'ha copiat l'identificador al porta-retalls!
|
||||
Imprint: Avís legal
|
||||
Information: Informació
|
||||
Language: Idioma
|
||||
Open: Obre
|
||||
Open Link: Obre l'enllaç
|
||||
Options: Opcions
|
||||
Warning: Advertiment
|
||||
11
app/i18n/ui/ce.yaml
Normal file
11
app/i18n/ui/ce.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Альтернативаш
|
||||
Close: ДӀакъовла
|
||||
Copy: Копи ян
|
||||
Identifier copied to clipboard!: Идентификатор буфере копи йина!
|
||||
Imprint: Бакъонан хаам
|
||||
Information: Хаам
|
||||
Language: Мотт
|
||||
Open: ДӀаелла
|
||||
Open Link: ТӀе хьажорг дӀаелла
|
||||
Options: Параметраш
|
||||
Warning: Кхайкхам
|
||||
11
app/i18n/ui/ch.yaml
Normal file
11
app/i18n/ui/ch.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Otro na ayek
|
||||
Close: Huchom
|
||||
Copy: Kopia
|
||||
Identifier copied to clipboard!: Ma kopia i identifier gi clipboard!
|
||||
Imprint: Infotmasion lai
|
||||
Information: Infotmasion
|
||||
Language: Lengguahi
|
||||
Open: Baba
|
||||
Open Link: Baba i link
|
||||
Options: Ayek
|
||||
Warning: Adbietensia
|
||||
11
app/i18n/ui/co.yaml
Normal file
11
app/i18n/ui/co.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Alternative
|
||||
Close: Chjode
|
||||
Copy: Cupià
|
||||
Identifier copied to clipboard!: L'identificatore hè statu cupiatu in u preme-papers!
|
||||
Imprint: Menzioni legali
|
||||
Information: Infurmazione
|
||||
Language: Lingua
|
||||
Open: Apre
|
||||
Open Link: Apre u ligame
|
||||
Options: Uzzione
|
||||
Warning: Avertimentu
|
||||
11
app/i18n/ui/cr.yaml
Normal file
11
app/i18n/ui/cr.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: kotakak
|
||||
Close: kipahamok
|
||||
Copy: ayisinahikêwin
|
||||
Identifier copied to clipboard!: kiskinowâcihcikan kî-ayisinahikâtêw!
|
||||
Imprint: wiyasiwêwin kiskêyihtamowin
|
||||
Information: kiskêyihtamowin
|
||||
Language: pîkiskwêwin
|
||||
Open: yôhtênamok
|
||||
Open Link: yôhtêna mâmawikan
|
||||
Options: nawasônamowina
|
||||
Warning: kitahamâkêwin
|
||||
11
app/i18n/ui/cs.yaml
Normal file
11
app/i18n/ui/cs.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Alternativy
|
||||
Close: Zavřít
|
||||
Copy: Kopírovat
|
||||
Identifier copied to clipboard!: Identifikátor zkopírován do schránky!
|
||||
Imprint: Tiráž
|
||||
Information: Informace
|
||||
Language: Jazyk
|
||||
Open: Otevřít
|
||||
Open Link: Otevřít odkaz
|
||||
Options: Možnosti
|
||||
Warning: Upozornění
|
||||
11
app/i18n/ui/cu.yaml
Normal file
11
app/i18n/ui/cu.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Инаѧ
|
||||
Close: Затвори
|
||||
Copy: Списати
|
||||
Identifier copied to clipboard!: Именованїе списано бысть!
|
||||
Imprint: Законное вѣдѣнїе
|
||||
Information: Вѣдѣнїе
|
||||
Language: Ѧзыкъ
|
||||
Open: Отверзи
|
||||
Open Link: Отверзи съвѧзь
|
||||
Options: Изволенїѧ
|
||||
Warning: Предостереженїе
|
||||
11
app/i18n/ui/cv.yaml
Normal file
11
app/i18n/ui/cv.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Альтернативӑсем
|
||||
Close: Хуп
|
||||
Copy: Копиле
|
||||
Identifier copied to clipboard!: Идентификатор буфера копиленӗ!
|
||||
Imprint: Саккун информацийӗ
|
||||
Information: Информаци
|
||||
Language: Чӗлхе
|
||||
Open: Уҫ
|
||||
Open Link: Каҫӑна уҫ
|
||||
Options: Параметрсем
|
||||
Warning: Асӑрхаттару
|
||||
11
app/i18n/ui/cy.yaml
Normal file
11
app/i18n/ui/cy.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Dewisiadau eraill
|
||||
Close: Cau
|
||||
Copy: Copïo
|
||||
Identifier copied to clipboard!: Wedi copïo'r dynodwr i'r clipfwrdd!
|
||||
Imprint: Gwybodaeth gyfreithiol
|
||||
Information: Gwybodaeth
|
||||
Language: Iaith
|
||||
Open: Agor
|
||||
Open Link: Agor y ddolen
|
||||
Options: Opsiynau
|
||||
Warning: Rhybudd
|
||||
11
app/i18n/ui/da.yaml
Normal file
11
app/i18n/ui/da.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Alternativer
|
||||
Close: Luk
|
||||
Copy: Kopiér
|
||||
Identifier copied to clipboard!: Identifikator kopieret til udklipsholder!
|
||||
Imprint: Juridisk information
|
||||
Information: Information
|
||||
Language: Sprog
|
||||
Open: Åbn
|
||||
Open Link: Åbn link
|
||||
Options: Valgmuligheder
|
||||
Warning: Advarsel
|
||||
11
app/i18n/ui/de.yaml
Normal file
11
app/i18n/ui/de.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Alternativen
|
||||
Close: Schließen
|
||||
Copy: Kopieren
|
||||
Identifier copied to clipboard!: Kennung in die Zwischenablage kopiert!
|
||||
Imprint: Impressum
|
||||
Information: Information
|
||||
Language: Sprache
|
||||
Open: Öffnen
|
||||
Open Link: Link öffnen
|
||||
Options: Optionen
|
||||
Warning: Warnung
|
||||
11
app/i18n/ui/dv.yaml
Normal file
11
app/i18n/ui/dv.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: ބަދަލު އިޚްތިޔާރުތައް
|
||||
Close: ބަންދުކުރޭ
|
||||
Copy: ކޮޕީކުރޭ
|
||||
Identifier copied to clipboard!: އައިޑެންޓިފަޔަރ ކްލިޕްބޯޑަށް ކޮޕީކުރެވިއްޖެ!
|
||||
Imprint: ޤާނޫނީ މަޢުލޫމާތު
|
||||
Information: މަޢުލޫމާތު
|
||||
Language: ބަސް
|
||||
Open: ހުޅުވާ
|
||||
Open Link: ލިންކު ހުޅުވާ
|
||||
Options: އިޚްތިޔާރުތައް
|
||||
Warning: އިންޒާރު
|
||||
11
app/i18n/ui/dz.yaml
Normal file
11
app/i18n/ui/dz.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: གདམ་ཁ་གཞན།
|
||||
Close: ཁ་བསྡམས།
|
||||
Copy: འདྲ་བཤུས།
|
||||
Identifier copied to clipboard!: ངོས་འཛིན་འདི་འདྲ་བཤུས་འབད་ཡི།
|
||||
Imprint: ཁྲིམས་དོན་བརྡ་དོན།
|
||||
Information: བརྡ་དོན།
|
||||
Language: ཁ་སྐད།
|
||||
Open: ཁ་ཕྱེ།
|
||||
Open Link: འབྲེལ་མཐུད་ཁ་ཕྱེ།
|
||||
Options: གདམ་ཁ།
|
||||
Warning: ཉེན་བརྡ།
|
||||
11
app/i18n/ui/ee.yaml
Normal file
11
app/i18n/ui/ee.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Tiatia bubuwo
|
||||
Close: Tu
|
||||
Copy: Kɔpi
|
||||
Identifier copied to clipboard!: Wokɔpi dzesidede ɖe clipboard dzi!
|
||||
Imprint: Se ŋuti nyatakakawo
|
||||
Information: Nyatakaka
|
||||
Language: Gbe
|
||||
Open: Ʋu
|
||||
Open Link: Ʋu kadodoa
|
||||
Options: Tiatiawo
|
||||
Warning: Nuxlɔ̃ame
|
||||
11
app/i18n/ui/el.yaml
Normal file
11
app/i18n/ui/el.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Εναλλακτικές
|
||||
Close: Κλείσιμο
|
||||
Copy: Αντιγραφή
|
||||
Identifier copied to clipboard!: Το αναγνωριστικό αντιγράφηκε στο πρόχειρο!
|
||||
Imprint: Νομικές πληροφορίες
|
||||
Information: Πληροφορίες
|
||||
Language: Γλώσσα
|
||||
Open: Άνοιγμα
|
||||
Open Link: Άνοιγμα συνδέσμου
|
||||
Options: Επιλογές
|
||||
Warning: Προειδοποίηση
|
||||
11
app/i18n/ui/eo.yaml
Normal file
11
app/i18n/ui/eo.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Alternativoj
|
||||
Close: Fermi
|
||||
Copy: Kopii
|
||||
Identifier copied to clipboard!: Identigilo kopiita al la tondujo!
|
||||
Imprint: Jura informo
|
||||
Information: Informo
|
||||
Language: Lingvo
|
||||
Open: Malfermi
|
||||
Open Link: Malfermi ligilon
|
||||
Options: Opcioj
|
||||
Warning: Averto
|
||||
11
app/i18n/ui/es.yaml
Normal file
11
app/i18n/ui/es.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Alternativas
|
||||
Close: Cerrar
|
||||
Copy: Copiar
|
||||
Identifier copied to clipboard!: ¡Identificador copiado al portapapeles!
|
||||
Imprint: Aviso legal
|
||||
Information: Información
|
||||
Language: Idioma
|
||||
Open: Abrir
|
||||
Open Link: Abrir enlace
|
||||
Options: Opciones
|
||||
Warning: Advertencia
|
||||
11
app/i18n/ui/et.yaml
Normal file
11
app/i18n/ui/et.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Alternatiivid
|
||||
Close: Sulge
|
||||
Copy: Kopeeri
|
||||
Identifier copied to clipboard!: Identifikaator kopeeriti lõikelauale!
|
||||
Imprint: Õiguslik teave
|
||||
Information: Teave
|
||||
Language: Keel
|
||||
Open: Ava
|
||||
Open Link: Ava link
|
||||
Options: Valikud
|
||||
Warning: Hoiatus
|
||||
11
app/i18n/ui/eu.yaml
Normal file
11
app/i18n/ui/eu.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Alternatibak
|
||||
Close: Itxi
|
||||
Copy: Kopiatu
|
||||
Identifier copied to clipboard!: Identifikatzailea arbelean kopiatu da!
|
||||
Imprint: Lege-oharra
|
||||
Information: Informazioa
|
||||
Language: Hizkuntza
|
||||
Open: Ireki
|
||||
Open Link: Ireki esteka
|
||||
Options: Aukerak
|
||||
Warning: Abisua
|
||||
11
app/i18n/ui/fa.yaml
Normal file
11
app/i18n/ui/fa.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: جایگزینها
|
||||
Close: بستن
|
||||
Copy: کپی
|
||||
Identifier copied to clipboard!: شناسه در کلیپبورد کپی شد!
|
||||
Imprint: اطلاعات حقوقی
|
||||
Information: اطلاعات
|
||||
Language: زبان
|
||||
Open: باز کردن
|
||||
Open Link: باز کردن پیوند
|
||||
Options: گزینهها
|
||||
Warning: هشدار
|
||||
11
app/i18n/ui/ff.yaml
Normal file
11
app/i18n/ui/ff.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Suɓaaji goɗɗi
|
||||
Close: Uddu
|
||||
Copy: Natto
|
||||
Identifier copied to clipboard!: Innde heɓtirde natta!
|
||||
Imprint: Humpito laawol
|
||||
Information: Humpito
|
||||
Language: Ɗemngal
|
||||
Open: Uddit
|
||||
Open Link: Uddit jokkorde
|
||||
Options: Suɓaaji
|
||||
Warning: Reentino
|
||||
11
app/i18n/ui/fi.yaml
Normal file
11
app/i18n/ui/fi.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Vaihtoehdot
|
||||
Close: Sulje
|
||||
Copy: Kopioi
|
||||
Identifier copied to clipboard!: Tunniste kopioitu leikepöydälle!
|
||||
Imprint: Oikeudelliset tiedot
|
||||
Information: Tiedot
|
||||
Language: Kieli
|
||||
Open: Avaa
|
||||
Open Link: Avaa linkki
|
||||
Options: Valinnat
|
||||
Warning: Varoitus
|
||||
11
app/i18n/ui/fj.yaml
Normal file
11
app/i18n/ui/fj.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Na ka tale e so
|
||||
Close: Sogota
|
||||
Copy: Kopitaka
|
||||
Identifier copied to clipboard!: Sa kopitaki na ivakatakilakila!
|
||||
Imprint: iTukutuku vakalawa
|
||||
Information: iTukutuku
|
||||
Language: Vosa
|
||||
Open: Dolava
|
||||
Open Link: Dolava na isema
|
||||
Options: Digidigi
|
||||
Warning: iVakasala
|
||||
11
app/i18n/ui/fo.yaml
Normal file
11
app/i18n/ui/fo.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Alternativ
|
||||
Close: Lat aftur
|
||||
Copy: Avrita
|
||||
Identifier copied to clipboard!: Eyðmerki avritað í setiborðið!
|
||||
Imprint: Løgfrøðilig kunning
|
||||
Information: Kunning
|
||||
Language: Mál
|
||||
Open: Lat upp
|
||||
Open Link: Lat leinkju upp
|
||||
Options: Valmøguleikar
|
||||
Warning: Ávaring
|
||||
11
app/i18n/ui/fr.yaml
Normal file
11
app/i18n/ui/fr.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Alternatives
|
||||
Close: Fermer
|
||||
Copy: Copier
|
||||
Identifier copied to clipboard!: Identifiant copié dans le presse-papiers !
|
||||
Imprint: Mentions légales
|
||||
Information: Informations
|
||||
Language: Langue
|
||||
Open: Ouvrir
|
||||
Open Link: Ouvrir le lien
|
||||
Options: Options
|
||||
Warning: Avertissement
|
||||
11
app/i18n/ui/fy.yaml
Normal file
11
app/i18n/ui/fy.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Alternativen
|
||||
Close: Slute
|
||||
Copy: Kopiearje
|
||||
Identifier copied to clipboard!: Identifikaasje nei it klamboerd kopiearre!
|
||||
Imprint: Kolofon
|
||||
Information: Ynformaasje
|
||||
Language: Taal
|
||||
Open: Iepenje
|
||||
Open Link: Keppeling iepenje
|
||||
Options: Opsjes
|
||||
Warning: Warskôging
|
||||
11
app/i18n/ui/ga.yaml
Normal file
11
app/i18n/ui/ga.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Roghanna eile
|
||||
Close: Dún
|
||||
Copy: Cóipeáil
|
||||
Identifier copied to clipboard!: Cóipeáladh an t-aitheantóir chuig an ghearrthaisce!
|
||||
Imprint: Fógra dlíthiúil
|
||||
Information: Faisnéis
|
||||
Language: Teanga
|
||||
Open: Oscail
|
||||
Open Link: Oscail an nasc
|
||||
Options: Roghanna
|
||||
Warning: Rabhadh
|
||||
11
app/i18n/ui/gd.yaml
Normal file
11
app/i18n/ui/gd.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Roghainnean eile
|
||||
Close: Dùin
|
||||
Copy: Dèan lethbhreac
|
||||
Identifier copied to clipboard!: Chaidh lethbhreac dhen aithnichear a chur air an stòr-bhòrd!
|
||||
Imprint: Fiosrachadh laghail
|
||||
Information: Fiosrachadh
|
||||
Language: Cànan
|
||||
Open: Fosgail
|
||||
Open Link: Fosgail an ceangal
|
||||
Options: Roghainnean
|
||||
Warning: Rabhadh
|
||||
11
app/i18n/ui/gl.yaml
Normal file
11
app/i18n/ui/gl.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Alternativas
|
||||
Close: Pechar
|
||||
Copy: Copiar
|
||||
Identifier copied to clipboard!: Identificador copiado no portapapeis!
|
||||
Imprint: Aviso legal
|
||||
Information: Información
|
||||
Language: Idioma
|
||||
Open: Abrir
|
||||
Open Link: Abrir ligazón
|
||||
Options: Opcións
|
||||
Warning: Advertencia
|
||||
11
app/i18n/ui/gn.yaml
Normal file
11
app/i18n/ui/gn.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Ambue jeporavorã
|
||||
Close: Mboty
|
||||
Copy: Monguatia
|
||||
Identifier copied to clipboard!: Oñemonguatia pe mboheraguapy!
|
||||
Imprint: Léi marandu
|
||||
Information: Marandu
|
||||
Language: Ñe'ẽ
|
||||
Open: Mbojuruja
|
||||
Open Link: Mbojuruja joajuha
|
||||
Options: Jeporavorã
|
||||
Warning: Ñemomarandu
|
||||
11
app/i18n/ui/gu.yaml
Normal file
11
app/i18n/ui/gu.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: અન્ય વિકલ્પો
|
||||
Close: બંધ કરો
|
||||
Copy: કૉપિ કરો
|
||||
Identifier copied to clipboard!: ઓળખકર્તા ક્લિપબોર્ડ પર કૉપિ કર્યો!
|
||||
Imprint: કાનૂની માહિતી
|
||||
Information: માહિતી
|
||||
Language: ભાષા
|
||||
Open: ખોલો
|
||||
Open Link: લિંક ખોલો
|
||||
Options: વિકલ્પો
|
||||
Warning: ચેતવણી
|
||||
11
app/i18n/ui/gv.yaml
Normal file
11
app/i18n/ui/gv.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Reihghyn elley
|
||||
Close: Dooney
|
||||
Copy: Jean lhiasaghey
|
||||
Identifier copied to clipboard!: Ta'n enmyssagh er ny lhiasaghey!
|
||||
Imprint: Fys leighoil
|
||||
Information: Fys
|
||||
Language: Çhengey
|
||||
Open: Foshil
|
||||
Open Link: Foshil y kiangley
|
||||
Options: Reihghyn
|
||||
Warning: Raaue
|
||||
11
app/i18n/ui/ha.yaml
Normal file
11
app/i18n/ui/ha.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Madadai
|
||||
Close: Rufe
|
||||
Copy: Kwafi
|
||||
Identifier copied to clipboard!: An kwafi mai ganowa zuwa allon rubutu!
|
||||
Imprint: Bayanin doka
|
||||
Information: Bayani
|
||||
Language: Harshe
|
||||
Open: Buɗe
|
||||
Open Link: Buɗe mahaɗi
|
||||
Options: Zaɓuɓɓuka
|
||||
Warning: Gargaɗi
|
||||
11
app/i18n/ui/he.yaml
Normal file
11
app/i18n/ui/he.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: חלופות
|
||||
Close: סגור
|
||||
Copy: העתק
|
||||
Identifier copied to clipboard!: המזהה הועתק ללוח!
|
||||
Imprint: מידע משפטי
|
||||
Information: מידע
|
||||
Language: שפה
|
||||
Open: פתח
|
||||
Open Link: פתח קישור
|
||||
Options: אפשרויות
|
||||
Warning: אזהרה
|
||||
11
app/i18n/ui/hi.yaml
Normal file
11
app/i18n/ui/hi.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: अन्य विकल्प
|
||||
Close: बंद करें
|
||||
Copy: कॉपी करें
|
||||
Identifier copied to clipboard!: पहचानकर्ता क्लिपबोर्ड पर कॉपी हो गया!
|
||||
Imprint: कानूनी सूचना
|
||||
Information: जानकारी
|
||||
Language: भाषा
|
||||
Open: खोलें
|
||||
Open Link: लिंक खोलें
|
||||
Options: विकल्प
|
||||
Warning: चेतावनी
|
||||
11
app/i18n/ui/ho.yaml
Normal file
11
app/i18n/ui/ho.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Ma haida
|
||||
Close: Koua
|
||||
Copy: Kopi
|
||||
Identifier copied to clipboard!: Ladana ia kopi vadaeni!
|
||||
Imprint: Taravatu sivaraina
|
||||
Information: Sivaraina
|
||||
Language: Gado
|
||||
Open: Kehoa
|
||||
Open Link: Link kehoa
|
||||
Options: Abia hidi
|
||||
Warning: Hadibaia
|
||||
11
app/i18n/ui/hr.yaml
Normal file
11
app/i18n/ui/hr.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Alternative
|
||||
Close: Zatvori
|
||||
Copy: Kopiraj
|
||||
Identifier copied to clipboard!: Identifikator je kopiran u međuspremnik!
|
||||
Imprint: Pravne informacije
|
||||
Information: Informacije
|
||||
Language: Jezik
|
||||
Open: Otvori
|
||||
Open Link: Otvori poveznicu
|
||||
Options: Mogućnosti
|
||||
Warning: Upozorenje
|
||||
11
app/i18n/ui/ht.yaml
Normal file
11
app/i18n/ui/ht.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Lòt chwa
|
||||
Close: Fèmen
|
||||
Copy: Kopye
|
||||
Identifier copied to clipboard!: Idantifyan an kopye nan klipbòd la!
|
||||
Imprint: Enfòmasyon legal
|
||||
Information: Enfòmasyon
|
||||
Language: Lang
|
||||
Open: Louvri
|
||||
Open Link: Louvri lyen an
|
||||
Options: Opsyon
|
||||
Warning: Avètisman
|
||||
11
app/i18n/ui/hu.yaml
Normal file
11
app/i18n/ui/hu.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Alternatívák
|
||||
Close: Bezárás
|
||||
Copy: Másolás
|
||||
Identifier copied to clipboard!: Az azonosító a vágólapra másolva!
|
||||
Imprint: Impresszum
|
||||
Information: Információ
|
||||
Language: Nyelv
|
||||
Open: Megnyitás
|
||||
Open Link: Hivatkozás megnyitása
|
||||
Options: Lehetőségek
|
||||
Warning: Figyelmeztetés
|
||||
11
app/i18n/ui/hy.yaml
Normal file
11
app/i18n/ui/hy.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Այլընտրանքներ
|
||||
Close: Փակել
|
||||
Copy: Պատճենել
|
||||
Identifier copied to clipboard!: Նույնացուցիչը պատճենվեց սեղմատախտակին!
|
||||
Imprint: Իրավական տեղեկություններ
|
||||
Information: Տեղեկություն
|
||||
Language: Լեզու
|
||||
Open: Բացել
|
||||
Open Link: Բացել հղումը
|
||||
Options: Ընտրանքներ
|
||||
Warning: Զգուշացում
|
||||
11
app/i18n/ui/hz.yaml
Normal file
11
app/i18n/ui/hz.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Ovikeṱa oviṱjaṱa
|
||||
Close: Pata
|
||||
Copy: Hiṱa
|
||||
Identifier copied to clipboard!: Ondjiviro ya hiṱwa!
|
||||
Imprint: Omambo woveta
|
||||
Information: Omambo
|
||||
Language: Ombango
|
||||
Open: Paturura
|
||||
Open Link: Paturura omukambo
|
||||
Options: Ovikeṱa
|
||||
Warning: Omaṱoororo
|
||||
11
app/i18n/ui/ia.yaml
Normal file
11
app/i18n/ui/ia.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Alternativas
|
||||
Close: Clauder
|
||||
Copy: Copiar
|
||||
Identifier copied to clipboard!: Identificator copiate al area de transferentia!
|
||||
Imprint: Information legal
|
||||
Information: Information
|
||||
Language: Lingua
|
||||
Open: Aperir
|
||||
Open Link: Aperir ligamine
|
||||
Options: Optiones
|
||||
Warning: Advertimento
|
||||
11
app/i18n/ui/id.yaml
Normal file
11
app/i18n/ui/id.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Alternatif
|
||||
Close: Tutup
|
||||
Copy: Salin
|
||||
Identifier copied to clipboard!: Pengenal disalin ke papan klip!
|
||||
Imprint: Informasi hukum
|
||||
Information: Informasi
|
||||
Language: Bahasa
|
||||
Open: Buka
|
||||
Open Link: Buka tautan
|
||||
Options: Opsi
|
||||
Warning: Peringatan
|
||||
11
app/i18n/ui/ie.yaml
Normal file
11
app/i18n/ui/ie.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Alternatives
|
||||
Close: Clúder
|
||||
Copy: Copiar
|
||||
Identifier copied to clipboard!: Identificator copiat al paperiere!
|
||||
Imprint: Information legal
|
||||
Information: Information
|
||||
Language: Lingue
|
||||
Open: Aperter
|
||||
Open Link: Aperter li ligament
|
||||
Options: Optiones
|
||||
Warning: Avise
|
||||
11
app/i18n/ui/ig.yaml
Normal file
11
app/i18n/ui/ig.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Nhọrọ ndị ọzọ
|
||||
Close: Mechie
|
||||
Copy: Detuo
|
||||
Identifier copied to clipboard!: Edepụtara njirimara na klipbọọdụ!
|
||||
Imprint: Ozi gbasara iwu
|
||||
Information: Ozi
|
||||
Language: Asụsụ
|
||||
Open: Mepee
|
||||
Open Link: Mepee njikọ
|
||||
Options: Nhọrọ
|
||||
Warning: Ịdọ aka na ntị
|
||||
11
app/i18n/ui/ii.yaml
Normal file
11
app/i18n/ui/ii.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: ꀋꅉ
|
||||
Close: ꏢ
|
||||
Copy: ꀕ
|
||||
Identifier copied to clipboard!: ꂓꂷ ꀕꀐ!
|
||||
Imprint: ꃀꇖ ꌋꑍ
|
||||
Information: ꌋꑍ
|
||||
Language: ꉙ
|
||||
Open: ꉈ
|
||||
Open Link: ꇤꉈ
|
||||
Options: ꀋꅉ
|
||||
Warning: ꋍꅉ
|
||||
11
app/i18n/ui/ik.yaml
Normal file
11
app/i18n/ui/ik.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Allat piksrat
|
||||
Close: Umiksiuŋ
|
||||
Copy: Aglaakkaŋ
|
||||
Identifier copied to clipboard!: Atiŋa aglaaksiruq!
|
||||
Imprint: Maligutaq ilisimaaġutit
|
||||
Information: Ilisimaaġutit
|
||||
Language: Uqausiq
|
||||
Open: Aŋmaġuŋ
|
||||
Open Link: Aŋmaġuŋ link
|
||||
Options: Piksrat
|
||||
Warning: Kaŋiqsiġutit
|
||||
11
app/i18n/ui/io.yaml
Normal file
11
app/i18n/ui/io.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Alternativi
|
||||
Close: Klozar
|
||||
Copy: Kopiar
|
||||
Identifier copied to clipboard!: Identigilo kopiesis a la tondilo!
|
||||
Imprint: Legala informo
|
||||
Information: Informo
|
||||
Language: Linguo
|
||||
Open: Apertar
|
||||
Open Link: Apertar ligilo
|
||||
Options: Opcioni
|
||||
Warning: Averto
|
||||
11
app/i18n/ui/is.yaml
Normal file
11
app/i18n/ui/is.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Aðrir kostir
|
||||
Close: Loka
|
||||
Copy: Afrita
|
||||
Identifier copied to clipboard!: Auðkenni afritað á klippispjald!
|
||||
Imprint: Lagalegar upplýsingar
|
||||
Information: Upplýsingar
|
||||
Language: Tungumál
|
||||
Open: Opna
|
||||
Open Link: Opna tengil
|
||||
Options: Valkostir
|
||||
Warning: Viðvörun
|
||||
11
app/i18n/ui/it.yaml
Normal file
11
app/i18n/ui/it.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Alternative
|
||||
Close: Chiudi
|
||||
Copy: Copia
|
||||
Identifier copied to clipboard!: Identificatore copiato negli appunti!
|
||||
Imprint: Note legali
|
||||
Information: Informazioni
|
||||
Language: Lingua
|
||||
Open: Apri
|
||||
Open Link: Apri collegamento
|
||||
Options: Opzioni
|
||||
Warning: Avviso
|
||||
11
app/i18n/ui/iu.yaml
Normal file
11
app/i18n/ui/iu.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: ᐊᓯᖏᑦ ᓂᕈᐊᒐᒃᓴᐃᑦ
|
||||
Close: ᒪᑐᐃᕐᓗᒍ
|
||||
Copy: ᐊᔾᔨᓕᐅᕐᓗᒍ
|
||||
Identifier copied to clipboard!: ᓇᓗᓇᐃᒃᑯᑕᖅ ᐊᔾᔨᓕᐅᖅᑕᐅᔪᖅ!
|
||||
Imprint: ᒪᓕᒐᓕᕆᓂᕐᒧᑦ ᑐᓴᒐᒃᓴᖅ
|
||||
Information: ᑐᓴᒐᒃᓴᖅ
|
||||
Language: ᐅᖃᐅᓯᖅ
|
||||
Open: ᐅᒃᑯᐃᓗᒍ
|
||||
Open Link: ᐊᑐᕆᐊᖅ ᐅᒃᑯᐃᓗᒍ
|
||||
Options: ᓂᕈᐊᒐᒃᓴᐃᑦ
|
||||
Warning: ᖃᐅᔨᒋᐊᕐᓂᖅ
|
||||
11
app/i18n/ui/ja.yaml
Normal file
11
app/i18n/ui/ja.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: 代替
|
||||
Close: 閉じる
|
||||
Copy: コピー
|
||||
Identifier copied to clipboard!: 識別子をクリップボードにコピーしました!
|
||||
Imprint: 法的情報
|
||||
Information: 情報
|
||||
Language: 言語
|
||||
Open: 開く
|
||||
Open Link: リンクを開く
|
||||
Options: オプション
|
||||
Warning: 警告
|
||||
11
app/i18n/ui/jv.yaml
Normal file
11
app/i18n/ui/jv.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Alternatif
|
||||
Close: Tutup
|
||||
Copy: Salin
|
||||
Identifier copied to clipboard!: Pengenal disalin menyang papan klip!
|
||||
Imprint: Informasi hukum
|
||||
Information: Informasi
|
||||
Language: Basa
|
||||
Open: Bukak
|
||||
Open Link: Bukak pranala
|
||||
Options: Pilihan
|
||||
Warning: Pèngetan
|
||||
11
app/i18n/ui/ka.yaml
Normal file
11
app/i18n/ui/ka.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: ალტერნატივები
|
||||
Close: დახურვა
|
||||
Copy: კოპირება
|
||||
Identifier copied to clipboard!: იდენტიფიკატორი დაკოპირდა ბუფერში!
|
||||
Imprint: იურიდიული ინფორმაცია
|
||||
Information: ინფორმაცია
|
||||
Language: ენა
|
||||
Open: გახსნა
|
||||
Open Link: ბმულის გახსნა
|
||||
Options: პარამეტრები
|
||||
Warning: გაფრთხილება
|
||||
11
app/i18n/ui/kg.yaml
Normal file
11
app/i18n/ui/kg.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Mapona ya nkaka
|
||||
Close: Kanga
|
||||
Copy: Kopia
|
||||
Identifier copied to clipboard!: Kinsinsu me kopiama!
|
||||
Imprint: Nsangu ya nsiku
|
||||
Information: Nsangu
|
||||
Language: Ndinga
|
||||
Open: Kangula
|
||||
Open Link: Kangula nkangu
|
||||
Options: Mapona
|
||||
Warning: Lukebisu
|
||||
11
app/i18n/ui/ki.yaml
Normal file
11
app/i18n/ui/ki.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Mathuura mangĩ
|
||||
Close: Hinga
|
||||
Copy: Kobia
|
||||
Identifier copied to clipboard!: Kĩmenyithia nĩ kĩakobio!
|
||||
Imprint: Ũhoro wa watho
|
||||
Information: Ũhoro
|
||||
Language: Rũthiomi
|
||||
Open: Hingũra
|
||||
Open Link: Hingũra kĩhoti
|
||||
Options: Mathuura
|
||||
Warning: Mũkaana
|
||||
11
app/i18n/ui/kj.yaml
Normal file
11
app/i18n/ui/kj.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Omahoololo vali
|
||||
Close: Pata
|
||||
Copy: Shanga
|
||||
Identifier copied to clipboard!: Edina la shangwa!
|
||||
Imprint: Omauyelele opaveta
|
||||
Information: Omauyelele
|
||||
Language: Elaka
|
||||
Open: Yeulula
|
||||
Open Link: Yeulula ekwatakanifo
|
||||
Options: Omahoololo
|
||||
Warning: Elondwelo
|
||||
11
app/i18n/ui/kk.yaml
Normal file
11
app/i18n/ui/kk.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Баламалар
|
||||
Close: Жабу
|
||||
Copy: Көшіру
|
||||
Identifier copied to clipboard!: Идентификатор алмасу буферіне көшірілді!
|
||||
Imprint: Құқықтық ақпарат
|
||||
Information: Ақпарат
|
||||
Language: Тіл
|
||||
Open: Ашу
|
||||
Open Link: Сілтемені ашу
|
||||
Options: Параметрлер
|
||||
Warning: Ескерту
|
||||
11
app/i18n/ui/kl.yaml
Normal file
11
app/i18n/ui/kl.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Allat periarfissat
|
||||
Close: Matuguk
|
||||
Copy: Assiliiguk
|
||||
Identifier copied to clipboard!: Kinaassusersiut assilineqarpoq!
|
||||
Imprint: Inatsisitigut paasissutissat
|
||||
Information: Paasissutissat
|
||||
Language: Oqaatsit
|
||||
Open: Ammaruk
|
||||
Open Link: Aqqutissiaq ammaruk
|
||||
Options: Periarfissat
|
||||
Warning: Mianersoqqussut
|
||||
11
app/i18n/ui/km.yaml
Normal file
11
app/i18n/ui/km.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: ជម្រើសផ្សេងទៀត
|
||||
Close: បិទ
|
||||
Copy: ចម្លង
|
||||
Identifier copied to clipboard!: បានចម្លងអត្តសញ្ញាណទៅក្ដារតម្បៀតខ្ទាស់!
|
||||
Imprint: ព័ត៌មានផ្លូវច្បាប់
|
||||
Information: ព័ត៌មាន
|
||||
Language: ភាសា
|
||||
Open: បើក
|
||||
Open Link: បើកតំណ
|
||||
Options: ជម្រើស
|
||||
Warning: ការព្រមាន
|
||||
11
app/i18n/ui/kn.yaml
Normal file
11
app/i18n/ui/kn.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: ಪರ್ಯಾಯಗಳು
|
||||
Close: ಮುಚ್ಚಿ
|
||||
Copy: ನಕಲಿಸಿ
|
||||
Identifier copied to clipboard!: ಗುರುತಿಸುವಿಕೆಯನ್ನು ಕ್ಲಿಪ್ಬೋರ್ಡ್ಗೆ ನಕಲಿಸಲಾಗಿದೆ!
|
||||
Imprint: ಕಾನೂನು ಮಾಹಿತಿ
|
||||
Information: ಮಾಹಿತಿ
|
||||
Language: ಭಾಷೆ
|
||||
Open: ತೆರೆಯಿರಿ
|
||||
Open Link: ಲಿಂಕ್ ತೆರೆಯಿರಿ
|
||||
Options: ಆಯ್ಕೆಗಳು
|
||||
Warning: ಎಚ್ಚರಿಕೆ
|
||||
11
app/i18n/ui/ko.yaml
Normal file
11
app/i18n/ui/ko.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: 대안
|
||||
Close: 닫기
|
||||
Copy: 복사
|
||||
Identifier copied to clipboard!: 식별자를 클립보드에 복사했습니다!
|
||||
Imprint: 법적 고지
|
||||
Information: 정보
|
||||
Language: 언어
|
||||
Open: 열기
|
||||
Open Link: 링크 열기
|
||||
Options: 옵션
|
||||
Warning: 경고
|
||||
11
app/i18n/ui/kr.yaml
Normal file
11
app/i18n/ui/kr.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: Kəla gade
|
||||
Close: Kaltə
|
||||
Copy: Kopi
|
||||
Identifier copied to clipboard!: Suna kopi dəwo!
|
||||
Imprint: Labar shariya
|
||||
Information: Labar
|
||||
Language: Təla
|
||||
Open: Buwo
|
||||
Open Link: Link buwo
|
||||
Options: Kəla
|
||||
Warning: Dəwo
|
||||
11
app/i18n/ui/ks.yaml
Normal file
11
app/i18n/ui/ks.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
Alternatives: مُتبادِل
|
||||
Close: بنٛد کٔرِو
|
||||
Copy: نقل کٔرِو
|
||||
Identifier copied to clipboard!: شناخت کلپ بورڈس پیٹھ نقل کرنہٕ آمٕژ!
|
||||
Imprint: قانوٗنی معلوٗمات
|
||||
Information: معلوٗمات
|
||||
Language: زبان
|
||||
Open: کھولِو
|
||||
Open Link: لِنک کھولِو
|
||||
Options: اِختیار
|
||||
Warning: خبردار
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user