feat: Add support for Russian, Hebrew (RTL), and Arabic (RTL) with visual width validation

This commit is contained in:
thePR0M3TH3AN committed 2026-03-13 16:17:54 -04:00
1 parent 6afbe29552
commit ece805a6d2
25 files changed
+1297 -10

No files matched your search

+41
View File
@@ -0,0 +1,41 @@
# AI Agent Instructions for VoxVera
This guide is for AI agents (like Gemini, Claude, or GPT) contributing to the VoxVera project. Follow these protocols to maintain the architectural integrity and multi-language support.
## Core Mandates
1. **Security-First**: VoxVera is a Tor-based tool. Never suggest features that leak IP addresses or use non-Tor external resources (CDNs, etc.).
2. **Local-First**: All assets (fonts, dependencies, binaries) must be self-contained within the `voxvera/` package to support the Universal Mirroring model.
3. **Tokenized UI**: Never hardcode user-facing strings. Always add them to `voxvera/locales/en.json` and use the `t('cli.token')` helper.
4. **Markdown Redaction**: Use `~~text~~` in locale JSONs for strike-through effects. Both the build system and the JS engine handle the conversion to `<span class="redacted">` automatically.
## Localization Protocol
When adding a new language or updating an existing one:
- **CLI/Flyers**: Update `voxvera/locales/{lang}.json`. Include `meta`, `cli`, `web`, and `landing` categories.
- **Landing Page**:
- Directly update the `const locales` object in `site/index.html`.
- **Crucial**: Run `voxvera build-site` to generate the latest portable bundle and refresh the pre-rendered HTML.
- **Documentation**:
- Master templates live in `docs/templates/`.
- Localized overrides live in `voxvera/locales/{lang}/docs/`.
- **Crucial**: After any change to locales or templates, you MUST run `voxvera build-docs` to synchronize the output in `docs/{en,es,de}/`.
## Universal Mirroring
VoxVera propagates through its own flyers.
- Every `voxvera build` and `voxvera build-site` generates `voxvera-portable.zip`.
- If you modify the core source code, ensure the `bundle_portable` function in `voxvera/cli.py` still correctly captures all necessary directories (especially `vendor/`, `locales/`, and `resources/`).
## Verification Checklist
Before finishing a task, ensure you have:
1. Run unit tests: `pytest tests/test_cli.py`.
2. Verified the localization: `voxvera --lang {lang} check`.
3. Rebuilt the documentation: `voxvera build-docs`.
4. Synchronized the public site: `voxvera build-site`.
5. Verified the portable bundle exists: `ls -l voxvera/host/*/download/voxvera-portable.zip` and `ls -l site/download/voxvera-portable.zip`.
---
Follow these rules to ensure VoxVera remains a robust, viral, and decentralized tool for truth.
+42
View File
@@ -0,0 +1,42 @@
# Contributing to VoxVera
Thank you for your interest in contributing to VoxVera! This project aims to be a global, censorship-resistant tool, and your help—especially with translations—is vital.
## Adding a New Language
VoxVera uses a centralized token system for the CLI and Web components, and a template-based system for Documentation. To add a new language (e.g., French/`fr`), follow this checklist:
### 1. The Token File
Create a new JSON file in `voxvera/locales/{lang_code}.json`.
- Use `en.json` as your master template.
- **`meta`**: Include the native language name and a flag emoji.
- **`cli`**: Translate all strings used in the terminal interface.
- **`web`**: Translate strings used on the generated landing pages and flyers.
- **`landing`**: Translate the specific content for the official VoxVera website.
- **Formatting**: You can use Markdown-style strike-throughs (`~~redacted text~~`) in any of these strings; they will be automatically converted to the stylized "redacted" effect on the flyers.
### 2. The Landing Page
The main landing page (`site/index.html`) must be synchronized to include your new language.
- Open `site/index.html` and add your language to the `const locales` object in the `<script>` tag.
- Run `voxvera build-site` to refresh the public assets and ensure the pre-rendered HTML matches your new locale.
### 3. Documentation
VoxVera uses the **Doc-Sync Engine** to keep manuals in sync.
- Create localized Markdown files in `voxvera/locales/{lang_code}/docs/`.
- Use the templates in `docs/templates/` as your guide.
- You can use `cli.token_name` to reference terminal commands automatically.
### 4. Build and Verify
Run the following commands to verify your changes:
- `voxvera --lang {lang_code} check` (Verify CLI translation)
- `voxvera build-docs` (Generate localized manuals)
- Open `site/index.html` in a browser and test the language switcher.
## Code Contributions
- Ensure all tests pass: `pytest tests/test_cli.py`.
- Follow the existing coding style (PEP 8 for Python).
- If adding a new feature, update the master templates in `docs/templates/` and run `voxvera build-docs`.
---
Together, we can ensure that truth remains accessible to everyone, everywhere.
+16
View File
@@ -0,0 +1,16 @@
# Docker Usage
A prebuilt image containing Tor, OnionShare and the `voxvera` CLI is published to
`ghcr.io/voxvera/voxvera`.
```bash
# pull the image
docker pull ghcr.io/voxvera/voxvera:latest
# run voxvera quickstart with flyers stored in ./flyers
mkdir -p flyers
docker run -it --rm -v "$(pwd)/flyers:/flyers" ghcr.io/voxvera/voxvera
```
The container uses `/flyers` as the working directory and runs `voxvera quickstart`
by default. All generated flyer files will appear in the mounted `flyers` folder.
+14
View File
@@ -0,0 +1,14 @@
# Flyer Templates
Pre-built flyer templates live in the `templates/` directory. Use the CLI to copy one with:
```bash
voxvera init --template <name>
```
This will create a new folder under `dist/` named `<name>-<YYYYMMDD>` containing the template files.
Available templates:
- **blank** – minimal placeholder files, also used when extracting fields from a PDF.
- **voxvera** – example flyer showcasing the VoxVera project.
+60
View File
@@ -0,0 +1,60 @@
# Troubleshooting
This page collects common issues encountered when hosting or accessing flyers.
## Tor connectivity
- Ensure Tor is allowed through your firewall. On systems using `ufw` you may need to run `sudo ufw allow tor`.
- Some networks block Tor entirely. If you cannot reach onion services, try connecting over a different network or use a Tor bridge.
## Tor port detection
`voxvera serve` auto-detects Tor by probing common ports (9050/9150 for SOCKS, 9051/9151 for control). If detection fails -- for example when Tor runs on non-standard ports -- you can override with environment variables:
```bash
TOR_SOCKS_PORT=19050 TOR_CONTROL_PORT=19051 voxvera serve
```
If no running Tor is found, the defaults (9050/9051) are used and OnionShare will attempt to connect on those ports.
## Firewall rules
- If `voxvera serve` fails to start OnionShare, verify that outbound connections on ports 9001 and 80 are permitted.
- Corporate or university firewalls can block the hidden service ports required by Tor.
## SELinux
- On SELinux-enabled distributions you may see `permission denied` errors when OnionShare writes to the `host` directory.
- Run `sudo chcon -Rt svirt_sandbox_file_t host` or disable SELinux enforcement for the folder.
## OnionShare crashes
If `voxvera serve` prints "OnionShare exited unexpectedly", check the log file
`host/<folder_name>/onionshare.log` for details. Common causes:
- Tor is not running (`sudo systemctl start tor`)
- A port conflict with another service
- Missing `onionshare-cli` (run `voxvera check`)
## Onion URL changed unexpectedly
The onion URL is tied to the Ed25519 keypair stored in
`host/<folder_name>/.onionshare-session`. If this file is deleted, a new
keypair (and URL) will be generated on the next `voxvera serve`. The
`voxvera import` command preserves this file automatically, but manual
deletion of the `host/` directory will destroy it.
## QR codes not generated
If `voxvera build` reports "No URLs configured yet", this is expected before the first `voxvera serve`. QR codes for tear-off links are generated automatically once the .onion address is known. QR codes for the content link are generated if you provided a URL during `voxvera init`.
## Electron GUI
If `npm start` fails with `spawn voxvera ENOENT`, the `voxvera` command is not in your `PATH`. Install it with `pipx install git+https://github.com/PR0M3TH3AN/VoxVera.git` or run `./install.sh` from the repository.
## Missing dependencies
Run `voxvera check` to verify your setup. The command checks for required
Python packages (`qrcode`, `Pillow`, `jsmin`, `htmlmin`, `pypdf`) and
external tools (`onionshare-cli`), then prints a summary of anything
missing. To install everything:
```bash
# Recommended: use the install script (handles Tor + OnionShare too)
curl -fsSL https://raw.githubusercontent.com/PR0M3TH3AN/VoxVera/main/install.sh | bash
# Or reinstall just the Python package
pipx install --force 'voxvera@git+https://github.com/PR0M3TH3AN/VoxVera.git@main'
```
+130
View File
@@ -0,0 +1,130 @@
# Detailed Usage
This guide covers common CLI workflows. See `docs/docker.md` for Docker instructions and `docs/templates.md` for available flyer templates.
## Prerequisites
VoxVera is designed to be highly portable and requires minimal system dependencies.
### 1. Standalone Binaries (Recommended)
You can download standalone, zero-dependency binaries for your operating system:
- **Linux:** `voxvera-linux`
- **Windows:** `voxvera-windows.exe`
- **macOS:** `voxvera-macos`
These binaries include everything needed to run VoxVera (except `onionshare-cli`).
### 2. One-Line Installer
Alternatively, install via our automated script:
```bash
curl -fsSL https://raw.githubusercontent.com/PR0M3TH3AN/VoxVera/main/install.sh | bash
```
### 3. Manual Python Install
If you prefer to run from source:
```bash
pipx install 'voxvera@git+https://github.com/PR0M3TH3AN/VoxVera.git@main'
sudo apt install tor onionshare-cli # Debian/Ubuntu
```
## Step-by-Step
1. **Initialize:** Run `voxvera init` and follow the prompts. You will be asked to select your language first.
2. **Build:** Generate the flyer assets. Every build automatically creates a `voxvera-portable.zip` in the flyer's folder, allowing others to download the full tool directly from your flyer.
```bash
voxvera build
```
3. **Serve:** Publish the flyer over Tor:
```bash
voxvera serve
```
This automatically detects your Tor instance, starts OnionShare, and writes the generated .onion address into the flyer's tear-off links.
## Language Support
VoxVera is fully localized. You can change your language preference permanently using either the interactive selector or a direct shortcut:
- **Interactive Selector:** `voxvera lang`
- **Direct Shortcut:** `voxvera --lang de` (sets preference to German)
### Supported Languages:
- **English:** `en`
- **Spanish:** `es` (alias: `--idioma`)
- **German:** `de` (alias: `--sprache`)
You can also force a specific language for a single command without changing your permanent preference:
- **English:** `voxvera --lang en check`
- **German:** `voxvera --sprache de check`
- **Spanish:** `voxvera --idioma es check`
The generated flyers automatically detect the visitor's browser language and switch the UI text accordingly.
## Server Management
Manage multiple flyers and their Tor identities from a single interactive menu:
```bash
voxvera manage
```
Features:
- **--- إنشاء موقع/منشور جديد ---**: Start the full setup sequence.
- **--- تشغيل جميع المواقع ---**: Launch or shut down every flyer in your fleet at once.
- **Real-time Status**: View active `.onion` URLs and Tor bootstrapping progress indicators.
- **Individual Control**: تصدير إلى Zip specific sites to ZIP or delete them.
## Universal Mirroring (Viral Distribution)
To ensure VoxVera remains accessible even if central repositories are censored, every flyer acts as a mirror for the tool.
When you host a flyer, the **"تحميل الأداة والكود"** button on the landing page provides a `voxvera-portable.zip` containing:
- The full source code and all supported languages.
- All Python dependencies (pre-vendored).
- Cross-platform Tor binaries.
This allows anyone who scans your flyer to become a new distributor of the VoxVera tool.
## Export & Backup
Back up your unique Tor identities (so your `.onion` URL never changes) or move your flyers to another machine.
- **Export a single site**: `voxvera export <folder_name>`
- **Export all sites**: `voxvera export-all`
**Storage location:** All exports are saved to `~/voxvera-exports/` on all platforms.
## Import & Recovery
Restore your entire setup on a new machine by moving your ZIP files to `~/voxvera-exports/` and running:
```bash
voxvera import-multiple
```
## Portability & Offline Use
If you need to run VoxVera on a machine without internet access, you can "vendorize" the dependencies first:
```bash
voxvera vendorize
```
This downloads all required Python libraries into `voxvera/vendor/`. The tool will then prioritize these local files, allowing it to run without `pip install`.
## Batch Import (JSON)
To bulk-generate flyers from multiple JSON configuration files, place them in the `imports/` directory and run:
```bash
voxvera batch-import
```
## How URLs Work
Each flyer has two separate URLs:
- **Tear-off link** (auto-generated): The .onion address where the flyer is hosted.
- **Content link** (user-configured): An external URL pointing to a website, video, or download.
You do not need to manually enter the .onion address; VoxVera handles this automatically during the `serve` phase.
+41
View File
@@ -0,0 +1,41 @@
# AI Agent Instructions for VoxVera
This guide is for AI agents (like Gemini, Claude, or GPT) contributing to the VoxVera project. Follow these protocols to maintain the architectural integrity and multi-language support.
## Core Mandates
1. **Security-First**: VoxVera is a Tor-based tool. Never suggest features that leak IP addresses or use non-Tor external resources (CDNs, etc.).
2. **Local-First**: All assets (fonts, dependencies, binaries) must be self-contained within the `voxvera/` package to support the Universal Mirroring model.
3. **Tokenized UI**: Never hardcode user-facing strings. Always add them to `voxvera/locales/en.json` and use the `t('cli.token')` helper.
4. **Markdown Redaction**: Use `~~text~~` in locale JSONs for strike-through effects. Both the build system and the JS engine handle the conversion to `<span class="redacted">` automatically.
## Localization Protocol
When adding a new language or updating an existing one:
- **CLI/Flyers**: Update `voxvera/locales/{lang}.json`. Include `meta`, `cli`, `web`, and `landing` categories.
- **Landing Page**:
- Directly update the `const locales` object in `site/index.html`.
- **Crucial**: Run `voxvera build-site` to generate the latest portable bundle and refresh the pre-rendered HTML.
- **Documentation**:
- Master templates live in `docs/templates/`.
- Localized overrides live in `voxvera/locales/{lang}/docs/`.
- **Crucial**: After any change to locales or templates, you MUST run `voxvera build-docs` to synchronize the output in `docs/{en,es,de}/`.
## Universal Mirroring
VoxVera propagates through its own flyers.
- Every `voxvera build` and `voxvera build-site` generates `voxvera-portable.zip`.
- If you modify the core source code, ensure the `bundle_portable` function in `voxvera/cli.py` still correctly captures all necessary directories (especially `vendor/`, `locales/`, and `resources/`).
## Verification Checklist
Before finishing a task, ensure you have:
1. Run unit tests: `pytest tests/test_cli.py`.
2. Verified the localization: `voxvera --lang {lang} check`.
3. Rebuilt the documentation: `voxvera build-docs`.
4. Synchronized the public site: `voxvera build-site`.
5. Verified the portable bundle exists: `ls -l voxvera/host/*/download/voxvera-portable.zip` and `ls -l site/download/voxvera-portable.zip`.
---
Follow these rules to ensure VoxVera remains a robust, viral, and decentralized tool for truth.
+42
View File
@@ -0,0 +1,42 @@
# Contributing to VoxVera
Thank you for your interest in contributing to VoxVera! This project aims to be a global, censorship-resistant tool, and your help—especially with translations—is vital.
## Adding a New Language
VoxVera uses a centralized token system for the CLI and Web components, and a template-based system for Documentation. To add a new language (e.g., French/`fr`), follow this checklist:
### 1. The Token File
Create a new JSON file in `voxvera/locales/{lang_code}.json`.
- Use `en.json` as your master template.
- **`meta`**: Include the native language name and a flag emoji.
- **`cli`**: Translate all strings used in the terminal interface.
- **`web`**: Translate strings used on the generated landing pages and flyers.
- **`landing`**: Translate the specific content for the official VoxVera website.
- **Formatting**: You can use Markdown-style strike-throughs (`~~redacted text~~`) in any of these strings; they will be automatically converted to the stylized "redacted" effect on the flyers.
### 2. The Landing Page
The main landing page (`site/index.html`) must be synchronized to include your new language.
- Open `site/index.html` and add your language to the `const locales` object in the `<script>` tag.
- Run `voxvera build-site` to refresh the public assets and ensure the pre-rendered HTML matches your new locale.
### 3. Documentation
VoxVera uses the **Doc-Sync Engine** to keep manuals in sync.
- Create localized Markdown files in `voxvera/locales/{lang_code}/docs/`.
- Use the templates in `docs/templates/` as your guide.
- You can use `cli.token_name` to reference terminal commands automatically.
### 4. Build and Verify
Run the following commands to verify your changes:
- `voxvera --lang {lang_code} check` (Verify CLI translation)
- `voxvera build-docs` (Generate localized manuals)
- Open `site/index.html` in a browser and test the language switcher.
## Code Contributions
- Ensure all tests pass: `pytest tests/test_cli.py`.
- Follow the existing coding style (PEP 8 for Python).
- If adding a new feature, update the master templates in `docs/templates/` and run `voxvera build-docs`.
---
Together, we can ensure that truth remains accessible to everyone, everywhere.
+16
View File
@@ -0,0 +1,16 @@
# Docker Usage
A prebuilt image containing Tor, OnionShare and the `voxvera` CLI is published to
`ghcr.io/voxvera/voxvera`.
```bash
# pull the image
docker pull ghcr.io/voxvera/voxvera:latest
# run voxvera quickstart with flyers stored in ./flyers
mkdir -p flyers
docker run -it --rm -v "$(pwd)/flyers:/flyers" ghcr.io/voxvera/voxvera
```
The container uses `/flyers` as the working directory and runs `voxvera quickstart`
by default. All generated flyer files will appear in the mounted `flyers` folder.
+14
View File
@@ -0,0 +1,14 @@
# Flyer Templates
Pre-built flyer templates live in the `templates/` directory. Use the CLI to copy one with:
```bash
voxvera init --template <name>
```
This will create a new folder under `dist/` named `<name>-<YYYYMMDD>` containing the template files.
Available templates:
- **blank** – minimal placeholder files, also used when extracting fields from a PDF.
- **voxvera** – example flyer showcasing the VoxVera project.
+60
View File
@@ -0,0 +1,60 @@
# Troubleshooting
This page collects common issues encountered when hosting or accessing flyers.
## Tor connectivity
- Ensure Tor is allowed through your firewall. On systems using `ufw` you may need to run `sudo ufw allow tor`.
- Some networks block Tor entirely. If you cannot reach onion services, try connecting over a different network or use a Tor bridge.
## Tor port detection
`voxvera serve` auto-detects Tor by probing common ports (9050/9150 for SOCKS, 9051/9151 for control). If detection fails -- for example when Tor runs on non-standard ports -- you can override with environment variables:
```bash
TOR_SOCKS_PORT=19050 TOR_CONTROL_PORT=19051 voxvera serve
```
If no running Tor is found, the defaults (9050/9051) are used and OnionShare will attempt to connect on those ports.
## Firewall rules
- If `voxvera serve` fails to start OnionShare, verify that outbound connections on ports 9001 and 80 are permitted.
- Corporate or university firewalls can block the hidden service ports required by Tor.
## SELinux
- On SELinux-enabled distributions you may see `permission denied` errors when OnionShare writes to the `host` directory.
- Run `sudo chcon -Rt svirt_sandbox_file_t host` or disable SELinux enforcement for the folder.
## OnionShare crashes
If `voxvera serve` prints "OnionShare exited unexpectedly", check the log file
`host/<folder_name>/onionshare.log` for details. Common causes:
- Tor is not running (`sudo systemctl start tor`)
- A port conflict with another service
- Missing `onionshare-cli` (run `voxvera check`)
## Onion URL changed unexpectedly
The onion URL is tied to the Ed25519 keypair stored in
`host/<folder_name>/.onionshare-session`. If this file is deleted, a new
keypair (and URL) will be generated on the next `voxvera serve`. The
`voxvera import` command preserves this file automatically, but manual
deletion of the `host/` directory will destroy it.
## QR codes not generated
If `voxvera build` reports "No URLs configured yet", this is expected before the first `voxvera serve`. QR codes for tear-off links are generated automatically once the .onion address is known. QR codes for the content link are generated if you provided a URL during `voxvera init`.
## Electron GUI
If `npm start` fails with `spawn voxvera ENOENT`, the `voxvera` command is not in your `PATH`. Install it with `pipx install git+https://github.com/PR0M3TH3AN/VoxVera.git` or run `./install.sh` from the repository.
## Missing dependencies
Run `voxvera check` to verify your setup. The command checks for required
Python packages (`qrcode`, `Pillow`, `jsmin`, `htmlmin`, `pypdf`) and
external tools (`onionshare-cli`), then prints a summary of anything
missing. To install everything:
```bash
# Recommended: use the install script (handles Tor + OnionShare too)
curl -fsSL https://raw.githubusercontent.com/PR0M3TH3AN/VoxVera/main/install.sh | bash
# Or reinstall just the Python package
pipx install --force 'voxvera@git+https://github.com/PR0M3TH3AN/VoxVera.git@main'
```
+130
View File
@@ -0,0 +1,130 @@
# Detailed Usage
This guide covers common CLI workflows. See `docs/docker.md` for Docker instructions and `docs/templates.md` for available flyer templates.
## Prerequisites
VoxVera is designed to be highly portable and requires minimal system dependencies.
### 1. Standalone Binaries (Recommended)
You can download standalone, zero-dependency binaries for your operating system:
- **Linux:** `voxvera-linux`
- **Windows:** `voxvera-windows.exe`
- **macOS:** `voxvera-macos`
These binaries include everything needed to run VoxVera (except `onionshare-cli`).
### 2. One-Line Installer
Alternatively, install via our automated script:
```bash
curl -fsSL https://raw.githubusercontent.com/PR0M3TH3AN/VoxVera/main/install.sh | bash
```
### 3. Manual Python Install
If you prefer to run from source:
```bash
pipx install 'voxvera@git+https://github.com/PR0M3TH3AN/VoxVera.git@main'
sudo apt install tor onionshare-cli # Debian/Ubuntu
```
## Step-by-Step
1. **Initialize:** Run `voxvera init` and follow the prompts. You will be asked to select your language first.
2. **Build:** Generate the flyer assets. Every build automatically creates a `voxvera-portable.zip` in the flyer's folder, allowing others to download the full tool directly from your flyer.
```bash
voxvera build
```
3. **Serve:** Publish the flyer over Tor:
```bash
voxvera serve
```
This automatically detects your Tor instance, starts OnionShare, and writes the generated .onion address into the flyer's tear-off links.
## Language Support
VoxVera is fully localized. You can change your language preference permanently using either the interactive selector or a direct shortcut:
- **Interactive Selector:** `voxvera lang`
- **Direct Shortcut:** `voxvera --lang de` (sets preference to German)
### Supported Languages:
- **English:** `en`
- **Spanish:** `es` (alias: `--idioma`)
- **German:** `de` (alias: `--sprache`)
You can also force a specific language for a single command without changing your permanent preference:
- **English:** `voxvera --lang en check`
- **German:** `voxvera --sprache de check`
- **Spanish:** `voxvera --idioma es check`
The generated flyers automatically detect the visitor's browser language and switch the UI text accordingly.
## Server Management
Manage multiple flyers and their Tor identities from a single interactive menu:
```bash
voxvera manage
```
Features:
- **--- צור אתר/פלייר חדש ---**: Start the full setup sequence.
- **--- הפעל את כל האתרים ---**: Launch or shut down every flyer in your fleet at once.
- **Real-time Status**: View active `.onion` URLs and Tor bootstrapping progress indicators.
- **Individual Control**: ייצא ל-Zip specific sites to ZIP or delete them.
## Universal Mirroring (Viral Distribution)
To ensure VoxVera remains accessible even if central repositories are censored, every flyer acts as a mirror for the tool.
When you host a flyer, the **"הורד כלי וקוד מקור"** button on the landing page provides a `voxvera-portable.zip` containing:
- The full source code and all supported languages.
- All Python dependencies (pre-vendored).
- Cross-platform Tor binaries.
This allows anyone who scans your flyer to become a new distributor of the VoxVera tool.
## Export & Backup
Back up your unique Tor identities (so your `.onion` URL never changes) or move your flyers to another machine.
- **Export a single site**: `voxvera export <folder_name>`
- **Export all sites**: `voxvera export-all`
**Storage location:** All exports are saved to `~/voxvera-exports/` on all platforms.
## Import & Recovery
Restore your entire setup on a new machine by moving your ZIP files to `~/voxvera-exports/` and running:
```bash
voxvera import-multiple
```
## Portability & Offline Use
If you need to run VoxVera on a machine without internet access, you can "vendorize" the dependencies first:
```bash
voxvera vendorize
```
This downloads all required Python libraries into `voxvera/vendor/`. The tool will then prioritize these local files, allowing it to run without `pip install`.
## Batch Import (JSON)
To bulk-generate flyers from multiple JSON configuration files, place them in the `imports/` directory and run:
```bash
voxvera batch-import
```
## How URLs Work
Each flyer has two separate URLs:
- **Tear-off link** (auto-generated): The .onion address where the flyer is hosted.
- **Content link** (user-configured): An external URL pointing to a website, video, or download.
You do not need to manually enter the .onion address; VoxVera handles this automatically during the `serve` phase.
+41
View File
@@ -0,0 +1,41 @@
# AI Agent Instructions for VoxVera
This guide is for AI agents (like Gemini, Claude, or GPT) contributing to the VoxVera project. Follow these protocols to maintain the architectural integrity and multi-language support.
## Core Mandates
1. **Security-First**: VoxVera is a Tor-based tool. Never suggest features that leak IP addresses or use non-Tor external resources (CDNs, etc.).
2. **Local-First**: All assets (fonts, dependencies, binaries) must be self-contained within the `voxvera/` package to support the Universal Mirroring model.
3. **Tokenized UI**: Never hardcode user-facing strings. Always add them to `voxvera/locales/en.json` and use the `t('cli.token')` helper.
4. **Markdown Redaction**: Use `~~text~~` in locale JSONs for strike-through effects. Both the build system and the JS engine handle the conversion to `<span class="redacted">` automatically.
## Localization Protocol
When adding a new language or updating an existing one:
- **CLI/Flyers**: Update `voxvera/locales/{lang}.json`. Include `meta`, `cli`, `web`, and `landing` categories.
- **Landing Page**:
- Directly update the `const locales` object in `site/index.html`.
- **Crucial**: Run `voxvera build-site` to generate the latest portable bundle and refresh the pre-rendered HTML.
- **Documentation**:
- Master templates live in `docs/templates/`.
- Localized overrides live in `voxvera/locales/{lang}/docs/`.
- **Crucial**: After any change to locales or templates, you MUST run `voxvera build-docs` to synchronize the output in `docs/{en,es,de}/`.
## Universal Mirroring
VoxVera propagates through its own flyers.
- Every `voxvera build` and `voxvera build-site` generates `voxvera-portable.zip`.
- If you modify the core source code, ensure the `bundle_portable` function in `voxvera/cli.py` still correctly captures all necessary directories (especially `vendor/`, `locales/`, and `resources/`).
## Verification Checklist
Before finishing a task, ensure you have:
1. Run unit tests: `pytest tests/test_cli.py`.
2. Verified the localization: `voxvera --lang {lang} check`.
3. Rebuilt the documentation: `voxvera build-docs`.
4. Synchronized the public site: `voxvera build-site`.
5. Verified the portable bundle exists: `ls -l voxvera/host/*/download/voxvera-portable.zip` and `ls -l site/download/voxvera-portable.zip`.
---
Follow these rules to ensure VoxVera remains a robust, viral, and decentralized tool for truth.
+42
View File
@@ -0,0 +1,42 @@
# Contributing to VoxVera
Thank you for your interest in contributing to VoxVera! This project aims to be a global, censorship-resistant tool, and your help—especially with translations—is vital.
## Adding a New Language
VoxVera uses a centralized token system for the CLI and Web components, and a template-based system for Documentation. To add a new language (e.g., French/`fr`), follow this checklist:
### 1. The Token File
Create a new JSON file in `voxvera/locales/{lang_code}.json`.
- Use `en.json` as your master template.
- **`meta`**: Include the native language name and a flag emoji.
- **`cli`**: Translate all strings used in the terminal interface.
- **`web`**: Translate strings used on the generated landing pages and flyers.
- **`landing`**: Translate the specific content for the official VoxVera website.
- **Formatting**: You can use Markdown-style strike-throughs (`~~redacted text~~`) in any of these strings; they will be automatically converted to the stylized "redacted" effect on the flyers.
### 2. The Landing Page
The main landing page (`site/index.html`) must be synchronized to include your new language.
- Open `site/index.html` and add your language to the `const locales` object in the `<script>` tag.
- Run `voxvera build-site` to refresh the public assets and ensure the pre-rendered HTML matches your new locale.
### 3. Documentation
VoxVera uses the **Doc-Sync Engine** to keep manuals in sync.
- Create localized Markdown files in `voxvera/locales/{lang_code}/docs/`.
- Use the templates in `docs/templates/` as your guide.
- You can use `cli.token_name` to reference terminal commands automatically.
### 4. Build and Verify
Run the following commands to verify your changes:
- `voxvera --lang {lang_code} check` (Verify CLI translation)
- `voxvera build-docs` (Generate localized manuals)
- Open `site/index.html` in a browser and test the language switcher.
## Code Contributions
- Ensure all tests pass: `pytest tests/test_cli.py`.
- Follow the existing coding style (PEP 8 for Python).
- If adding a new feature, update the master templates in `docs/templates/` and run `voxvera build-docs`.
---
Together, we can ensure that truth remains accessible to everyone, everywhere.
+16
View File
@@ -0,0 +1,16 @@
# Docker Usage
A prebuilt image containing Tor, OnionShare and the `voxvera` CLI is published to
`ghcr.io/voxvera/voxvera`.
```bash
# pull the image
docker pull ghcr.io/voxvera/voxvera:latest
# run voxvera quickstart with flyers stored in ./flyers
mkdir -p flyers
docker run -it --rm -v "$(pwd)/flyers:/flyers" ghcr.io/voxvera/voxvera
```
The container uses `/flyers` as the working directory and runs `voxvera quickstart`
by default. All generated flyer files will appear in the mounted `flyers` folder.
+14
View File
@@ -0,0 +1,14 @@
# Flyer Templates
Pre-built flyer templates live in the `templates/` directory. Use the CLI to copy one with:
```bash
voxvera init --template <name>
```
This will create a new folder under `dist/` named `<name>-<YYYYMMDD>` containing the template files.
Available templates:
- **blank** – minimal placeholder files, also used when extracting fields from a PDF.
- **voxvera** – example flyer showcasing the VoxVera project.
+60
View File
@@ -0,0 +1,60 @@
# Troubleshooting
This page collects common issues encountered when hosting or accessing flyers.
## Tor connectivity
- Ensure Tor is allowed through your firewall. On systems using `ufw` you may need to run `sudo ufw allow tor`.
- Some networks block Tor entirely. If you cannot reach onion services, try connecting over a different network or use a Tor bridge.
## Tor port detection
`voxvera serve` auto-detects Tor by probing common ports (9050/9150 for SOCKS, 9051/9151 for control). If detection fails -- for example when Tor runs on non-standard ports -- you can override with environment variables:
```bash
TOR_SOCKS_PORT=19050 TOR_CONTROL_PORT=19051 voxvera serve
```
If no running Tor is found, the defaults (9050/9051) are used and OnionShare will attempt to connect on those ports.
## Firewall rules
- If `voxvera serve` fails to start OnionShare, verify that outbound connections on ports 9001 and 80 are permitted.
- Corporate or university firewalls can block the hidden service ports required by Tor.
## SELinux
- On SELinux-enabled distributions you may see `permission denied` errors when OnionShare writes to the `host` directory.
- Run `sudo chcon -Rt svirt_sandbox_file_t host` or disable SELinux enforcement for the folder.
## OnionShare crashes
If `voxvera serve` prints "OnionShare exited unexpectedly", check the log file
`host/<folder_name>/onionshare.log` for details. Common causes:
- Tor is not running (`sudo systemctl start tor`)
- A port conflict with another service
- Missing `onionshare-cli` (run `voxvera check`)
## Onion URL changed unexpectedly
The onion URL is tied to the Ed25519 keypair stored in
`host/<folder_name>/.onionshare-session`. If this file is deleted, a new
keypair (and URL) will be generated on the next `voxvera serve`. The
`voxvera import` command preserves this file automatically, but manual
deletion of the `host/` directory will destroy it.
## QR codes not generated
If `voxvera build` reports "No URLs configured yet", this is expected before the first `voxvera serve`. QR codes for tear-off links are generated automatically once the .onion address is known. QR codes for the content link are generated if you provided a URL during `voxvera init`.
## Electron GUI
If `npm start` fails with `spawn voxvera ENOENT`, the `voxvera` command is not in your `PATH`. Install it with `pipx install git+https://github.com/PR0M3TH3AN/VoxVera.git` or run `./install.sh` from the repository.
## Missing dependencies
Run `voxvera check` to verify your setup. The command checks for required
Python packages (`qrcode`, `Pillow`, `jsmin`, `htmlmin`, `pypdf`) and
external tools (`onionshare-cli`), then prints a summary of anything
missing. To install everything:
```bash
# Recommended: use the install script (handles Tor + OnionShare too)
curl -fsSL https://raw.githubusercontent.com/PR0M3TH3AN/VoxVera/main/install.sh | bash
# Or reinstall just the Python package
pipx install --force 'voxvera@git+https://github.com/PR0M3TH3AN/VoxVera.git@main'
```
+130
View File
@@ -0,0 +1,130 @@
# Detailed Usage
This guide covers common CLI workflows. See `docs/docker.md` for Docker instructions and `docs/templates.md` for available flyer templates.
## Prerequisites
VoxVera is designed to be highly portable and requires minimal system dependencies.
### 1. Standalone Binaries (Recommended)
You can download standalone, zero-dependency binaries for your operating system:
- **Linux:** `voxvera-linux`
- **Windows:** `voxvera-windows.exe`
- **macOS:** `voxvera-macos`
These binaries include everything needed to run VoxVera (except `onionshare-cli`).
### 2. One-Line Installer
Alternatively, install via our automated script:
```bash
curl -fsSL https://raw.githubusercontent.com/PR0M3TH3AN/VoxVera/main/install.sh | bash
```
### 3. Manual Python Install
If you prefer to run from source:
```bash
pipx install 'voxvera@git+https://github.com/PR0M3TH3AN/VoxVera.git@main'
sudo apt install tor onionshare-cli # Debian/Ubuntu
```
## Step-by-Step
1. **Initialize:** Run `voxvera init` and follow the prompts. You will be asked to select your language first.
2. **Build:** Generate the flyer assets. Every build automatically creates a `voxvera-portable.zip` in the flyer's folder, allowing others to download the full tool directly from your flyer.
```bash
voxvera build
```
3. **Serve:** Publish the flyer over Tor:
```bash
voxvera serve
```
This automatically detects your Tor instance, starts OnionShare, and writes the generated .onion address into the flyer's tear-off links.
## Language Support
VoxVera is fully localized. You can change your language preference permanently using either the interactive selector or a direct shortcut:
- **Interactive Selector:** `voxvera lang`
- **Direct Shortcut:** `voxvera --lang de` (sets preference to German)
### Supported Languages:
- **English:** `en`
- **Spanish:** `es` (alias: `--idioma`)
- **German:** `de` (alias: `--sprache`)
You can also force a specific language for a single command without changing your permanent preference:
- **English:** `voxvera --lang en check`
- **German:** `voxvera --sprache de check`
- **Spanish:** `voxvera --idioma es check`
The generated flyers automatically detect the visitor's browser language and switch the UI text accordingly.
## Server Management
Manage multiple flyers and their Tor identities from a single interactive menu:
```bash
voxvera manage
```
Features:
- **--- Создать новый сайт/листовку ---**: Start the full setup sequence.
- **--- Запустить все сайты ---**: Launch or shut down every flyer in your fleet at once.
- **Real-time Status**: View active `.onion` URLs and Tor bootstrapping progress indicators.
- **Individual Control**: Экспорт в Zip specific sites to ZIP or delete them.
## Universal Mirroring (Viral Distribution)
To ensure VoxVera remains accessible even if central repositories are censored, every flyer acts as a mirror for the tool.
When you host a flyer, the **"Скачать инструмент и код"** button on the landing page provides a `voxvera-portable.zip` containing:
- The full source code and all supported languages.
- All Python dependencies (pre-vendored).
- Cross-platform Tor binaries.
This allows anyone who scans your flyer to become a new distributor of the VoxVera tool.
## Export & Backup
Back up your unique Tor identities (so your `.onion` URL never changes) or move your flyers to another machine.
- **Export a single site**: `voxvera export <folder_name>`
- **Export all sites**: `voxvera export-all`
**Storage location:** All exports are saved to `~/voxvera-exports/` on all platforms.
## Import & Recovery
Restore your entire setup on a new machine by moving your ZIP files to `~/voxvera-exports/` and running:
```bash
voxvera import-multiple
```
## Portability & Offline Use
If you need to run VoxVera on a machine without internet access, you can "vendorize" the dependencies first:
```bash
voxvera vendorize
```
This downloads all required Python libraries into `voxvera/vendor/`. The tool will then prioritize these local files, allowing it to run without `pip install`.
## Batch Import (JSON)
To bulk-generate flyers from multiple JSON configuration files, place them in the `imports/` directory and run:
```bash
voxvera batch-import
```
## How URLs Work
Each flyer has two separate URLs:
- **Tear-off link** (auto-generated): The .onion address where the flyer is hosted.
- **Content link** (user-configured): An external URL pointing to a website, video, or download.
You do not need to manually enter the .onion address; VoxVera handles this automatically during the `serve` phase.
Binary file not shown.
+48 -2
View File
@@ -7,7 +7,7 @@
<title id="site-title">Vox Vera Printable Flyers</title>
<style>
body {
font-family: 'Courier New', Courier, monospace;
font-family: 'Courier New', Courier, 'Nimbus Mono L', 'Liberation Mono', 'Noto Sans Arabic', 'Noto Sans Hebrew', monospace;
margin: 0;
padding: 20px 0;
display: flex;
@@ -19,6 +19,45 @@
background-color: #ffffff;
}
/* RTL Layout Overrides */
[dir="rtl"] .container {
flex-direction: row-reverse;
}
[dir="rtl"] .left-tear-offs {
border-right: none;
border-left: 1px dashed #000;
}
[dir="rtl"] .tear-off {
flex-direction: row-reverse;
}
[dir="rtl"] .qr-code {
margin-left: 0;
margin-right: 10px;
}
[dir="rtl"] .qr-code-body {
flex-direction: row-reverse;
}
[dir="rtl"] .qr-code-url {
text-align: right;
margin-right: 0;
margin-left: 10px;
}
[dir="rtl"] .message {
text-align: right;
}
[dir="rtl"] .text-container {
text-align: right;
}
/* ... existing styles ... */
a {
color: #b80000;
text-decoration: none;
@@ -294,7 +333,7 @@
</style>
<script>
// VoxVera Multi-Language Engine
const locales = {"de": {"meta": {"language_name": "Deutsch", "flag": "\ud83c\udde9\ud83c\uddea"}, "web": {"tear_off_tor_browser": "Tor-Browser verwenden", "tear_off_desc": "um diese Nachricht zu verbreiten, gehen Sie zu", "tear_off_footer": "Klicken Sie auf die Schaltfl\u00e4che unten zum Drucken", "built_with": "erstellt mit", "print_button": "Flyer drucken", "download_button": "Tool & Quellcode herunterladen"}, "landing": {"title": "STRENG GEHEIM", "subtitle": "~~NICHT~~ VERBREITEN", "headline": "OPERATION VOX VERA", "content": "Befreien Sie sich von Zensur mit VoxVera. Ein anonymes Guerilla-Marketing- und Nachrichtenverbreitungs-Tool. Ob online oder in der physischen Welt, es bef\u00e4higt Sie, Ihre Ideen mutig zu verbreiten, gesch\u00fctzt durch vollst\u00e4ndige Anonymit\u00e4t (wenn Sie \u00fcber Tor hosten). Laden Sie den Code herunter, entwerfen Sie eine Flyer-Website, hosten Sie diese online und verst\u00e4rken Sie Ihre Botschaft.\n\nNutzen Sie memetische Kraft, um Ihre Ideen in Ihrer Schule, an Ihrem Arbeitsplatz, in Online-Communities oder sogar weltweit zu teilen. VoxVera stellt sicher, dass Ihre Botschaft weit und breit Resonanz findet, mit Abrei\u00dfabschnitten, die \u00fcber einzigartige URLs und QR-Codes f\u00fcr einen einfachen Nachdruck verf\u00fcgen.\n\nPrivatsph\u00e4re kann gewahrt bleiben. Flyer k\u00f6nnen \u00fcber das Tor-Network geteilt werden, was Hoster und Nutzer vor Zensur sch\u00fctzt.\n\nSchlie\u00dfen Sie sich uns in einer Revolution an, die Wahrheit und Transparenz sch\u00e4tzt. Gemeinsam k\u00f6nnen wir ein Netzwerk informierter B\u00fcrger aufbauen, die keine Angst haben, ihre Meinung zu sagen.", "url_message": "Holen Sie sich VoxVera und beginnen Sie, Ihre Nachricht zu verbreiten."}}, "en": {"meta": {"language_name": "English", "flag": "\ud83c\uddfa\ud83c\uddf8"}, "web": {"tear_off_tor_browser": "Use Tor Browser", "tear_off_desc": "to help spread this message go to", "tear_off_footer": "click the button below to print", "built_with": "built with", "print_button": "Print this page", "download_button": "Download Tool & Source"}, "landing": {"title": "TOP SECRET", "subtitle": "DO ~~NOT~~ DISTRIBUTE", "headline": "OPERATION VOX VERA", "content": "Break free from censorship with VoxVera. An anonymous guerrilla marketing and message-spreading tool. Whether online or in the physical world, it can empower you to spread your ideas boldly, shielded by complete anonymity (if you host over Tor). Download the code, design a flyer site, host it online and amplify your message.\n\nUse memetic power to share your ideas in your school, workplace, online communities, or even globally. VoxVera ensures your message resonates far and wide, with tear-off sections featuring unique URLs and QR codes for easy reprinting.\n\nPrivacy can be maintained. Flyers can be shared via the Tor network, protecting hoster and users from censorship.\n\nJoin us in a revolution that values truth and transparency. Together, we can build a network of informed citizens who are unafraid to speak out.", "url_message": "Get VoxVera and start spreading your message."}}, "es": {"meta": {"language_name": "Espa\u00f1ol", "flag": "\ud83c\uddea\ud83c\uddf8"}, "web": {"tear_off_tor_browser": "Use el Navegador Tor", "tear_off_desc": "para ayudar a difundir este mensaje vaya a", "tear_off_footer": "haga clic en el bot\u00f3n de abajo para imprimir", "built_with": "construido con", "print_button": "Imprimir volante", "download_button": "Descargar Herramienta y C\u00f3digo"}, "landing": {"title": "ALTO SECRETO", "subtitle": "~~NO~~ DISTRIBUIR", "headline": "OPERACI\u00d3N VOX VERA", "content": "Lib\u00e9rese de la censura con VoxVera. Una herramienta an\u00f3nima de marketing de guerrilla y difusi\u00f3n de mensajes. Ya sea en l\u00ednea o en el mundo f\u00edsico, puede empoderarlo para difundir sus ideas con audacia, protegido por un anonimato completo (si hospeda a trav\u00e9s de Tor). Descargue el c\u00f3digo, dise\u00f1e un sitio de volantes, hosp\u00e9delo en l\u00ednea y amplifique su mensaje.\n\nUse el poder mem\u00e9tico para compartir sus ideas en su escuela, lugar de trabajo, comunidades en l\u00ednea o incluso globalmente. VoxVera asegura que su mensaje resuene a lo largo und ancho, con secciones desprendibles que cuentan con URLs y c\u00f3digos QR \u00fanicos para una f\u00e1cil reimpresi\u00f3n.\n\nLa privacidad puede mantenerse. Los volantes se pueden compartir a trav\u00e9s de la red Tor, protegiendo al anfitri\u00f3n y a los usuarios de la censura.\n\n\u00danase a nosotros en una revoluci\u00f3n que valora la verdad y la transparencia. Juntos, podemos construir una red de ciudadanos informados que no temen hablar.", "url_message": "Obtenga VoxVera y comience a difundir su mensaje."}}}; // Injected at build time
const locales = {"ar": {"meta": {"language_name": "\u0627\u0644\u0639\u0631\u0628\u064a\u0629", "flag": "\ud83c\uddf8\ud83c\udde6", "direction": "rtl"}, "web": {"tear_off_tor_browser": "\u0627\u0633\u062a\u062e\u062f\u0645 \u0645\u062a\u0635\u0641\u062d Tor", "tear_off_desc": "\u0644\u0644\u0645\u0633\u0627\u0639\u062f\u0629 \u0641\u064a \u0646\u0634\u0631 \u0647\u0630\u0647 \u0627\u0644\u0631\u0633\u0627\u0644\u0629\u060c \u0627\u0646\u062a\u0642\u0644 \u0625\u0644\u0649", "tear_off_footer": "\u0627\u0646\u0642\u0631 \u0641\u0648\u0642 \u0627\u0644\u0632\u0631 \u0623\u062f\u0646\u0627\u0647 \u0644\u0644\u0637\u0628\u0627\u0639\u0629", "built_with": "\u0628\u0646\u064a \u0628\u0648\u0627\u0633\u0637\u0629", "print_button": "\u0627\u0637\u0628\u0639 \u0627\u0644\u0645\u0646\u0634\u0648\u0631", "download_button": "\u062a\u062d\u0645\u064a\u0644 \u0627\u0644\u0623\u062f\u0627\u0629 \u0648\u0627\u0644\u0643\u0648\u062f"}, "landing": {"title": "\u0633\u0631\u064a \u0644\u0644\u063a\u0627\u064a\u0629", "subtitle": "~~\u0644\u0627~~ \u0644\u0644\u0646\u0634\u0631", "headline": "\u0639\u0645\u0644\u064a\u0629 VOX VERA", "content": "\u062a\u062d\u0631\u0631 \u0645\u0646 \u0627\u0644\u0631\u0642\u0627\u0628\u0629 \u0645\u0639 VoxVera. \u0623\u062f\u0627\u0629 \u062a\u0633\u0648\u064a\u0642 \u0639\u0635\u0627\u0628\u0627\u062a \u0645\u062c\u0647\u0648\u0644\u0629 \u0648\u0646\u0634\u0631 \u0631\u0633\u0627\u0626\u0644. \u0633\u0648\u0627\u0621 \u0639\u0628\u0631 \u0627\u0644\u0625\u0646\u062a\u0631\u0646\u062a \u0623\u0648 \u0641\u064a \u0627\u0644\u0639\u0627\u0644\u0645 \u0627\u0644\u0645\u0627\u062f\u064a\u060c \u064a\u0645\u0643\u0646\u0647\u0627 \u062a\u0645\u0643\u064a\u0646\u0643 \u0645\u0646 \u0646\u0634\u0631 \u0623\u0641\u0643\u0627\u0631\u0643 \u0628\u062c\u0631\u0623\u0629\u060c \u0645\u062d\u0645\u064a\u0629 \u0628\u0625\u062e\u0641\u0627\u0621 \u0627\u0644\u0647\u0648\u064a\u0629 \u0627\u0644\u0643\u0627\u0645\u0644 (\u0625\u0630\u0627 \u0643\u0646\u062a \u062a\u0633\u062a\u0636\u064a\u0641 \u0639\u0628\u0631 Tor). \u0642\u0645 \u0628\u062a\u062d\u0645\u064a\u0644 \u0627\u0644\u0643\u0648\u062f\u060c \u0648\u0635\u0645\u0645 \u0645\u0648\u0642\u0639 \u0645\u0646\u0634\u0648\u0631\u060c \u0648\u0627\u0633\u062a\u0636\u064a\u0641\u0647 \u0639\u0628\u0631 \u0627\u0644\u0625\u0646\u062a\u0631\u0646\u062a \u0648\u0642\u0645 \u0628\u062a\u0636\u062e\u064a\u0645 \u0631\u0633\u0627\u0644\u062a\u0643.\n\n\u0627\u0633\u062a\u062e\u062f\u0645 \u0627\u0644\u0642\u0648\u0629 \u0627\u0644\u0645\u064a\u0645\u064a\u0629 \u0644\u0645\u0634\u0627\u0631\u0643\u0629 \u0623\u0641\u0643\u0627\u0631\u0643 \u0641\u064a \u0645\u062f\u0631\u0633\u062a\u0643\u060c \u0623\u0648 \u0645\u0643\u0627\u0646 \u0639\u0645\u0644\u0643\u060c \u0623\u0648 \u0645\u062c\u062a\u0645\u0639\u0627\u062a\u0643 \u0639\u0628\u0631 \u0627\u0644\u0625\u0646\u062a\u0631\u0646\u062a\u060c \u0623\u0648 \u062d\u062a\u0649 \u0639\u0644\u0649 \u0645\u0633\u062a\u0648\u0649 \u0627\u0644\u0639\u0627\u0644\u0645. \u062a\u0636\u0645\u0646 VoxVera \u062a\u0631\u062f\u062f \u0635\u062f\u0649 \u0631\u0633\u0627\u0644\u062a\u0643 \u0628\u0639\u064a\u062f\u0627\u064b \u0648\u0639\u0644\u0649 \u0646\u0637\u0627\u0642 \u0648\u0627\u0633\u0639\u060c \u0645\u0639 \u0623\u0642\u0633\u0627\u0645 \u0642\u0627\u0628\u0644\u0629 \u0644\u0644\u0646\u0632\u0639 \u062a\u062a\u0645\u064a\u0632 \u0628\u0631\u0648\u0627\u0628\u0637 \u0641\u0631\u064a\u062f\u0629 \u0648\u0623\u0643\u0648\u0627\u062f QR \u0644\u0633\u0647\u0648\u0644\u0629 \u0625\u0639\u0627\u062f\u0629 \u0627\u0644\u0637\u0628\u0627\u0639\u0629.\n\n\u064a\u0645\u0643\u0646 \u0627\u0644\u062d\u0641\u0627\u0638 \u0639\u0644\u0649 \u0627\u0644\u062e\u0635\u0648\u0635\u064a\u0629. \u064a\u0645\u0643\u0646 \u0645\u0634\u0627\u0631\u0643\u0629 \u0627\u0644\u0645\u0646\u0634\u0648\u0631\u0627\u062a \u0639\u0628\u0631 \u0634\u0628\u0643\u0629 Tor\u060c \u0645\u0645\u0627 \u064a\u062d\u0645\u064a \u0627\u0644\u0645\u0636\u064a\u0641 \u0648\u0627\u0644\u0645\u0633\u062a\u062e\u062f\u0645\u064a\u0646 \u0645\u0646 \u0627\u0644\u0631\u0642\u0627\u0628\u0629.\n\n\u0627\u0646\u0636\u0645 \u0625\u0644\u064a\u0646\u0627 \u0641\u064a \u062b\u0648\u0631\u0629 \u062a\u0642\u062f\u0631 \u0627\u0644\u062d\u0642\u064a\u0642\u0629 \u0648\u0627\u0644\u0634\u0641\u0627\u0641\u064a\u0629. \u0645\u0639\u0627\u064b\u060c \u064a\u0645\u0643\u0646\u0646\u0627 \u0628\u0646\u0627\u0621 \u0634\u0628\u0643\u0629 \u0645\u0646 \u0627\u0644\u0645\u0648\u0627\u0637\u0646\u064a\u0646 \u0627\u0644\u0645\u0633\u062a\u0646\u064a\u0631\u064a\u0646 \u0627\u0644\u0630\u064a\u0646 \u0644\u0627 \u064a\u062e\u0634\u0648\u0646 \u0627\u0644\u062a\u062d\u062f\u062b.", "url_message": "\u0627\u062d\u0635\u0644 \u0639\u0644\u0649 VoxVera \u0648\u0627\u0628\u062f\u0623 \u0628\u0646\u0634\u0631 \u0631\u0633\u0627\u0644\u062a\u0643."}}, "de": {"meta": {"language_name": "Deutsch", "flag": "\ud83c\udde9\ud83c\uddea"}, "web": {"tear_off_tor_browser": "Tor-Browser verLine truncated
function t(path, lang) {
const parts = path.split('.');
@@ -309,6 +348,12 @@
function setLanguage(lang) {
if (!locales[lang]) lang = 'en';
localStorage.setItem('voxvera_lang', lang);
// Set text direction
const direction = locales[lang].meta.direction || 'ltr';
document.documentElement.dir = direction;
document.documentElement.lang = lang;
document.querySelectorAll('[data-i18n]').forEach(el => {
const path = el.getAttribute('data-i18n');
let translation = t(path, lang);
@@ -475,6 +520,7 @@ Privacy can be maintained. Flyers can be shared via the Tor network, protecting
Join us in a revolution that values truth and transparency. Together, we can build a network of informed citizens who are unafraid to speak out.</div>
<div class="qr-code-body">
<div class="qr-code-url">
<span class="url-message" data-i18n="landing.url_message">Get VoxVera and start spreading your message.</span><br><br>
<a id="qr-link-2" href="https://github.com/PR0M3TH3AN/VoxVera" target="_blank">https://github.com/PR0M3TH3AN/VoxVera</a>
+17 -7
View File
@@ -176,21 +176,31 @@ def open_editor(initial: str) -> str:
return result["text"]
def _get_visual_width(text: str) -> int:
"""Calculate the visual width of a string (handling CJK double-width)."""
try:
from wcwidth import wcswidth
width = wcswidth(text)
return width if width >= 0 else len(text)
except ImportError:
return len(text)
def _len_transform(limit: int):
def _t(val: str) -> str:
length = len(val)
if length > limit:
return f"[red]{val} ({length}/{limit})[/red]"
return f"{val} ({length}/{limit})"
width = _get_visual_width(val)
if width > limit:
return f"[red]{val} ({width}/{limit})[/red]"
return f"{val} ({width}/{limit})"
return _t
def _len_validator(limit: int):
def _v(val: str):
length = len(val)
if length > limit:
return f"❌ Input too long: {length}/{limit} characters. Please shorten it."
width = _get_visual_width(val)
if width > limit:
return f"❌ Input too wide: {width}/{limit} visual units. Please shorten it."
return True
return _v
+92
View File
@@ -0,0 +1,92 @@
{
"meta": {
"language_name": "العربية",
"flag": "🇸🇦",
"direction": "rtl"
},
"cli": {
"dep_check_header": "فحص التبعيات",
"dep_found": "موجود",
"dep_missing": "مفقود",
"all_deps_installed": "تم تثبيت جميع التبعيات.",
"missing_deps_error": "بعض التبعيات مفقودة. تشغيل: pip install 'voxvera[all]'",
"init_name": "الاسم",
"init_folder": "اسم المجلد",
"init_title": "العنوان",
"init_subtitle": "العنوان الفرعي",
"init_headline": "العنوان الرئيسي",
"init_body": "نص الرسالة",
"init_links": "الروابط",
"edit_again": "تعديل مرة أخرى؟",
"body_length": "طول النص",
"body_limit_exceeded": "يتجاوز الحد",
"url_label": "URL (رابط المحتوى - مورد خارجي)",
"url_message_label": "رسالة الـ URL",
"binary_message_label": "رسالة ثنائية",
"template_not_found": "القالب {name} غير موجود",
"template_copied": "تم نسخ القالب إلى {dest}",
"flyer_created": "تم إنشاء ملفات المنشور تحت {dest}",
"dir_not_found": "المجلد {dir_path} غير موجود",
"onionshare_error": "خرج OnionShare لـ {folder_name} بشكل غير متوقع.",
"onionshare_causes": "الأسباب الشائعة: الشبكة محظورة، وقت النظام غير دقيق، أو OnionShare يعمل بالفعل.",
"onionshare_logs_tip": "راجع {logfile} للحصول على السجلات الكاملة.",
"onion_url_msg": "[{folder_name}] رابط Onion: {onion_url}",
"onionshare_running_pid": "[{folder_name}] OnionShare يعمل (PID {pid}).",
"export_not_found": "خطأ: مجلد الموقع '{folder_name}' غير موجود في {host_root}",
"export_success": "تم تصدير '{folder_name}' إلى {output_path}",
"export_all_none": "لم يتم العثور على مواقع لتصديرها.",
"export_all_starting": "جاري تصدير {count} مواقع إلى {dir}...",
"export_all_complete": "اكتمل تصدير الكل.",
"import_zip_not_found": "خطأ: ملف Zip '{path}' غير موجود.",
"import_invalid_config": "خطأ: ملف تصدير غير صالح. 'config.json' مفقود.",
"import_invalid_folder": "خطأ: config.json غير صالح. 'folder_name' مفقود.",
"import_overwrite_confirm": "الموقع '{folder_name}' موجود بالفعل. هل تريد الاستبدال؟",
"import_cancelled": "تم إلغاء الاستيراد.",
"import_success": "تم استيراد '{folder_name}' بنجاح إلى {dest_dir}",
"import_dir_not_found": "خطأ: المجلد '{dir}' غير موجود.",
"import_multiple_none": "لم يتم العثور على ملفات zip في {dir}",
"import_multiple_found": "تم العثور على {count} موقع للاستيراد من {dir}...",
"importing_file": "جاري استيراد {name}...",
"import_multiple_complete": "اكتمل استيراد الكل.",
"batch_import_none": "لم يتم العثور على ملفات JSON في الواردات",
"batch_import_processing": "جاري معالجة {file}",
"stop_server_not_running": "الخادم {folder_name} لا يعمل.",
"stop_server_success": "تم إيقاف {folder_name} (PID {pid})",
"delete_server_success": "تم حذف الخادم {folder_name}",
"manage_select_server": "اختر خادماً لإدارته (أو خروج):",
"manage_create_new": "--- إنشاء موقع/منشور جديد ---",
"manage_start_all": "--- تشغيل جميع المواقع ---",
"manage_stop_all": "--- إيقاف جميع المواقع ---",
"manage_export_all": "--- تصدير جميع المواقع ---",
"manage_import_multiple": "--- استيراد مواقع متعددة ---",
"manage_exit": "خروج",
"manage_startup_summary": "ملخص التشغيل",
"manage_press_enter": "اضغط Enter للمتابعة",
"manage_action_menu": "إدارة {name}:",
"manage_action_toggle_stop": "إيقاف",
"manage_action_toggle_start": "تشغيل",
"manage_action_export": "تصدير إلى Zip",
"manage_action_delete": "حذف",
"manage_action_back": "رجوع",
"manage_delete_confirm": "هل أنت متأكد أنك تريد حذف {name}؟",
"bootstrapping_msg": "[{name}] جاري الاتصال بمرحلات Tor وإعداد الخدمة المخفية...",
"bootstrapping_progress": "[{name}] تمهيد Tor: {progress}% ...",
"no_urls_qr_skip": "لم يتم تكوين أي روابط بعد. سيتم إنشاء أكواد QR بعد 'voxvera serve'",
"input_file_not_found": "ملف الإدخال {path} غير موجود."
},
"web": {
"tear_off_tor_browser": "استخدم متصفح Tor",
"tear_off_desc": "للمساعدة في نشر هذه الرسالة، انتقل إلى",
"tear_off_footer": "انقر فوق الزر أدناه للطباعة",
"built_with": "بني بواسطة",
"print_button": "اطبع المنشور",
"download_button": "تحميل الأداة والكود"
},
"landing": {
"title": "سري للغاية",
"subtitle": "~~لا~~ للنشر",
"headline": "عملية VOX VERA",
"content": "تحرر من الرقابة مع VoxVera. أداة تسويق عصابات مجهولة ونشر رسائل. سواء عبر الإنترنت أو في العالم المادي، يمكنها تمكينك من نشر أفكارك بجرأة، محمية بإخفاء الهوية الكامل (إذا كنت تستضيف عبر Tor). قم بتحميل الكود، وصمم موقع منشور، واستضيفه عبر الإنترنت وقم بتضخيم رسالتك.\n\nاستخدم القوة الميمية لمشاركة أفكارك في مدرستك، أو مكان عملك، أو مجتمعاتك عبر الإنترنت، أو حتى على مستوى العالم. تضمن VoxVera تردد صدى رسالتك بعيداً وعلى نطاق واسع، مع أقسام قابلة للنزع تتميز بروابط فريدة وأكواد QR لسهولة إعادة الطباعة.\n\nيمكن الحفاظ على الخصوصية. يمكن مشاركة المنشورات عبر شبكة Tor، مما يحمي المضيف والمستخدمين من الرقابة.\n\nانضم إلينا في ثورة تقدر الحقيقة والشفافية. معاً، يمكننا بناء شبكة من المواطنين المستنيرين الذين لا يخشون التحدث.",
"url_message": "احصل على VoxVera وابدأ بنشر رسالتك."
}
}
+92
View File
@@ -0,0 +1,92 @@
{
"meta": {
"language_name": "עברית",
"flag": "🇮🇱",
"direction": "rtl"
},
"cli": {
"dep_check_header": "בדיקת תלות",
"dep_found": "נמצא",
"dep_missing": "חסר",
"all_deps_installed": "כל התלויות מותקנות.",
"missing_deps_error": "חלק מהתלויות חסרות. הרץ: pip install 'voxvera[all]'",
"init_name": "שם",
"init_folder": "שם תיקייה",
"init_title": "כותרת",
"init_subtitle": "כותרת משנה",
"init_headline": "כותרת ראשית",
"init_body": "טקסט ההודעה",
"init_links": "קישורים",
"edit_again": "לערוך שוב?",
"body_length": "אורך הטקסט",
"body_limit_exceeded": "חורג מהמגבלה",
"url_label": "URL (קישור לתוכן - משאב חיצוני)",
"url_message_label": "הודעת URL",
"binary_message_label": "הודעה בינארית",
"template_not_found": "התבנית {name} לא נמצאה",
"template_copied": "התבנית הועתקה אל {dest}",
"flyer_created": "קובצי הפלייר נוצרו תחת {dest}",
"dir_not_found": "התיקייה {dir_path} לא נמצאה",
"onionshare_error": "OnionShare עבור {folder_name} נסגר באופן לא צפוי.",
"onionshare_causes": "סיבות נפוצות: רשת חסומה, שעון מערכת לא מדויק, או ש-OnionShare כבר פועל.",
"onionshare_logs_tip": "ראה {logfile} ליומנים מלאים.",
"onion_url_msg": "[{folder_name}] כתובת Onion: {onion_url}",
"onionshare_running_pid": "[{folder_name}] OnionShare פועל (PID {pid}).",
"export_not_found": "שגיאה: תיקיית האתר '{folder_name}' לא נמצאה ב-{host_root}",
"export_success": "יוצא '{folder_name}' אל {output_path}",
"export_all_none": "לא נמצאו אתרים לייצוא.",
"export_all_starting": "מייצא {count} אתרים אל {dir}...",
"export_all_complete": "ייצוא הכל הושלם.",
"import_zip_not_found": "שגיאה: קובץ ה-Zip '{path}' לא נמצא.",
"import_invalid_config": "שגיאה: קובץ ייצוא לא תקין. 'config.json' חסר.",
"import_invalid_folder": "שגיאה: config.json לא תקין. 'folder_name' חסר.",
"import_overwrite_confirm": "האתר '{folder_name}' כבר קיים. לדרוס?",
"import_cancelled": "הייבוא בוטל.",
"import_success": "ייבוא '{folder_name}' אל {dest_dir} הושלם בהצלחה",
"import_dir_not_found": "שגיאה: התיקייה '{dir}' לא נמצאה.",
"import_multiple_none": "לא נמצאו קובצי Zip ב-{dir}",
"import_multiple_found": "נמצאו {count} אתרים לייבוא מ-{dir}...",
"importing_file": "מייבא את {name}...",
"import_multiple_complete": "ייבוא מרובה הושלם.",
"batch_import_none": "לא נמצאו קובצי JSON ב-imports",
"batch_import_processing": "מעבד את {file}",
"stop_server_not_running": "השרת {folder_name} אינו פועל.",
"stop_server_success": "הופסק {folder_name} (PID {pid})",
"delete_server_success": "נמחק שרת {folder_name}",
"manage_select_server": "בחר שרת לניהול (או יציאה):",
"manage_create_new": "--- צור אתר/פלייר חדש ---",
"manage_start_all": "--- הפעל את כל האתרים ---",
"manage_stop_all": "--- הפסק את כל האתרים ---",
"manage_export_all": "--- ייצא את כל האתרים ---",
"manage_import_multiple": "--- ייבא מספר אתרים ---",
"manage_exit": "יציאה",
"manage_startup_summary": "סיכום הפעלה",
"manage_press_enter": "לחץ על Enter להמשך",
"manage_action_menu": "נהל את {name}:",
"manage_action_toggle_stop": "הפסק",
"manage_action_toggle_start": "הפעל",
"manage_action_export": "ייצא ל-Zip",
"manage_action_delete": "מחק",
"manage_action_back": "חזרה",
"manage_delete_confirm": "האם אתה בטוח שברצונך למחוק את {name}?",
"bootstrapping_msg": "[{name}] מתחבר לממסרי Tor ומגדיר שירות נסתר...",
"bootstrapping_progress": "[{name}] איתחול Tor: {progress}% ...",
"no_urls_qr_skip": "עדיין לא הוגדרו כתובות URL. קודי QR ייווצרו לאחר 'voxvera serve'",
"input_file_not_found": "קובץ הקלט {path} אינו קיים."
},
"web": {
"tear_off_tor_browser": "השתמש בדפדפן Tor",
"tear_off_desc": "כדי לעזור להפיץ את המסר הזה עבור אל",
"tear_off_footer": "לחץ על הכפתור למטה להדפסה",
"built_with": "נבנה עם",
"print_button": "הדפס פלייר",
"download_button": "הורד כלי וקוד מקור"
},
"landing": {
"title": "סודי ביותר",
"subtitle": "~~לא~~ להפצה",
"headline": "מבצע VOX VERA",
"content": "השתחרר מהצנזורה עם VoxVera. כלי אנונימי לשיווק גרילה והפצת מסרים. בין אם באינטרנט ובין אם בעולם הפיזי, הוא יכול להעצים אותך להפיץ את הרעיונות שלך בתעוזה, מוגן באנונימיות מלאה (אם אתה מארח דרך Tor). הורד את הקוד, עצב אתר פלייר, ארח אותו אונליין והגבר את המסר שלך.\n\nהשתמש בכוח הממטי כדי לשתף את הרעיונות שלך בבית הספר, במקום העבודה, בקהילות מקוונות או אפילו ברמה עולמית. VoxVera מבטיחה שהמסר שלך יהדהד למרחקים, עם חלקים נתלשים הכוללים כתובות URL ייחודיות וקודי QR להדפסה חוזרת קלה.\n\nניתן לשמור על פרטיות. ניתן לשתף פליירים דרך רשת Tor, ובכך להגן על המארח ועל המשתמשים מפני צנזורה.\n\nהצטרפו אלינו למהפכה שמעריכה אמת ושקיפות. יחד, נוכל לבנות רשת של אזרחים מושכלים שאינם פוחדים להתבטא.",
"url_message": "קבלו את VoxVera והתחילו להפיץ את המסר שלכם."
}
}
+92
View File
@@ -0,0 +1,92 @@
{
"meta": {
"language_name": "Русский",
"flag": "🇷🇺",
"direction": "ltr"
},
"cli": {
"dep_check_header": "Проверка зависимостей",
"dep_found": "найдено",
"dep_missing": "отсутствует",
"all_deps_installed": "Все зависимости установлены.",
"missing_deps_error": "Некоторые зависимости отсутствуют. Выполните: pip install 'voxvera[all]'",
"init_name": "Имя",
"init_folder": "Имя папки",
"init_title": "Заголовок",
"init_subtitle": "Подзаголовок",
"init_headline": "Главный заголовок",
"init_body": "Текст сообщения",
"init_links": "Ссылки",
"edit_again": "Редактировать снова?",
"body_length": "Длина текста",
"body_limit_exceeded": "превышает лимит",
"url_label": "URL (ссылка на контент - внешний ресурс)",
"url_message_label": "Сообщение URL",
"binary_message_label": "Бинарное сообщение",
"template_not_found": "Шаблон {name} не найден",
"template_copied": "Шаблон скопирован в {dest}",
"flyer_created": "Файлы листовки созданы в {dest}",
"dir_not_found": "Директория {dir_path} не найдена",
"onionshare_error": "OnionShare для {folder_name} неожиданно завершил работу.",
"onionshare_causes": "Общие причины: заблокирована сеть, неточное системное время или OnionShare уже запущен.",
"onionshare_logs_tip": "Полные логи см. в {logfile}.",
"onion_url_msg": "[{folder_name}] Onion URL: {onion_url}",
"onionshare_running_pid": "[{folder_name}] OnionShare запущен (PID {pid}).",
"export_not_found": "Ошибка: папка сайта '{folder_name}' не найдена в {host_root}",
"export_success": "Экспортировано '{folder_name}' в {output_path}",
"export_all_none": "Сайты для экспорта не найдены.",
"export_all_starting": "Экспорт {count} сайтов в {dir}...",
"export_all_complete": "Общий экспорт завершен.",
"import_zip_not_found": "Ошибка: Zip-файл '{path}' не найден.",
"import_invalid_config": "Ошибка: неверный файл экспорта. Отсутствует 'config.json'.",
"import_invalid_folder": "Ошибка: неверный config.json. Отсутствует 'folder_name'.",
"import_overwrite_confirm": "Сайт '{folder_name}' уже существует. Перезаписать?",
"import_cancelled": "Импорт отменен.",
"import_success": "Сайт '{folder_name}' успешно импортирован в {dest_dir}",
"import_dir_not_found": "Ошибка: Директория '{dir}' не найдена.",
"import_multiple_none": "Zip-файлы в {dir} не найдены",
"import_multiple_found": "Найдено {count} сайт(ов) для импорта из {dir}...",
"importing_file": "Импорт {name}...",
"import_multiple_complete": "Множественный импорт завершен.",
"batch_import_none": "JSON-файлы в imports не найдены",
"batch_import_processing": "Обработка {file}",
"stop_server_not_running": "Сервер {folder_name} не запущен.",
"stop_server_success": "{folder_name} остановлен (PID {pid})",
"delete_server_success": "Сервер {folder_name} удален",
"manage_select_server": "Выберите сервер для управления (или Выход):",
"manage_create_new": "--- Создать новый сайт/листовку ---",
"manage_start_all": "--- Запустить все сайты ---",
"manage_stop_all": "--- Остановить все сайты ---",
"manage_export_all": "--- Экспортировать все сайты ---",
"manage_import_multiple": "--- Импортировать несколько сайтов ---",
"manage_exit": "Выход",
"manage_startup_summary": "Сводка запуска",
"manage_press_enter": "Нажмите Enter для продолжения",
"manage_action_menu": "Управление {name}:",
"manage_action_toggle_stop": "Остановить",
"manage_action_toggle_start": "Запустить",
"manage_action_export": "Экспорт в Zip",
"manage_action_delete": "Удалить",
"manage_action_back": "Назад",
"manage_delete_confirm": "Вы уверены, что хотите удалить {name}?",
"bootstrapping_msg": "[{name}] Подключение к реле Tor и настройка скрытого сервиса...",
"bootstrapping_progress": "[{name}] Загрузка Tor: {progress}% ...",
"no_urls_qr_skip": "URL еще не настроены. QR-коды будут созданы после 'voxvera serve'",
"input_file_not_found": "Входной файл {path} не существует."
},
"web": {
"tear_off_tor_browser": "Используйте Tor Browser",
"tear_off_desc": "чтобы помочь распространить это сообщение, перейдите на",
"tear_off_footer": "нажмите кнопку ниже, чтобы распечатать",
"built_with": "создано с помощью",
"print_button": "Печать листовки",
"download_button": "Скачать инструмент и код"
},
"landing": {
"title": "СОВЕРШЕННО СЕКРЕТНО",
"subtitle": "~~НЕ~~ РАСПРОСТРАНЯТЬ",
"headline": "ОПЕРАЦИЯ VOX VERA",
"content": "Освободитесь от цензуры с помощью VoxVera. Анонимный инструмент для партизанского маркетинга и распространения сообщений. Будь то в сети или в физическом мире, он дает вам возможность смело распространять свои идеи, будучи защищенным полной анонимностью (при хостинге через Tor). Скачайте код, создайте сайт-листовку, разместите его в сети и усильте свой голос.\n\nИспользуйте силу мемов, чтобы поделиться своими идеями в школе, на работе, в онлайн-сообществах или даже по всему миру. VoxVera гарантирует, что ваше сообщение найдет отклик повсюду, благодаря отрывным разделам с уникальными URL-адресами и QR-кодами для легкой перепечатки.\n\nКонфиденциальность может быть сохранена. Листовками можно делиться через сеть Tor, защищая хостера и пользователей от цензуры.\n\nПрисоединяйтесь к нам в революции, которая ценит правду и прозрачность. Вместе мы сможем построить сеть информированных граждан, которые не боятся высказываться.",
"url_message": "Получите VoxVera и начните распространять свое сообщение."
}
}
+47 -1
View File
@@ -7,7 +7,7 @@
<title id="site-title">{{name}}</title>
<style>
body {
font-family: 'Courier New', Courier, monospace;
font-family: 'Courier New', Courier, 'Nimbus Mono L', 'Liberation Mono', 'Noto Sans Arabic', 'Noto Sans Hebrew', monospace;
margin: 0;
padding: 20px 0;
display: flex;
@@ -19,6 +19,45 @@
background-color: #ffffff;
}
/* RTL Layout Overrides */
[dir="rtl"] .container {
flex-direction: row-reverse;
}
[dir="rtl"] .left-tear-offs {
border-right: none;
border-left: 1px dashed #000;
}
[dir="rtl"] .tear-off {
flex-direction: row-reverse;
}
[dir="rtl"] .qr-code {
margin-left: 0;
margin-right: 10px;
}
[dir="rtl"] .qr-code-body {
flex-direction: row-reverse;
}
[dir="rtl"] .qr-code-url {
text-align: right;
margin-right: 0;
margin-left: 10px;
}
[dir="rtl"] .message {
text-align: right;
}
[dir="rtl"] .text-container {
text-align: right;
}
/* ... existing styles ... */
a {
color: #b80000;
text-decoration: none;
@@ -309,6 +348,12 @@
function setLanguage(lang) {
if (!locales[lang]) lang = 'en';
localStorage.setItem('voxvera_lang', lang);
// Set text direction
const direction = locales[lang].meta.direction || 'ltr';
document.documentElement.dir = direction;
document.documentElement.lang = lang;
document.querySelectorAll('[data-i18n]').forEach(el => {
const path = el.getAttribute('data-i18n');
let translation = t(path, lang);
@@ -469,6 +514,7 @@
<div class="message">
<div class="text-container" id="main-content" contenteditable="false" data-i18n="landing.content">{{t_landing_content}}</div>
<div class="qr-code-body">
<div class="qr-code-url">
<span class="url-message" data-i18n="landing.url_message">{{t_landing_url_message}}</span><br><br>
<a id="qr-link-2" href="{{url}}" target="_blank">{{url}}</a>