initialization

This commit is contained in:
Reindl David (IT-PTR-CEN2-SL10) committed 2026-10-07 01:16:57 +02:00
commit 9ff09693ed
92 files changed
+46209

No files matched your search

+18
View File
@@ -0,0 +1,18 @@
.git
.gitignore
.github
.idea
.vscode
.env
.env.local
.env.*
!.env.example
logs/
vendor/
node_modules/
coverage/
.DS_Store
**/*.log
**/*.tmp
**/*.bak
**/~*
+21
View File
@@ -0,0 +1,21 @@
# Local development defaults
APP_ENV=development
APP_DEBUG=true
APP_NAME="Password Generator API"
APP_VERSION="2.0.0"
# Security
API_KEY_ENABLED=false
API_KEY=""
# CORS (keep local development origin explicit instead of wildcard)
CORS_ORIGIN="http://localhost:8080"
CORS_METHODS="GET,POST,OPTIONS"
CORS_HEADERS="Content-Type,Authorization"
# Logging
LOG_LEVEL=debug
LOG_PATH=logs/app.log
# Rate limiting
RATE_LIMIT=60
+7
View File
@@ -0,0 +1,7 @@
cache/
vendor/
config/config.local.php
logs/app.log
agent-prompts
.env
~*
+22
View File
@@ -0,0 +1,22 @@
# Agent Instructions: Password Generator API & Web App
This project is a stateless PHP 8.4 portal for issuing device setup payloads and provisioning configuration for authenticated users via Authentik OIDC.
This project is about creating a modern, secure password generator with REST API and interactive multilingual web frontend. It must be built with a Slim PHP Framework, featuring multiple password generation algorithms, comprehensive password manager integrations, and full internationalization support.
## Core Workflows & Responsibilities
The project has two main features
1. A multilingual web UI for the user to interactively create passwords using different algorithms. It must be intuitively understood and make it very easy for the user to select and copy a generated password of choice.
2. REST API to make the different password generation algorithms available via a well documented API. The API must be documents with a Swagger OpenAPI 3 specifications.
## Dependencies
Some algorithms rely on the shell commands xkcdpass and pwgen so these preferable should be available although this functionality is represented in PHP as a fallback as well.
## Deployments
Deployments is preferably via a docker image but can be done manually on bare metal as well. Both approaches must be possible and well documented.
The docker image must contain the necessary shell commands.
+303
View File
@@ -0,0 +1,303 @@
# Deployment Guide
## Quick Start
### 1. Installation
```bash
# Make installation script executable
chmod +x install.sh
# Run installation
./install.sh
```
### 2. Development Server
```bash
# Start with Composer
composer start
# Or manually
php -S localhost:8080 -t public
```
### 3. Access Application
- **Web Interface**: http://localhost:8080
- **API Documentation**: http://localhost:8080/docs
- **Health Check**: http://localhost:8080/api/v1/health
## Production Deployment
### Apache Configuration
```apache
<VirtualHost *:80>
ServerName passwords.yourdomain.com
DocumentRoot /var/www/password-generator/public
<Directory /var/www/password-generator/public>
AllowOverride All
Require all granted
</Directory>
# Security headers
Header always set X-Content-Type-Options nosniff
Header always set X-Frame-Options DENY
Header always set X-XSS-Protection "1; mode=block"
</VirtualHost>
```
### Nginx Configuration
```nginx
server {
listen 80;
server_name passwords.yourdomain.com;
root /var/www/password-generator/public;
index index.php;
# Security headers
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options DENY;
add_header X-XSS-Protection "1; mode=block";
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
# Cache static assets
location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg)$ {
expires 1M;
add_header Cache-Control "public, immutable";
}
}
```
### Docker Deployment
```dockerfile
FROM php:8.4-cli
WORKDIR /var/www/html
ENV APP_ENV=production \
APP_DEBUG=false \
PORT=8080
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
bash \
ca-certificates \
curl \
git \
unzip \
pwgen \
xkcdpass \
&& rm -rf /var/lib/apt/lists/*
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
COPY . /var/www/html/
RUN composer install --no-dev --optimize-autoloader --no-interaction --prefer-dist \
&& mkdir -p logs \
&& chmod 755 logs \
&& cp -n .env.example .env
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
CMD curl -fsS http://127.0.0.1:8080/ >/dev/null || exit 1
CMD ["php", "-S", "0.0.0.0:8080", "-t", "public"]
```
### Docker Compose
```yaml
services:
password-generator:
build:
context: .
dockerfile: Dockerfile
ports:
- "8080:8080"
environment:
APP_ENV: production
APP_DEBUG: "false"
LOG_LEVEL: warning
RATE_LIMIT: "120"
CORS_ORIGIN: "https://yourdomain.com"
CORS_METHODS: "GET,POST,OPTIONS"
CORS_HEADERS: "Content-Type,Authorization"
volumes:
- ./logs:/var/www/html/logs
restart: unless-stopped
```
## Environment Configuration
### Production Environment Variables
```bash
# Application
APP_ENV=production
APP_DEBUG=false
APP_NAME="Password Generator API"
APP_VERSION="2.0.0"
APP_PUBLIC_BASE_URL="https://passwords.example.com"
# Security
API_KEY_ENABLED=false
RATE_LIMIT=120
# CORS (adjust for your domain)
CORS_ORIGIN="https://yourdomain.com"
CORS_METHODS="GET,POST,OPTIONS"
CORS_HEADERS="Content-Type,Authorization"
# Logging
LOG_LEVEL=warning
LOG_PATH=logs/app.log
```
### Secure Production Baseline
Use local development values from `.env.example` and set deployment values explicitly in the production environment. The application does not assume a public-safe default; it expects a secure deployment configuration to be provided at runtime.
Production checklist:
- [ ] `APP_ENV=production`
- [ ] `APP_DEBUG=false`
- [ ] `LOG_LEVEL=warning` or `error`
- [ ] `CORS_ORIGIN` restricted to trusted frontend domains only
- [ ] `RATE_LIMIT` tuned for the production deployment and not left at dev defaults
- [ ] `require_https=true` enforced at the reverse proxy or load balancer
- [ ] TLS termination with HSTS and strict security headers in front of the PHP app
- [ ] Secrets and runtime hostnames stored outside the repository
Remaining production hardening risks:
- In-memory rate limiting is still single-instance only; multi-node deployments should use shared storage.
- CORS must remain an explicit allow-list and should not be widened to wildcard origins.
- Log aggregation and retention should be added for production operations.
- Browser-facing security headers should be applied at the edge layer whenever the app is public.
### Regenerate the OpenAPI File
The public OpenAPI document is generated from the live route registry and the configured public base URL. Regenerate it after route changes or deployment configuration changes:
```bash
composer generate:openapi
# or
php utils/generate-openapi.php
```
This updates `public/pwgenerator-swagger-spec.json` with the current public API contract.
### SSL/TLS Configuration
For production, always use HTTPS:
```nginx
server {
listen 443 ssl http2;
server_name passwords.yourdomain.com;
ssl_certificate /path/to/certificate.crt;
ssl_certificate_key /path/to/private.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;
# ... rest of configuration
}
# Redirect HTTP to HTTPS
server {
listen 80;
server_name passwords.yourdomain.com;
return 301 https://$server_name$request_uri;
}
```
## Monitoring and Maintenance
### Health Checks
```bash
# API health check
curl -f http://localhost:8080/api/v1/health || exit 1
# Test password generation
curl -X POST http://localhost:8080/api/v1/passwords/generate \
-H "Content-Type: application/json" \
-d '{"type":"high-entropy","count":1}' \
| jq '.success'
```
### Log Monitoring
```bash
# Monitor application logs
tail -f logs/app.log
# Check for errors
grep -i error logs/app.log
```
### Performance Optimization
```bash
# Enable OPcache for PHP
echo "opcache.enable=1" >> /etc/php/8.1/apache2/conf.d/99-opcache.ini
echo "opcache.memory_consumption=128" >> /etc/php/8.1/apache2/conf.d/99-opcache.ini
# Restart Apache
systemctl restart apache2
```
## Security Considerations
1. **Rate Limiting**: Adjust `RATE_LIMIT` based on your needs
2. **CORS**: Configure `CORS_ORIGIN` for your domain only
3. **HTTPS**: Always use SSL/TLS in production
4. **Firewall**: Restrict access to necessary ports only
5. **Updates**: Keep PHP and dependencies updated
6. **Logs**: Monitor logs for suspicious activity
## Backup Strategy
### Files to Backup
- Configuration files (`.env`)
- Custom wordlists (if added)
- Application logs
- Web server configuration
### Database
This application is stateless and doesn't require database backups.
## Troubleshooting
### Common Issues
**Error: "Class not found"**
```bash
composer dump-autoload
```
**Error: "Permission denied on logs"**
```bash
chmod 755 logs/
chown www-data:www-data logs/
```
**Error: "CORS blocked"**
- Check `CORS_ORIGIN` in `.env`
- Ensure proper headers in web server config
**High Memory Usage**
- Enable OPcache
- Increase PHP memory limit if needed
### Debug Mode
For troubleshooting, enable debug mode:
```bash
APP_DEBUG=true
LOG_LEVEL=debug
```
Remember to disable debug mode in production!
+39
View File
@@ -0,0 +1,39 @@
FROM php:8.4-cli
WORKDIR /var/www/html
ENV APP_ENV=production \
APP_DEBUG=false \
PHP_OPCACHE_ENABLE=1 \
PHP_OPCACHE_VALIDATE_TIMESTAMPS=0 \
PHP_OPCACHE_MAX_ACCELERATED_FILES=10000 \
PHP_OPCACHE_MEMORY_CONSUMPTION=128 \
PORT=8080
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
bash \
ca-certificates \
curl \
git \
unzip \
pwgen \
xkcdpass \
&& rm -rf /var/lib/apt/lists/*
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
COPY . /var/www/html/
RUN composer install --no-dev --optimize-autoloader --no-interaction --prefer-dist \
&& mkdir -p logs \
&& chmod 755 logs \
&& cp -n .env.example .env \
&& chown -R www-data:www-data /var/www/html
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
CMD curl -fsS http://127.0.0.1:8080/ >/dev/null || exit 1
CMD ["php", "-S", "0.0.0.0:8080", "-t", "public"]
+270
View File
@@ -0,0 +1,270 @@
# Password Generator API and Web App
A modern, secure password generator with a public REST API and multilingual web UI.
The project is built with Slim PHP and focuses on production-ready password generation.
## Features
### Password Generators
- High Entropy: cryptographically secure random passwords with configurable character sets.
- XKCD Passphrase: memorable multi-word passphrases with language-specific wordlists.
- Diceware: dice-style passphrases with security metadata.
- PWGen Enhanced: pronounceable passwords with optional separator logic.
- Passkey: Apple-style pronounceable strings using syllable patterns.
### Product Capabilities
- Public JSON API for password generation.
- Responsive browser UI.
- Localization support with runtime locale endpoint.
- Security controls (rate limiting, CORS, secure random generation).
- Generated OpenAPI contract.
## Supported Languages
- en
- de
- de_CH
- fr
- it
- es
- pt
- ja
- zh
## Requirements
- PHP 8.1+
- Composer
- Optional CLI tools for enhanced generator behavior: pwgen, xkcdpass
## Quick Start
### 1. Install
```bash
git clone <repository-url>
cd password-generator
composer install
```
### 2. Configure
```bash
cp .env.example .env
cp config/config.example.json config/config.json
mkdir -p logs
chmod 755 logs
```
### 3. Run
```bash
composer start
# or
php -S localhost:8080 -t public
```
Open:
- [http://localhost:8080](http://localhost:8080)
- [http://localhost:8080/docs](http://localhost:8080/docs)
## API Usage
### List available generator types
```bash
curl http://localhost:8080/api/v1/passwords
```
### Generate passwords
```bash
curl -X POST http://localhost:8080/api/v1/passwords/generate \
-H "Content-Type: application/json" \
-d '{"type":"diceware","count":2,"options":{"words":6}}'
```
### Get locale payload
```bash
curl http://localhost:8080/api/v1/locales/ja
```
## Generator Option Examples
### High Entropy
```json
{
"type": "high-entropy",
"count": 1,
"options": {
"length": 16,
"include_lowercase": true,
"include_uppercase": true,
"include_numbers": true,
"include_symbols": true,
"safe_symbols_only": false,
"exclude_ambiguous": false
}
}
```
### XKCD Passphrase
```json
{
"type": "xkcdpass",
"count": 1,
"options": {
"words": 4,
"wordlist": "en",
"separator": "-",
"max_word_length": 0,
"space_handling": "allow",
"add_number": true,
"number_position": "end"
}
}
```
### Diceware
```json
{
"type": "diceware",
"count": 1,
"options": {
"words": 6,
"show_dice_rolls": true,
"uppercase_first": false
}
}
```
### PWGen Enhanced
```json
{
"type": "pwgen",
"count": 1,
"options": {
"length": 16,
"separator": "-_",
"include_numbers": true,
"include_capitals": true,
"secure_mode": false,
"no_ambiguous": false
}
}
```
### Passkey
```json
{
"type": "passkey",
"count": 1,
"options": {
"syllables": 6
}
}
```
## Configuration Notes
Primary runtime configuration files:
- config/config.json
- .env
Useful environment overrides:
- LLM_DEFAULT_PROVIDER
- OPENAI_API_KEY
- CLAUDE_API_KEY
- GEMINI_API_KEY
- PERPLEXITY_API_KEY
## OpenAPI Contract
Regenerate the public API specification when routes or response schemas change:
```bash
composer generate:openapi
# or
php utils/generate-openapi.php
```
Generated file:
- public/pwgenerator-swagger-spec.json
## Docker Deployment
### Build image
```bash
docker build -t password-generator:latest .
```
### Run image
```bash
docker run --rm -p 8080:8080 --name password-generator password-generator:latest
```
### Docker Compose (project-local setup)
The repository includes a project-specific Compose file for local development and testing.
```bash
cp .env.example .env
cp config/config.example.json config/config.json
mkdir -p logs
docker compose up --build -d
```
Then open:
- [http://localhost:8080](http://localhost:8080)
- [http://localhost:8080/docs](http://localhost:8080/docs)
Useful commands:
```bash
docker compose logs -f password-generator
docker compose restart password-generator
docker compose down
```
### Docker Hub publish
```bash
docker login
docker tag password-generator:latest YOUR_DOCKERHUB_USERNAME/password-generator:latest
docker push YOUR_DOCKERHUB_USERNAME/password-generator:latest
```
## Testing
```bash
composer test
```
## Project Structure
```text
public/ - web root and generated OpenAPI spec
src/ - API, generators, middleware, and utilities
config/ - route, dependency, and app configuration
assets/ - frontend CSS and JavaScript
templates/ - HTML templates
locales/ - translation files
data/ - source wordlists
tests/ - PHPUnit tests
```
+93
View File
@@ -0,0 +1,93 @@
/* Enhanced styling for detailed documentation */
.option-detail {
background: #f8f9fa;
border-left: 4px solid #007bff;
padding: 15px;
margin: 10px 0;
border-radius: 0 4px 4px 0;
}
.option-detail h4 {
color: #007bff;
margin-bottom: 8px;
}
.option-detail .option-type {
background: #6c757d;
color: white;
padding: 2px 6px;
border-radius: 3px;
font-size: 11px;
font-weight: bold;
margin-right: 8px;
}
.option-detail .option-default {
background: #28a745;
color: white;
padding: 2px 6px;
border-radius: 3px;
font-size: 11px;
margin-right: 8px;
}
.example-request,
.example-response {
background: #2d3748;
color: #e2e8f0;
padding: 15px;
border-radius: 5px;
font-family: 'Courier New', monospace;
font-size: 13px;
overflow-x: auto;
margin: 10px 0;
}
.generator-section {
background: white;
margin: 20px 0;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.generator-header {
background: #778491;
color: white;
padding: 20px;
}
.generator-content {
padding: 30px;
}
.language-selector {
position: fixed;
top: 20px;
right: 20px;
background: white;
border: 1px solid #ddd;
border-radius: 5px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
z-index: 1000;
}
.language-selector select {
border: none;
padding: 10px 15px;
font-size: 14px;
cursor: pointer;
}
.security-note {
background: #fff3cd;
border: 1px solid #ffeaa7;
border-radius: 4px;
padding: 12px;
margin: 15px 0;
}
.security-note::before {
content: "🔒 ";
font-weight: bold;
}
+3096
View File
File diff suppressed because it is too large. Load diff
+1319
View File
File diff suppressed because it is too large. Load diff
+29
View File
@@ -0,0 +1,29 @@
{
"name": "drei/password-generator-api",
"description": "Secure password generator with REST API and web frontend (i18n-enabled)",
"type": "project",
"require": {
"php": "^8.1",
"slim/slim": "^4.12",
"slim/psr7": "^1.6",
"psr/http-message": "^1.0",
"psr/log": "^3.0",
"monolog/monolog": "^3.0",
"vlucas/phpdotenv": "^5.5",
"tuupola/cors-middleware": "^1.4",
"php-di/php-di": "^7.0"
},
"require-dev": {
"phpunit/phpunit": "^10.0"
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"scripts": {
"start": ["Composer\\Config::disableProcessTimeout","php -S localhost:8080 -t public"],
"test": "phpunit",
"generate:openapi": "php utils/generate-openapi.php"
}
}
Generated
+3345
View File
File diff suppressed because it is too large. Load diff
+133
View File
@@ -0,0 +1,133 @@
{
"app": {
"name": "Password Generator",
"version": "4.1.0",
"environment": "production"
},
"locale": {
"default": "en",
"supported": [
"en",
"de",
"de_CH",
"fr",
"it",
"es",
"pt",
"ja",
"zh"
],
"fallback": "en",
"languages": {
"en": "English",
"de": "Deutsch",
"de_CH": "Schwyzerdütsch",
"fr": "Français",
"it": "Italiano",
"es": "Español",
"pt": "Português",
"ja": "日本語",
"zh": "中文"
}
},
"llm": {
"default_provider": "openai",
"timeout": 30,
"max_tokens": 500,
"temperature": 0.7,
"retry_attempts": 3,
"retry_delay": 1000,
"api_keys": {
"openai": "sk-example-openai-key",
"claude": "sk-ant-example-claude-key",
"gemini": "example-gemini-api-key",
"perplexity": "pplx-example-perplexity-key"
},
"providers": {
"openai": {
"name": "OpenAI",
"models": {
"gpt-3.5-turbo": "GPT-3.5 Turbo",
"gpt-4": "GPT-4",
"gpt-4-turbo": "GPT-4 Turbo",
"gpt-4o": "GPT-4 Omni"
},
"default_model": "gpt-3.5-turbo"
},
"claude": {
"name": "Claude",
"models": {
"claude-3-opus-20240229": "Claude 3 Opus",
"claude-3-sonnet-20240229": "Claude 3 Sonnet",
"claude-3-haiku-20240307": "Claude 3 Haiku"
},
"default_model": "claude-3-sonnet-20240229"
},
"gemini": {
"name": "Gemini",
"models": {
"gemini-pro": "Gemini Pro",
"gemini-1.5-pro": "Gemini 1.5 Pro",
"gemini-1.5-flash": "Gemini 1.5 Flash"
},
"default_model": "gemini-pro"
},
"perplexity": {
"name": "Perplexity",
"models": {
"llama-3-sonar-small-32k-online": "Llama 3 Sonar Small (32k)",
"llama-3-sonar-large-32k-online": "Llama 3 Sonar Large (32k)",
"llama-3-8b-instruct": "Llama 3 8B Instruct",
"llama-3-70b-instruct": "Llama 3 70B Instruct"
},
"default_model": "llama-3-sonar-small-32k-online"
}
}
},
"generator": {
"max_passwords_per_request": 100,
"default_count": 1,
"roulette_interval_seconds": 3,
"xkcd": {
"wordlists_path": "data/wordlists",
"wordlists": {
"en": {
"label": "EFF Large Wordlist (English)",
"file": "example_en_wordlist.txt"
},
"de": {
"label": "German Common Words",
"file": "example_de_wordlist.txt"
},
"fr": {
"label": "French Common Words",
"file": "example_fr_wordlist.txt"
},
"it": {
"label": "Italian Common Words",
"file": "example_it_wordlist.txt"
},
"es": {
"label": "Spanish Common Words",
"file": "example_es_wordlist.txt"
}
}
},
"cache_enabled": true,
"cache_ttl": 3600
},
"security": {
"rate_limit_enabled": true,
"rate_limit_max_requests": 120,
"rate_limit_window": 3600,
"require_https": true,
"cors_enabled": true,
"cors_origins": [
"https://yourdomain.com"
]
},
"logging": {
"enabled": true,
"level": "info"
}
}
+131
View File
@@ -0,0 +1,131 @@
{
"app": {
"name": "Password Generator",
"version": "4.1.0",
"environment": "production"
},
"locale": {
"default": "en",
"supported": [
"en",
"de",
"de_CH",
"fr",
"it",
"es",
"pt",
"ja",
"zh"
],
"fallback": "en",
"languages": {
"en": "English",
"de": "Deutsch",
"de_CH": "Schwyzerdütsch",
"fr": "Français",
"it": "Italiano",
"es": "Español",
"pt": "Português",
"ja": "日本語",
"zh": "中文"
}
},
"llm": {
"default_provider": "openai",
"timeout": 30,
"max_tokens": 500,
"temperature": 0.7,
"retry_attempts": 3,
"retry_delay": 1000,
"providers": {
"openai": {
"name": "OpenAI",
"models": {
"gpt-3.5-turbo": "GPT-3.5 Turbo",
"gpt-4": "GPT-4",
"gpt-4-turbo": "GPT-4 Turbo",
"gpt-4o": "GPT-4 Omni"
},
"default_model": "gpt-3.5-turbo"
},
"claude": {
"name": "Claude",
"models": {
"claude-3-opus-20240229": "Claude 3 Opus",
"claude-3-sonnet-20240229": "Claude 3 Sonnet",
"claude-3-haiku-20240307": "Claude 3 Haiku"
},
"default_model": "claude-3-sonnet-20240229"
},
"gemini": {
"name": "Gemini",
"models": {
"gemini-pro": "Gemini Pro",
"gemini-1.5-pro": "Gemini 1.5 Pro",
"gemini-1.5-flash": "Gemini 1.5 Flash"
},
"default_model": "gemini-pro"
},
"perplexity": {
"name": "Perplexity",
"models": {
"llama-3-sonar-small-32k-online": "Llama 3 Sonar Small (32k)",
"llama-3-sonar-large-32k-online": "Llama 3 Sonar Large (32k)",
"llama-3-8b-instruct": "Llama 3 8B Instruct",
"llama-3-70b-instruct": "Llama 3 70B Instruct"
},
"default_model": "llama-3-sonar-small-32k-online"
}
}
},
"generator": {
"max_passwords_per_request": 100,
"default_count": 1,
"roulette_interval_seconds": 3,
"xkcd": {
"wordlists_path": "data/wordlists",
"wordlists": {
"en": {
"label": "EFF English Wordlist",
"file": "eff_large_wordlist_en.txt"
},
"de": {
"label": "German Common Words",
"file": "german_common_words_de.txt"
},
"ch": {
"label": "Slängikon",
"file": "swiss_german_slaengikon.txt"
},
"fr": {
"label": "French Common Words",
"file": "french_common_words_fr.txt"
},
"it": {
"label": "Italian Common Words",
"file": "italian_common_words_it.txt"
},
"es": {
"label": "Spanish Common Words",
"file": "spanish_common_words_es.txt"
}
}
},
"cache_enabled": true,
"cache_ttl": 3600
},
"security": {
"rate_limit_enabled": true,
"rate_limit_max_requests": 120,
"rate_limit_window": 3600,
"require_https": true,
"cors_enabled": true,
"cors_origins": [
"https://yourdomain.com"
]
},
"logging": {
"enabled": true,
"level": "info"
}
}
+52
View File
@@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
use App\Api\PasswordController;
use App\Managers\PasswordGeneratorManager;
use App\Utils\ResponseHelper;
use App\Utils\SecurityConfig;
use DI\ContainerBuilder;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;
use Monolog\Processor\UidProcessor;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
return function (ContainerBuilder $containerBuilder) {
$containerBuilder->addDefinitions([
LoggerInterface::class => function (ContainerInterface $c) {
$settings = [
'name' => $_ENV['APP_NAME'] ?? 'password-generator',
'path' => __DIR__ . '/../' . ($_ENV['LOG_PATH'] ?? 'logs/app.log'),
'level' => SecurityConfig::resolveLogLevel($_ENV['LOG_LEVEL'] ?? 'warning'),
];
$logger = new Logger($settings['name']);
$processor = new UidProcessor();
$logger->pushProcessor($processor);
$handler = new StreamHandler($settings['path'], $settings['level']);
$logger->pushHandler($handler);
return $logger;
},
ResponseHelper::class => function (ContainerInterface $c) {
return new ResponseHelper();
},
PasswordGeneratorManager::class => function (ContainerInterface $c) {
return new PasswordGeneratorManager();
},
PasswordController::class => function (ContainerInterface $c) {
return new PasswordController(
$c->get(PasswordGeneratorManager::class),
$c->get(ResponseHelper::class),
$c->get(LoggerInterface::class)
);
},
]);
};
+35
View File
@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
use App\Middleware\CorsMiddleware;
use App\Middleware\JsonResponseMiddleware;
use App\Middleware\RateLimitMiddleware;
use Slim\App;
use Slim\Middleware\ErrorMiddleware;
return function (App $app) {
// Parse JSON, form data and XML
$app->addBodyParsingMiddleware();
// Add CORS middleware
$app->add(new CorsMiddleware());
// Add JSON response middleware
$app->add(new JsonResponseMiddleware());
// Add rate limiting middleware
$app->add(new RateLimitMiddleware());
// Add routing middleware
$app->addRoutingMiddleware();
// Add error middleware
$displayErrorDetails = filter_var($_ENV['APP_DEBUG'] ?? 'false', FILTER_VALIDATE_BOOLEAN);
$logErrors = true;
$logErrorDetails = $displayErrorDetails;
$errorMiddleware = $app->addErrorMiddleware($displayErrorDetails, $logErrors, $logErrorDetails);
return $app;
};
+179
View File
@@ -0,0 +1,179 @@
<?php
declare(strict_types=1);
use App\Api\PasswordController;
use Slim\App;
use Slim\Interfaces\RouteCollectorProxyInterface as Group;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
return function (App $app) {
// Serve static frontend
$app->get('/', function (Request $request, Response $response, $args) {
$html = file_get_contents(__DIR__ . '/../templates/index.html');
$response->getBody()->write($html);
return $response->withHeader('Content-Type', 'text/html');
});
// API routes
$app->group('/api/v1', function (Group $group) {
// Password generation endpoints
$group->get('/passwords', PasswordController::class . ':getPasswordTypes');
$group->get('/passwords/generators', PasswordController::class . ':getGenerators');
$group->post('/passwords/generate', PasswordController::class . ':generatePasswords');
// NEW: Available wordlists endpoint for dynamic language dropdown
$group->get('/passwords/wordlists', PasswordController::class . ':getAvailableWordlists');
// Health check
$group->get('/health', function (Request $request, Response $response) {
$response->getBody()->write(json_encode([
'status' => 'healthy',
'version' => $_ENV['APP_VERSION'] ?? '1.0.0',
'timestamp' => date('c')
]));
return $response->withHeader('Content-Type', 'application/json');
});
// Updated locales endpoint with fixed pattern validation
$group->get('/locales/{lang:[A-Za-z_]+}', function (Request $request, Response $response, array $args) {
$lang = $args['lang'];
// Updated supported languages list to match frontend and wordlists
$supportedLangs = ['en', 'de', 'de_CH', 'fr', 'it', 'es', 'pt', 'ja', 'zh'];
if (!in_array($lang, $supportedLangs, true)) {
$lang = 'en'; // fallback
}
// Preserve Swiss German locale when available, otherwise fall back to German
if ($lang === 'ch') {
$lang = 'de';
}
if ($lang === 'de_CH' && !file_exists(__DIR__ . '/../locales/de_CH.json')) {
$lang = 'de';
}
$path = __DIR__ . "/../locales/{$lang}.json";
if (!file_exists($path)) {
return $response->withStatus(404);
}
$content = file_get_contents($path);
$response->getBody()->write($content);
return $response->withHeader('Content-Type', 'application/json');
});
});
/*
// API documentation
$app->get('/docs', function (Request $request, Response $response, $args) {
$html = file_get_contents(__DIR__ . '/../templates/docs.html');
$response->getBody()->write($html);
return $response->withHeader('Content-Type', 'text/html');
});
*/
// Serve static assets
$app->get('/assets/{type}/{file}', function (Request $request, Response $response, array $args) {
$type = $args['type'];
$file = $args['file'];
$path = __DIR__ . "/../assets/{$type}/{$file}";
if (!file_exists($path)) {
return $response->withStatus(404);
}
$content = file_get_contents($path);
$contentType = $type === 'css' ? 'text/css' : 'application/javascript';
$response->getBody()->write($content);
return $response->withHeader('Content-Type', $contentType);
});
// Serve locale files directly with improved pattern matching
$app->get('/locales/{file:[A-Za-z_]+\.json}', function (Request $request, Response $response, array $args) {
$file = $args['file'];
$path = __DIR__ . "/../locales/{$file}";
if (!file_exists($path)) {
return $response->withStatus(404);
}
$content = file_get_contents($path);
$response->getBody()->write($content);
return $response->withHeader('Content-Type', 'application/json');
});
// Main documentation route (English - default)
$app->get('/docs', function ($request, $response) {
$docFile = __DIR__ . '/../docs/index.html';
if (file_exists($docFile)) {
$response->getBody()->write(file_get_contents($docFile));
return $response->withHeader('Content-Type', 'text/html; charset=utf-8');
} else {
return $response->withStatus(404)->write('Documentation not found');
}
});
// Language-specific documentation routes
$app->get('/docs-{lang}', function ($request, $response, array $args) {
$lang = $args['lang'];
// Allowed languages
$allowedLanguages = ['de', 'fr', 'es', 'it', 'ja', 'zh'];
if (!in_array($lang, $allowedLanguages)) {
return $response->withStatus(302)->withHeader('Location', '/docs');
}
$docFile = __DIR__ . "/../docs/docs-{$lang}.html";
if (file_exists($docFile)) {
$response->getBody()->write(file_get_contents($docFile));
return $response->withHeader('Content-Type', 'text/html; charset=utf-8');
} else {
// Fallback to English
$fallbackFile = __DIR__ . '/../docs/index.html';
if (file_exists($fallbackFile)) {
$response->getBody()->write(file_get_contents($fallbackFile));
return $response->withHeader('Content-Type', 'text/html; charset=utf-8');
} else {
return $response->withStatus(404)->write('Documentation not found');
}
}
});
// Static assets for documentation
$app->get('/docs/assets/{type}/{file}', function ($request, $response, array $args) {
$type = $args['type'];
$file = $args['file'];
// Security: prevent directory traversal
if (strpos($file, '..') !== false || strpos($type, '..') !== false) {
return $response->withStatus(403)->write('Access denied');
}
$assetFile = __DIR__ . "/../docs/assets/{$type}/{$file}";
if (file_exists($assetFile)) {
$mimeTypes = [
'css' => 'text/css',
'js' => 'application/javascript',
'png' => 'image/png',
'jpg' => 'image/jpeg',
'svg' => 'image/svg+xml'
];
$extension = pathinfo($file, PATHINFO_EXTENSION);
$mimeType = $mimeTypes[$extension] ?? 'text/plain';
$response->getBody()->write(file_get_contents($assetFile));
return $response->withHeader('Content-Type', $mimeType);
} else {
return $response->withStatus(404)->write('Asset not found');
}
});
};
+74
View File
@@ -0,0 +1,74 @@
#!/bin/bash
# Password Generator Package Creator
# Creates a distributable archive of the complete i18n-enabled password generator
echo "📦 Creating Password Generator Distribution Package"
echo "================================================="
# Get version from environment or use default
VERSION=${1:-"2.0.0-i18n"}
PACKAGE_NAME="password-generator-${VERSION}"
# Create temporary directory structure
echo "📁 Preparing package structure..."
rm -rf dist/
mkdir -p dist/${PACKAGE_NAME}
# Copy all necessary files
cp -r config/ dist/${PACKAGE_NAME}/
cp -r src/ dist/${PACKAGE_NAME}/
cp -r templates/ dist/${PACKAGE_NAME}/
cp -r assets/ dist/${PACKAGE_NAME}/
cp -r locales/ dist/${PACKAGE_NAME}/
cp -r public/ dist/${PACKAGE_NAME}/
# Copy configuration and documentation
cp composer.json dist/${PACKAGE_NAME}/
cp .env dist/${PACKAGE_NAME}/.env.example
cp README.md dist/${PACKAGE_NAME}/
cp DEPLOYMENT.md dist/${PACKAGE_NAME}/
cp install.sh dist/${PACKAGE_NAME}/
# Create logs directory with placeholder
mkdir -p dist/${PACKAGE_NAME}/logs
cp logs/README.md dist/${PACKAGE_NAME}/logs/
# Create archive
cd dist/
echo "🗜️ Creating ZIP archive..."
zip -r ${PACKAGE_NAME}.zip ${PACKAGE_NAME}/ -x "*.DS_Store" "*.git*" "vendor/*" "node_modules/*"
# Create tar.gz archive
echo "🗜️ Creating TAR.GZ archive..."
tar -czf ${PACKAGE_NAME}.tar.gz ${PACKAGE_NAME}/
# Calculate checksums
echo "🔐 Calculating checksums..."
sha256sum ${PACKAGE_NAME}.zip > ${PACKAGE_NAME}.zip.sha256
sha256sum ${PACKAGE_NAME}.tar.gz > ${PACKAGE_NAME}.tar.gz.sha256
cd ..
# Display results
echo ""
echo "✅ Package creation completed!"
echo ""
echo "📁 Generated files:"
ls -la dist/
echo ""
echo "📊 Package statistics:"
echo "Files: $(find dist/${PACKAGE_NAME}/ -type f | wc -l)"
echo "Directories: $(find dist/${PACKAGE_NAME}/ -type d | wc -l)"
echo "ZIP size: $(du -h dist/${PACKAGE_NAME}.zip | cut -f1)"
echo "TAR.GZ size: $(du -h dist/${PACKAGE_NAME}.tar.gz | cut -f1)"
echo ""
echo "🚀 Ready for distribution!"
echo "Share the files in dist/ directory"
echo ""
echo "📋 Installation instructions for end users:"
echo "1. Extract the archive"
echo "2. cd ${PACKAGE_NAME}/"
echo "3. chmod +x install.sh && ./install.sh"
echo "4. composer start"
echo "5. Visit: http://localhost:8080"
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+168
View File
@@ -0,0 +1,168 @@
# French Common Words - Wordlist
# Mots français courants pour phrases de passe sécurisées
avec
être
avoir
faire
tout
aller
voir
savoir
pouvoir
falloir
vouloir
venir
dire
prendre
donner
mettre
partir
parler
finir
arriver
rester
entendre
demander
monter
passer
regarder
aimer
porter
appeler
ouvrir
temps
personne
année
façon
jour
homme
chose
enfant
pays
problème
monde
vie
main
famille
groupe
moment
question
droit
service
parti
nombre
maison
côté
travail
place
heure
état
pouvoir
part
moins
ville
grand
petit
premier
dernier
eau
grande
livre
nouveau
porter
raison
fois
tête
avant
après
france
français
politique
français
national
centre
euro
gouvernement
ministre
présent
conseil
paris
point
cours
formation
information
système
équipe
projet
entreprise
activité
recherche
résultat
programme
société
sécurité
communauté
développement
économie
marché
relation
culture
qualité
niveau
histoire
région
territoire
administration
communication
direction
mission
objectif
organisation
public
international
service
social
national
général
particulier
personnel
professionnel
politique
économique
public
social
européen
français
grand
nouveau
premier
dernier
important
possible
différent
général
local
national
international
européen
public
social
économique
politique
technique
militaire
culturel
naturel
humain
civil
commercial
industriel
financier
médical
scientifique
artistique
sportif
éducatif
alimentaire
environnemental
+275
View File
@@ -0,0 +1,275 @@
# German Common Words - Wordlist
# Häufige deutsche Wörter für sichere Passphrasen
aber
alle
alles
also
andere
Anfang
arbeiten
auch
auf
aus
bei
bekommen
bereits
bis
bleiben
dabei
damit
dann
der
deutsche
doch
drei
durch
eine
einem
einen
einer
eines
einige
einzelne
entsprechend
ersten
etwa
etwas
Euro
finden
Frage
Fragen
gegen
gehen
gibt
grosse
grösste
gruppe
haben
Haus
heute
hier
hinter
jedoch
jetzt
kann
kommen
können
kurz
Land
lassen
laut
Leben
machen
mann
mehr
mein
Menschen
mit
nach
nachdem
name
nicht
noch
oder
ohne
platz
recht
sagen
schon
sehr
sein
sich
sie
sind
über
unter
verschiedene
viel
vom
vor
wann
was
weil
wasser
weg
welt
werden
wie
wieder
will
wir
wissen
wo
woche
wohl
während
zwischen
abenteuer
ablehnung
absicht
abteilung
achtung
aktivität
anfrage
angebot
ankunft
antwort
arbeit
aufgabe
aufmerksamkeit
auswahl
bedeutung
bedingung
behandlung
bereich
besuch
bewegung
bewertung
beziehung
bildung
chance
darstellung
denken
deutschland
entwicklung
erfahrung
erfolg
ergebnis
erhöhung
erinnerung
erklärung
erwartung
erzählung
familie
farbe
fehler
feier
fieber
fläche
form
frage
frau
freiheit
freude
freund
function
gefahr
gefühl
gegenwart
geld
gemeinde
geschichte
gesellschaft
gespräch
gewalt
gewicht
glaube
grund
gruppe
handel
haupt
herr
hoffnung
information
inhalt
interesse
jahr
jahrhundert
kind
kirche
klasse
kraft
krieg
kultur
kunst
leben
lehrer
leistung
Liebe
Licht
Luft
macht
mann
markt
masse
material
medizin
meinung
mensch
möglichkeit
moment
monat
musik
nacht
nachricht
natur
nummer
ort
person
plan
politik
position
preis
problem
prozent
prozess
punkt
raum
recht
region
regierung
reise
rolle
sache
schule
seite
situation
sommer
sprache
staat
stadt
stelle
stimme
stunde
system
tag
teil
text
thema
tisch
titel
tod
tradition
uhr
unternehmen
unterschied
ursache
veränderung
verhalten
verkehr
vermögen
versuch
vertrag
vertrauen
verwaltung
verwendung
volk
wahrheit
ware
wasser
weg
welt
wert
wirtschaft
wissenschaft
woche
wohnung
Wort
Zahl
Zeit
Zeitung
Ziel
Zukunft
Zusammenhang
Zustand
+166
View File
@@ -0,0 +1,166 @@
# Italian Common Words - Wordlist
# Parole italiane comuni per passphrase sicure
essere
avere
fare
dire
andare
potere
dovere
volere
sapere
dare
stare
vedere
uscire
partire
parlare
leggere
scrivere
sentire
mettere
portare
anno
giorno
tempo
volta
persona
modo
casa
parte
cosa
paese
problema
mondo
vita
mano
famiglia
gruppo
momento
domanda
diritto
servizio
lavoro
posto
ora
stato
governo
italia
italiano
nazionale
centro
euro
politica
ministro
presente
consiglio
roma
punto
corso
formazione
informazione
sistema
squadra
progetto
azienda
attività
ricerca
risultato
programma
società
sicurezza
comunità
sviluppo
economia
mercato
relazione
cultura
qualità
livello
storia
regione
territorio
amministrazione
comunicazione
direzione
missione
obiettivo
organizzazione
pubblico
internazionale
sociale
generale
particolare
personale
professionale
economico
europeo
grande
nuovo
primo
ultimo
importante
possibile
diverso
locale
militare
culturale
naturale
umano
civile
commerciale
industriale
finanziario
medico
scientifico
artistico
sportivo
educativo
alimentare
ambientale
bene
male
molto
poco
tutto
niente
sempre
mai
oggi
ieri
domani
ora
prima
dopo
qui
dove
come
perché
quando
quale
quanto
chi
cosa
sopra
sotto
dentro
fuori
davanti
dietro
prima
dopo
insieme
solo
anche
ancora
già
più
meno
bene
meglio
peggio
tanto
poco
troppo
abbastanza
+216
View File
@@ -0,0 +1,216 @@
# Spanish Common Words - Wordlist
# Palabras españolas comunes para frases de contraseña seguras
ser
haber
estar
tener
hacer
poder
decir
ir
ver
saber
querer
llegar
pasar
deber
poner
parecer
quedar
creer
hablar
llevar
tiempo
persona
año
vez
día
hombre
cosa
país
problema
mundo
vida
mano
familia
grupo
momento
pregunta
derecho
servicio
trabajo
lugar
hora
estado
gobierno
españa
español
nacional
centro
euro
política
ministro
presente
consejo
madrid
punto
curso
formación
información
sistema
equipo
proyecto
empresa
actividad
investigación
resultado
programa
sociedad
seguridad
comunidad
desarrollo
economía
mercado
relación
cultura
calidad
nivel
historia
región
territorio
administración
comunicación
dirección
misión
objetivo
organización
público
internacional
social
general
particular
personal
profesional
económico
europeo
grande
nuevo
primero
último
importante
posible
diferente
local
militar
cultural
natural
humano
civil
comercial
industrial
financiero
médico
científico
artístico
deportivo
educativo
alimentario
ambiental
bueno
malo
mucho
poco
todo
nada
siempre
nunca
hoy
ayer
mañana
ahora
antes
después
aquí
allí
donde
como
porque
cuando
cual
cuanto
quien
que
sobre
bajo
dentro
fuera
delante
detrás
junto
solo
también
todavía
más
menos
bien
mejor
peor
tanto
demasiado
bastante
agua
aire
tierra
fuego
sol
luna
estrella
mar
río
montaña
bosque
flor
árbol
casa
ciudad
pueblo
calle
camino
puerta
ventana
mesa
silla
cama
libro
papel
palabra
número
letra
color
blanco
negro
rojo
azul
verde
amarillo
grande
pequeño
alto
bajo
largo
corto
ancho
estrecho
gordo
delgado
joven
viejo
nuevo
antiguo
bueno
malo
bonito
feo
fácil
difícil
File diff suppressed because it is too large. Load diff
+30
View File
@@ -0,0 +1,30 @@
services:
password-generator:
build:
context: .
dockerfile: Dockerfile
image: pw-generator:local
container_name: pw-generator
restart: unless-stopped
ports:
- "8080:8080"
env_file:
- .env
environment:
APP_ENV: production
APP_DEBUG: "false"
LOG_LEVEL: warning
PORT: "8080"
RATE_LIMIT: "120"
CORS_ORIGIN: "http://localhost:8080"
CORS_METHODS: "GET,POST,OPTIONS"
CORS_HEADERS: "Content-Type,Authorization"
volumes:
- ./logs:/var/www/html/logs
- ./config/config.json:/var/www/html/config/config.json:ro
healthcheck:
test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:8080/ >/dev/null || exit 1"]
interval: 30s
timeout: 5s
retries: 3
start_period: 20s
+41
View File
@@ -0,0 +1,41 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Passwort-Generator API - Vollständige Dokumentation</title>
<!-- Same styling -->
</head>
<body>
<header class="header">
<div class="container">
<h1>🔐 Passwort-Generator API - Vollständiger Leitfaden</h1>
<nav class="nav-links">
<a href="/">← Zurück zum Generator</a>
</nav>
</div>
</header>
<!-- High Entropy Generator in German -->
<div class="generator-section">
<div class="generator-header">
<h3>🔐 Hochentropie-Generator</h3>
<p>Kryptographisch sichere Zufallspasswörter mit vollständiger Zeichensatzkontrolle.</p>
</div>
<div class="generator-content">
<h4>Zweck & Anwendungsfall</h4>
<p>Generiert traditionelle Zufallspasswörter mit vollständiger Kontrolle über Zeichentypen. Am besten für Systeme, die spezifische Zeichensatzkonformität erfordern oder wenn Sie maximale Zufälligkeit benötigen.</p>
<h4>Verfügbare Optionen</h4>
<div class="option-detail">
<h4><span class="option-type">Zahl</span><span class="option-default">16</span>length (Länge)</h4>
<p><strong>Bereich:</strong> 8-128 Zeichen</p>
<p><strong>Beschreibung:</strong> Gesamte Passwortlänge. Längere Passwörter bieten exponentiell mehr Sicherheit.</p>
<p><strong>Sicherheitsauswirkung:</strong> Jedes zusätzliche Zeichen verdoppelt ungefähr die Zeit, die zum Knacken des Passworts erforderlich ist.</p>
</div>
<!-- Continue with other options in German -->
</div>
</div>
</body>
</html>
View File
Whitespace-only changes.
View File
Whitespace-only changes.
View File
Whitespace-only changes.
View File
Whitespace-only changes.
+974
View File
@@ -0,0 +1,974 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>API Documentation - Password Generator</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
line-height: 1.6;
color: #333;
background: #f5f5f5;
}
.header {
background: white;
padding: 20px 0;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
margin-bottom: 30px;
}
.header .container {
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
display: flex;
justify-content: space-between;
align-items: center;
}
.header h1 {
color: #2c3e50;
font-size: 2rem;
}
.header .nav-links {
display: flex;
gap: 20px;
align-items: center;
}
.header .nav-links a {
color: #3498db;
text-decoration: none;
font-weight: 500;
padding: 8px 16px;
border-radius: 5px;
transition: background-color 0.2s ease;
}
.header .nav-links a:hover {
background: #ecf0f1;
}
.main-container {
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
}
.intro-section {
background: white;
padding: 30px;
margin-bottom: 30px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.intro-section h2 {
color: #2c3e50;
margin-bottom: 15px;
}
.intro-section p {
color: #7f8c8d;
margin-bottom: 20px;
}
.base-url {
background: #3498db;
color: white;
padding: 15px;
border-radius: 5px;
text-align: center;
margin: 20px 0;
font-family: monospace;
font-weight: bold;
}
.downloads-section {
background: white;
padding: 30px;
margin-bottom: 30px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.downloads-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
margin-top: 20px;
}
.download-card {
background: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 8px;
padding: 20px;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.download-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
}
.download-card h4 {
color: #2c3e50;
margin-bottom: 10px;
display: flex;
align-items: center;
gap: 10px;
}
.download-card .icon {
font-size: 1.5rem;
}
.download-card p {
color: #7f8c8d;
font-size: 14px;
margin-bottom: 15px;
}
.download-btn {
display: inline-block;
background: #3498db;
color: white;
text-decoration: none;
padding: 10px 20px;
border-radius: 5px;
font-weight: 500;
transition: background-color 0.2s ease;
}
.download-btn:hover {
background: #2980b9;
}
.swagger-container {
background: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
overflow: hidden;
margin-bottom: 30px;
}
.swagger-header {
background: #34495e;
color: white;
padding: 20px 30px;
}
.swagger-header h3 {
margin: 0;
font-size: 1.3rem;
}
.swagger-content {
padding: 0;
}
#swagger-ui {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
}
.method-tag {
display: inline-block;
padding: 4px 8px;
border-radius: 3px;
font-size: 12px;
font-weight: bold;
text-transform: uppercase;
margin-right: 10px;
}
.method-get { background: #27ae60; color: white; }
.method-post { background: #e74c3c; color: white; }
.method-put { background: #f39c12; color: white; }
.method-delete { background: #e67e22; color: white; }
.features-section {
background: white;
padding: 30px;
margin-bottom: 30px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.features-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin-top: 20px;
}
.feature-item {
padding: 20px;
border-left: 4px solid #3498db;
background: #f8f9fa;
border-radius: 0 5px 5px 0;
}
.feature-item h4 {
color: #2c3e50;
margin-bottom: 8px;
}
.feature-item p {
color: #7f8c8d;
font-size: 14px;
}
.code-example {
background: #2c3e50;
color: #ecf0f1;
padding: 20px;
border-radius: 5px;
font-family: 'Courier New', monospace;
font-size: 14px;
overflow-x: auto;
margin: 15px 0;
}
.copy-btn {
background: #34495e;
color: white;
border: none;
padding: 5px 10px;
border-radius: 3px;
cursor: pointer;
float: right;
font-size: 12px;
margin-bottom: 10px;
}
.copy-btn:hover {
background: #2c3e50;
}
.footer {
margin-top: 60px;
padding: 40px 0;
background: white;
border-top: 1px solid #ecf0f1;
text-align: center;
color: #7f8c8d;
}
.footer a {
color: #3498db;
text-decoration: none;
}
.footer a:hover {
text-decoration: underline;
}
.notification {
position: fixed;
top: 20px;
right: 20px;
padding: 15px 20px;
background: #27ae60;
color: white;
border-radius: 5px;
font-weight: 500;
z-index: 1000;
transform: translateX(100%);
transition: transform 0.3s ease;
}
.notification.show {
transform: translateX(0);
}
/* Swagger UI Customizations */
.swagger-ui .info .title {
color: #2c3e50;
}
.swagger-ui .scheme-container {
background: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 5px;
padding: 10px;
margin: 20px 0;
}
.swagger-ui .info .description {
color: #7f8c8d;
}
.swagger-ui .opblock.opblock-get .opblock-summary-method {
background: #27ae60;
}
.swagger-ui .opblock.opblock-post .opblock-summary-method {
background: #e74c3c;
}
.swagger-ui .opblock-tag {
color: #2c3e50;
}
@media (max-width: 768px) {
.header .container {
flex-direction: column;
gap: 15px;
}
.downloads-grid {
grid-template-columns: 1fr;
}
.features-grid {
grid-template-columns: 1fr;
}
}
</style>
<link rel="stylesheet" type="text/css" href="https://unpkg.com/swagger-ui-dist@5.9.0/swagger-ui.css" />
</head>
<body>
<header class="header">
<div class="container">
<h1>🔐 Password Generator API</h1>
<nav class="nav-links">
<a href="/">← Back to Generator</a>
</nav>
</div>
</header>
<main class="main-container">
<div class="intro-section">
<h2>API Documentation</h2>
<p>Complete OpenAPI 3.0 specification for the Password Generator API. Generate secure passwords with multiple algorithms, manage wordlists dynamically, and support international languages.</p>
<div class="base-url">
Base URL: <code>http://localhost:8080/api/v1</code>
</div>
<div class="features-grid">
<div class="feature-item">
<h4>🔐 Multiple Algorithms</h4>
<p>High-entropy, XKCD, Diceware, PWGen, and Passkey generation methods</p>
</div>
<div class="feature-item">
<h4>🌍 Internationalization</h4>
<p>Dynamic wordlist discovery and multi-language UI support</p>
</div>
<div class="feature-item">
<h4>📊 Entropy Analysis</h4>
<p>Real-time password strength calculation and security metrics</p>
</div>
<div class="feature-item">
<h4>🔧 Configurable</h4>
<p>Extensive customization options for all password types</p>
</div>
</div>
</div>
<div class="downloads-section">
<h2>Download Configuration Files</h2>
<p>Get the complete API specification in various formats for integration with your tools and workflows.</p>
<div class="downloads-grid">
<div class="download-card">
<h4><span class="icon">📄</span> OpenAPI JSON</h4>
<p>Complete OpenAPI 3.0 specification in JSON format. Perfect for code generation, API testing, and integration with development tools.</p>
<a href="#" class="download-btn" id="download-json">Download JSON</a>
</div>
<div class="download-card">
<h4><span class="icon">📋</span> OpenAPI YAML</h4>
<p>Human-readable YAML format specification. Ideal for documentation, version control, and collaborative API development.</p>
<a href="#" class="download-btn" id="download-yaml">Download YAML</a>
</div>
<div class="download-card">
<h4><span class="icon">🚀</span> Postman Collection</h4>
<p>Ready-to-use Postman collection with all endpoints, examples, and environment variables for immediate API testing.</p>
<a href="#" class="download-btn" id="download-postman">Download Collection</a>
</div>
<div class="download-card">
<h4><span class="icon">⚡</span> cURL Examples</h4>
<p>Complete set of cURL commands with examples for all endpoints. Perfect for command-line testing and scripting.</p>
<a href="#" class="download-btn" id="download-curl">Download Examples</a>
</div>
</div>
</div>
<div class="swagger-container">
<div class="swagger-header">
<h3>Interactive API Explorer</h3>
</div>
<div class="swagger-content">
<div id="swagger-ui"></div>
</div>
</div>
<div class="features-section">
<h2>Quick Start</h2>
<p>Get started with the Password Generator API in minutes. Here are some common examples:</p>
<h3>Generate XKCD Passphrase</h3>
<button class="copy-btn" onclick="copyToClipboard('curl-xkcd')">Copy</button>
<div class="code-example" id="curl-xkcd">curl -X POST "http://localhost:8080/api/v1/passwords/generate" \
-H "Content-Type: application/json" \
-d '{
"type": "xkcdpass",
"count": 3,
"options": {
"words": 4,
"wordlist": "en",
"separator": "-",
"add_number": true,
"number_position": "random"
}
}'</div>
<h3>Check Available Wordlists</h3>
<button class="copy-btn" onclick="copyToClipboard('curl-wordlists')">Copy</button>
<div class="code-example" id="curl-wordlists">curl -X GET "http://localhost:8080/api/v1/passwords/wordlists"</div>
<h3>Generate High-Entropy Password</h3>
<button class="copy-btn" onclick="copyToClipboard('curl-entropy')">Copy</button>
<div class="code-example" id="curl-entropy">curl -X POST "http://localhost:8080/api/v1/passwords/generate" \
-H "Content-Type: application/json" \
-d '{
"type": "high-entropy",
"count": 1,
"options": {
"length": 20,
"include_symbols": true
}
}'</div>
</div>
</main>
<footer class="footer">
<p>
Password Generator API v2.0.0 •
<a href="/">Main Application</a> •
<a href="#swagger-ui">Interactive Docs</a>
</p>
</footer>
<script src="https://unpkg.com/swagger-ui-dist@5.9.0/swagger-ui-bundle.js"></script>
<script src="https://unpkg.com/swagger-ui-dist@5.9.0/swagger-ui-standalone-preset.js"></script>
<script>
// OpenAPI Specification (embedded)
const spec = {
"openapi": "3.0.3",
"info": {
"title": "Password Generator API",
"description": "Secure password generation with multiple algorithms and customizable options. Generate high-entropy passwords, XKCD-style passphrases, Diceware passwords, and more with comprehensive internationalization support.",
"version": "2.0.0",
"contact": {
"name": "Password Generator API",
"url": "http://localhost:8080"
},
"license": {
"name": "MIT",
"url": "https://opensource.org/licenses/MIT"
}
},
"servers": [
{
"url": "http://localhost:8080/api/v1",
"description": "Development server"
}
],
"paths": {
"/passwords": {
"get": {
"summary": "Get available password types",
"description": "Returns all available password generation types with their descriptions and configuration options.",
"operationId": "getPasswordTypes",
"tags": ["Password Generation"],
"responses": {
"200": {
"description": "List of available password generation types",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PasswordTypesResponse"
}
}
}
}
}
}
},
"/passwords/generate": {
"post": {
"summary": "Generate passwords",
"description": "Generate one or more passwords using the specified type and custom options.",
"operationId": "generatePasswords",
"tags": ["Password Generation"],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GeneratePasswordRequest"
}
}
}
},
"responses": {
"200": {
"description": "Successfully generated passwords",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GeneratePasswordResponse"
}
}
}
},
"400": {
"description": "Invalid request parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/passwords/wordlists": {
"get": {
"summary": "Get available wordlists",
"description": "Returns all available wordlists for XKCD password generation with availability status. Used by frontend to dynamically populate language options.",
"operationId": "getAvailableWordlists",
"tags": ["Wordlists"],
"responses": {
"200": {
"description": "List of available wordlists",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/WordlistsResponse"
}
}
}
}
}
}
},
"/locales/{lang}": {
"get": {
"summary": "Get localization strings",
"description": "Returns translation strings for the specified language code.",
"operationId": "getLocalization",
"tags": ["Internationalization"],
"parameters": [
{
"name": "lang",
"in": "path",
"required": true,
"description": "Language code (2-3 letters)",
"schema": {
"type": "string",
"pattern": "^[a-z]{2,3}(_[A-Z]{2})?$",
"enum": ["en", "de", "de_CH", "fr", "it", "es", "pt", "ja", "zh"]
}
}
],
"responses": {
"200": {
"description": "Localization strings for the specified language",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/LocalizationResponse"
}
}
}
}
}
}
},
"/health": {
"get": {
"summary": "Health check",
"description": "Returns the current health status of the API service.",
"operationId": "healthCheck",
"tags": ["System"],
"responses": {
"200": {
"description": "API is healthy",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HealthResponse"
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"PasswordTypesResponse": {
"type": "object",
"properties": {
"success": { "type": "boolean" },
"data": {
"type": "object",
"properties": {
"types": {
"type": "object",
"additionalProperties": {
"$ref": "#/components/schemas/PasswordType"
}
}
}
},
"message": { "type": "string" },
"timestamp": { "type": "string", "format": "date-time" }
}
},
"PasswordType": {
"type": "object",
"properties": {
"name": { "type": "string" },
"description": { "type": "string" },
"options": {
"type": "object",
"additionalProperties": true
}
}
},
"GeneratePasswordRequest": {
"type": "object",
"required": ["type"],
"properties": {
"type": {
"type": "string",
"enum": ["high-entropy", "xkcdpass", "diceware", "pwgen", "passkey"]
},
"count": {
"type": "integer",
"minimum": 1,
"maximum": 50,
"default": 1
},
"options": {
"type": "object",
"additionalProperties": true
}
}
},
"GeneratePasswordResponse": {
"type": "object",
"properties": {
"success": { "type": "boolean" },
"data": {
"type": "object",
"properties": {
"passwords": {
"type": "array",
"items": { "type": "string" }
},
"entropy_bits": { "type": "number" },
"generator_info": { "type": "string" },
"options_used": {
"type": "object",
"additionalProperties": true
}
}
},
"message": { "type": "string" },
"timestamp": { "type": "string", "format": "date-time" }
}
},
"WordlistsResponse": {
"type": "object",
"properties": {
"success": { "type": "boolean" },
"data": {
"type": "object",
"properties": {
"wordlists": {
"type": "object",
"additionalProperties": {
"$ref": "#/components/schemas/Wordlist"
}
},
"count": { "type": "integer" }
}
},
"message": { "type": "string" },
"timestamp": { "type": "string", "format": "date-time" }
}
},
"Wordlist": {
"type": "object",
"properties": {
"name": { "type": "string" },
"description": { "type": "string" },
"file": { "type": "string" },
"available": { "type": "boolean" },
"fallback": { "type": "boolean" }
}
},
"LocalizationResponse": {
"type": "object",
"additionalProperties": { "type": "string" }
},
"HealthResponse": {
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": ["healthy", "degraded", "unhealthy"]
},
"version": { "type": "string" },
"timestamp": { "type": "string", "format": "date-time" }
}
},
"ErrorResponse": {
"type": "object",
"properties": {
"success": { "type": "boolean", "example": false },
"error": {
"type": "object",
"properties": {
"message": { "type": "string" },
"code": { "type": "string" },
"status": { "type": "integer" }
}
},
"timestamp": { "type": "string", "format": "date-time" }
}
}
}
},
"tags": [
{ "name": "Password Generation", "description": "Core password generation endpoints" },
{ "name": "Wordlists", "description": "Wordlist management for XKCD passwords" },
{ "name": "Internationalization", "description": "Localization and language support" },
{ "name": "System", "description": "System health and monitoring" }
]
};
// Initialize Swagger UI
window.onload = function() {
SwaggerUIBundle({
url: '',
spec: spec,
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout",
tryItOutEnabled: true,
requestInterceptor: (request) => {
// Add any custom headers or modifications
return request;
},
responseInterceptor: (response) => {
// Handle responses
return response;
}
});
};
// Download functions
function downloadFile(content, filename, contentType) {
const blob = new Blob([content], { type: contentType });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
showNotification(`Downloaded ${filename}`);
}
function showNotification(message) {
const notification = document.createElement('div');
notification.className = 'notification';
notification.textContent = message;
document.body.appendChild(notification);
setTimeout(() => notification.classList.add('show'), 10);
setTimeout(() => {
notification.classList.remove('show');
setTimeout(() => notification.remove(), 300);
}, 3000);
}
// Download event handlers
document.getElementById('download-json').addEventListener('click', function(e) {
e.preventDefault();
downloadFile(JSON.stringify(spec, null, 2), 'password-generator-api.json', 'application/json');
});
document.getElementById('download-yaml').addEventListener('click', function(e) {
e.preventDefault();
// Convert JSON to YAML (simplified)
const yaml = jsonToYaml(spec);
downloadFile(yaml, 'password-generator-api.yaml', 'text/yaml');
});
document.getElementById('download-postman').addEventListener('click', function(e) {
e.preventDefault();
const postmanCollection = generatePostmanCollection(spec);
downloadFile(JSON.stringify(postmanCollection, null, 2), 'password-generator-api.postman_collection.json', 'application/json');
});
document.getElementById('download-curl').addEventListener('click', function(e) {
e.preventDefault();
const curlExamples = generateCurlExamples(spec);
downloadFile(curlExamples, 'password-generator-api-examples.sh', 'text/plain');
});
// Copy to clipboard function
function copyToClipboard(elementId) {
const element = document.getElementById(elementId);
const text = element.textContent;
navigator.clipboard.writeText(text).then(() => {
showNotification('Copied to clipboard!');
});
}
// Utility functions for file generation
function jsonToYaml(obj, indent = 0) {
let yaml = '';
const spaces = ' '.repeat(indent);
for (const [key, value] of Object.entries(obj)) {
if (value === null) {
yaml += `${spaces}${key}: null\n`;
} else if (typeof value === 'object' && !Array.isArray(value)) {
yaml += `${spaces}${key}:\n`;
yaml += jsonToYaml(value, indent + 1);
} else if (Array.isArray(value)) {
yaml += `${spaces}${key}:\n`;
value.forEach(item => {
if (typeof item === 'object') {
yaml += `${spaces} -\n`;
yaml += jsonToYaml(item, indent + 2);
} else {
yaml += `${spaces} - ${item}\n`;
}
});
} else {
yaml += `${spaces}${key}: ${typeof value === 'string' ? `"${value}"` : value}\n`;
}
}
return yaml;
}
function generatePostmanCollection(spec) {
return {
info: {
name: spec.info.title,
description: spec.info.description,
version: spec.info.version,
schema: "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
variable: [
{
key: "baseUrl",
value: spec.servers[0].url,
type: "string"
}
],
item: Object.entries(spec.paths).map(([path, methods]) => {
return Object.entries(methods).map(([method, operation]) => ({
name: operation.summary,
request: {
method: method.toUpperCase(),
header: method === 'post' ? [
{
key: "Content-Type",
value: "application/json"
}
] : [],
url: {
raw: `{{baseUrl}}${path}`,
host: ["{{baseUrl}}"],
path: path.split('/').filter(p => p)
},
body: method === 'post' ? {
mode: "raw",
raw: JSON.stringify({
type: "xkcdpass",
count: 1,
options: {
words: 4,
language: "en"
}
}, null, 2)
} : undefined
}
}));
}).flat()
};
}
function generateCurlExamples(spec) {
const baseUrl = spec.servers[0].url;
let examples = `#!/bin/bash
# Password Generator API - cURL Examples
# Base URL: ${baseUrl}
echo "Password Generator API Examples"
echo "=============================="
`;
Object.entries(spec.paths).forEach(([path, methods]) => {
Object.entries(methods).forEach(([method, operation]) => {
examples += `# ${operation.summary}\n`;
examples += `echo "\\n${operation.summary}:"\n`;
if (method === 'get') {
examples += `curl -X GET "${baseUrl}${path}"\n\n`;
} else if (method === 'post') {
examples += `curl -X POST "${baseUrl}${path}" \\\n`;
examples += ` -H "Content-Type: application/json" \\\n`;
examples += ` -d '{\n`;
examples += ` "type": "xkcdpass",\n`;
examples += ` "count": 1,\n`;
examples += ` "options": {\n`;
examples += ` "words": 4,\n`;
examples += ` "wordlist": "en",\n`;
examples += ` "add_number": true\n`;
examples += ` }\n`;
examples += ` }'\n\n`;
}
});
});
return examples;
}
</script>
</body>
</html>
+268
View File
@@ -0,0 +1,268 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Password Generator API - Complete Documentation</title>
<link rel="stylesheet" href="../assets/css/style.css" />
<link rel="stylesheet" href="../assets/css/style-docs.css" />
</head>
<body>
<div class="language-selector">
<select id="language-select" class="language-select form-control form-control--language" onchange="switchLanguage(this.value)">
<option value="en">🇺🇸 English</option>
<option value="de">🇩🇪 Deutsch</option>
<option value="de_CH">🇨🇭 Schwyzerdütsch</option>
<option value="fr">🇫🇷 Français</option>
<option value="es">🇪🇸 Español</option>
<option value="pt">🇵🇹 Portugues</option>
<option value="it">🇮🇹 Italiano</option>
<option value="ja">🇯🇵 日本語</option>
<option value="zh">🇨🇳 中文</option>
</select>
</div>
<header class="header">
<div class="container">
<h1>🔐 Password Generator API - Complete Guide</h1>
<nav class="nav-links">
<a href="/">← Back to Generator</a>
</nav>
</div>
</header>
<main class="main-container">
<!-- Base URL and Quick Start -->
<div class="intro-section">
<h2>API Overview</h2>
<div class="base-url">
Base URL: <code>http://localhost:8080/api/v1</code>
</div>
<div class="security-note">
All password generation uses cryptographically secure random number generation for maximum security.
</div>
</div>
<!-- High Entropy Generator -->
<div class="generator-section">
<div class="generator-header">
<h3>🔐 High Entropy Generator</h3>
<p>Cryptographically secure random passwords with full character set control.</p>
</div>
<div class="generator-content">
<h4>Purpose & Use Case</h4>
<p>Generate traditional random passwords with complete control over character types. Best for systems requiring specific character set compliance or when you need maximum randomness.</p>
<h4>Available Options</h4>
<div class="option-detail">
<h4><span class="option-type">number</span><span class="option-default">16</span>length</h4>
<p><strong>Range:</strong> 8-128 characters</p>
<p><strong>Description:</strong> Total password length. Longer passwords provide exponentially more security.</p>
<p><strong>Security Impact:</strong> Each additional character roughly doubles the time required to crack the password.</p>
</div>
<div class="option-detail">
<h4><span class="option-type">boolean</span><span class="option-default">true</span>include_uppercase</h4>
<p><strong>Description:</strong> Include uppercase letters (A-Z) in the character set.</p>
<p><strong>Character Set:</strong> ABCDEFGHIJKLMNOPQRSTUVWXYZ (26 characters)</p>
<p><strong>Security Impact:</strong> Adds 26 possible characters, significantly increasing entropy.</p>
</div>
<div class="option-detail">
<h4><span class="option-type">boolean</span><span class="option-default">true</span>include_lowercase</h4>
<p><strong>Description:</strong> Include lowercase letters (a-z) in the character set.</p>
<p><strong>Character Set:</strong> abcdefghijklmnopqrstuvwxyz (26 characters)</p>
<p><strong>Note:</strong> At least one character type must be selected.</p>
</div>
<div class="option-detail">
<h4><span class="option-type">boolean</span><span class="option-default">true</span>include_numbers</h4>
<p><strong>Description:</strong> Include numeric digits (0-9) in the character set.</p>
<p><strong>Character Set:</strong> 0123456789 (10 characters)</p>
<p><strong>Compliance:</strong> Required by many corporate password policies.</p>
</div>
<div class="option-detail">
<h4><span class="option-type">boolean</span><span class="option-default">true</span>include_symbols</h4>
<p><strong>Description:</strong> Include special symbols in the character set.</p>
<p><strong>Character Set:</strong> !@#$%^&*()_+-=[]{}|;:,.<>? (25 characters)</p>
<p><strong>Note:</strong> Some systems have restrictions on certain symbols. Validate accepted characters in your target environment.</p>
</div>
<h4>Example Request</h4>
<div class="example-request">
POST /api/v1/passwords/generate
Content-Type: application/json
{
"type": "high-entropy",
"count": 2,
"options": {
"length": 20,
"include_uppercase": true,
"include_lowercase": true,
"include_numbers": true,
"include_symbols": false
}
}</div>
<h4>Example Response</h4>
<div class="example-response">
{
"success": true,
"data": {
"passwords": [
"Km9xB3nQ2pL8vR5wE6sA",
"T7gY4hJ9kM1nP6qU8zC3"
],
"entropy_bits": 119.5,
"generator_info": "High entropy generator with alphanumeric characters",
"options_used": {
"length": 20,
"include_uppercase": true,
"include_lowercase": true,
"include_numbers": true,
"include_symbols": false
}
},
"message": "Generated 2 password(s) of type 'high-entropy'",
"timestamp": "2025-08-23T21:24:00+02:00"
}</div>
</div>
</div>
<!-- XKCD Generator -->
<div class="generator-section">
<div class="generator-header">
<h3>🗣️ XKCD Pass Generator</h3>
<p>Memorable passphrases using word combinations with multilingual support.</p>
</div>
<div class="generator-content">
<h4>Purpose & Use Case</h4>
<p>Generate memorable passphrases based on the famous XKCD comic "Password Strength". Ideal when users need to remember passwords without writing them down. Supports multiple languages for international users.</p>
<h4>Available Options</h4>
<div class="option-detail">
<h4><span class="option-type">number</span><span class="option-default">4</span>words</h4>
<p><strong>Range:</strong> 3-8 words</p>
<p><strong>Description:</strong> Number of words in the passphrase.</p>
<p><strong>Security Impact:</strong> Each additional word exponentially increases security. 4 words ≈ 52 bits, 6 words ≈ 77 bits entropy.</p>
<p><strong>Memorability:</strong> 4-5 words strike the best balance between security and memorability.</p>
</div>
<div class="option-detail">
<h4><span class="option-type">select</span><span class="option-default">en</span>wordlist</h4>
<p><strong>Options:</strong> en, de, de_CH, fr, it, es, pt, ja, zh</p>
<p><strong>Description:</strong> Wordlist to use for generation.</p>
<p><strong>Wordlist Quality:</strong> Built-in curated collections are selected per language and can be checked dynamically from the wordlist endpoint.</p>
<p><strong>Availability:</strong> Check <code>/api/v1/passwords/wordlists</code> for current language availability.</p>
</div>
<div class="option-detail">
<h4><span class="option-type">text</span><span class="option-default">-</span>separator</h4>
<p><strong>Description:</strong> Character(s) used to separate words in the passphrase.</p>
<p><strong>Single Character:</strong> All separations use the same character (e.g., "word-word-word").</p>
<p><strong>Multiple Characters:</strong> Each separation randomly selects from the character set (e.g., "word-word_word.word").</p>
<p><strong>Space Handling:</strong> Spaces are excluded from multi-character separator sets for security.</p>
<p><strong>Examples:</strong> "-", "_", ".", "-_.", "!@#"</p>
</div>
<div class="option-detail">
<h4><span class="option-type">boolean</span><span class="option-default">false</span>add_number</h4>
<p><strong>Description:</strong> Add a 2-digit number to the passphrase.</p>
<p><strong>Security Benefit:</strong> Increases entropy by ~6.6 bits (100 possible values).</p>
<p><strong>Position Control:</strong> Use <code>number_position</code> to control placement.</p>
</div>
<div class="option-detail">
<h4><span class="option-type">select</span><span class="option-default">end</span>number_position</h4>
<p><strong>Requires:</strong> <code>add_number: true</code></p>
<p><strong>Options:</strong></p>
<ul>
<li><code>beginning</code>: "42-word-word-word"</li>
<li><code>end</code>: "word-word-word-42"</li>
<li><code>random</code>: "word-42-word-word" (random separator position)</li>
</ul>
<p><strong>Security Note:</strong> Random position provides additional unpredictability.</p>
</div>
<h4>Example Request</h4>
<div class="example-request">
POST /api/v1/passwords/generate
Content-Type: application/json
{
"type": "xkcdpass",
"count": 1,
"options": {
"words": 5,
"wordlist": "en",
"separator": "-_.",
"add_number": true,
"number_position": "random"
}
}</div>
<h4>Example Response</h4>
<div class="example-response">
{
"success": true,
"data": {
"passwords": ["horse-battery_42.correct_staple"],
"explanations": ["large four-legged animal – device that stores electrical energy – free from error; accurate – basic or principal item"],
"entropy_bits": 58.7,
"generator_info": "XKCD-style passphrase using 5 words from en wordlist",
"options_used": {
"words": 5,
"wordlist": "en",
"separator": "-_.",
"add_number": true,
"number_position": "random"
}
},
"message": "Generated 1 password(s) of type 'xkcdpass'",
"timestamp": "2025-08-23T21:24:00+02:00"
}</div>
</div>
</div>
<!-- Add similar detailed sections for Diceware, PWGen, and Passkey generators -->
<!-- Security Recommendations -->
<div class="generator-section">
<div class="generator-header">
<h3>🔒 Security Recommendations</h3>
</div>
<div class="generator-content">
<h4>Choosing the Right Generator</h4>
<ul>
<li><strong>High Entropy:</strong> Maximum security with strict policy control</li>
<li><strong>XKCD Pass:</strong> User memorability, multi-language support needed</li>
<li><strong>Diceware:</strong> Maximum security with educational value</li>
<li><strong>PWGen:</strong> Pronounceable passwords with balanced strength and usability</li>
<li><strong>Passkey:</strong> Pronounceable passwords for verbal communication</li>
</ul>
<h4>Entropy Guidelines</h4>
<ul>
<li><strong>50+ bits:</strong> Basic security (personal accounts)</li>
<li><strong>65+ bits:</strong> Strong security (business accounts)</li>
<li><strong>80+ bits:</strong> Excellent security (high-value accounts)</li>
</ul>
</div>
</div>
</main>
<script>
function switchLanguage(lang) {
// Redirect to language-specific documentation
const baseUrl = window.location.origin + window.location.pathname;
const newUrl = baseUrl.replace('/docs', `/docs-${lang}`);
window.location.href = newUrl;
}
</script>
</body>
</html>
+92
View File
@@ -0,0 +1,92 @@
#!/bin/bash
# Password Generator Installation Script
# This script sets up the password generator with i18n support
set -e
echo "🔐 Password Generator Installation"
echo "=================================="
# Check PHP version
php_version=$(php -v | head -n1 | cut -d' ' -f2 | cut -d'.' -f1,2)
required_version="8.1"
if [ "$(printf '%s\n' "$required_version" "$php_version" | sort -V | head -n1)" != "$required_version" ]; then
echo "❌ PHP 8.1+ required (found: $php_version)"
exit 1
fi
echo "✅ PHP version: $php_version"
# Check if composer is installed
if ! command -v composer &> /dev/null; then
echo "❌ Composer not found. Please install Composer first:"
echo " https://getcomposer.org/download/"
exit 1
fi
echo "✅ Composer found"
# Install PHP dependencies
echo ""
echo "📦 Installing PHP dependencies..."
composer install --no-dev --optimize-autoloader
# Set up environment file
if [ ! -f .env ]; then
echo ""
echo "⚙️ Setting up environment configuration..."
cp .env.example .env
echo "✅ Environment file created (.env)"
echo " You can edit this file to customize settings"
else
echo "✅ Environment file already exists"
fi
# Create logs directory and set permissions
echo ""
echo "📁 Setting up directories..."
mkdir -p logs
chmod 755 logs
echo "✅ Logs directory created"
# Check if pwgen is available (optional)
if command -v pwgen &> /dev/null; then
echo "✅ pwgen found (enhanced compatibility enabled)"
else
echo "ℹ️ pwgen not found (will use PHP fallback)"
fi
# Test the installation
echo ""
echo "🧪 Testing installation..."
php -l public/index.php > /dev/null
if [ $? -eq 0 ]; then
echo "✅ PHP syntax check passed"
else
echo "❌ PHP syntax error found"
exit 1
fi
# Start the development server
echo ""
echo "🚀 Installation completed successfully!"
echo ""
echo "To start the development server:"
echo " php -S localhost:8080 -t public"
echo ""
echo "To start with composer:"
echo " composer start"
echo ""
echo "Then visit: http://localhost:8080"
echo ""
echo "📚 Features:"
echo " • REST API for password generation"
echo " • Multilingual web interface (8 languages)"
echo " • 5 different password generation algorithms"
echo " • Security analysis and entropy calculation"
echo " • Password manager integration examples"
echo ""
echo "📖 Documentation: http://localhost:8080/docs"
echo "🌍 Supported languages: EN, DE, FR, IT, ES, PT, JA, ZH"
+545
View File
@@ -0,0 +1,545 @@
# Password Generator API Integration Guide
This guide provides comprehensive examples for integrating the Password Generator API with popular password managers, launchers, and automation tools.
## Table of Contents
- [Quick Start](#quick-start)
- [Password Managers](#password-managers)
- [Launchers & Tools](#launchers--tools)
- [Automation Scripts](#automation-scripts)
- [Security Considerations](#security-considerations)
## Quick Start
The API provides several endpoints for password generation:
**Base URL:** `http://localhost:8080/api/v1`
**Generate Password:**
```bash
curl -X POST http://localhost:8080/api/v1/passwords/generate \
-H "Content-Type: application/json" \
-d '{"type":"diceware","count":1,"options":{"words":6}}'
```
## Password Managers
### Bitwarden
#### Bitwarden CLI Integration
**Installation:**
```bash
npm install -g @bitwarden/cli
```
**Basic Password Generation and Storage:**
```bash
#!/bin/bash
# generate-and-store-bitwarden.sh
# Login to Bitwarden (do this once)
bw login
# Unlock vault (enter master password when prompted)
export BW_SESSION="$(bw unlock --raw)"
# Generate high-entropy password
PASSWORD=$(curl -s -X POST http://localhost:8080/api/v1/passwords/generate \
-H "Content-Type: application/json" \
-d '{"type":"high-entropy","count":1,"options":{"length":20,"includeSymbols":true}}' | \
jq -r '.data.passwords[0]')
# Create new login item
ITEM_DATA=$(cat <<EOF
{
"type": 1,
"name": "Generated Password $(date +%Y-%m-%d)",
"login": {
"username": "user@example.com",
"password": "$PASSWORD"
}
}
EOF
)
echo "$ITEM_DATA" | bw encode | bw create item
echo "Password created and stored in Bitwarden: $PASSWORD"
```
**Advanced: Generate Different Password Types:**
```bash
#!/bin/bash
# bitwarden-multi-generator.sh
generate_and_store() {
local type=$1
local name=$2
local options=$3
PASSWORD=$(curl -s -X POST http://localhost:8080/api/v1/passwords/generate \
-H "Content-Type: application/json" \
-d "{\"type\":\"$type\",\"count\":1,\"options\":$options}" | \
jq -r '.data.passwords[0]')
ITEM_DATA="{\"type\": 1, \"name\": \"$name\", \"login\": {\"password\": \"$PASSWORD\"}}"
echo "$ITEM_DATA" | bw encode | bw create item
echo "Created $name: $PASSWORD"
}
# Generate different types
generate_and_store "diceware" "Diceware Passphrase" '{"words":6,"capitalize":true}'
generate_and_store "passkey" "Apple-style Password" '{"syllables":4,"addNumbers":true}'
generate_and_store "xkcdpass" "XKCD Passphrase" '{"words":4,"language":"english","addNumber":true}'
```
### 1Password
#### 1Password CLI Integration
**Installation:**
Follow 1Password CLI installation guide for your platform.
**Basic Integration:**
```bash
#!/bin/bash
# generate-and-store-1password.sh
# Generate secure password
PASSWORD=$(curl -s -X POST http://localhost:8080/api/v1/passwords/generate \
-H "Content-Type: application/json" \
-d '{"type":"high-entropy","count":1,"options":{"length":24}}' | \
jq -r '.data.passwords[0]')
# Create login item
op item create \
--category=login \
--title="Generated Password $(date +%Y-%m-%d)" \
--field="username=user@example.com" \
--field="password=$PASSWORD"
echo "Password stored in 1Password: $PASSWORD"
```
**Batch Password Generation:**
```bash
#!/bin/bash
# 1password-batch-generator.sh
TYPES=("diceware" "high-entropy" "passkey" "xkcdpass")
NAMES=("Diceware Phrase" "High Entropy" "Passkey Style" "XKCD Phrase")
for i in "${!TYPES[@]}"; do
TYPE=${TYPES[$i]}
NAME=${NAMES[$i]}
PASSWORD=$(curl -s -X POST http://localhost:8080/api/v1/passwords/generate \
-H "Content-Type: application/json" \
-d "{\"type\":\"$TYPE\",\"count\":1}" | \
jq -r '.data.passwords[0]')
op item create \
--category=login \
--title="$NAME $(date +%Y-%m-%d)" \
--field="password=$PASSWORD"
echo "Created $NAME: $PASSWORD"
done
```
### KeePass
#### KeePassXC CLI Integration
```bash
#!/bin/bash
# keepassxc-generator.sh
DB_PATH="$HOME/passwords.kdbx"
KEY_FILE="$HOME/keyfile.key"
# Generate password
PASSWORD=$(curl -s -X POST http://localhost:8080/api/v1/passwords/generate \
-H "Content-Type: application/json" \
-d '{"type":"diceware","count":1,"options":{"words":6}}' | \
jq -r '.data.passwords[0]')
# Add to KeePass database
keepassxc-cli add "$DB_PATH" \
--key-file "$KEY_FILE" \
"Generated/$(date +%Y-%m-%d)" \
--username "user@example.com" \
--password-prompt
echo "Password: $PASSWORD"
```
## Launchers & Tools
### Raycast
#### Simple Password Generator Extension
Create a Raycast script command:
**generate-password.sh:**
```bash
#!/bin/bash
# Required parameters:
# @raycast.schemaVersion 1
# @raycast.title Generate Secure Password
# @raycast.mode compact
# @raycast.icon 🔐
# @raycast.packageName Password Generator
# @raycast.argument1 {"type": "dropdown", "placeholder": "Password Type", "data": [{"title": "High Entropy", "value": "high-entropy"}, {"title": "Diceware", "value": "diceware"}, {"title": "Passkey", "value": "passkey"}, {"title": "XKCD", "value": "xkcdpass"}]}
TYPE=${1:-"passkey"}
case $TYPE in
"high-entropy")
OPTIONS='{"length":16,"includeSymbols":true}'
;;
"diceware")
OPTIONS='{"words":5,"capitalize":true}'
;;
"passkey")
OPTIONS='{"syllables":3,"addNumbers":true}'
;;
"xkcdpass")
OPTIONS='{"words":4,"capitalize":true,"addNumber":true}'
;;
esac
PASSWORD=$(curl -s -X POST http://localhost:8080/api/v1/passwords/generate \
-H "Content-Type: application/json" \
-d "{\"type\":\"$TYPE\",\"count\":1,\"options\":$OPTIONS}" | \
jq -r '.data.passwords[0]')
if [ -n "$PASSWORD" ]; then
echo "$PASSWORD" | pbcopy
echo "✅ Password copied: $PASSWORD"
else
echo "❌ Failed to generate password"
fi
```
#### Advanced Raycast Extension with Options
**raycast-advanced.sh:**
```bash
#!/bin/bash
# Required parameters:
# @raycast.schemaVersion 1
# @raycast.title Advanced Password Generator
# @raycast.mode fullOutput
# @raycast.icon 🔐
# @raycast.packageName Password Generator
# @raycast.argument1 {"type": "text", "placeholder": "Number of passwords", "optional": true}
COUNT=${1:-1}
# Generate multiple password types
echo "🔐 Password Generator Results"
echo "================================"
# High Entropy
PASSWORD=$(curl -s -X POST http://localhost:8080/api/v1/passwords/generate \
-H "Content-Type: application/json" \
-d "{\"type\":\"high-entropy\",\"count\":$COUNT,\"options\":{\"length\":16}}" | \
jq -r '.data.passwords[]' | head -1)
echo "🔒 High Entropy: $PASSWORD"
# Diceware
PASSWORD=$(curl -s -X POST http://localhost:8080/api/v1/passwords/generate \
-H "Content-Type: application/json" \
-d "{\"type\":\"diceware\",\"count\":$COUNT,\"options\":{\"words\":5}}" | \
jq -r '.data.passwords[]' | head -1)
echo "🎲 Diceware: $PASSWORD"
# Passkey
PASSWORD=$(curl -s -X POST http://localhost:8080/api/v1/passwords/generate \
-H "Content-Type: application/json" \
-d "{\"type\":\"passkey\",\"count\":$COUNT,\"options\":{\"syllables\":3}}" | \
jq -r '.data.passwords[]' | head -1)
echo "🗝️ Passkey: $PASSWORD"
echo ""
echo "Select a password above to copy it."
```
### Alfred
#### Alfred Workflow Script Filter
**alfred-password-generator.php:**
```php
<?php
header('Content-Type: application/json');
$types = [
'high-entropy' => ['name' => 'High Entropy', 'icon' => '🔒', 'options' => '{"length":16}'],
'diceware' => ['name' => 'Diceware', 'icon' => '🎲', 'options' => '{"words":5}'],
'passkey' => ['name' => 'Passkey', 'icon' => '🗝️', 'options' => '{"syllables":3}'],
'xkcdpass' => ['name' => 'XKCD', 'icon' => '💭', 'options' => '{"words":4}']
];
$items = [];
foreach ($types as $type => $config) {
$items[] = [
'uid' => $type,
'title' => $config['name'] . ' Password',
'subtitle' => 'Generate ' . strtolower($config['name']) . ' password',
'arg' => $type,
'icon' => ['emoji' => $config['icon']]
];
}
echo json_encode(['items' => $items]);
?>
```
**alfred-generate.sh:**
```bash
#!/bin/bash
TYPE=$1
case $TYPE in
"high-entropy")
OPTIONS='{"length":16,"includeSymbols":true}'
;;
"diceware")
OPTIONS='{"words":5,"capitalize":true}'
;;
"passkey")
OPTIONS='{"syllables":3,"addNumbers":true}'
;;
"xkcdpass")
OPTIONS='{"words":4,"capitalize":true,"addNumber":true}'
;;
esac
PASSWORD=$(curl -s -X POST http://localhost:8080/api/v1/passwords/generate \
-H "Content-Type: application/json" \
-d "{\"type\":\"$TYPE\",\"count\":1,\"options\":$OPTIONS}" | \
jq -r '.data.passwords[0]')
echo "$PASSWORD" | pbcopy
echo "$PASSWORD"
```
### LaunchBar
#### LaunchBar Action
**PasswordGenerator.lbaction/Contents/Scripts/default.sh:**
```bash
#!/bin/bash
PASSWORD=$(curl -s -X POST http://localhost:8080/api/v1/passwords/generate \
-H "Content-Type: application/json" \
-d '{"type":"passkey","count":1,"options":{"syllables":4,"addNumbers":true}}' | \
jq -r '.data.passwords[0]')
if [ -n "$PASSWORD" ]; then
echo "$PASSWORD" | pbcopy
echo "$PASSWORD"
else
echo "Error generating password"
exit 1
fi
```
### Ubar / Menu Bar Apps
#### BitBar Plugin
**password-generator.1m.sh:**
```bash
#!/bin/bash
echo "🔐 Password Generator"
echo "---"
# Generate different types
TYPES=("high-entropy:High Entropy" "diceware:Diceware" "passkey:Passkey" "xkcdpass:XKCD")
for TYPE_PAIR in "${TYPES[@]}"; do
TYPE="${TYPE_PAIR%%:*}"
NAME="${TYPE_PAIR##*:}"
PASSWORD=$(curl -s -X POST http://localhost:8080/api/v1/passwords/generate \
-H "Content-Type: application/json" \
-d "{\"type\":\"$TYPE\",\"count\":1}" | \
jq -r '.data.passwords[0]' 2>/dev/null)
if [ -n "$PASSWORD" ]; then
echo "$NAME: $PASSWORD | bash='echo \"$PASSWORD\" | pbcopy' terminal=false"
fi
done
echo "---"
echo "API Status | bash='curl -s http://localhost:8080/api/v1/health' terminal=true"
```
## Automation Scripts
### Automated Password Rotation
```bash
#!/bin/bash
# rotate-passwords.sh
SERVICES=("email:user@example.com" "bank:john.doe" "social:johndoe123")
for SERVICE_PAIR in "${SERVICES[@]}"; do
SERVICE="${SERVICE_PAIR%%:*}"
USERNAME="${SERVICE_PAIR##*:}"
# Generate new password
PASSWORD=$(curl -s -X POST http://localhost:8080/api/v1/passwords/generate \
-H "Content-Type: application/json" \
-d '{"type":"high-entropy","count":1,"options":{"length":20}}' | \
jq -r '.data.passwords[0]')
# Update in password manager (example with Bitwarden)
ITEM_ID=$(bw list items --search "$SERVICE" | jq -r '.[0].id')
if [ "$ITEM_ID" != "null" ]; then
ITEM=$(bw get item "$ITEM_ID")
UPDATED_ITEM=$(echo "$ITEM" | jq ".login.password = \"$PASSWORD\"")
echo "$UPDATED_ITEM" | bw encode | bw edit item "$ITEM_ID"
echo "Rotated password for $SERVICE: $PASSWORD"
fi
done
```
### Bulk Password Generation for Teams
```bash
#!/bin/bash
# team-password-generator.sh
TEAM_MEMBERS=("alice" "bob" "charlie" "diana")
SERVICES=("staging" "development" "testing")
for MEMBER in "${TEAM_MEMBERS[@]}"; do
echo "Generating passwords for $MEMBER:"
for SERVICE in "${SERVICES[@]}"; do
PASSWORD=$(curl -s -X POST http://localhost:8080/api/v1/passwords/generate \
-H "Content-Type: application/json" \
-d '{"type":"diceware","count":1,"options":{"words":4,"addNumber":true}}' | \
jq -r '.data.passwords[0]')
# Store in team password manager
op item create \
--category=login \
--title="$SERVICE-$MEMBER" \
--field="username=$MEMBER" \
--field="password=$PASSWORD" \
--vault="Team Vault"
echo " $SERVICE: $PASSWORD"
done
echo ""
done
```
## Security Considerations
### API Security
1. **Local Deployment**: Run the API locally or on a secure internal network
2. **Rate Limiting**: The API includes built-in rate limiting (60 requests/minute by default)
3. **No Logging of Passwords**: Passwords are not logged in server logs
4. **HTTPS**: Use HTTPS in production deployments
### Integration Security
1. **Environment Variables**: Store API endpoints in environment variables:
```bash
export PASSWORD_API_URL="https://passwords.internal.company.com/api/v1"
```
2. **Script Permissions**: Set appropriate permissions on scripts:
```bash
chmod 700 password-scripts/
chmod 600 password-scripts/*.sh
```
3. **Secure Storage**: Ensure integration scripts don't leave passwords in temporary files:
```bash
# Good: Use pipes and variables
PASSWORD=$(curl -s ... | jq -r '.data.passwords[0]')
# Bad: Don't write to temporary files
# curl -s ... > /tmp/password.json
```
### Production Deployment
For production use, consider:
1. **Authentication**: Add API key authentication
2. **Network Security**: Deploy behind a firewall or VPN
3. **Monitoring**: Monitor API usage and failed requests
4. **Backup**: Regular backups of configuration and logs
### Password Manager Sync
When integrating with password managers:
1. **Verify Storage**: Always verify passwords are correctly stored
2. **Error Handling**: Implement proper error handling for API failures
3. **Rollback**: Have procedures to rollback failed password updates
4. **Audit Trail**: Maintain logs of password generation and updates
## Troubleshooting
### Common Issues
**API Connection Issues:**
```bash
# Test API connectivity
curl -s http://localhost:8080/api/v1/health
# Check if service is running
netstat -tlnp | grep 8080
```
**JSON Parsing Issues:**
```bash
# Validate JSON response
curl -s http://localhost:8080/api/v1/passwords | jq '.'
# Check for errors
curl -s http://localhost:8080/api/v1/passwords | jq '.error'
```
**Password Manager Integration:**
```bash
# Test CLI tools separately
bw --version
op --version
keepassxc-cli --version
# Verify authentication
bw status
op whoami
```
This integration guide provides comprehensive examples for connecting the Password Generator API with popular tools and services. Adapt these examples based on your specific requirements and security policies.
+223
View File
@@ -0,0 +1,223 @@
{
"api_documentation": "API-Dokumentation",
"app_title": "Passwort Generator",
"close": "Schließen",
"copied_to_clipboard": "In die Zwischenablage kopiert!",
"copy": "Kopieren",
"copy_failed": "Kopieren fehlgeschlagen",
"count": "Anzahl",
"deutsch": "Deutsch",
"dice_rolls": "Würfelwürfe",
"diceware_badge": "Fokus Sicherheit",
"diceware_description": "Authentische EFF Diceware-Methode mit kryptographisch sicheren Würfelwürfen für maximale Sicherheit",
"diceware_option_show_dice_rolls": "Zeige Würfelwürfe an",
"diceware_option_show_dice_rolls_description": "Informativ: zeige die Würfelwürfe an, welche die Basis für die Auswahl der Wörter ist.",
"diceware_option_uppercase_first": "Grossschreibung des ersten Wortes",
"diceware_option_uppercase_first_description": "Nur das erste Wort wird gross geschrieben (traditionelle Diceware Mechanik)",
"diceware_option_words": "Wörter",
"diceware_option_words_description": "Das empfohlene Minimum sind 6 Wörter um ein sicheres Passwort zu erhalten (77.5 Bits Entropie).",
"diceware_title": "Diceware",
"english": "Englisch",
"entropy": "Entropie",
"length": "Länge",
"details": "Details",
"security_level": "Sicherheitsstufe",
"very_strong": "Sehr stark",
"strong": "Stark",
"adequate": "Ausreichend",
"weak": "Schwach",
"very_weak": "Sehr schwach",
"chars": "Zeichen",
"error_generating_passwords": "Fehler beim Generieren der Passwörter",
"espanol": "Español",
"footer_text": "Ein Werkzeug um sichere Passwörter mit verschiedenen Algorithmen zu erstellen. Open Source von Reindl Informatik.",
"francais": "Français",
"generate": "Generieren",
"generating": "Generiere...",
"high-entropy_description": "Kryptographisch sichere Zufallspasswörter mit maximaler Entropie",
"high-entropy_option_include_lowercase": "Kleinbuchstaben einbeziehen",
"high-entropy_option_include_numbers": "Zahlen einbeziehen",
"high-entropy_option_include_symbols": "Sonderzeichen einbeziehen",
"high-entropy_option_include_uppercase": "Grossbuchstaben einbeziehen",
"high-entropy_option_length": "Länge",
"high-entropy_title": "Hohe Entropie",
"info_spaces_replaced_with_underscores": "Leerzeichen in ausgewählten Wörtern werden durch Unterstriche (_) ersetzt.",
"info_words_with_spaces_excluded": "Wörter mit Leerzeichen werden von der Auswahl ausgeschlossen.",
"italiano": "italienisch",
"language": "Sprache:",
"loading_generators": "Generatoren laden …",
"loading_password_generators": "Passwort-Generatoren werden geladen...",
"native_group_explanation": "Native Optionen des PwGen-Generators zum Erzeugen aussprachefähiger Passwörter.",
"native_pwgen": "Native Optionen",
"no_spaces_in_separator": "Keine Leerzeichen im Trennzeichen verwenden, wenn die Leerzeichenbehandlung auf „ändern“ oder „überspringen“ gesetzt ist.",
"passkey_description": "Apple-Style aussprechbare Passwörter mit integrierten Zeichenersetzungen",
"passkey_option_syllables": "Silben",
"passkey_title": "Passkey",
"password_generator": "Passwort-Generator",
"php_fallback": "PHP-Fallback-Code, wenn native pwgen nicht verfügbar ist.",
"portugues": "portugiesisch",
"pwgen_badge": "Erweitert",
"pwgen_description": "Klassische pwgen-Style Passwörter mit lesbaren Mustern",
"pwgen_option_include_capitals": "Großbuchstaben einschließen",
"pwgen_option_include_capitals_description": "Generierte Passwörter enthalten Großbuchstaben.",
"pwgen_option_include_numbers": "Zahlen einschließen",
"pwgen_option_include_numbers_description": "Generierte Passwörter enthalten Ziffern.",
"pwgen_option_length": "Länge",
"pwgen_option_length_description": "Gesamtpasswortlänge einschließlich Trennzeichen (mindestens 4 Zeichen)",
"pwgen_option_no_ambiguous": "Keine mehrdeutigen Zeichen",
"pwgen_option_no_ambiguous_description": "Schließe Zeichen wie 0, O, l, 1 usw. aus, um die Lesbarkeit zu verbessern",
"pwgen_option_pattern_complexity": "Komplexes Muster",
"pwgen_option_pattern_complexity_description": "Steuert die Komplexität der erzeugten Muster.",
"pwgen_option_pattern_complexity_list_balanced": "Ausgewogene Komplexität",
"pwgen_option_pattern_complexity_list_complex": "Komplexe Muster",
"pwgen_option_pattern_complexity_list_simple": "Einfache Muster",
"pwgen_option_secure_mode": "Sicherer Modus",
"pwgen_option_secure_mode_description": "Erzeuge vollständig zufällige Passwörter statt aussprechbarer Wörter",
"pwgen_option_separator": "Trennzeichen",
"pwgen_option_separator_description": "Zeichen, die Silben trennen. Wenn mehrere Zeichen definiert sind, werden sie durch zufällig ausgewählte Zeichen getrennt. Wenn dies leer ist, findet keine Trennung statt.",
"pwgen_option_syllable_count": "Silbenanzahl",
"pwgen_option_syllable_count_description": "Anzahl der zu erzeugenden Silben (nur mit Trennzeichen verwendet)",
"pwgen_title": "PWGen",
"results": "Ergebnisse",
"schwyzerduetsch": "Schwyzerdütsch",
"secure_mode_disables_syllable_separation": "Sicherer Modus deaktiviert die Trennung von Silben.",
"separator_method_info": "Infos zur Trennmethode",
"single_space_allowed": "Einzelnes Leerzeichen erlaubt",
"success": "Erfolg",
"syllable_group_explanation": "Erklärungen zu Silben",
"syllable_length_too_short": "Silbenlänge zu kurz",
"syllable_length_very_long": "Silbenlänge sehr lang",
"warning_short_word_limits_reduces_choices": "Warnung: kurze Wortlimits verringern die Auswahlmöglichkeiten",
"warning_short_words_reduce_memorability": "Warnung: kurze Wörter verringern die Merkbarkeit",
"xkcdpass_badge": "Fokus Merkbarkeit",
"xkcdpass_description": "Einfach merkbare Passwörter aus Wortkombinationen aus auswählbaren Wortlisten mit zusätzlichen Optionen und Merkhilfen.",
"xkcdpass_option_addNumber": "Zahl hinzufügen",
"xkcdpass_option_max_word_length": "Maximale Wortlänge",
"xkcdpass_option_max_word_length_description": "0 = keine Einschränkung, >0 = maximale Anzahl Buchstaben pro Wort.",
"xkcdpass_option_numberPosition": "Zahlenposition",
"xkcdpass_option_numberPosition_list_beginning": "am Anfang",
"xkcdpass_option_numberPosition_list_end": "am Ende",
"xkcdpass_option_numberPosition_list_random": "zufällige Position",
"xkcdpass_option_separator": "Trennzeichen",
"xkcdpass_option_separator_description": "Ein einzelnes oder mehrere Zeichen zur Worttrennung. Bei mehr als einem Zeichen wird zufällig ausgewählt. Leerzeichen werde ignoriert.",
"xkcdpass_option_space_handling": "Leerzeichenverarbeitung",
"xkcdpass_option_space_handling_description": "Wie sollen Leerzeichen in ausgewählten Wörtern behandelt werden.",
"xkcdpass_option_space_handling_list_allow": "erlauben",
"xkcdpass_option_space_handling_list_modify": "ersetzen mit '_'",
"xkcdpass_option_space_handling_list_skip": "Wort überspringen",
"xkcdpass_option_wordlist": "Wortliste",
"xkcdpass_option_words": "Wörter",
"xkcdpass_option_words_description": "Anzahl Wörter im generierten Passwort (Minimum 2, empfohlen 3 und mehr).",
"xkcdpass_title": "XKCD Pass",
"phraseabbreviator_number_zero": "null",
"phraseabbreviator_number_one": "eins",
"phraseabbreviator_number_two": "zwei",
"phraseabbreviator_number_three": "drei",
"phraseabbreviator_number_four": "vier",
"phraseabbreviator_number_five": "fünf",
"phraseabbreviator_number_six": "sechs",
"phraseabbreviator_number_seven": "sieben",
"phraseabbreviator_number_eight": "acht",
"phraseabbreviator_number_nine": "neun",
"phraseabbreviator_number_ten": "zehn",
"phraseabbreviator_number_eleven": "elf",
"phraseabbreviator_number_twelve": "zwölf",
"phraseabbreviator_number_thirteen": "dreizehn",
"phraseabbreviator_number_fourteen": "vierzehn",
"phraseabbreviator_number_fifteen": "fünfzehn",
"phraseabbreviator_number_sixteen": "sechzehn",
"phraseabbreviator_number_seventeen": "siebzehn",
"phraseabbreviator_number_eighteen": "achtzehn",
"phraseabbreviator_number_nineteen": "neunzehn",
"phraseabbreviator_number_twenty": "zwanzig",
"xkcdpass_option_use_ai_wordlist": "KI-Wortliste verwenden",
"xkcdpass_option_use_ai_wordlist_description": "Erzeugt eine benutzerdefinierte Wortliste mit einem KI-Modell",
"xkcdpass_option_ai_wordlist_prompt": "KI-Wortlisten-Prompt",
"xkcdpass_option_ai_wordlist_prompt_description": "Vervollständige den Prompt: \"Erstelle eine Liste von X Wörtern mit mindestens 5 Zeichen, die...\"",
"xkcdpass_option_ai_provider": "KI-Anbieter",
"xkcdpass_option_ai_provider_description": "Welcher LLM-Anbieter verwendet werden soll",
"phrase-abbreviator_option_use_ai_sentence": "KI-Satz verwenden",
"phrase-abbreviator_option_use_ai_sentence_description": "Lässt die KI einen Satz aus deinem Prompt generieren",
"phrase-abbreviator_option_ai_prompt": "KI-Prompt",
"phrase-abbreviator_option_ai_prompt_description": "Prompt für die KI, um einen einprägsamen Satz zu generieren (mindestens 12 Wörter)",
"phrase-abbreviator_option_ai_provider": "KI-Anbieter",
"phrase-abbreviator_option_ai_provider_description": "Welcher LLM-Anbieter für die Satzgenerierung verwendet werden soll",
"phrase-abbreviator_option_ai_model": "KI-Modell",
"phrase-abbreviator_option_ai_model_description": "Welches KI-Modell verwendet werden soll",
"high-entropy_option_length_description": "Gesamtpasswortlänge",
"high-entropy_option_include_uppercase_description": "Grossbuchstaben einbeziehen (A-Z)",
"high-entropy_option_include_lowercase_description": "Kleinbuchstaben einbeziehen (a-z)",
"high-entropy_option_include_numbers_description": "Zahlen einbeziehen (0-9)",
"high-entropy_option_include_symbols_description": "Sonderzeichen einbeziehen (z. B. !@#$%)",
"xkcdpass_option_wordlist_description": "Wortliste für die Passphrase",
"xkcdpass_option_wordlist_choice_en": "Englisch",
"xkcdpass_option_wordlist_choice_de": "Deutsch",
"xkcdpass_option_wordlist_choice_fr": "Französisch",
"xkcdpass_option_wordlist_choice_it": "Italienisch",
"xkcdpass_option_wordlist_choice_es": "Spanisch",
"xkcdpass_option_space_handling_choice_allow": "Erlauben",
"xkcdpass_option_space_handling_choice_skip": "Überspringen",
"xkcdpass_option_space_handling_choice_modify": "Ändern",
"xkcdpass_option_add_number": "Zahl hinzufügen",
"xkcdpass_option_add_number_description": "Zufällige Zahl zur Passphrase hinzufügen",
"xkcdpass_option_number_position": "Zahlenposition",
"xkcdpass_option_number_position_description": "Wo die Zufallszahl platziert werden soll",
"xkcdpass_option_number_position_choice_beginning": "Am Anfang",
"xkcdpass_option_number_position_choice_end": "Am Ende",
"xkcdpass_option_number_position_choice_random": "Zufällige Position",
"xkcdpass_option_ai_provider_choice_openai": "OpenAI",
"xkcdpass_option_ai_provider_choice_claude": "Claude",
"xkcdpass_option_ai_provider_choice_gemini": "Gemini",
"xkcdpass_option_ai_provider_choice_perplexity": "Perplexity",
"pwgen_option_type": "Längenvoreinstellung",
"pwgen_option_type_description": "Optionale Längenvoreinstellung. Wenn 8/12/16 Zeichen gewählt sind, überschreibt sie den obigen Längenwert.",
"pwgen_option_type_choice_8-char": "8 Zeichen",
"pwgen_option_type_choice_12-char": "12 Zeichen",
"pwgen_option_type_choice_16-char": "16 Zeichen",
"passkey_option_syllables_description": "Anzahl der aussprachefähigen Silben im Passkey",
"phrase-abbreviator_option_sentence": "Satz",
"phrase-abbreviator_option_sentence_description": "Vom Benutzer eingegebener Satz (mindestens 8 Wörter, muss mit Großbuchstaben beginnen)",
"phrase-abbreviator_option_character_extraction": "Zeichenerfassung",
"phrase-abbreviator_option_character_extraction_description": "Methode zum Extrahieren von Zeichen aus jedem Wort",
"phrase-abbreviator_option_character_extraction_choice_single": "Nur erster Buchstabe",
"phrase-abbreviator_option_character_extraction_choice_dual": "Erster Buchstabe + nächster Vokal/Konsonant",
"phrase-abbreviator_option_language": "Sprache",
"phrase-abbreviator_option_language_description": "Sprache für Zahlwortkonvertierung",
"phrase-abbreviator_option_language_choice_en": "Englisch",
"phrase-abbreviator_option_language_choice_de": "Deutsch",
"phrase-abbreviator_option_language_choice_de_CH": "Schwyzerdütsch",
"phrase-abbreviator_option_language_choice_fr": "Französisch",
"phrase-abbreviator_option_language_choice_it": "Italienisch",
"phrase-abbreviator_option_language_choice_es": "Spanisch",
"phrase-abbreviator_option_language_choice_pt": "Portugiesisch",
"phrase-abbreviator_option_language_choice_ja": "Japanisch",
"phrase-abbreviator_option_language_choice_zh": "Chinesisch",
"phrase-abbreviator_option_add_number": "Zahl hinzufügen",
"phrase-abbreviator_option_add_number_description": "Zufällige Ziffern zum Passwort hinzufügen",
"phrase-abbreviator_option_number_digits": "Ziffernanzahl",
"phrase-abbreviator_option_number_digits_description": "Wie viele zufällige Ziffern hinzugefügt werden sollen (1-4)",
"phrase-abbreviator_option_number_position": "Zahlenposition",
"phrase-abbreviator_option_number_position_description": "Zahl vor oder nach dem Passwort platzieren",
"phrase-abbreviator_option_number_position_choice_prepend": "Vorher",
"phrase-abbreviator_option_number_position_choice_append": "Danach",
"phrase-abbreviator_option_ai_provider_choice_openai": "OpenAI",
"phrase-abbreviator_option_ai_provider_choice_claude": "Claude",
"phrase-abbreviator_option_ai_provider_choice_gemini": "Gemini",
"phrase-abbreviator_option_ai_provider_choice_perplexity": "Perplexity",
"phrase-abbreviator_option_ai_model_choice_gpt-3.5-turbo": "GPT-3.5 Turbo",
"phrase-abbreviator_option_ai_model_choice_gpt-4": "GPT-4",
"phrase-abbreviator_option_ai_model_choice_gpt-4-turbo": "GPT-4 Turbo",
"phrase-abbreviator_option_ai_model_choice_gpt-4o": "GPT-4 Omni",
"collapse_group_basic": "Basisoptionen",
"collapse_group_advanced": "Erweiterte Optionen",
"collapse_group_ai": "KI-Funktionen",
"collapse_group_expert": "Expertenoptionen",
"collapse_group_character_set": "Zeichensatz",
"copied": "Kopiert",
"pwgen_option_type_choice_custom": "Längeneinstellung verwenden",
"roulette": "Roulette",
"stop": "Stopp",
"roulette_interval": "Intervall (s)",
"roulette_slow_down": "Intervall erhöhen (langsamer)",
"roulette_speed_up": "Intervall verringern (schneller)"
}
+223
View File
@@ -0,0 +1,223 @@
{
"api_documentation": "API-Dokumentation",
"app_title": "Passwort-Generator",
"close": "Schliessen",
"copied_to_clipboard": "I d'Zwüscheablag kopiert!",
"copy": "Kopierä",
"copy_failed": "Kopierä fählgschlage",
"count": "Azahl",
"deutsch": "Deutsch",
"dice_rolls": "Würfelwürf",
"diceware_badge": "Sicherheits-Fokus",
"diceware_description": "Authentischi EFF Diceware-Methode mit kryptographisch sicherä Würfelwürf för maximali Sicherheit",
"diceware_option_show_dice_rolls": "Show Dice Rolls",
"diceware_option_show_dice_rolls_description": "Educational: display the dice rolls used to select each word",
"diceware_option_uppercase_first": "Capitalize First Word",
"diceware_option_uppercase_first_description": "Capitalize only the first word (traditional Diceware style)",
"diceware_option_words": "Words",
"diceware_option_words_description": "Minimum 6 words recommended for security (77.5 bits entropy)",
"diceware_title": "Diceware",
"english": "Englisch",
"entropy": "Entropie",
"length": "Länge",
"details": "Details",
"security_level": "Sicherheitsstufe",
"very_strong": "Sehr stark",
"strong": "Stark",
"adequate": "Ausreichend",
"weak": "Schwach",
"very_weak": "Sehr schwach",
"chars": "Zeichen",
"error_generating_passwords": "Fähler bim Generierä vo dä Passwörter",
"espanol": "Español",
"footer_text": "Footertext",
"francais": "Français",
"generate": "Generierä",
"generating": "Generierä...",
"high-entropy_description": "Kryptographisch sicheri Zuefallspasswörter mit maximalär Entropie",
"high-entropy_option_include_lowercase": "Include Lowercase",
"high-entropy_option_include_numbers": "Include Numbers",
"high-entropy_option_include_symbols": "Include Symbols",
"high-entropy_option_include_uppercase": "Include Uppercase",
"high-entropy_option_length": "Length",
"high-entropy_title": "High Entropy",
"info_spaces_replaced_with_underscores": "Leerziiche in gwählte Wörter werde dur Unterstrich (_) ersetzt.",
"info_words_with_spaces_excluded": "Wörter mit Leerziiche werde vo de Uswahl usgschlosse.",
"italiano": "Italiano",
"language": "Sprach:",
"loading_generators": "Generatorä wärded glade",
"loading_password_generators": "Passwort-Generatorä wärded glade...",
"native_group_explanation": "Native Uswahloptionä vom PwGen-Generator für verständlichi Passwörter.",
"native_pwgen": "Native Optionen",
"no_spaces_in_separator": "Kei Leerziiche im Trennzeiche verwende, wenn d Leerziichebehandlig uf „ändere“ oder „überspringe“ gsetzt isch.",
"passkey_description": "Apple-Style ussprechbari Passwörter mit integriertene Zeichenersetzige",
"passkey_option_syllables": "Syllables",
"passkey_title": "Passkey",
"password_generator": "Passwort-Generator",
"php_fallback": "PHP-Fallback-Code, wenn native pwgen nöd verfügbar isch.",
"portugues": "Portugiesisch",
"pwgen_badge": "Verbessert",
"pwgen_description": "Klassischi pwgen-Style Passwörter mit läsbari Muschter",
"pwgen_option_include_capitals": "Grossbuchstäbe einbeziehe",
"pwgen_option_include_capitals_description": "Generierti Passwörter enthalte Grossbuchstäbe.",
"pwgen_option_include_numbers": "Zahle einbeziehe",
"pwgen_option_include_numbers_description": "Generierti Passwörter enthalte Zifferä.",
"pwgen_option_length": "Länge",
"pwgen_option_length_description": "Total Passwortlänge inklusive Trennzeiche (mindescht 4 Zeichen)",
"pwgen_option_no_ambiguous": "Kei mehrdeutigi Zeichen",
"pwgen_option_no_ambiguous_description": "Zeiche wie 0, O, l, 1 usw. usschliesse, damit sie besser lesbar sind",
"pwgen_option_pattern_complexity": "Komplexes Muster",
"pwgen_option_pattern_complexity_description": "Steuert d Komplexität vo de generierte Muster.",
"pwgen_option_pattern_complexity_list_balanced": "Ausgewogene Komplexität",
"pwgen_option_pattern_complexity_list_complex": "Komplexi Muster",
"pwgen_option_pattern_complexity_list_simple": "Einfachi Muster",
"pwgen_option_secure_mode": "Sicherer Modus",
"pwgen_option_secure_mode_description": "Vollständig zufälligi Passwörter statt verständlichi Wörter generiere",
"pwgen_option_separator": "Trennzeiche",
"pwgen_option_separator_description": "Zeichen, mit dene Silbe getrennt werde. Wenn mehre Zeichen definiert sind, werde si dur zufällig gwählti Zeichen getrennt. Wenn leer, findet kei Trennung statt.",
"pwgen_option_syllable_count": "Silbezahl",
"pwgen_option_syllable_count_description": "Zahl vo de zu generierende Silbe (nur mit Trennzeiche verwendet)",
"pwgen_title": "PWGen",
"results": "Resultate",
"schwyzerduetsch": "Schwizerdütsch",
"secure_mode_disables_syllable_separation": "Sicherer Modus deaktiviert d Silbe-Trennung.",
"separator_method_info": "Info zur Trennmethode",
"single_space_allowed": "Einzel Leerziiche erlaubt",
"success": "Erfolg",
"syllable_group_explanation": "Erklärig zu Silbe",
"syllable_length_too_short": "Silbenlänge z kurz",
"syllable_length_very_long": "Silbenlänge sehr lang",
"warning_short_word_limits_reduces_choices": "Warnig: churi Wortlimit reducere d Uswahl",
"warning_short_words_reduce_memorability": "Warnig: churi Wörter verringe d Merkbarkeit",
"xkcdpass_badge": "Märkbarkeits-Fokus",
"xkcdpass_description": "xkcdpass-Beschriibig",
"xkcdpass_option_addNumber": "Add Number",
"xkcdpass_option_max_word_length": "Maximal Längi",
"xkcdpass_option_max_word_length_description": "0 = no limit, >0 = maximum characters per word",
"xkcdpass_option_numberPosition": "Number Position",
"xkcdpass_option_numberPosition_list_beginning": "Am Afang",
"xkcdpass_option_numberPosition_list_end": "Am Ente",
"xkcdpass_option_numberPosition_list_random": "Zufälligi Position",
"xkcdpass_option_separator": "Trennzeiche",
"xkcdpass_option_separator_description": "Single character or character set (random selection). Spaces excluded from multi-character sets.",
"xkcdpass_option_space_handling": "Leerziiche behandle",
"xkcdpass_option_space_handling_description": "Behandlig vo Wörter mit Zwüschehräum",
"xkcdpass_option_space_handling_list_allow": "Erlaube (Leerziiche behalte)",
"xkcdpass_option_space_handling_list_modify": "Ändere (dur _ ersetze)",
"xkcdpass_option_space_handling_list_skip": "Überspringe (nit wähle)",
"xkcdpass_option_wordlist": "Wörterliste",
"xkcdpass_option_words": "Wörter",
"xkcdpass_option_words_description": "Number of words (minimal 2)",
"xkcdpass_title": "XKCD Pass",
"phraseabbreviator_number_zero": "null",
"phraseabbreviator_number_one": "eins",
"phraseabbreviator_number_two": "zwei",
"phraseabbreviator_number_three": "drei",
"phraseabbreviator_number_four": "vier",
"phraseabbreviator_number_five": "fünf",
"phraseabbreviator_number_six": "sechs",
"phraseabbreviator_number_seven": "sieben",
"phraseabbreviator_number_eight": "acht",
"phraseabbreviator_number_nine": "neun",
"phraseabbreviator_number_ten": "zehn",
"phraseabbreviator_number_eleven": "elf",
"phraseabbreviator_number_twelve": "zwölf",
"phraseabbreviator_number_thirteen": "dreizehn",
"phraseabbreviator_number_fourteen": "vierzehn",
"phraseabbreviator_number_fifteen": "fünfzehn",
"phraseabbreviator_number_sixteen": "sechzehn",
"phraseabbreviator_number_seventeen": "siebzehn",
"phraseabbreviator_number_eighteen": "achtzehn",
"phraseabbreviator_number_nineteen": "neunzehn",
"phraseabbreviator_number_twenty": "zwanzig",
"xkcdpass_option_use_ai_wordlist": "KI-Wörterliste verwende",
"xkcdpass_option_use_ai_wordlist_description": "Erzeig eine personalisierte Wörterliste mit em KI-Modell",
"xkcdpass_option_ai_wordlist_prompt": "KI-Wörterliste-Prompt",
"xkcdpass_option_ai_wordlist_prompt_description": "Vervollständigi de Prompt: \"Erstell e List vo X Wörter mit mindescht 5 Zeichen, wo...\"",
"xkcdpass_option_ai_provider": "KI-Anbieter",
"xkcdpass_option_ai_provider_description": "Welcher LLM-Anbieter verwendet werde soll",
"phrase-abbreviator_option_use_ai_sentence": "KI-Satz verwende",
"phrase-abbreviator_option_use_ai_sentence_description": "Lass d KI en Satz us dim Prompt generiere",
"phrase-abbreviator_option_ai_prompt": "KI-Prompt",
"phrase-abbreviator_option_ai_prompt_description": "Prompt für d KI, um en einprägsame Satz z generiere (mindescht 12 Wörter)",
"phrase-abbreviator_option_ai_provider": "KI-Anbieter",
"phrase-abbreviator_option_ai_provider_description": "Welcher LLM-Anbieter für d Satzgenerierig verwendet werde soll",
"phrase-abbreviator_option_ai_model": "KI-Modell",
"phrase-abbreviator_option_ai_model_description": "Welches KI-Modell verwendet werde soll",
"high-entropy_option_length_description": "Total Passwortlänge",
"high-entropy_option_include_uppercase_description": "Grossbuchstaabe einbeziege (A-Z)",
"high-entropy_option_include_lowercase_description": "Chliibuechsstaabe einbeziege (a-z)",
"high-entropy_option_include_numbers_description": "Zifferä einbeziege (0-9)",
"high-entropy_option_include_symbols_description": "Sonderzeiche einbeziege (z. B. !@#$%)",
"xkcdpass_option_wordlist_description": "Wörterliste für d Passphrase",
"xkcdpass_option_wordlist_choice_en": "Englisch",
"xkcdpass_option_wordlist_choice_de": "Deutsch",
"xkcdpass_option_wordlist_choice_fr": "Französisch",
"xkcdpass_option_wordlist_choice_it": "Italienisch",
"xkcdpass_option_wordlist_choice_es": "Spanisch",
"xkcdpass_option_space_handling_choice_allow": "Erlaube",
"xkcdpass_option_space_handling_choice_skip": "Überspringe",
"xkcdpass_option_space_handling_choice_modify": "Ändere",
"xkcdpass_option_add_number": "Zahl hinzuefüege",
"xkcdpass_option_add_number_description": "Zufälligi Zahl zur Passphrase hinzuefüege",
"xkcdpass_option_number_position": "Zahlposition",
"xkcdpass_option_number_position_description": "Wo d zufälligi Zahl platziert werde söll",
"xkcdpass_option_number_position_choice_beginning": "Am Afang",
"xkcdpass_option_number_position_choice_end": "Am Ände",
"xkcdpass_option_number_position_choice_random": "Zufälligi Position",
"xkcdpass_option_ai_provider_choice_openai": "OpenAI",
"xkcdpass_option_ai_provider_choice_claude": "Claude",
"xkcdpass_option_ai_provider_choice_gemini": "Gemini",
"xkcdpass_option_ai_provider_choice_perplexity": "Perplexity",
"pwgen_option_type": "Längi-Voreistellig",
"pwgen_option_type_description": "Optionali Längi-Voreistellig. Wenn 8/12/16 Zeiche gwählt sind, überschriibt sie de obigi Längiwärt.",
"pwgen_option_type_choice_8-char": "8 Zeichen",
"pwgen_option_type_choice_12-char": "12 Zeichen",
"pwgen_option_type_choice_16-char": "16 Zeichen",
"passkey_option_syllables_description": "Zahl vo de verständliche Silbe im Passkey",
"phrase-abbreviator_option_sentence": "Satz",
"phrase-abbreviator_option_sentence_description": "Vom Benutzer igäh Satz (mindescht 8 Wörter, muess mit Grossbuächstaabe afange)",
"phrase-abbreviator_option_character_extraction": "Zeichenextraktion",
"phrase-abbreviator_option_character_extraction_description": "Methode zum Extrahiere vo Zeiche us jedem Wort",
"phrase-abbreviator_option_character_extraction_choice_single": "Nur erster Buächstab",
"phrase-abbreviator_option_character_extraction_choice_dual": "Erster Buächstab + nächster Vokal/Konsonant",
"phrase-abbreviator_option_language": "Sproch",
"phrase-abbreviator_option_language_description": "Sproch für Ziffere-Wort-Umsetzung",
"phrase-abbreviator_option_language_choice_en": "Englisch",
"phrase-abbreviator_option_language_choice_de": "Deutsch",
"phrase-abbreviator_option_language_choice_de_CH": "Schwyzerdütsch",
"phrase-abbreviator_option_language_choice_fr": "Französisch",
"phrase-abbreviator_option_language_choice_it": "Italienisch",
"phrase-abbreviator_option_language_choice_es": "Spanisch",
"phrase-abbreviator_option_language_choice_pt": "Portugiesisch",
"phrase-abbreviator_option_language_choice_ja": "Japanisch",
"phrase-abbreviator_option_language_choice_zh": "Chinesisch",
"phrase-abbreviator_option_add_number": "Zahl hinzuefüege",
"phrase-abbreviator_option_add_number_description": "Zufälligi Zifferä zum Passwort hinzuefüege",
"phrase-abbreviator_option_number_digits": "Zifferäzahl",
"phrase-abbreviator_option_number_digits_description": "Wieviel zufälligi Zifferä solle hinzugefügt werde (1-4)",
"phrase-abbreviator_option_number_position": "Zahlposition",
"phrase-abbreviator_option_number_position_description": "Zahl vor oder nach em Passwort platziere",
"phrase-abbreviator_option_number_position_choice_prepend": "Vorher",
"phrase-abbreviator_option_number_position_choice_append": "Darnach",
"phrase-abbreviator_option_ai_provider_choice_openai": "OpenAI",
"phrase-abbreviator_option_ai_provider_choice_claude": "Claude",
"phrase-abbreviator_option_ai_provider_choice_gemini": "Gemini",
"phrase-abbreviator_option_ai_provider_choice_perplexity": "Perplexity",
"phrase-abbreviator_option_ai_model_choice_gpt-3.5-turbo": "GPT-3.5 Turbo",
"phrase-abbreviator_option_ai_model_choice_gpt-4": "GPT-4",
"phrase-abbreviator_option_ai_model_choice_gpt-4-turbo": "GPT-4 Turbo",
"phrase-abbreviator_option_ai_model_choice_gpt-4o": "GPT-4 Omni",
"collapse_group_basic": "Basisoptione",
"collapse_group_advanced": "Erwiitereti Optione",
"collapse_group_ai": "KI-Funktione",
"collapse_group_expert": "Experte-Optione",
"collapse_group_character_set": "Zeichesatz",
"copied": "Kopiert",
"pwgen_option_type_choice_custom": "Längi-Istellig verwände",
"roulette": "Roulette",
"stop": "Stopp",
"roulette_interval": "Intervall (s)",
"roulette_slow_down": "Intervall erhöche (langsamer)",
"roulette_speed_up": "Intervall verringerä (schneller)"
}
+223
View File
@@ -0,0 +1,223 @@
{
"api_documentation": "API Documentation",
"app_title": "Password Generator",
"close": "close",
"copied_to_clipboard": "Copied to clipboard!",
"copy": "Copy",
"copy_failed": "Copy failed",
"count": "Count",
"deutsch": "Deutsch",
"dice_rolls": "Dice Rolls",
"diceware_badge": "Security Focus",
"diceware_description": "Authentic EFF Diceware method using cryptographically secure dice rolls for maximum security",
"diceware_option_show_dice_rolls": "Show Dice Rolls",
"diceware_option_show_dice_rolls_description": "Educational: display the dice rolls used to select each word",
"diceware_option_uppercase_first": "Capitalize First Word",
"diceware_option_uppercase_first_description": "Capitalize only the first word (traditional Diceware style)",
"diceware_option_words": "Words",
"diceware_option_words_description": "Minimum 6 words recommended for recommended security.",
"diceware_title": "Diceware",
"english": "English",
"entropy": "Entropy",
"length": "Length",
"details": "Details",
"security_level": "Security level",
"very_strong": "Very strong",
"strong": "Strong",
"adequate": "Adequate",
"weak": "Weak",
"very_weak": "Very weak",
"chars": "chars",
"error_generating_passwords": "Error generating passwords",
"espanol": "Español",
"footer_text": "A tool to generate secure password with various algorithms. Source code available on GitHub.",
"francais": "Français",
"generate": "Generate",
"generating": "Generating …",
"high-entropy_description": "Cryptographically secure random passwords with maximum entropy",
"high-entropy_option_include_lowercase": "Include Lowercase",
"high-entropy_option_include_numbers": "Include Numbers",
"high-entropy_option_include_symbols": "Include Symbols",
"high-entropy_option_include_uppercase": "Include Uppercase",
"high-entropy_option_length": "Length",
"high-entropy_title": "High Entropy",
"info_spaces_replaced_with_underscores": "Spaces in selected words are replaced with underscores (_).",
"info_words_with_spaces_excluded": "Words containing spaces are excluded from selection.",
"italiano": "Italiano",
"language": "Language:",
"loading_generators": "Loading generators …",
"loading_password_generators": "Loading password generators …",
"native_group_explanation": "PwGen native generator's options for generating pronounceable passwords.",
"native_pwgen": "Native Options",
"no_spaces_in_separator": "Don't use spaces in the separator when space handling is set to 'modify' or 'skip'.",
"passkey_description": "Apple-style pronounceable passwords with integrated character substitutions",
"passkey_option_syllables": "Syllables",
"passkey_title": "Passkey",
"password_generator": "Password Generator",
"php_fallback": "Fallback code in PHP when native pwgen is not available.",
"portugues": "Portugues",
"pwgen_badge": "Enhanced",
"pwgen_description": "Classic pwgen-style passwords with readable patterns",
"pwgen_option_include_capitals": "Include Capitals",
"pwgen_option_include_capitals_description": "Generated passwords will contain uppercase letters.",
"pwgen_option_include_numbers": "Include Numbers",
"pwgen_option_include_numbers_description": "Generated passwords will contain digits.",
"pwgen_option_length": "Length",
"pwgen_option_length_description": "Total password length including separators (minimum 4 characters)",
"pwgen_option_no_ambiguous": "No Ambiguous Characters",
"pwgen_option_no_ambiguous_description": "Exclude characters like 0, O, l, 1, etc. to improve readability",
"pwgen_option_pattern_complexity": "Complex Pattern",
"pwgen_option_pattern_complexity_description": "Control the complexity of generated patterns.",
"pwgen_option_pattern_complexity_list_balanced": "Balanced complexity",
"pwgen_option_pattern_complexity_list_complex": "Complex patterns",
"pwgen_option_pattern_complexity_list_simple": "Simple patterns",
"pwgen_option_secure_mode": "Secure Mode",
"pwgen_option_secure_mode_description": "Generate fully random passwords instead of pronounceable ones",
"pwgen_option_separator": "Separator",
"pwgen_option_separator_description": "Character(s) to separate syllables. If multiple characters are defined, parts are separate by a randomly selected character. If this is empty, no separation is done.",
"pwgen_option_syllable_count": "Syllable Count",
"pwgen_option_syllable_count_description": "Number of syllables to generate (only used with separator)",
"pwgen_title": "PwGen",
"results": "Results",
"schwyzerduetsch": "Schwyzerdütsch",
"secure_mode_disables_syllable_separation": "Secure mode disables syllable separation.",
"separator_method_info": "Separator method info",
"single_space_allowed": "Single Space Allowed",
"success": "Success",
"syllable_group_explanation": "syllable_group_explanation",
"syllable_length_too_short": "syllable_length_too_short",
"syllable_length_very_long": "syllable_length_very_long",
"warning_short_word_limits_reduces_choices": "warning_short_word_limits_reduces_choices",
"warning_short_words_reduce_memorability": "warning_short_words_reduce_memorability",
"xkcdpass_badge": "Memorability Focus",
"xkcdpass_description": "Memorable passphrases using word combinations with enhanced filtering and separator options",
"xkcdpass_option_addNumber": "Add Number",
"xkcdpass_option_max_word_length": "Max Word Length",
"xkcdpass_option_max_word_length_description": "0 = no limit, >0 = maximum characters per word",
"xkcdpass_option_numberPosition": "Number Position",
"xkcdpass_option_numberPosition_list_beginning": "At Beginning",
"xkcdpass_option_numberPosition_list_end": "At End",
"xkcdpass_option_numberPosition_list_random": "Random Position",
"xkcdpass_option_separator": "Separator",
"xkcdpass_option_separator_description": "Single character or character set (random selection). Spaces excluded from multi-character sets.",
"xkcdpass_option_space_handling": "Space Handling",
"xkcdpass_option_space_handling_description": "How to handle words containing spaces",
"xkcdpass_option_space_handling_list_allow": "Allow (keep spaces)",
"xkcdpass_option_space_handling_list_modify": "Modify (replace with _)",
"xkcdpass_option_space_handling_list_skip": "Skip (don\"t select)",
"xkcdpass_option_wordlist": "wordlist",
"xkcdpass_option_words": "Words",
"xkcdpass_option_words_description": "Number of words (minimal 2)",
"xkcdpass_title": "xkcdpass",
"phraseabbreviator_number_zero": "zero",
"phraseabbreviator_number_one": "one",
"phraseabbreviator_number_two": "two",
"phraseabbreviator_number_three": "three",
"phraseabbreviator_number_four": "four",
"phraseabbreviator_number_five": "five",
"phraseabbreviator_number_six": "six",
"phraseabbreviator_number_seven": "seven",
"phraseabbreviator_number_eight": "eight",
"phraseabbreviator_number_nine": "nine",
"phraseabbreviator_number_ten": "ten",
"phraseabbreviator_number_eleven": "eleven",
"phraseabbreviator_number_twelve": "twelve",
"phraseabbreviator_number_thirteen": "thirteen",
"phraseabbreviator_number_fourteen": "fourteen",
"phraseabbreviator_number_fifteen": "fifteen",
"phraseabbreviator_number_sixteen": "sixteen",
"phraseabbreviator_number_seventeen": "seventeen",
"phraseabbreviator_number_eighteen": "eighteen",
"phraseabbreviator_number_nineteen": "nineteen",
"phraseabbreviator_number_twenty": "twenty",
"xkcdpass_option_use_ai_wordlist": "Use AI Wordlist",
"xkcdpass_option_use_ai_wordlist_description": "Generate a custom wordlist using an AI model",
"xkcdpass_option_ai_wordlist_prompt": "AI Wordlist Prompt",
"xkcdpass_option_ai_wordlist_prompt_description": "Complete the prompt: \"Create a list of X words of at least 5 characters, which...\"",
"xkcdpass_option_ai_provider": "AI Provider",
"xkcdpass_option_ai_provider_description": "Which LLM provider to use",
"phrase-abbreviator_option_use_ai_sentence": "Use AI Sentence",
"phrase-abbreviator_option_use_ai_sentence_description": "Let AI generate a sentence from your prompt",
"phrase-abbreviator_option_ai_prompt": "AI Prompt",
"phrase-abbreviator_option_ai_prompt_description": "Prompt for AI to generate a memorable sentence (minimum 12 words)",
"phrase-abbreviator_option_ai_provider": "AI Provider",
"phrase-abbreviator_option_ai_provider_description": "Which LLM provider to use for sentence generation",
"phrase-abbreviator_option_ai_model": "AI Model",
"phrase-abbreviator_option_ai_model_description": "Which AI model to use",
"high-entropy_option_length_description": "Total length of the generated password",
"high-entropy_option_include_uppercase_description": "Include uppercase letters (A-Z)",
"high-entropy_option_include_lowercase_description": "Include lowercase letters (a-z)",
"high-entropy_option_include_numbers_description": "Include numbers (0-9)",
"high-entropy_option_include_symbols_description": "Include symbols (e.g., !@#$%)",
"xkcdpass_option_wordlist_description": "Wordlist to use for generating the passphrase",
"xkcdpass_option_wordlist_choice_en": "English",
"xkcdpass_option_wordlist_choice_de": "Deutsch",
"xkcdpass_option_wordlist_choice_fr": "Français",
"xkcdpass_option_wordlist_choice_it": "Italiano",
"xkcdpass_option_wordlist_choice_es": "Español",
"xkcdpass_option_space_handling_choice_allow": "Allow",
"xkcdpass_option_space_handling_choice_skip": "Skip",
"xkcdpass_option_space_handling_choice_modify": "Modify",
"xkcdpass_option_add_number": "Add Number",
"xkcdpass_option_add_number_description": "Add random number to the passphrase",
"xkcdpass_option_number_position": "Number Position",
"xkcdpass_option_number_position_description": "Where to place the random number",
"xkcdpass_option_number_position_choice_beginning": "At Beginning",
"xkcdpass_option_number_position_choice_end": "At End",
"xkcdpass_option_number_position_choice_random": "Random Position",
"xkcdpass_option_ai_provider_choice_openai": "OpenAI",
"xkcdpass_option_ai_provider_choice_claude": "Claude",
"xkcdpass_option_ai_provider_choice_gemini": "Gemini",
"xkcdpass_option_ai_provider_choice_perplexity": "Perplexity",
"pwgen_option_type": "Length Preset",
"pwgen_option_type_description": "Optional preset length. If set to 8/12/16 characters, it overrides the Length value above.",
"pwgen_option_type_choice_8-char": "8 characters",
"pwgen_option_type_choice_12-char": "12 characters",
"pwgen_option_type_choice_16-char": "16 characters",
"passkey_option_syllables_description": "Number of pronounceable syllables in the passkey",
"phrase-abbreviator_option_sentence": "Sentence",
"phrase-abbreviator_option_sentence_description": "User-supplied sentence (minimum 8 words, must start with capital letter)",
"phrase-abbreviator_option_character_extraction": "Character Extraction",
"phrase-abbreviator_option_character_extraction_description": "Method for extracting characters from each word",
"phrase-abbreviator_option_character_extraction_choice_single": "First letter only",
"phrase-abbreviator_option_character_extraction_choice_dual": "First letter + next vowel/consonant",
"phrase-abbreviator_option_language": "Language",
"phrase-abbreviator_option_language_description": "Language for number word conversion",
"phrase-abbreviator_option_language_choice_en": "English",
"phrase-abbreviator_option_language_choice_de": "Deutsch",
"phrase-abbreviator_option_language_choice_de_CH": "Schwyzerdütsch",
"phrase-abbreviator_option_language_choice_fr": "Français",
"phrase-abbreviator_option_language_choice_it": "Italiano",
"phrase-abbreviator_option_language_choice_es": "Español",
"phrase-abbreviator_option_language_choice_pt": "Português",
"phrase-abbreviator_option_language_choice_ja": "日本語",
"phrase-abbreviator_option_language_choice_zh": "中文",
"phrase-abbreviator_option_add_number": "Add Number",
"phrase-abbreviator_option_add_number_description": "Add random digits to the password",
"phrase-abbreviator_option_number_digits": "Digit Count",
"phrase-abbreviator_option_number_digits_description": "How many random digits to add (1-4)",
"phrase-abbreviator_option_number_position": "Number Position",
"phrase-abbreviator_option_number_position_description": "Place number before or after the password",
"phrase-abbreviator_option_number_position_choice_prepend": "Before",
"phrase-abbreviator_option_number_position_choice_append": "After",
"phrase-abbreviator_option_ai_provider_choice_openai": "OpenAI",
"phrase-abbreviator_option_ai_provider_choice_claude": "Claude",
"phrase-abbreviator_option_ai_provider_choice_gemini": "Gemini",
"phrase-abbreviator_option_ai_provider_choice_perplexity": "Perplexity",
"phrase-abbreviator_option_ai_model_choice_gpt-3.5-turbo": "GPT-3.5 Turbo",
"phrase-abbreviator_option_ai_model_choice_gpt-4": "GPT-4",
"phrase-abbreviator_option_ai_model_choice_gpt-4-turbo": "GPT-4 Turbo",
"phrase-abbreviator_option_ai_model_choice_gpt-4o": "GPT-4 Omni",
"collapse_group_basic": "Basic Options",
"collapse_group_advanced": "Advanced Options",
"collapse_group_ai": "AI Features",
"collapse_group_expert": "Expert Options",
"collapse_group_character_set": "Character Set",
"copied": "Copied",
"pwgen_option_type_choice_custom": "Use length setting",
"roulette": "Roulette",
"stop": "Stop",
"roulette_interval": "Interval (s)",
"roulette_slow_down": "Increase interval (slower)",
"roulette_speed_up": "Decrease interval (faster)"
}
+223
View File
@@ -0,0 +1,223 @@
{
"api_documentation": "Documentación de API",
"app_title": "app_title",
"close": "Cerrar",
"copied_to_clipboard": "Copied to clipboard!",
"copy": "Copiar",
"copy_failed": "Error al copiar",
"count": "Count",
"deutsch": "Deutsch",
"dice_rolls": "Lanzamientos de dados",
"diceware_badge": "Enfoque en seguridad",
"diceware_description": "diceware description",
"diceware_option_show_dice_rolls": "Show Dice Rolls",
"diceware_option_show_dice_rolls_description": "Educativo: muestra los lanzamientos de dados usados para seleccionar cada palabra",
"diceware_option_uppercase_first": "Capitalize First Word",
"diceware_option_uppercase_first_description": "Solo capitaliza la primera palabra (estilo Diceware tradicional)",
"diceware_option_words": "Words",
"diceware_option_words_description": "Se recomienda un mínimo de 6 palabras para mayor seguridad (77,5 bits de entropía).",
"diceware_title": "Diceware",
"english": "English",
"entropy": "Entropía",
"length": "Longitud",
"details": "Detalles",
"security_level": "Nivel de seguridad",
"very_strong": "Muy fuerte",
"strong": "Fuerte",
"adequate": "Adecuado",
"weak": "Débil",
"very_weak": "Muy débil",
"chars": "caracteres",
"error_generating_passwords": "Error al generar contraseñas",
"espanol": "Español",
"footer_text": "footer_text",
"francais": "Français",
"generate": "Generar",
"generating": "Generating...",
"high-entropy_description": "high-entropy description",
"high-entropy_option_include_lowercase": "Include Lowercase",
"high-entropy_option_include_numbers": "Include Numbers",
"high-entropy_option_include_symbols": "Include Symbols",
"high-entropy_option_include_uppercase": "Include Uppercase",
"high-entropy_option_length": "Length",
"high-entropy_title": "High Entropy",
"info_spaces_replaced_with_underscores": "Los espacios en las palabras seleccionadas se reemplazan con guiones bajos (_).",
"info_words_with_spaces_excluded": "Las palabras que contienen espacios se excluyen de la selección.",
"italiano": "Italiano",
"language": "Language:",
"loading_generators": "loading_generators",
"loading_password_generators": "Loading password generators...",
"native_group_explanation": "Opciones nativas del generador PwGen para crear contraseñas pronunciables.",
"native_pwgen": "Opciones nativas",
"no_spaces_in_separator": "No uses espacios en el separador cuando la gestión de espacios esté configurada como “modificar” o “omitir”.",
"passkey_description": "passkey description",
"passkey_option_syllables": "Syllables",
"passkey_title": "Passkey",
"password_generator": "Generador de contraseñas",
"php_fallback": "Código de respaldo en PHP cuando no está disponible pwgen nativo.",
"portugues": "Portugués",
"pwgen_badge": "Mejorado",
"pwgen_description": "pwgen description",
"pwgen_option_include_capitals": "Incluir mayúsculas",
"pwgen_option_include_capitals_description": "Las contraseñas generadas contendrán letras mayúsculas.",
"pwgen_option_include_numbers": "Incluir números",
"pwgen_option_include_numbers_description": "Las contraseñas generadas contendrán dígitos.",
"pwgen_option_length": "Longitud",
"pwgen_option_length_description": "Longitud total de la contraseña, incluidos los separadores (mínimo 4 caracteres)",
"pwgen_option_no_ambiguous": "Sin caracteres ambiguos",
"pwgen_option_no_ambiguous_description": "Excluye caracteres como 0, O, l, 1, etc. para mejorar la legibilidad",
"pwgen_option_pattern_complexity": "Patrón complejo",
"pwgen_option_pattern_complexity_description": "Controla la complejidad de los patrones generados.",
"pwgen_option_pattern_complexity_list_balanced": "Complejidad equilibrada",
"pwgen_option_pattern_complexity_list_complex": "Patrones complejos",
"pwgen_option_pattern_complexity_list_simple": "Patrones simples",
"pwgen_option_secure_mode": "Modo seguro",
"pwgen_option_secure_mode_description": "Genera contraseñas totalmente aleatorias en lugar de palabras pronunciables",
"pwgen_option_separator": "Separador",
"pwgen_option_separator_description": "Caracter(es) para separar sílabas. Si se definen varios, se separan con un carácter elegido aleatoriamente. Si está vacío, no se realiza ninguna separación.",
"pwgen_option_syllable_count": "Número de sílabas",
"pwgen_option_syllable_count_description": "Número de sílabas a generar (solo se usa con separador)",
"pwgen_title": "PWGen",
"results": "Resultados",
"schwyzerduetsch": "Schwyzerdütsch",
"secure_mode_disables_syllable_separation": "El modo seguro desactiva la separación por sílabas.",
"separator_method_info": "Información del método de separación",
"single_space_allowed": "Se permite un solo espacio",
"success": "Éxito",
"syllable_group_explanation": "Explicación de las sílabas",
"syllable_length_too_short": "La sílaba es demasiado corta",
"syllable_length_very_long": "La sílaba es muy larga",
"warning_short_word_limits_reduces_choices": "Advertencia: los límites cortos de palabras reducen las opciones",
"warning_short_words_reduce_memorability": "Advertencia: las palabras cortas reducen la memorización",
"xkcdpass_badge": "Enfoque en la memorización",
"xkcdpass_description": "xkcdpass description",
"xkcdpass_option_addNumber": "Add Number",
"xkcdpass_option_max_word_length": "Longitud máxima de palabra",
"xkcdpass_option_max_word_length_description": "0 = sin límite, >0 = máximo de caracteres por palabra",
"xkcdpass_option_numberPosition": "Number Position",
"xkcdpass_option_numberPosition_list_beginning": "Al principio",
"xkcdpass_option_numberPosition_list_end": "Al final",
"xkcdpass_option_numberPosition_list_random": "Posición aleatoria",
"xkcdpass_option_separator": "Separador",
"xkcdpass_option_separator_description": "Carácter único o conjunto de caracteres (selección aleatoria). Los espacios se excluyen en conjuntos de varios caracteres.",
"xkcdpass_option_space_handling": "Manejo de espacios",
"xkcdpass_option_space_handling_description": "Cómo manejar las palabras que contienen espacios",
"xkcdpass_option_space_handling_list_allow": "Permitir (mantener espacios)",
"xkcdpass_option_space_handling_list_modify": "Modificar (reemplazar con _)",
"xkcdpass_option_space_handling_list_skip": "Omitir (no seleccionar)",
"xkcdpass_option_wordlist": "Lista de palabras",
"xkcdpass_option_words": "Palabras",
"xkcdpass_option_words_description": "Número de palabras (mínimo 2)",
"xkcdpass_title": "XKCD Pass",
"phraseabbreviator_number_zero": "cero",
"phraseabbreviator_number_one": "uno",
"phraseabbreviator_number_two": "dos",
"phraseabbreviator_number_three": "tres",
"phraseabbreviator_number_four": "cuatro",
"phraseabbreviator_number_five": "cinco",
"phraseabbreviator_number_six": "seis",
"phraseabbreviator_number_seven": "siete",
"phraseabbreviator_number_eight": "ocho",
"phraseabbreviator_number_nine": "nueve",
"phraseabbreviator_number_ten": "diez",
"phraseabbreviator_number_eleven": "once",
"phraseabbreviator_number_twelve": "doce",
"phraseabbreviator_number_thirteen": "trece",
"phraseabbreviator_number_fourteen": "catorce",
"phraseabbreviator_number_fifteen": "quince",
"phraseabbreviator_number_sixteen": "dieciséis",
"phraseabbreviator_number_seventeen": "diecisiete",
"phraseabbreviator_number_eighteen": "dieciocho",
"phraseabbreviator_number_nineteen": "diecinueve",
"phraseabbreviator_number_twenty": "veinte",
"xkcdpass_option_use_ai_wordlist": "Usar lista de palabras con IA",
"xkcdpass_option_use_ai_wordlist_description": "Genera una lista de palabras personalizada con un modelo de IA",
"xkcdpass_option_ai_wordlist_prompt": "Indicador de lista de palabras de IA",
"xkcdpass_option_ai_wordlist_prompt_description": "Completa la instrucción: \"Crea una lista de X palabras de al menos 5 caracteres, que...\"",
"xkcdpass_option_ai_provider": "Proveedor de IA",
"xkcdpass_option_ai_provider_description": "Qué proveedor LLM usar",
"phrase-abbreviator_option_use_ai_sentence": "Usar frase de IA",
"phrase-abbreviator_option_use_ai_sentence_description": "Permite que la IA genere una frase a partir de tu instrucción",
"phrase-abbreviator_option_ai_prompt": "Indicador de IA",
"phrase-abbreviator_option_ai_prompt_description": "Instrucción para que la IA genere una frase memorable (mínimo 12 palabras)",
"phrase-abbreviator_option_ai_provider": "Proveedor de IA",
"phrase-abbreviator_option_ai_provider_description": "Qué proveedor LLM usar para generar la frase",
"phrase-abbreviator_option_ai_model": "Modelo de IA",
"phrase-abbreviator_option_ai_model_description": "Qué modelo de IA usar",
"high-entropy_option_length_description": "Total length of the generated password",
"high-entropy_option_include_uppercase_description": "Include uppercase letters (A-Z)",
"high-entropy_option_include_lowercase_description": "Include lowercase letters (a-z)",
"high-entropy_option_include_numbers_description": "Include numbers (0-9)",
"high-entropy_option_include_symbols_description": "Include symbols (e.g., !@#$%)",
"xkcdpass_option_wordlist_description": "Wordlist to use for generating the passphrase",
"xkcdpass_option_wordlist_choice_en": "English",
"xkcdpass_option_wordlist_choice_de": "Deutsch",
"xkcdpass_option_wordlist_choice_fr": "Français",
"xkcdpass_option_wordlist_choice_it": "Italiano",
"xkcdpass_option_wordlist_choice_es": "Español",
"xkcdpass_option_space_handling_choice_allow": "Permitir",
"xkcdpass_option_space_handling_choice_skip": "Omitir",
"xkcdpass_option_space_handling_choice_modify": "Modificar",
"xkcdpass_option_add_number": "Agregar número",
"xkcdpass_option_add_number_description": "Add random number to the passphrase",
"xkcdpass_option_number_position": "Posición del número",
"xkcdpass_option_number_position_description": "Where to place the random number",
"xkcdpass_option_number_position_choice_beginning": "Al principio",
"xkcdpass_option_number_position_choice_end": "Al final",
"xkcdpass_option_number_position_choice_random": "Posición aleatoria",
"xkcdpass_option_ai_provider_choice_openai": "OpenAI",
"xkcdpass_option_ai_provider_choice_claude": "Claude",
"xkcdpass_option_ai_provider_choice_gemini": "Gemini",
"xkcdpass_option_ai_provider_choice_perplexity": "Perplexity",
"pwgen_option_type": "Preajuste de longitud",
"pwgen_option_type_description": "Preajuste heredado de longitud opcional. Si se establece en 8/12/16 caracteres, sobrescribe el valor de Longitud de arriba.",
"pwgen_option_type_choice_8-char": "8 caracteres",
"pwgen_option_type_choice_12-char": "12 caracteres",
"pwgen_option_type_choice_16-char": "16 caracteres",
"passkey_option_syllables_description": "Number of pronounceable syllables in the passkey",
"phrase-abbreviator_option_sentence": "Frase",
"phrase-abbreviator_option_sentence_description": "User-supplied sentence (minimum 8 words, must start with capital letter)",
"phrase-abbreviator_option_character_extraction": "Extracción de caracteres",
"phrase-abbreviator_option_character_extraction_description": "Method for extracting characters from each word",
"phrase-abbreviator_option_character_extraction_choice_single": "Solo la primera letra",
"phrase-abbreviator_option_character_extraction_choice_dual": "Primera letra + siguiente vocal/consonante",
"phrase-abbreviator_option_language": "Idioma",
"phrase-abbreviator_option_language_description": "Language for number word conversion",
"phrase-abbreviator_option_language_choice_en": "English",
"phrase-abbreviator_option_language_choice_de": "Alemán",
"phrase-abbreviator_option_language_choice_de_CH": "Suizo alemán",
"phrase-abbreviator_option_language_choice_fr": "Francés",
"phrase-abbreviator_option_language_choice_it": "Italiano",
"phrase-abbreviator_option_language_choice_es": "Español",
"phrase-abbreviator_option_language_choice_pt": "Portugués",
"phrase-abbreviator_option_language_choice_ja": "Japonés",
"phrase-abbreviator_option_language_choice_zh": "Chino",
"phrase-abbreviator_option_add_number": "Agregar número",
"phrase-abbreviator_option_add_number_description": "Agregar dígitos aleatorios a la contraseña",
"phrase-abbreviator_option_number_digits": "Cantidad de dígitos",
"phrase-abbreviator_option_number_digits_description": "Cuántos dígitos aleatorios agregar (1-4)",
"phrase-abbreviator_option_number_position": "Posición del número",
"phrase-abbreviator_option_number_position_description": "Colocar el número antes o después de la contraseña",
"phrase-abbreviator_option_number_position_choice_prepend": "Antes",
"phrase-abbreviator_option_number_position_choice_append": "Después",
"phrase-abbreviator_option_ai_provider_choice_openai": "OpenAI",
"phrase-abbreviator_option_ai_provider_choice_claude": "Claude",
"phrase-abbreviator_option_ai_provider_choice_gemini": "Gemini",
"phrase-abbreviator_option_ai_provider_choice_perplexity": "Perplexity",
"phrase-abbreviator_option_ai_model_choice_gpt-3.5-turbo": "GPT-3.5 Turbo",
"phrase-abbreviator_option_ai_model_choice_gpt-4": "GPT-4",
"phrase-abbreviator_option_ai_model_choice_gpt-4-turbo": "GPT-4 Turbo",
"phrase-abbreviator_option_ai_model_choice_gpt-4o": "GPT-4 Omni",
"collapse_group_basic": "Opciones básicas",
"collapse_group_advanced": "Opciones avanzadas",
"collapse_group_ai": "Funciones de IA",
"collapse_group_expert": "Opciones expertas",
"collapse_group_character_set": "Conjunto de caracteres",
"copied": "Copiado",
"pwgen_option_type_choice_custom": "Usar ajuste de longitud",
"roulette": "Ruleta",
"stop": "Detener",
"roulette_interval": "Intervalo (s)",
"roulette_slow_down": "Aumentar intervalo (más lento)",
"roulette_speed_up": "Reducir intervalo (más rápido)"
}
+223
View File
@@ -0,0 +1,223 @@
{
"api_documentation": "Documentation API",
"app_title": "app_title",
"close": "Fermer",
"copied_to_clipboard": "Copié dans le presse-papiers !",
"copy": "Copier",
"copy_failed": "Échec de la copie",
"count": "Nombre",
"deutsch": "Deutsch",
"dice_rolls": "Lancers de dés",
"diceware_badge": "Priorité à la sécurité",
"diceware_description": "Méthode EFF Diceware authentique utilisant des lancers de dés cryptographiquement sécurisés pour une sécurité maximale",
"diceware_option_show_dice_rolls": "Show Dice Rolls",
"diceware_option_show_dice_rolls_description": "Pédagogique : affiche les lancers de dés utilisés pour choisir chaque mot",
"diceware_option_uppercase_first": "Capitalize First Word",
"diceware_option_uppercase_first_description": "Met uniquement la première lettre en majuscule (style Diceware traditionnel)",
"diceware_option_words": "Words",
"diceware_option_words_description": "Au moins 6 mots recommandés pour une sécurité optimale (77,5 bits d’entropie).",
"diceware_title": "Diceware",
"english": "Anglais",
"entropy": "Entropie",
"length": "Longueur",
"details": "Détails",
"security_level": "Niveau de sécurité",
"very_strong": "Très fort",
"strong": "Fort",
"adequate": "Adéquat",
"weak": "Faible",
"very_weak": "Très faible",
"chars": "caractères",
"error_generating_passwords": "Erreur lors de la génération des mots de passe",
"espanol": "Español",
"footer_text": "footer_text",
"francais": "Français",
"generate": "Générer",
"generating": "Génération...",
"high-entropy_description": "Mots de passe aléatoires cryptographiquement sécurisés avec entropie maximale",
"high-entropy_option_include_lowercase": "Include Lowercase",
"high-entropy_option_include_numbers": "Include Numbers",
"high-entropy_option_include_symbols": "Include Symbols",
"high-entropy_option_include_uppercase": "Include Uppercase",
"high-entropy_option_length": "Length",
"high-entropy_title": "High Entropy",
"info_spaces_replaced_with_underscores": "Les espaces dans les mots sélectionnés sont remplacés par des traits de soulignement (_).",
"info_words_with_spaces_excluded": "Les mots contenant des espaces sont exclus de la sélection.",
"italiano": "Italiano",
"language": "Langue :",
"loading_generators": "loading_generators",
"loading_password_generators": "Chargement des générateurs de mots de passe...",
"native_group_explanation": "Options natives du générateur PwGen pour créer des mots de passe prononçables.",
"native_pwgen": "Options natives",
"no_spaces_in_separator": "N’utilisez pas d’espaces dans le séparateur lorsque le mode de traitement des espaces est défini sur « modifier » ou « ignorer ».",
"passkey_description": "Mots de passe prononçables de style Apple avec substitutions de caractères intégrées",
"passkey_option_syllables": "Syllables",
"passkey_title": "Passkey",
"password_generator": "Générateur de mots de passe",
"php_fallback": "Code de secours en PHP lorsque pwgen natif n’est pas disponible.",
"portugues": "Portugais",
"pwgen_badge": "Amélioré",
"pwgen_description": "Mots de passe de style pwgen classique avec motifs lisibles",
"pwgen_option_include_capitals": "Inclure les majuscules",
"pwgen_option_include_capitals_description": "Les mots de passe générés contiendront des lettres majuscules.",
"pwgen_option_include_numbers": "Inclure les chiffres",
"pwgen_option_include_numbers_description": "Les mots de passe générés contiendront des chiffres.",
"pwgen_option_length": "Longueur",
"pwgen_option_length_description": "Longueur totale du mot de passe, y compris les séparateurs (minimum 4 caractères)",
"pwgen_option_no_ambiguous": "Sans caractères ambiguës",
"pwgen_option_no_ambiguous_description": "Exclure des caractères comme 0, O, l, 1, etc. pour améliorer la lisibilité",
"pwgen_option_pattern_complexity": "Motif complexe",
"pwgen_option_pattern_complexity_description": "Contrôle la complexité des motifs générés.",
"pwgen_option_pattern_complexity_list_balanced": "Complexité équilibrée",
"pwgen_option_pattern_complexity_list_complex": "Motifs complexes",
"pwgen_option_pattern_complexity_list_simple": "Motifs simples",
"pwgen_option_secure_mode": "Mode sécurisé",
"pwgen_option_secure_mode_description": "Génère des mots de passe entièrement aléatoires au lieu de mots prononçables",
"pwgen_option_separator": "Séparateur",
"pwgen_option_separator_description": "Caractère(s) pour séparer les syllabes. Si plusieurs caractères sont définis, ils sont séparés par un caractère choisi au hasard. Si ce champ est vide, aucune séparation n’est effectuée.",
"pwgen_option_syllable_count": "Nombre de syllabes",
"pwgen_option_syllable_count_description": "Nombre de syllabes à générer (n’utilisé qu’avec un séparateur)",
"pwgen_title": "PWGen",
"results": "Résultats",
"schwyzerduetsch": "Schwyzerdütsch",
"secure_mode_disables_syllable_separation": "Le mode sécurisé désactive la séparation des syllabes.",
"separator_method_info": "Informations sur la méthode de séparation",
"single_space_allowed": "Un seul espace autorisé",
"success": "Succès",
"syllable_group_explanation": "Explication des syllabes",
"syllable_length_too_short": "La syllabe est trop courte",
"syllable_length_very_long": "La syllabe est très longue",
"warning_short_word_limits_reduces_choices": "Avertissement : les limites de mots courts réduisent les choix",
"warning_short_words_reduce_memorability": "Avertissement : les mots courts réduisent la mémorisation",
"xkcdpass_badge": "Priorité à la mémorisation",
"xkcdpass_description": "Description xkcdpass",
"xkcdpass_option_addNumber": "Add Number",
"xkcdpass_option_max_word_length": "Longueur maximale du mot",
"xkcdpass_option_max_word_length_description": "0 = pas de limite, >0 = nombre maximal de caractères par mot",
"xkcdpass_option_numberPosition": "Number Position",
"xkcdpass_option_numberPosition_list_beginning": "Au début",
"xkcdpass_option_numberPosition_list_end": "À la fin",
"xkcdpass_option_numberPosition_list_random": "Position aléatoire",
"xkcdpass_option_separator": "Séparateur",
"xkcdpass_option_separator_description": "Caractère unique ou ensemble de caractères (sélection aléatoire). Les espaces sont exclus des ensembles multi-caractères.",
"xkcdpass_option_space_handling": "Gestion des espaces",
"xkcdpass_option_space_handling_description": "Comment gérer les mots contenant des espaces",
"xkcdpass_option_space_handling_list_allow": "Autoriser (conserver les espaces)",
"xkcdpass_option_space_handling_list_modify": "Modifier (remplacer par _)",
"xkcdpass_option_space_handling_list_skip": "Ignorer (ne pas sélectionner)",
"xkcdpass_option_wordlist": "Liste de mots",
"xkcdpass_option_words": "Mots",
"xkcdpass_option_words_description": "Nombre de mots (minimum 2)",
"xkcdpass_title": "XKCD Pass",
"phraseabbreviator_number_zero": "zéro",
"phraseabbreviator_number_one": "un",
"phraseabbreviator_number_two": "deux",
"phraseabbreviator_number_three": "trois",
"phraseabbreviator_number_four": "quatre",
"phraseabbreviator_number_five": "cinq",
"phraseabbreviator_number_six": "six",
"phraseabbreviator_number_seven": "sept",
"phraseabbreviator_number_eight": "huit",
"phraseabbreviator_number_nine": "neuf",
"phraseabbreviator_number_ten": "dix",
"phraseabbreviator_number_eleven": "onze",
"phraseabbreviator_number_twelve": "douze",
"phraseabbreviator_number_thirteen": "treize",
"phraseabbreviator_number_fourteen": "quatorze",
"phraseabbreviator_number_fifteen": "quinze",
"phraseabbreviator_number_sixteen": "seize",
"phraseabbreviator_number_seventeen": "dix-sept",
"phraseabbreviator_number_eighteen": "dix-huit",
"phraseabbreviator_number_nineteen": "dix-neuf",
"phraseabbreviator_number_twenty": "vingt",
"xkcdpass_option_use_ai_wordlist": "Utiliser la liste de mots IA",
"xkcdpass_option_use_ai_wordlist_description": "Génère une liste de mots personnalisée avec un modèle d’IA",
"xkcdpass_option_ai_wordlist_prompt": "Invite de liste de mots IA",
"xkcdpass_option_ai_wordlist_prompt_description": "Complétez l’invite : \"Crée une liste de X mots d’au moins 5 caractères, qui...\"",
"xkcdpass_option_ai_provider": "Fournisseur IA",
"xkcdpass_option_ai_provider_description": "Quel fournisseur LLM utiliser",
"phrase-abbreviator_option_use_ai_sentence": "Utiliser une phrase IA",
"phrase-abbreviator_option_use_ai_sentence_description": "Permet à l’IA de générer une phrase à partir de votre invite",
"phrase-abbreviator_option_ai_prompt": "Invite IA",
"phrase-abbreviator_option_ai_prompt_description": "Invite pour que l’IA génère une phrase mémorable (minimum 12 mots)",
"phrase-abbreviator_option_ai_provider": "Fournisseur IA",
"phrase-abbreviator_option_ai_provider_description": "Quel fournisseur LLM utiliser pour la génération de phrases",
"phrase-abbreviator_option_ai_model": "Modèle IA",
"phrase-abbreviator_option_ai_model_description": "Quel modèle IA utiliser",
"high-entropy_option_length_description": "Total length of the generated password",
"high-entropy_option_include_uppercase_description": "Include uppercase letters (A-Z)",
"high-entropy_option_include_lowercase_description": "Include lowercase letters (a-z)",
"high-entropy_option_include_numbers_description": "Include numbers (0-9)",
"high-entropy_option_include_symbols_description": "Include symbols (e.g., !@#$%)",
"xkcdpass_option_wordlist_description": "Wordlist to use for generating the passphrase",
"xkcdpass_option_wordlist_choice_en": "English",
"xkcdpass_option_wordlist_choice_de": "Deutsch",
"xkcdpass_option_wordlist_choice_fr": "Français",
"xkcdpass_option_wordlist_choice_it": "Italiano",
"xkcdpass_option_wordlist_choice_es": "Español",
"xkcdpass_option_space_handling_choice_allow": "Autoriser",
"xkcdpass_option_space_handling_choice_skip": "Ignorer",
"xkcdpass_option_space_handling_choice_modify": "Modifier",
"xkcdpass_option_add_number": "Ajouter un nombre",
"xkcdpass_option_add_number_description": "Add random number to the passphrase",
"xkcdpass_option_number_position": "Position du nombre",
"xkcdpass_option_number_position_description": "Where to place the random number",
"xkcdpass_option_number_position_choice_beginning": "Au début",
"xkcdpass_option_number_position_choice_end": "À la fin",
"xkcdpass_option_number_position_choice_random": "Position aléatoire",
"xkcdpass_option_ai_provider_choice_openai": "OpenAI",
"xkcdpass_option_ai_provider_choice_claude": "Claude",
"xkcdpass_option_ai_provider_choice_gemini": "Gemini",
"xkcdpass_option_ai_provider_choice_perplexity": "Perplexity",
"pwgen_option_type": "Préréglage de longueur",
"pwgen_option_type_description": "Préréglage de longueur facultatif. S'il est défini sur 8/12/16 caractères, il remplace la valeur Longueur ci-dessus.",
"pwgen_option_type_choice_8-char": "8 caractères",
"pwgen_option_type_choice_12-char": "12 caractères",
"pwgen_option_type_choice_16-char": "16 caractères",
"passkey_option_syllables_description": "Number of pronounceable syllables in the passkey",
"phrase-abbreviator_option_sentence": "Phrase",
"phrase-abbreviator_option_sentence_description": "User-supplied sentence (minimum 8 words, must start with capital letter)",
"phrase-abbreviator_option_character_extraction": "Extraction des caractères",
"phrase-abbreviator_option_character_extraction_description": "Method for extracting characters from each word",
"phrase-abbreviator_option_character_extraction_choice_single": "Première lettre uniquement",
"phrase-abbreviator_option_character_extraction_choice_dual": "Première lettre + prochaine voyelle/consonne",
"phrase-abbreviator_option_language": "Langue",
"phrase-abbreviator_option_language_description": "Language for number word conversion",
"phrase-abbreviator_option_language_choice_en": "English",
"phrase-abbreviator_option_language_choice_de": "Allemand",
"phrase-abbreviator_option_language_choice_de_CH": "Suisse allemand",
"phrase-abbreviator_option_language_choice_fr": "Français",
"phrase-abbreviator_option_language_choice_it": "Italien",
"phrase-abbreviator_option_language_choice_es": "Espagnol",
"phrase-abbreviator_option_language_choice_pt": "Portugais",
"phrase-abbreviator_option_language_choice_ja": "Japonais",
"phrase-abbreviator_option_language_choice_zh": "Chinois",
"phrase-abbreviator_option_add_number": "Ajouter un nombre",
"phrase-abbreviator_option_add_number_description": "Ajouter des chiffres aléatoires au mot de passe",
"phrase-abbreviator_option_number_digits": "Nombre de chiffres",
"phrase-abbreviator_option_number_digits_description": "Combien de chiffres aléatoires ajouter (1-4)",
"phrase-abbreviator_option_number_position": "Position du nombre",
"phrase-abbreviator_option_number_position_description": "Place le nombre avant ou après le mot de passe",
"phrase-abbreviator_option_number_position_choice_prepend": "Avant",
"phrase-abbreviator_option_number_position_choice_append": "Après",
"phrase-abbreviator_option_ai_provider_choice_openai": "OpenAI",
"phrase-abbreviator_option_ai_provider_choice_claude": "Claude",
"phrase-abbreviator_option_ai_provider_choice_gemini": "Gemini",
"phrase-abbreviator_option_ai_provider_choice_perplexity": "Perplexity",
"phrase-abbreviator_option_ai_model_choice_gpt-3.5-turbo": "GPT-3.5 Turbo",
"phrase-abbreviator_option_ai_model_choice_gpt-4": "GPT-4",
"phrase-abbreviator_option_ai_model_choice_gpt-4-turbo": "GPT-4 Turbo",
"phrase-abbreviator_option_ai_model_choice_gpt-4o": "GPT-4 Omni",
"collapse_group_basic": "Options de base",
"collapse_group_advanced": "Options avancées",
"collapse_group_ai": "Fonctionnalités IA",
"collapse_group_expert": "Options expertes",
"collapse_group_character_set": "Jeu de caractères",
"copied": "Copié",
"pwgen_option_type_choice_custom": "Utiliser le réglage de longueur",
"roulette": "Roulette",
"stop": "Arrêter",
"roulette_interval": "Intervalle (s)",
"roulette_slow_down": "Augmenter l'intervalle (plus lent)",
"roulette_speed_up": "Réduire l'intervalle (plus rapide)"
}
+223
View File
@@ -0,0 +1,223 @@
{
"api_documentation": "Documentazione API",
"app_title": "Generatore di Password",
"close": "Chiudi",
"copied_to_clipboard": "Copiato negli appunti!",
"copy": "Copia",
"copy_failed": "Copia fallita",
"count": "Conteggio",
"deutsch": "Deutsch",
"dice_rolls": "Lanci di dadi",
"diceware_badge": "Focus Sicurezza",
"diceware_description": "Metodo EFF Diceware autentico utilizzando lanci di dadi crittograficamente sicuri per la massima sicurezza",
"diceware_option_show_dice_rolls": "Show Dice Rolls",
"diceware_option_show_dice_rolls_description": "Educational: display the dice rolls used to select each word",
"diceware_option_uppercase_first": "Capitalize First Word",
"diceware_option_uppercase_first_description": "Capitalize only the first word (traditional Diceware style)",
"diceware_option_words": "Words",
"diceware_option_words_description": "Minimum 6 words recommended for security (77.5 bits entropy)",
"diceware_title": "Diceware",
"english": "Inglese",
"entropy": "Entropia",
"length": "Lunghezza",
"details": "Dettagli",
"security_level": "Livello di sicurezza",
"very_strong": "Molto forte",
"strong": "Forte",
"adequate": "Adeguata",
"weak": "Debole",
"very_weak": "Molto debole",
"chars": "caratteri",
"error_generating_passwords": "Errore nella generazione delle password",
"espanol": "Español",
"footer_text": "testo del piè di pagina",
"francais": "Français",
"generate": "Genera",
"generating": "Generazione...",
"high-entropy_description": "Password casuali crittograficamente sicure con entropia massima",
"high-entropy_option_include_lowercase": "Include Lowercase",
"high-entropy_option_include_numbers": "Include Numbers",
"high-entropy_option_include_symbols": "Include Symbols",
"high-entropy_option_include_uppercase": "Include Uppercase",
"high-entropy_option_length": "Length",
"high-entropy_title": "High Entropy",
"info_spaces_replaced_with_underscores": "Gli spazi nelle parole selezionate vengono sostituiti con underscore (_).",
"info_words_with_spaces_excluded": "Le parole che contengono spazi vengono escluse dalla selezione.",
"italiano": "Italiano",
"language": "Lingua:",
"loading_generators": "Caricamento generatori",
"loading_password_generators": "Caricamento generatori di password...",
"native_group_explanation": "Opzioni native del generatore PwGen per creare password pronunciabili.",
"native_pwgen": "Opzioni native",
"no_spaces_in_separator": "Non usare spazi nel separatore quando la gestione degli spazi è impostata su “modifica” o “salta”.",
"passkey_description": "Password pronunciabili in stile Apple con sostituzioni di caratteri integrate",
"passkey_option_syllables": "Syllables",
"passkey_title": "Passkey",
"password_generator": "Generatore password",
"php_fallback": "Codice di fallback in PHP quando pwgen nativo non è disponibile.",
"portugues": "Portoghese",
"pwgen_badge": "Migliorato",
"pwgen_description": "Password in stile pwgen classico con pattern leggibili",
"pwgen_option_include_capitals": "Includi maiuscole",
"pwgen_option_include_capitals_description": "Le password generate conterranno lettere maiuscole.",
"pwgen_option_include_numbers": "Includi numeri",
"pwgen_option_include_numbers_description": "Le password generate conterranno cifre.",
"pwgen_option_length": "Lunghezza",
"pwgen_option_length_description": "Lunghezza totale della password inclusi i separatori (minimo 4 caratteri)",
"pwgen_option_no_ambiguous": "Nessun carattere ambiguo",
"pwgen_option_no_ambiguous_description": "Escludi caratteri come 0, O, l, 1, ecc. per migliorare la leggibilità",
"pwgen_option_pattern_complexity": "Pattern complesso",
"pwgen_option_pattern_complexity_description": "Controlla la complessità dei pattern generati.",
"pwgen_option_pattern_complexity_list_balanced": "Complessità bilanciata",
"pwgen_option_pattern_complexity_list_complex": "Pattern complessi",
"pwgen_option_pattern_complexity_list_simple": "Pattern semplici",
"pwgen_option_secure_mode": "Modalità sicura",
"pwgen_option_secure_mode_description": "Genera password completamente casuali invece di parole pronunciabili",
"pwgen_option_separator": "Separatore",
"pwgen_option_separator_description": "Carattere(i) per separare le sillabe. Se ne vengono definiti più di uno, vengono separati da un carattere scelto casualmente. Se è vuoto, non viene eseguita alcuna separazione.",
"pwgen_option_syllable_count": "Numero di sillabe",
"pwgen_option_syllable_count_description": "Numero di sillabe da generare (usato solo con separatore)",
"pwgen_title": "PWGen",
"results": "Risultati",
"schwyzerduetsch": "Tedesco Svizzero",
"secure_mode_disables_syllable_separation": "La modalità sicura disattiva la separazione delle sillabe.",
"separator_method_info": "Informazioni sul metodo di separazione",
"single_space_allowed": "È consentito un solo spazio",
"success": "Successo",
"syllable_group_explanation": "Spiegazione delle sillabe",
"syllable_length_too_short": "La sillaba è troppo breve",
"syllable_length_very_long": "La sillaba è molto lunga",
"warning_short_word_limits_reduces_choices": "Avviso: i limiti di parole brevi riducono le scelte",
"warning_short_words_reduce_memorability": "Avviso: le parole brevi riducono la memorizzazione",
"xkcdpass_badge": "Focus Memorabilità",
"xkcdpass_description": "Descrizione xkcdpass",
"xkcdpass_option_addNumber": "Add Number",
"xkcdpass_option_max_word_length": "Lunghezza massima parola",
"xkcdpass_option_max_word_length_description": "0 = no limit, >0 = maximum characters per word",
"xkcdpass_option_numberPosition": "Number Position",
"xkcdpass_option_numberPosition_list_beginning": "All’inizio",
"xkcdpass_option_numberPosition_list_end": "Alla fine",
"xkcdpass_option_numberPosition_list_random": "Posizione casuale",
"xkcdpass_option_separator": "Separatore",
"xkcdpass_option_separator_description": "Single character or character set (random selection). Spaces excluded from multi-character sets.",
"xkcdpass_option_space_handling": "Gestione spazi",
"xkcdpass_option_space_handling_description": "Come gestire le parole contenenti spazi",
"xkcdpass_option_space_handling_list_allow": "Consenti (mantieni spazi)",
"xkcdpass_option_space_handling_list_modify": "Modifica (sostituisci con _)",
"xkcdpass_option_space_handling_list_skip": "Salta (non selezionare)",
"xkcdpass_option_wordlist": "Lista di parole",
"xkcdpass_option_words": "Parole",
"xkcdpass_option_words_description": "Number of words (minimal 2)",
"xkcdpass_title": "XKCD Pass",
"phraseabbreviator_number_zero": "zero",
"phraseabbreviator_number_one": "uno",
"phraseabbreviator_number_two": "due",
"phraseabbreviator_number_three": "tre",
"phraseabbreviator_number_four": "quattro",
"phraseabbreviator_number_five": "cinque",
"phraseabbreviator_number_six": "sei",
"phraseabbreviator_number_seven": "sette",
"phraseabbreviator_number_eight": "otto",
"phraseabbreviator_number_nine": "nove",
"phraseabbreviator_number_ten": "dieci",
"phraseabbreviator_number_eleven": "undici",
"phraseabbreviator_number_twelve": "dodici",
"phraseabbreviator_number_thirteen": "tredici",
"phraseabbreviator_number_fourteen": "quattordici",
"phraseabbreviator_number_fifteen": "quindici",
"phraseabbreviator_number_sixteen": "sedici",
"phraseabbreviator_number_seventeen": "diciassette",
"phraseabbreviator_number_eighteen": "diciotto",
"phraseabbreviator_number_nineteen": "diciannove",
"phraseabbreviator_number_twenty": "venti",
"xkcdpass_option_use_ai_wordlist": "Usa lista parole IA",
"xkcdpass_option_use_ai_wordlist_description": "Genera una lista di parole personalizzata con un modello IA",
"xkcdpass_option_ai_wordlist_prompt": "Prompt lista parole IA",
"xkcdpass_option_ai_wordlist_prompt_description": "Completa il prompt: \"Crea una lista di X parole di almeno 5 caratteri, che...\"",
"xkcdpass_option_ai_provider": "Provider IA",
"xkcdpass_option_ai_provider_description": "Quale provider LLM usare",
"phrase-abbreviator_option_use_ai_sentence": "Usa frase IA",
"phrase-abbreviator_option_use_ai_sentence_description": "Consente all’IA di generare una frase dal tuo prompt",
"phrase-abbreviator_option_ai_prompt": "Prompt IA",
"phrase-abbreviator_option_ai_prompt_description": "Prompt per far generare all’IA una frase memorabile (minimo 12 parole)",
"phrase-abbreviator_option_ai_provider": "Provider IA",
"phrase-abbreviator_option_ai_provider_description": "Quale provider LLM usare per la generazione della frase",
"phrase-abbreviator_option_ai_model": "Modello IA",
"phrase-abbreviator_option_ai_model_description": "Quale modello IA usare",
"high-entropy_option_length_description": "Total length of the generated password",
"high-entropy_option_include_uppercase_description": "Include uppercase letters (A-Z)",
"high-entropy_option_include_lowercase_description": "Include lowercase letters (a-z)",
"high-entropy_option_include_numbers_description": "Include numbers (0-9)",
"high-entropy_option_include_symbols_description": "Include symbols (e.g., !@#$%)",
"xkcdpass_option_wordlist_description": "Wordlist to use for generating the passphrase",
"xkcdpass_option_wordlist_choice_en": "English",
"xkcdpass_option_wordlist_choice_de": "Deutsch",
"xkcdpass_option_wordlist_choice_fr": "Français",
"xkcdpass_option_wordlist_choice_it": "Italiano",
"xkcdpass_option_wordlist_choice_es": "Español",
"xkcdpass_option_space_handling_choice_allow": "Consenti",
"xkcdpass_option_space_handling_choice_skip": "Salta",
"xkcdpass_option_space_handling_choice_modify": "Modifica",
"xkcdpass_option_add_number": "Aggiungi numero",
"xkcdpass_option_add_number_description": "Add random number to the passphrase",
"xkcdpass_option_number_position": "Posizione del numero",
"xkcdpass_option_number_position_description": "Where to place the random number",
"xkcdpass_option_number_position_choice_beginning": "All’inizio",
"xkcdpass_option_number_position_choice_end": "Alla fine",
"xkcdpass_option_number_position_choice_random": "Posizione casuale",
"xkcdpass_option_ai_provider_choice_openai": "OpenAI",
"xkcdpass_option_ai_provider_choice_claude": "Claude",
"xkcdpass_option_ai_provider_choice_gemini": "Gemini",
"xkcdpass_option_ai_provider_choice_perplexity": "Perplexity",
"pwgen_option_type": "Preset lunghezza",
"pwgen_option_type_description": "Preset di lunghezza opzionale. Se impostato su 8/12/16 caratteri, sovrascrive il valore Lunghezza sopra.",
"pwgen_option_type_choice_8-char": "8 caratteri",
"pwgen_option_type_choice_12-char": "12 caratteri",
"pwgen_option_type_choice_16-char": "16 caratteri",
"passkey_option_syllables_description": "Number of pronounceable syllables in the passkey",
"phrase-abbreviator_option_sentence": "Frase",
"phrase-abbreviator_option_sentence_description": "User-supplied sentence (minimum 8 words, must start with capital letter)",
"phrase-abbreviator_option_character_extraction": "Estrazione caratteri",
"phrase-abbreviator_option_character_extraction_description": "Method for extracting characters from each word",
"phrase-abbreviator_option_character_extraction_choice_single": "Solo prima lettera",
"phrase-abbreviator_option_character_extraction_choice_dual": "Prima lettera + prossima vocale/consonante",
"phrase-abbreviator_option_language": "Lingua",
"phrase-abbreviator_option_language_description": "Language for number word conversion",
"phrase-abbreviator_option_language_choice_en": "English",
"phrase-abbreviator_option_language_choice_de": "Tedesco",
"phrase-abbreviator_option_language_choice_de_CH": "Svizzero tedesco",
"phrase-abbreviator_option_language_choice_fr": "Francese",
"phrase-abbreviator_option_language_choice_it": "Italiano",
"phrase-abbreviator_option_language_choice_es": "Spagnolo",
"phrase-abbreviator_option_language_choice_pt": "Portoghese",
"phrase-abbreviator_option_language_choice_ja": "Giapponese",
"phrase-abbreviator_option_language_choice_zh": "Cinese",
"phrase-abbreviator_option_add_number": "Aggiungi numero",
"phrase-abbreviator_option_add_number_description": "Aggiungi cifre casuali alla password",
"phrase-abbreviator_option_number_digits": "Numero di cifre",
"phrase-abbreviator_option_number_digits_description": "Quante cifre casuali aggiungere (1-4)",
"phrase-abbreviator_option_number_position": "Posizione del numero",
"phrase-abbreviator_option_number_position_description": "Posiziona il numero prima o dopo la password",
"phrase-abbreviator_option_number_position_choice_prepend": "Prima",
"phrase-abbreviator_option_number_position_choice_append": "Dopo",
"phrase-abbreviator_option_ai_provider_choice_openai": "OpenAI",
"phrase-abbreviator_option_ai_provider_choice_claude": "Claude",
"phrase-abbreviator_option_ai_provider_choice_gemini": "Gemini",
"phrase-abbreviator_option_ai_provider_choice_perplexity": "Perplexity",
"phrase-abbreviator_option_ai_model_choice_gpt-3.5-turbo": "GPT-3.5 Turbo",
"phrase-abbreviator_option_ai_model_choice_gpt-4": "GPT-4",
"phrase-abbreviator_option_ai_model_choice_gpt-4-turbo": "GPT-4 Turbo",
"phrase-abbreviator_option_ai_model_choice_gpt-4o": "GPT-4 Omni",
"collapse_group_basic": "Opzioni di base",
"collapse_group_advanced": "Opzioni avanzate",
"collapse_group_ai": "Funzionalità IA",
"collapse_group_expert": "Opzioni esperte",
"collapse_group_character_set": "Set di caratteri",
"copied": "Copiato",
"pwgen_option_type_choice_custom": "Usa impostazione lunghezza",
"roulette": "Roulette",
"stop": "Ferma",
"roulette_interval": "Intervallo (s)",
"roulette_slow_down": "Aumenta intervallo (più lento)",
"roulette_speed_up": "Riduci intervallo (più veloce)"
}
+223
View File
@@ -0,0 +1,223 @@
{
"api_documentation": "APIドキュメント",
"app_title": "app_title",
"close": "閉じる",
"copied_to_clipboard": "クリップボードにコピーしました!",
"copy": "コピー",
"copy_failed": "コピーに失敗",
"count": "カウント",
"deutsch": "ドイツ語",
"dice_rolls": "サイコロの目",
"diceware_badge": "セキュリティ重視",
"diceware_description": "最大のセキュリティのために暗号学的に安全なサイコロを使用した本格的なEFF Diceware方式",
"diceware_option_show_dice_rolls": "Show Dice Rolls",
"diceware_option_show_dice_rolls_description": "学習用: 各単語の選択に使ったサイコロの目を表示します",
"diceware_option_uppercase_first": "Capitalize First Word",
"diceware_option_uppercase_first_description": "最初の単語だけを大文字にします(従来の Diceware 形式)",
"diceware_option_words": "Words",
"diceware_option_words_description": "安全性のため最低 6 単語を推奨します(77.5 ビットのエントロピー)。",
"diceware_title": "Diceware",
"english": "英語",
"entropy": "エントロピー",
"length": "長さ",
"details": "詳細",
"security_level": "セキュリティレベル",
"very_strong": "非常に強い",
"strong": "強い",
"adequate": "十分",
"weak": "弱い",
"very_weak": "非常に弱い",
"chars": "文字",
"error_generating_passwords": "パスワード生成エラー",
"espanol": "スペイン語",
"footer_text": "footer_text",
"francais": "フランス語",
"generate": "生成",
"generating": "生成中...",
"high-entropy_description": "最大エントロピーを持つ暗号学的に安全なランダムパスワード",
"high-entropy_option_include_lowercase": "Include Lowercase",
"high-entropy_option_include_numbers": "Include Numbers",
"high-entropy_option_include_symbols": "Include Symbols",
"high-entropy_option_include_uppercase": "Include Uppercase",
"high-entropy_option_length": "Length",
"high-entropy_title": "High Entropy",
"info_spaces_replaced_with_underscores": "選択した単語内のスペースはアンダースコア (_) に置き換えられます。",
"info_words_with_spaces_excluded": "スペースを含む単語は選択対象から除外されます。",
"italiano": "イタリア語",
"language": "言語:",
"loading_generators": "loading_generators",
"loading_password_generators": "パスワードジェネレーターを読み込み中...",
"native_group_explanation": "発音可能なパスワードを生成するための PwGen ネイティブ生成器オプションです。",
"native_pwgen": "ネイティブ設定",
"no_spaces_in_separator": "スペース処理が「変更」または「スキップ」の場合、区切り文字にはスペースを使わないでください。",
"passkey_description": "文字置換が統合されたAppleスタイルの発音可能なパスワード",
"passkey_option_syllables": "Syllables",
"passkey_title": "Passkey",
"password_generator": "パスワードジェネレーター",
"php_fallback": "ネイティブ pwgen が利用できない場合の PHP フォールバックコード。",
"portugues": "ポルトガル語",
"pwgen_badge": "強化版",
"pwgen_description": "読みやすいパターンを持つ古典的なpwgenスタイルのパスワード",
"pwgen_option_include_capitals": "大文字を含める",
"pwgen_option_include_capitals_description": "生成されるパスワードには大文字が含まれます。",
"pwgen_option_include_numbers": "数字を含める",
"pwgen_option_include_numbers_description": "生成されるパスワードには数字が含まれます。",
"pwgen_option_length": "長さ",
"pwgen_option_length_description": "区切り文字を含むパスワード全体の長さ(最小 4 文字)",
"pwgen_option_no_ambiguous": "曖昧な文字を避ける",
"pwgen_option_no_ambiguous_description": "読みやすさのため 0、O、l、1 などの文字を除外します",
"pwgen_option_pattern_complexity": "複雑さ",
"pwgen_option_pattern_complexity_description": "生成するパターンの複雑さを調整します。",
"pwgen_option_pattern_complexity_list_balanced": "バランス重視",
"pwgen_option_pattern_complexity_list_complex": "複雑なパターン",
"pwgen_option_pattern_complexity_list_simple": "シンプルなパターン",
"pwgen_option_secure_mode": "セキュアモード",
"pwgen_option_secure_mode_description": "発音可能な単語ではなく、完全にランダムなパスワードを生成します",
"pwgen_option_separator": "区切り文字",
"pwgen_option_separator_description": "音節を区切る文字。複数の文字が定義されている場合は、ランダムに選ばれた 1 つが使用されます。空欄の場合は区切りを行いません。",
"pwgen_option_syllable_count": "音節数",
"pwgen_option_syllable_count_description": "生成する音節数(区切り文字使用時のみ)",
"pwgen_title": "PWGen",
"results": "結果",
"schwyzerduetsch": "シュヴィーツァーデュッチ",
"secure_mode_disables_syllable_separation": "セキュアモードでは音節の分離は無効になります。",
"separator_method_info": "区切り方法の情報",
"single_space_allowed": "1 つのスペースのみ許可",
"success": "成功",
"syllable_group_explanation": "音節の説明",
"syllable_length_too_short": "音節が短すぎます",
"syllable_length_very_long": "音節が長すぎます",
"warning_short_word_limits_reduces_choices": "警告: 単語数の上限が低いと選択肢が減ります",
"warning_short_words_reduce_memorability": "警告: 短い単語は記憶しにくくなります",
"xkcdpass_badge": "記憶しやすさ重視",
"xkcdpass_description": "xkcdpassの説明",
"xkcdpass_option_addNumber": "Add Number",
"xkcdpass_option_max_word_length": "単語の最大長",
"xkcdpass_option_max_word_length_description": "0 = 制限なし、>0 = 単語ごとの最大文字数",
"xkcdpass_option_numberPosition": "Number Position",
"xkcdpass_option_numberPosition_list_beginning": "先頭",
"xkcdpass_option_numberPosition_list_end": "末尾",
"xkcdpass_option_numberPosition_list_random": "ランダム位置",
"xkcdpass_option_separator": "区切り文字",
"xkcdpass_option_separator_description": "1 文字または文字セット(ランダム選択)。複数文字セットではスペースは除外されます。",
"xkcdpass_option_space_handling": "スペースの扱い",
"xkcdpass_option_space_handling_description": "スペースを含む単語の扱い方",
"xkcdpass_option_space_handling_list_allow": "許可(スペースを残す)",
"xkcdpass_option_space_handling_list_modify": "変更(_ に置換)",
"xkcdpass_option_space_handling_list_skip": "スキップ(選択しない)",
"xkcdpass_option_wordlist": "単語一覧",
"xkcdpass_option_words": "単語",
"xkcdpass_option_words_description": "単語数(最小 2)",
"xkcdpass_title": "XKCD Pass",
"phraseabbreviator_number_zero": "零",
"phraseabbreviator_number_one": "一",
"phraseabbreviator_number_two": "二",
"phraseabbreviator_number_three": "三",
"phraseabbreviator_number_four": "四",
"phraseabbreviator_number_five": "五",
"phraseabbreviator_number_six": "六",
"phraseabbreviator_number_seven": "七",
"phraseabbreviator_number_eight": "八",
"phraseabbreviator_number_nine": "九",
"phraseabbreviator_number_ten": "十",
"phraseabbreviator_number_eleven": "十一",
"phraseabbreviator_number_twelve": "十二",
"phraseabbreviator_number_thirteen": "十三",
"phraseabbreviator_number_fourteen": "十四",
"phraseabbreviator_number_fifteen": "十五",
"phraseabbreviator_number_sixteen": "十六",
"phraseabbreviator_number_seventeen": "十七",
"phraseabbreviator_number_eighteen": "十八",
"phraseabbreviator_number_nineteen": "十九",
"phraseabbreviator_number_twenty": "二十",
"xkcdpass_option_use_ai_wordlist": "AI 単語リストを使用",
"xkcdpass_option_use_ai_wordlist_description": "AI モデルを使ってカスタム単語リストを生成します",
"xkcdpass_option_ai_wordlist_prompt": "AI 単語リストのプロンプト",
"xkcdpass_option_ai_wordlist_prompt_description": "プロンプトを完成させてください: \"少なくとも 5 文字の X 個の単語リストを作成し、…\"",
"xkcdpass_option_ai_provider": "AI プロバイダー",
"xkcdpass_option_ai_provider_description": "どの LLM プロバイダーを使用するか",
"phrase-abbreviator_option_use_ai_sentence": "AI 文を使用",
"phrase-abbreviator_option_use_ai_sentence_description": "AI にプロンプトから文章を生成させます",
"phrase-abbreviator_option_ai_prompt": "AI プロンプト",
"phrase-abbreviator_option_ai_prompt_description": "AI に記憶しやすい文章を生成させるためのプロンプト(最低 12 語)",
"phrase-abbreviator_option_ai_provider": "AI プロバイダー",
"phrase-abbreviator_option_ai_provider_description": "文章生成にどの LLM プロバイダーを使用するか",
"phrase-abbreviator_option_ai_model": "AI モデル",
"phrase-abbreviator_option_ai_model_description": "どの AI モデルを使用するか",
"high-entropy_option_length_description": "Total length of the generated password",
"high-entropy_option_include_uppercase_description": "Include uppercase letters (A-Z)",
"high-entropy_option_include_lowercase_description": "Include lowercase letters (a-z)",
"high-entropy_option_include_numbers_description": "Include numbers (0-9)",
"high-entropy_option_include_symbols_description": "Include symbols (e.g., !@#$%)",
"xkcdpass_option_wordlist_description": "Wordlist to use for generating the passphrase",
"xkcdpass_option_wordlist_choice_en": "English",
"xkcdpass_option_wordlist_choice_de": "Deutsch",
"xkcdpass_option_wordlist_choice_fr": "Français",
"xkcdpass_option_wordlist_choice_it": "Italiano",
"xkcdpass_option_wordlist_choice_es": "Español",
"xkcdpass_option_space_handling_choice_allow": "許可",
"xkcdpass_option_space_handling_choice_skip": "スキップ",
"xkcdpass_option_space_handling_choice_modify": "変更",
"xkcdpass_option_add_number": "数字を追加",
"xkcdpass_option_add_number_description": "Add random number to the passphrase",
"xkcdpass_option_number_position": "数字の位置",
"xkcdpass_option_number_position_description": "Where to place the random number",
"xkcdpass_option_number_position_choice_beginning": "先頭",
"xkcdpass_option_number_position_choice_end": "末尾",
"xkcdpass_option_number_position_choice_random": "ランダム",
"xkcdpass_option_ai_provider_choice_openai": "OpenAI",
"xkcdpass_option_ai_provider_choice_claude": "Claude",
"xkcdpass_option_ai_provider_choice_gemini": "Gemini",
"xkcdpass_option_ai_provider_choice_perplexity": "Perplexity",
"pwgen_option_type": "長さプリセット",
"pwgen_option_type_description": "任意の長さプリセットです。8/12/16 文字を選ぶと、上の「長さ」設定を上書きします。",
"pwgen_option_type_choice_8-char": "8 文字",
"pwgen_option_type_choice_12-char": "12 文字",
"pwgen_option_type_choice_16-char": "16 文字",
"passkey_option_syllables_description": "Number of pronounceable syllables in the passkey",
"phrase-abbreviator_option_sentence": "文章",
"phrase-abbreviator_option_sentence_description": "User-supplied sentence (minimum 8 words, must start with capital letter)",
"phrase-abbreviator_option_character_extraction": "文字抽出",
"phrase-abbreviator_option_character_extraction_description": "Method for extracting characters from each word",
"phrase-abbreviator_option_character_extraction_choice_single": "先頭文字のみ",
"phrase-abbreviator_option_character_extraction_choice_dual": "先頭文字 + 次の母音/子音",
"phrase-abbreviator_option_language": "言語",
"phrase-abbreviator_option_language_description": "Language for number word conversion",
"phrase-abbreviator_option_language_choice_en": "English",
"phrase-abbreviator_option_language_choice_de": "ドイツ語",
"phrase-abbreviator_option_language_choice_de_CH": "シュワイツァー・ドイツ語",
"phrase-abbreviator_option_language_choice_fr": "フランス語",
"phrase-abbreviator_option_language_choice_it": "イタリア語",
"phrase-abbreviator_option_language_choice_es": "スペイン語",
"phrase-abbreviator_option_language_choice_pt": "ポルトガル語",
"phrase-abbreviator_option_language_choice_ja": "日本語",
"phrase-abbreviator_option_language_choice_zh": "中国語",
"phrase-abbreviator_option_add_number": "数字を追加",
"phrase-abbreviator_option_add_number_description": "パスワードにランダムな数字を追加",
"phrase-abbreviator_option_number_digits": "数字の個数",
"phrase-abbreviator_option_number_digits_description": "追加するランダム数字の数(1〜4)",
"phrase-abbreviator_option_number_position": "数字の位置",
"phrase-abbreviator_option_number_position_description": "数字をパスワードの前または後に配置",
"phrase-abbreviator_option_number_position_choice_prepend": "前",
"phrase-abbreviator_option_number_position_choice_append": "後",
"phrase-abbreviator_option_ai_provider_choice_openai": "OpenAI",
"phrase-abbreviator_option_ai_provider_choice_claude": "Claude",
"phrase-abbreviator_option_ai_provider_choice_gemini": "Gemini",
"phrase-abbreviator_option_ai_provider_choice_perplexity": "Perplexity",
"phrase-abbreviator_option_ai_model_choice_gpt-3.5-turbo": "GPT-3.5 Turbo",
"phrase-abbreviator_option_ai_model_choice_gpt-4": "GPT-4",
"phrase-abbreviator_option_ai_model_choice_gpt-4-turbo": "GPT-4 Turbo",
"phrase-abbreviator_option_ai_model_choice_gpt-4o": "GPT-4 Omni",
"collapse_group_basic": "基本オプション",
"collapse_group_advanced": "詳細オプション",
"collapse_group_ai": "AI 機能",
"collapse_group_expert": "上級オプション",
"collapse_group_character_set": "文字セット",
"copied": "コピー済み",
"pwgen_option_type_choice_custom": "長さ設定を使用",
"roulette": "ルーレット",
"stop": "停止",
"roulette_interval": "間隔 (秒)",
"roulette_slow_down": "間隔を増やす(遅く)",
"roulette_speed_up": "間隔を減らす(速く)"
}
+223
View File
@@ -0,0 +1,223 @@
{
"api_documentation": "Documentação API",
"app_title": "Gerador de Senhas",
"close": "Fechar",
"copied_to_clipboard": "Copiado para a área de transferência!",
"copy": "Copiar",
"copy_failed": "Falha ao copiar",
"count": "Quantidade",
"deutsch": "Deutsch",
"dice_rolls": "Lançamentos de dados",
"diceware_badge": "Foco na Segurança",
"diceware_description": "Método EFF Diceware autêntico usando jogadas de dados criptograficamente seguras para máxima segurança",
"diceware_option_show_dice_rolls": "Show Dice Rolls",
"diceware_option_show_dice_rolls_description": "Educational: display the dice rolls used to select each word",
"diceware_option_uppercase_first": "Capitalize First Word",
"diceware_option_uppercase_first_description": "Capitalize only the first word (traditional Diceware style)",
"diceware_option_words": "Words",
"diceware_option_words_description": "Minimum 6 words recommended for security (77.5 bits entropy)",
"diceware_title": "Diceware",
"english": "Inglês",
"entropy": "Entropia",
"length": "Comprimento",
"details": "Detalhes",
"security_level": "Nível de segurança",
"very_strong": "Muito forte",
"strong": "Forte",
"adequate": "Adequado",
"weak": "Fraca",
"very_weak": "Muito fraca",
"chars": "caracteres",
"error_generating_passwords": "Erro ao gerar senhas",
"espanol": "Español",
"footer_text": "texto do rodapé",
"francais": "Français",
"generate": "Gerar",
"generating": "Gerando...",
"high-entropy_description": "Senhas aleatórias criptograficamente seguras com entropia máxima",
"high-entropy_option_include_lowercase": "Include Lowercase",
"high-entropy_option_include_numbers": "Include Numbers",
"high-entropy_option_include_symbols": "Include Symbols",
"high-entropy_option_include_uppercase": "Include Uppercase",
"high-entropy_option_length": "Length",
"high-entropy_title": "High Entropy",
"info_spaces_replaced_with_underscores": "Espaços nas palavras selecionadas são substituídos por underscores (_).",
"info_words_with_spaces_excluded": "Palavras que contêm espaços são excluídas da seleção.",
"italiano": "Italiano",
"language": "Idioma:",
"loading_generators": "Carregando geradores",
"loading_password_generators": "Carregando geradores de senhas...",
"native_group_explanation": "Opções nativas do gerador PwGen para gerar senhas pronunciáveis.",
"native_pwgen": "Opções nativas",
"no_spaces_in_separator": "Não use espaços no separador quando o tratamento de espaço estiver definido como “modificar” ou “pular”.",
"passkey_description": "Senhas pronunciáveis estilo Apple com substituições de caracteres integradas",
"passkey_option_syllables": "Syllables",
"passkey_title": "Passkey",
"password_generator": "Gerador de senhas",
"php_fallback": "Código de fallback em PHP quando o pwgen nativo não está disponível.",
"portugues": "Português",
"pwgen_badge": "Melhorado",
"pwgen_description": "Senhas estilo pwgen clássico com padrões legíveis",
"pwgen_option_include_capitals": "Incluir maiúsculas",
"pwgen_option_include_capitals_description": "As senhas geradas conterão letras maiúsculas.",
"pwgen_option_include_numbers": "Incluir números",
"pwgen_option_include_numbers_description": "As senhas geradas conterão dígitos.",
"pwgen_option_length": "Comprimento",
"pwgen_option_length_description": "Comprimento total da senha incluindo separadores (mínimo de 4 caracteres)",
"pwgen_option_no_ambiguous": "Sem caracteres ambíguos",
"pwgen_option_no_ambiguous_description": "Exclui caracteres como 0, O, l, 1, etc. para melhorar a legibilidade",
"pwgen_option_pattern_complexity": "Padrão complexo",
"pwgen_option_pattern_complexity_description": "Controla a complexidade dos padrões gerados.",
"pwgen_option_pattern_complexity_list_balanced": "Complexidade equilibrada",
"pwgen_option_pattern_complexity_list_complex": "Padrões complexos",
"pwgen_option_pattern_complexity_list_simple": "Padrões simples",
"pwgen_option_secure_mode": "Modo seguro",
"pwgen_option_secure_mode_description": "Gera senhas totalmente aleatórias em vez de pronunciáveis",
"pwgen_option_separator": "Separador",
"pwgen_option_separator_description": "Caractere(s) para separar sílabas. Se vários caracteres forem definidos, os trechos são separados por um caractere escolhido aleatoriamente. Se estiver vazio, não há separação.",
"pwgen_option_syllable_count": "Número de sílabas",
"pwgen_option_syllable_count_description": "Número de sílabas a gerar (usado somente com separador)",
"pwgen_title": "PWGen",
"results": "Resultados",
"schwyzerduetsch": "Alemão Suíço",
"secure_mode_disables_syllable_separation": "O modo seguro desativa a separação por sílabas.",
"separator_method_info": "Informações sobre o método de separação",
"single_space_allowed": "Permite apenas um espaço",
"success": "Sucesso",
"syllable_group_explanation": "Explicação das sílabas",
"syllable_length_too_short": "A sílaba é muito curta",
"syllable_length_very_long": "A sílaba é muito longa",
"warning_short_word_limits_reduces_choices": "Aviso: limites de palavras curtas reduzem as opções",
"warning_short_words_reduce_memorability": "Aviso: palavras curtas reduzem a memorização",
"xkcdpass_badge": "Foco na Memorabilidade",
"xkcdpass_description": "Descrição xkcdpass",
"xkcdpass_option_addNumber": "Add Number",
"xkcdpass_option_max_word_length": "Comprimento máximo da palavra",
"xkcdpass_option_max_word_length_description": "0 = no limit, >0 = maximum characters per word",
"xkcdpass_option_numberPosition": "Number Position",
"xkcdpass_option_numberPosition_list_beginning": "No início",
"xkcdpass_option_numberPosition_list_end": "No fim",
"xkcdpass_option_numberPosition_list_random": "Posição aleatória",
"xkcdpass_option_separator": "Separador",
"xkcdpass_option_separator_description": "Single character or character set (random selection). Spaces excluded from multi-character sets.",
"xkcdpass_option_space_handling": "Tratamento de espaços",
"xkcdpass_option_space_handling_description": "Como lidar com palavras contendo espaços",
"xkcdpass_option_space_handling_list_allow": "Permitir (manter espaços)",
"xkcdpass_option_space_handling_list_modify": "Modificar (substituir por _)",
"xkcdpass_option_space_handling_list_skip": "Pular (não selecionar)",
"xkcdpass_option_wordlist": "Lista de palavras",
"xkcdpass_option_words": "Palavras",
"xkcdpass_option_words_description": "Number of words (minimal 2)",
"xkcdpass_title": "XKCD Pass",
"phraseabbreviator_number_zero": "zero",
"phraseabbreviator_number_one": "um",
"phraseabbreviator_number_two": "dois",
"phraseabbreviator_number_three": "três",
"phraseabbreviator_number_four": "quatro",
"phraseabbreviator_number_five": "cinco",
"phraseabbreviator_number_six": "seis",
"phraseabbreviator_number_seven": "sete",
"phraseabbreviator_number_eight": "oito",
"phraseabbreviator_number_nine": "nove",
"phraseabbreviator_number_ten": "dez",
"phraseabbreviator_number_eleven": "onze",
"phraseabbreviator_number_twelve": "doze",
"phraseabbreviator_number_thirteen": "treze",
"phraseabbreviator_number_fourteen": "catorze",
"phraseabbreviator_number_fifteen": "quinze",
"phraseabbreviator_number_sixteen": "dezesseis",
"phraseabbreviator_number_seventeen": "dezessete",
"phraseabbreviator_number_eighteen": "dezoito",
"phraseabbreviator_number_nineteen": "dezenove",
"phraseabbreviator_number_twenty": "vinte",
"xkcdpass_option_use_ai_wordlist": "Usar lista de palavras com IA",
"xkcdpass_option_use_ai_wordlist_description": "Gera uma lista de palavras personalizada usando um modelo de IA",
"xkcdpass_option_ai_wordlist_prompt": "Prompt da lista de palavras de IA",
"xkcdpass_option_ai_wordlist_prompt_description": "Complete o prompt: \"Crie uma lista de X palavras com pelo menos 5 caracteres, que...\"",
"xkcdpass_option_ai_provider": "Provedor de IA",
"xkcdpass_option_ai_provider_description": "Qual provedor LLM usar",
"phrase-abbreviator_option_use_ai_sentence": "Usar frase de IA",
"phrase-abbreviator_option_use_ai_sentence_description": "Permite que a IA gere uma frase a partir da sua instrução",
"phrase-abbreviator_option_ai_prompt": "Prompt de IA",
"phrase-abbreviator_option_ai_prompt_description": "Prompt para a IA gerar uma frase memorável (mínimo de 12 palavras)",
"phrase-abbreviator_option_ai_provider": "Provedor de IA",
"phrase-abbreviator_option_ai_provider_description": "Qual provedor LLM usar para gerar a frase",
"phrase-abbreviator_option_ai_model": "Modelo de IA",
"phrase-abbreviator_option_ai_model_description": "Qual modelo de IA usar",
"high-entropy_option_length_description": "Total length of the generated password",
"high-entropy_option_include_uppercase_description": "Include uppercase letters (A-Z)",
"high-entropy_option_include_lowercase_description": "Include lowercase letters (a-z)",
"high-entropy_option_include_numbers_description": "Include numbers (0-9)",
"high-entropy_option_include_symbols_description": "Include symbols (e.g., !@#$%)",
"xkcdpass_option_wordlist_description": "Wordlist to use for generating the passphrase",
"xkcdpass_option_wordlist_choice_en": "English",
"xkcdpass_option_wordlist_choice_de": "Deutsch",
"xkcdpass_option_wordlist_choice_fr": "Français",
"xkcdpass_option_wordlist_choice_it": "Italiano",
"xkcdpass_option_wordlist_choice_es": "Español",
"xkcdpass_option_space_handling_choice_allow": "Permitir",
"xkcdpass_option_space_handling_choice_skip": "Pular",
"xkcdpass_option_space_handling_choice_modify": "Modificar",
"xkcdpass_option_add_number": "Adicionar número",
"xkcdpass_option_add_number_description": "Add random number to the passphrase",
"xkcdpass_option_number_position": "Posição do número",
"xkcdpass_option_number_position_description": "Where to place the random number",
"xkcdpass_option_number_position_choice_beginning": "No início",
"xkcdpass_option_number_position_choice_end": "No fim",
"xkcdpass_option_number_position_choice_random": "Posição aleatória",
"xkcdpass_option_ai_provider_choice_openai": "OpenAI",
"xkcdpass_option_ai_provider_choice_claude": "Claude",
"xkcdpass_option_ai_provider_choice_gemini": "Gemini",
"xkcdpass_option_ai_provider_choice_perplexity": "Perplexity",
"pwgen_option_type": "Predefinição de comprimento",
"pwgen_option_type_description": "Predefinição opcional de comprimento. Se definida para 8/12/16 caracteres, substitui o valor de Comprimento acima.",
"pwgen_option_type_choice_8-char": "8 caracteres",
"pwgen_option_type_choice_12-char": "12 caracteres",
"pwgen_option_type_choice_16-char": "16 caracteres",
"passkey_option_syllables_description": "Number of pronounceable syllables in the passkey",
"phrase-abbreviator_option_sentence": "Frase",
"phrase-abbreviator_option_sentence_description": "User-supplied sentence (minimum 8 words, must start with capital letter)",
"phrase-abbreviator_option_character_extraction": "Extração de caracteres",
"phrase-abbreviator_option_character_extraction_description": "Method for extracting characters from each word",
"phrase-abbreviator_option_character_extraction_choice_single": "Apenas primeira letra",
"phrase-abbreviator_option_character_extraction_choice_dual": "Primeira letra + próxima vogal/consoante",
"phrase-abbreviator_option_language": "Idioma",
"phrase-abbreviator_option_language_description": "Language for number word conversion",
"phrase-abbreviator_option_language_choice_en": "English",
"phrase-abbreviator_option_language_choice_de": "Alemão",
"phrase-abbreviator_option_language_choice_de_CH": "Suíço-alemão",
"phrase-abbreviator_option_language_choice_fr": "Francês",
"phrase-abbreviator_option_language_choice_it": "Italiano",
"phrase-abbreviator_option_language_choice_es": "Espanhol",
"phrase-abbreviator_option_language_choice_pt": "Português",
"phrase-abbreviator_option_language_choice_ja": "Japonês",
"phrase-abbreviator_option_language_choice_zh": "Chinês",
"phrase-abbreviator_option_add_number": "Adicionar número",
"phrase-abbreviator_option_add_number_description": "Adicionar dígitos aleatórios à senha",
"phrase-abbreviator_option_number_digits": "Número de dígitos",
"phrase-abbreviator_option_number_digits_description": "Quantos dígitos aleatórios adicionar (1-4)",
"phrase-abbreviator_option_number_position": "Posição do número",
"phrase-abbreviator_option_number_position_description": "Colocar o número antes ou depois da senha",
"phrase-abbreviator_option_number_position_choice_prepend": "Antes",
"phrase-abbreviator_option_number_position_choice_append": "Depois",
"phrase-abbreviator_option_ai_provider_choice_openai": "OpenAI",
"phrase-abbreviator_option_ai_provider_choice_claude": "Claude",
"phrase-abbreviator_option_ai_provider_choice_gemini": "Gemini",
"phrase-abbreviator_option_ai_provider_choice_perplexity": "Perplexity",
"phrase-abbreviator_option_ai_model_choice_gpt-3.5-turbo": "GPT-3.5 Turbo",
"phrase-abbreviator_option_ai_model_choice_gpt-4": "GPT-4",
"phrase-abbreviator_option_ai_model_choice_gpt-4-turbo": "GPT-4 Turbo",
"phrase-abbreviator_option_ai_model_choice_gpt-4o": "GPT-4 Omni",
"collapse_group_basic": "Opções básicas",
"collapse_group_advanced": "Opções avançadas",
"collapse_group_ai": "Recursos de IA",
"collapse_group_expert": "Opções avançadas de especialista",
"collapse_group_character_set": "Conjunto de caracteres",
"copied": "Copiado",
"pwgen_option_type_choice_custom": "Usar configuração de comprimento",
"roulette": "Roleta",
"stop": "Parar",
"roulette_interval": "Intervalo (s)",
"roulette_slow_down": "Aumentar intervalo (mais lento)",
"roulette_speed_up": "Reduzir intervalo (mais rápido)"
}
+223
View File
@@ -0,0 +1,223 @@
{
"api_documentation": "API文档",
"app_title": "密码生成器",
"close": "关闭",
"copied_to_clipboard": "Copied to clipboard!",
"copy": "复制",
"copy_failed": "复制失败",
"count": "数量",
"deutsch": "Deutsch",
"dice_rolls": "骰子结果",
"diceware_badge": "安全重点",
"diceware_description": "真正的EFF Diceware方法",
"diceware_option_show_dice_rolls": "Show Dice Rolls",
"diceware_option_show_dice_rolls_description": "Educational: display the dice rolls used to select each word",
"diceware_option_uppercase_first": "Capitalize First Word",
"diceware_option_uppercase_first_description": "Capitalize only the first word (traditional Diceware style)",
"diceware_option_words": "Words",
"diceware_option_words_description": "Minimum 6 words recommended for security (77.5 bits entropy)",
"diceware_title": "Diceware",
"english": "英语",
"entropy": "熵",
"length": "长度",
"details": "详细信息",
"security_level": "安全等级",
"very_strong": "非常强",
"strong": "强",
"adequate": "适中",
"weak": "弱",
"very_weak": "非常弱",
"chars": "字符",
"error_generating_passwords": "生成密码时出错",
"espanol": "西班牙语",
"footer_text": "页脚文本",
"francais": "法语",
"generate": "生成",
"generating": "Generating...",
"high-entropy_description": "高熵描述",
"high-entropy_option_include_lowercase": "Include Lowercase",
"high-entropy_option_include_numbers": "Include Numbers",
"high-entropy_option_include_symbols": "Include Symbols",
"high-entropy_option_include_uppercase": "Include Uppercase",
"high-entropy_option_length": "Length",
"high-entropy_title": "High Entropy",
"info_spaces_replaced_with_underscores": "所选单词中的空格会替换为下划线 (_) 。",
"info_words_with_spaces_excluded": "包含空格的单词会被排除在选择范围之外。",
"italiano": "意大利语",
"language": "Language:",
"loading_generators": "正在加载生成器",
"loading_password_generators": "Loading password generators...",
"native_group_explanation": "用于生成可发音密码的 PwGen 原生生成器选项。",
"native_pwgen": "原生选项",
"no_spaces_in_separator": "当空格处理设为“修改”或“跳过”时,不要在分隔符中使用空格。",
"passkey_description": "通行密钥描述",
"passkey_option_syllables": "Syllables",
"passkey_title": "Passkey",
"password_generator": "Password Generator",
"php_fallback": "在原生 pwgen 不可用时的 PHP 回退代码。",
"portugues": "葡萄牙语",
"pwgen_badge": "增强版",
"pwgen_description": "PWGen描述",
"pwgen_option_include_capitals": "包含大写字母",
"pwgen_option_include_capitals_description": "生成的密码将包含大写字母。",
"pwgen_option_include_numbers": "包含数字",
"pwgen_option_include_numbers_description": "生成的密码将包含数字。",
"pwgen_option_length": "长度",
"pwgen_option_length_description": "密码总长度,包括分隔符(最少 4 个字符)",
"pwgen_option_no_ambiguous": "不含歧义字符",
"pwgen_option_no_ambiguous_description": "排除 0、O、l、1 等字符,以提高可读性",
"pwgen_option_pattern_complexity": "复杂模式",
"pwgen_option_pattern_complexity_description": "控制生成模式的复杂度。",
"pwgen_option_pattern_complexity_list_balanced": "平衡复杂度",
"pwgen_option_pattern_complexity_list_complex": "复杂模式",
"pwgen_option_pattern_complexity_list_simple": "简单模式",
"pwgen_option_secure_mode": "安全模式",
"pwgen_option_secure_mode_description": "生成完全随机的密码,而不是可发音的单词",
"pwgen_option_separator": "分隔符",
"pwgen_option_separator_description": "用于分隔音节的字符。如果定义了多个字符,则随机选择其中一个作为分隔符。为空时不进行分隔。",
"pwgen_option_syllable_count": "音节数",
"pwgen_option_syllable_count_description": "要生成的音节数(仅在使用分隔符时生效)",
"pwgen_title": "PWGen",
"results": "结果",
"schwyzerduetsch": "瑞士德语",
"secure_mode_disables_syllable_separation": "安全模式会禁用音节分隔。",
"separator_method_info": "分隔方法说明",
"single_space_allowed": "仅允许单个空格",
"success": "成功",
"syllable_group_explanation": "音节说明",
"syllable_length_too_short": "音节太短",
"syllable_length_very_long": "音节太长",
"warning_short_word_limits_reduces_choices": "警告:较短的单词限制会减少可选项",
"warning_short_words_reduce_memorability": "警告:短单词会降低可记忆性",
"xkcdpass_badge": "记忆性重点",
"xkcdpass_description": "XKCDPass描述",
"xkcdpass_option_addNumber": "Add Number",
"xkcdpass_option_max_word_length": "单词最大长度",
"xkcdpass_option_max_word_length_description": "0 = no limit, >0 = maximum characters per word",
"xkcdpass_option_numberPosition": "Number Position",
"xkcdpass_option_numberPosition_list_beginning": "开头",
"xkcdpass_option_numberPosition_list_end": "结尾",
"xkcdpass_option_numberPosition_list_random": "随机位置",
"xkcdpass_option_separator": "分隔符",
"xkcdpass_option_separator_description": "Single character or character set (random selection). Spaces excluded from multi-character sets.",
"xkcdpass_option_space_handling": "空格处理",
"xkcdpass_option_space_handling_description": "如何处理包含空格的单词",
"xkcdpass_option_space_handling_list_allow": "允许(保留空格)",
"xkcdpass_option_space_handling_list_modify": "修改(替换为 _)",
"xkcdpass_option_space_handling_list_skip": "跳过(不选择)",
"xkcdpass_option_wordlist": "单词列表",
"xkcdpass_option_words": "单词",
"xkcdpass_option_words_description": "Number of words (minimal 2)",
"xkcdpass_title": "XKCD Pass",
"phraseabbreviator_number_zero": "零",
"phraseabbreviator_number_one": "一",
"phraseabbreviator_number_two": "二",
"phraseabbreviator_number_three": "三",
"phraseabbreviator_number_four": "四",
"phraseabbreviator_number_five": "五",
"phraseabbreviator_number_six": "六",
"phraseabbreviator_number_seven": "七",
"phraseabbreviator_number_eight": "八",
"phraseabbreviator_number_nine": "九",
"phraseabbreviator_number_ten": "十",
"phraseabbreviator_number_eleven": "十一",
"phraseabbreviator_number_twelve": "十二",
"phraseabbreviator_number_thirteen": "十三",
"phraseabbreviator_number_fourteen": "十四",
"phraseabbreviator_number_fifteen": "十五",
"phraseabbreviator_number_sixteen": "十六",
"phraseabbreviator_number_seventeen": "十七",
"phraseabbreviator_number_eighteen": "十八",
"phraseabbreviator_number_nineteen": "十九",
"phraseabbreviator_number_twenty": "二十",
"xkcdpass_option_use_ai_wordlist": "使用 AI 词表",
"xkcdpass_option_use_ai_wordlist_description": "使用 AI 模型生成自定义词表",
"xkcdpass_option_ai_wordlist_prompt": "AI 词表提示词",
"xkcdpass_option_ai_wordlist_prompt_description": "完成提示词:\"创建一个至少 5 个字符的 X 个单词列表,要求...\"",
"xkcdpass_option_ai_provider": "AI 提供商",
"xkcdpass_option_ai_provider_description": "使用哪个 LLM 提供商",
"phrase-abbreviator_option_use_ai_sentence": "使用 AI 句子",
"phrase-abbreviator_option_use_ai_sentence_description": "让 AI 根据你的提示生成句子",
"phrase-abbreviator_option_ai_prompt": "AI 提示词",
"phrase-abbreviator_option_ai_prompt_description": "让 AI 生成易记句子的提示词(至少 12 个词)",
"phrase-abbreviator_option_ai_provider": "AI 提供商",
"phrase-abbreviator_option_ai_provider_description": "用于句子生成的 LLM 提供商",
"phrase-abbreviator_option_ai_model": "AI 模型",
"phrase-abbreviator_option_ai_model_description": "使用哪个 AI 模型",
"high-entropy_option_length_description": "Total length of the generated password",
"high-entropy_option_include_uppercase_description": "Include uppercase letters (A-Z)",
"high-entropy_option_include_lowercase_description": "Include lowercase letters (a-z)",
"high-entropy_option_include_numbers_description": "Include numbers (0-9)",
"high-entropy_option_include_symbols_description": "Include symbols (e.g., !@#$%)",
"xkcdpass_option_wordlist_description": "Wordlist to use for generating the passphrase",
"xkcdpass_option_wordlist_choice_en": "English",
"xkcdpass_option_wordlist_choice_de": "Deutsch",
"xkcdpass_option_wordlist_choice_fr": "Français",
"xkcdpass_option_wordlist_choice_it": "Italiano",
"xkcdpass_option_wordlist_choice_es": "Español",
"xkcdpass_option_space_handling_choice_allow": "允许",
"xkcdpass_option_space_handling_choice_skip": "跳过",
"xkcdpass_option_space_handling_choice_modify": "修改",
"xkcdpass_option_add_number": "添加数字",
"xkcdpass_option_add_number_description": "Add random number to the passphrase",
"xkcdpass_option_number_position": "数字位置",
"xkcdpass_option_number_position_description": "Where to place the random number",
"xkcdpass_option_number_position_choice_beginning": "开头",
"xkcdpass_option_number_position_choice_end": "结尾",
"xkcdpass_option_number_position_choice_random": "随机位置",
"xkcdpass_option_ai_provider_choice_openai": "OpenAI",
"xkcdpass_option_ai_provider_choice_claude": "Claude",
"xkcdpass_option_ai_provider_choice_gemini": "Gemini",
"xkcdpass_option_ai_provider_choice_perplexity": "Perplexity",
"pwgen_option_type": "长度预设",
"pwgen_option_type_description": "可选的长度预设。若设置为 8/12/16 个字符,将覆盖上方的长度值。",
"pwgen_option_type_choice_8-char": "8 个字符",
"pwgen_option_type_choice_12-char": "12 个字符",
"pwgen_option_type_choice_16-char": "16 个字符",
"passkey_option_syllables_description": "Number of pronounceable syllables in the passkey",
"phrase-abbreviator_option_sentence": "句子",
"phrase-abbreviator_option_sentence_description": "User-supplied sentence (minimum 8 words, must start with capital letter)",
"phrase-abbreviator_option_character_extraction": "字符提取",
"phrase-abbreviator_option_character_extraction_description": "Method for extracting characters from each word",
"phrase-abbreviator_option_character_extraction_choice_single": "仅首字母",
"phrase-abbreviator_option_character_extraction_choice_dual": "首字母 + 下一个元音/辅音",
"phrase-abbreviator_option_language": "语言",
"phrase-abbreviator_option_language_description": "Language for number word conversion",
"phrase-abbreviator_option_language_choice_en": "English",
"phrase-abbreviator_option_language_choice_de": "德语",
"phrase-abbreviator_option_language_choice_de_CH": "瑞士德语",
"phrase-abbreviator_option_language_choice_fr": "法语",
"phrase-abbreviator_option_language_choice_it": "意大利语",
"phrase-abbreviator_option_language_choice_es": "西班牙语",
"phrase-abbreviator_option_language_choice_pt": "葡萄牙语",
"phrase-abbreviator_option_language_choice_ja": "日语",
"phrase-abbreviator_option_language_choice_zh": "中文",
"phrase-abbreviator_option_add_number": "添加数字",
"phrase-abbreviator_option_add_number_description": "向密码中添加随机数字",
"phrase-abbreviator_option_number_digits": "数字数量",
"phrase-abbreviator_option_number_digits_description": "添加多少个随机数字(1-4)",
"phrase-abbreviator_option_number_position": "数字位置",
"phrase-abbreviator_option_number_position_description": "将数字放在密码前面或后面",
"phrase-abbreviator_option_number_position_choice_prepend": "前面",
"phrase-abbreviator_option_number_position_choice_append": "后面",
"phrase-abbreviator_option_ai_provider_choice_openai": "OpenAI",
"phrase-abbreviator_option_ai_provider_choice_claude": "Claude",
"phrase-abbreviator_option_ai_provider_choice_gemini": "Gemini",
"phrase-abbreviator_option_ai_provider_choice_perplexity": "Perplexity",
"phrase-abbreviator_option_ai_model_choice_gpt-3.5-turbo": "GPT-3.5 Turbo",
"phrase-abbreviator_option_ai_model_choice_gpt-4": "GPT-4",
"phrase-abbreviator_option_ai_model_choice_gpt-4-turbo": "GPT-4 Turbo",
"phrase-abbreviator_option_ai_model_choice_gpt-4o": "GPT-4 Omni",
"collapse_group_basic": "基础选项",
"collapse_group_advanced": "高级选项",
"collapse_group_ai": "AI 功能",
"collapse_group_expert": "专家选项",
"collapse_group_character_set": "字符集",
"copied": "已复制",
"pwgen_option_type_choice_custom": "使用长度设置",
"roulette": "轮盘",
"stop": "停止",
"roulette_interval": "间隔(秒)",
"roulette_slow_down": "增加间隔(更慢)",
"roulette_speed_up": "减少间隔(更快)"
}
+11
View File
@@ -0,0 +1,11 @@
# Logs Directory
This directory contains application logs.
Make sure this directory is writable by the web server:
```bash
chmod 755 logs/
```
Log files will be created automatically when the application runs.
+36
View File
@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
use App\Application\ApplicationFactory;
use DI\ContainerBuilder;
use Psr\Log\LoggerInterface;
use Slim\Factory\AppFactory;
require __DIR__ . '/../vendor/autoload.php';
// Load environment variables
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__ . '/../');
$dotenv->load();
// Build DI container
$containerBuilder = new ContainerBuilder();
// Set up dependencies
$dependencies = require __DIR__ . '/../config/dependencies.php';
$dependencies($containerBuilder);
$container = $containerBuilder->build();
AppFactory::setContainer($container);
$app = AppFactory::create();
// Add middleware
$middleware = require __DIR__ . '/../config/middleware.php';
$middleware($app);
// Register routes
$routes = require __DIR__ . '/../config/routes.php';
$routes($app);
$app->run();
+480
View File
@@ -0,0 +1,480 @@
{
"openapi": "3.0.3",
"info": {
"title": "Password Generator API",
"version": "2.0.0",
"description": "Public password generation API and locale metadata for the password generator web app."
},
"servers": [
{
"url": "http://localhost:8080"
}
],
"paths": {
"/api/v1/health": {
"get": {
"summary": "Health check",
"description": "Returns the health status of the API service.",
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HealthSuccess"
}
}
}
},
"500": {
"description": "Error response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/api/v1/locales/{lang}": {
"get": {
"summary": "Fetch locale data",
"description": "Returns the translation resource for a given locale code.",
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/LocaleSuccess"
}
}
}
},
"404": {
"description": "Error response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
},
"parameters": [
{
"name": "lang",
"in": "path",
"required": true,
"schema": {
"type": "string",
"enum": [
"en",
"de",
"de_CH",
"fr",
"it",
"es",
"pt",
"ja",
"zh"
]
},
"description": "Locale code to fetch translation data for."
}
]
}
},
"/api/v1/passwords": {
"get": {
"summary": "List available password generation types",
"description": "Returns the supported password generation algorithms.",
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"success": {
"type": "boolean",
"example": true
},
"data": {
"type": "object",
"properties": {
"types": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"timestamp": {
"type": "string",
"format": "date-time"
},
"message": {
"type": "string",
"example": "Available password generation types"
}
},
"required": [
"success",
"data",
"timestamp"
]
}
}
}
},
"500": {
"description": "Error response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/api/v1/passwords/generate": {
"post": {
"summary": "Generate one or more passwords",
"description": "Generates passwords using the chosen algorithm and options.",
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PasswordGenerationSuccess"
}
}
}
},
"400": {
"description": "Error response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
},
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PasswordGenerationRequest"
}
}
}
}
}
},
"/api/v1/passwords/generators": {
"get": {
"summary": "API route",
"description": "Generated from the public Slim route registry.",
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": true
}
}
}
},
"500": {
"description": "Error response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/api/v1/passwords/wordlists": {
"get": {
"summary": "List available XKCD wordlists",
"description": "Returns available language wordlists for passphrase generation.",
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/WordlistsSuccess"
}
}
}
},
"500": {
"description": "Error response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"ErrorResponse": {
"type": "object",
"properties": {
"success": {
"type": "boolean",
"example": false
},
"error": {
"type": "object",
"properties": {
"message": {
"type": "string",
"example": "Request failed"
},
"code": {
"type": "string",
"example": "ERROR"
},
"status": {
"type": "integer",
"example": 400
}
}
},
"timestamp": {
"type": "string",
"format": "date-time"
}
},
"required": [
"success",
"error",
"timestamp"
]
},
"PasswordGenerationRequest": {
"type": "object",
"properties": {
"type": {
"type": "string",
"example": "high-entropy"
},
"count": {
"type": "integer",
"example": 1
},
"options": {
"type": "object",
"additionalProperties": true
}
}
},
"PasswordGenerationSuccess": {
"type": "object",
"properties": {
"success": {
"type": "boolean",
"example": true
},
"data": {
"type": "object",
"properties": {
"passwords": {
"type": "array",
"items": {
"type": "string"
}
},
"entropy_bits": {
"type": "number",
"format": "float",
"example": 128
},
"security_assessment": {
"type": "string",
"example": "Strong \u2014 this password has good entropy and is suitable for most uses."
},
"raw_technical_details": {
"type": "object",
"properties": {
"entropy_bits": {
"type": "number",
"format": "float",
"example": 77.5
},
"generator": {
"type": "string",
"example": "XKCD Pass"
},
"length": {
"type": "integer",
"example": 28
},
"security_level": {
"type": "string",
"example": "Strong"
},
"entropy_breakdown": {
"type": "object",
"properties": {
"entropy_model": {
"type": "string",
"example": "xkcd-candidate-space-v1"
},
"model_confidence": {
"type": "string",
"enum": [
"high",
"medium",
"low"
]
},
"entropy_bits": {
"type": "number",
"format": "float",
"example": 77.5
}
},
"additionalProperties": true
}
},
"additionalProperties": true
}
}
},
"timestamp": {
"type": "string",
"format": "date-time"
},
"message": {
"type": "string",
"example": "Generated 1 password(s) of type 'high-entropy'"
}
},
"required": [
"success",
"data",
"timestamp"
]
},
"PasswordTypesSuccess": {
"type": "object",
"properties": {
"success": {
"type": "boolean",
"example": true
},
"data": {
"type": "object",
"properties": {
"types": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"timestamp": {
"type": "string",
"format": "date-time"
}
},
"required": [
"success",
"data",
"timestamp"
]
},
"WordlistsSuccess": {
"type": "object",
"properties": {
"success": {
"type": "boolean",
"example": true
},
"data": {
"type": "object",
"properties": {
"wordlists": {
"type": "object",
"additionalProperties": true
},
"count": {
"type": "integer",
"example": 2
}
}
},
"timestamp": {
"type": "string",
"format": "date-time"
},
"message": {
"type": "string",
"example": "Available wordlists for XKCD password generation"
}
},
"required": [
"success",
"data",
"timestamp"
]
},
"HealthSuccess": {
"type": "object",
"properties": {
"status": {
"type": "string",
"example": "healthy"
},
"version": {
"type": "string",
"example": "2.0.0"
},
"timestamp": {
"type": "string",
"format": "date-time"
}
},
"required": [
"status",
"version",
"timestamp"
]
},
"LocaleSuccess": {
"type": "object",
"additionalProperties": true
}
}
}
}
+360
View File
@@ -0,0 +1,360 @@
<?php
declare(strict_types=1);
namespace App\Api;
use Slim\Factory\AppFactory;
final class OpenApiGenerator
{
/**
* Generate the public OpenAPI document from the registered Slim routes.
*/
public static function generate(string $baseUrl): array
{
$baseUrl = rtrim($baseUrl, '/');
$paths = [];
foreach (self::registeredPublicRoutes() as $routePath => $methods) {
$normalizedPath = self::normalizeRoutePattern($routePath);
$paths[$normalizedPath] = self::buildPathOperations($normalizedPath, $methods);
}
return [
'openapi' => '3.0.3',
'info' => [
'title' => $_ENV['APP_NAME'] ?? 'Password Generator API',
'version' => $_ENV['APP_VERSION'] ?? '2.0.0',
'description' => 'Public password generation API and locale metadata for the password generator web app.',
],
'servers' => [
['url' => $baseUrl],
],
'paths' => $paths,
'components' => [
'schemas' => [
'ErrorResponse' => [
'type' => 'object',
'properties' => [
'success' => ['type' => 'boolean', 'example' => false],
'error' => [
'type' => 'object',
'properties' => [
'message' => ['type' => 'string', 'example' => 'Request failed'],
'code' => ['type' => 'string', 'example' => 'ERROR'],
'status' => ['type' => 'integer', 'example' => 400],
],
],
'timestamp' => ['type' => 'string', 'format' => 'date-time'],
],
'required' => ['success', 'error', 'timestamp'],
],
'PasswordGenerationRequest' => [
'type' => 'object',
'properties' => [
'type' => ['type' => 'string', 'example' => 'high-entropy'],
'count' => ['type' => 'integer', 'example' => 1],
'options' => ['type' => 'object', 'additionalProperties' => true],
],
],
'PasswordGenerationSuccess' => [
'type' => 'object',
'properties' => [
'success' => ['type' => 'boolean', 'example' => true],
'data' => [
'type' => 'object',
'properties' => [
'passwords' => [
'type' => 'array',
'items' => ['type' => 'string'],
],
'entropy_bits' => ['type' => 'number', 'format' => 'float', 'example' => 128],
'security_assessment' => [
'type' => 'string',
'example' => 'Strong — this password has good entropy and is suitable for most uses.',
],
'raw_technical_details' => [
'type' => 'object',
'properties' => [
'entropy_bits' => ['type' => 'number', 'format' => 'float', 'example' => 77.5],
'generator' => ['type' => 'string', 'example' => 'XKCD Pass'],
'length' => ['type' => 'integer', 'example' => 28],
'security_level' => ['type' => 'string', 'example' => 'Strong'],
'entropy_breakdown' => [
'type' => 'object',
'properties' => [
'entropy_model' => ['type' => 'string', 'example' => 'xkcd-candidate-space-v1'],
'model_confidence' => ['type' => 'string', 'enum' => ['high', 'medium', 'low']],
'entropy_bits' => ['type' => 'number', 'format' => 'float', 'example' => 77.5],
],
'additionalProperties' => true,
],
],
'additionalProperties' => true,
],
],
],
'timestamp' => ['type' => 'string', 'format' => 'date-time'],
'message' => ['type' => 'string', 'example' => 'Generated 1 password(s) of type \'high-entropy\''],
],
'required' => ['success', 'data', 'timestamp'],
],
'PasswordTypesSuccess' => [
'type' => 'object',
'properties' => [
'success' => ['type' => 'boolean', 'example' => true],
'data' => [
'type' => 'object',
'properties' => [
'types' => [
'type' => 'array',
'items' => ['type' => 'string'],
],
],
],
'timestamp' => ['type' => 'string', 'format' => 'date-time'],
],
'required' => ['success', 'data', 'timestamp'],
],
'WordlistsSuccess' => [
'type' => 'object',
'properties' => [
'success' => ['type' => 'boolean', 'example' => true],
'data' => [
'type' => 'object',
'properties' => [
'wordlists' => [
'type' => 'object',
'additionalProperties' => true,
],
'count' => ['type' => 'integer', 'example' => 2],
],
],
'timestamp' => ['type' => 'string', 'format' => 'date-time'],
'message' => ['type' => 'string', 'example' => 'Available wordlists for XKCD password generation'],
],
'required' => ['success', 'data', 'timestamp'],
],
'HealthSuccess' => [
'type' => 'object',
'properties' => [
'status' => ['type' => 'string', 'example' => 'healthy'],
'version' => ['type' => 'string', 'example' => '2.0.0'],
'timestamp' => ['type' => 'string', 'format' => 'date-time'],
],
'required' => ['status', 'version', 'timestamp'],
],
'LocaleSuccess' => [
'type' => 'object',
'additionalProperties' => true,
],
],
],
];
}
public static function write(string $baseUrl, string $destination): void
{
$directory = dirname($destination);
if (!is_dir($directory)) {
mkdir($directory, 0777, true);
}
$json = json_encode(self::generate($baseUrl), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
if ($json === false) {
throw new \RuntimeException('Unable to encode OpenAPI document as JSON.');
}
file_put_contents($destination, $json . PHP_EOL);
}
/**
* Collect the public API routes as registered by config/routes.php.
*
* @return array<string, array<int, string>>
*/
private static function registeredPublicRoutes(): array
{
AppFactory::setContainer(new \DI\Container());
$app = AppFactory::create();
$routesDefinition = require __DIR__ . '/../../config/routes.php';
$routesDefinition($app);
$routes = [];
foreach ($app->getRouteCollector()->getRoutes() as $route) {
$pattern = $route->getPattern();
if (!str_starts_with($pattern, '/api/v1')) {
continue;
}
$methods = array_map('strtoupper', $route->getMethods());
$routes[$pattern] = array_values(array_unique($methods));
}
ksort($routes);
return $routes;
}
private static function normalizeRoutePattern(string $pattern): string
{
return preg_replace('/\{([a-zA-Z0-9_]+):.*\}/', '{$1}', $pattern) ?? $pattern;
}
/**
* @param array<int, string> $methods
* @return array<string, mixed>
*/
private static function buildPathOperations(string $routePath, array $methods): array
{
$operations = [];
$metadata = self::routeMetadata($routePath);
foreach ($methods as $method) {
$methodName = strtolower($method);
$operations[$methodName] = [
'summary' => $metadata['summary'],
'description' => $metadata['description'],
'responses' => self::responses($routePath, $metadata['successCode'], $metadata['errorCode']),
];
if ($method === 'POST' && $routePath === '/api/v1/passwords/generate') {
$operations[$methodName]['requestBody'] = [
'required' => true,
'content' => [
'application/json' => [
'schema' => [
'$ref' => '#/components/schemas/PasswordGenerationRequest',
],
],
],
];
}
if (str_contains($routePath, '{lang}')) {
$operations[$methodName]['parameters'] = [[
'name' => 'lang',
'in' => 'path',
'required' => true,
'schema' => ['type' => 'string', 'enum' => ['en', 'de', 'de_CH', 'fr', 'it', 'es', 'pt', 'ja', 'zh']],
'description' => 'Locale code to fetch translation data for.',
]];
}
}
return $operations;
}
/**
* @return array{summary:string,description:string,successCode:int,errorCode:int}
*/
private static function routeMetadata(string $routePath): array
{
$metadata = [
'/api/v1/passwords' => [
'summary' => 'List available password generation types',
'description' => 'Returns the supported password generation algorithms.',
'successCode' => 200,
'errorCode' => 500,
],
'/api/v1/passwords/generate' => [
'summary' => 'Generate one or more passwords',
'description' => 'Generates passwords using the chosen algorithm and options.',
'successCode' => 200,
'errorCode' => 400,
],
'/api/v1/passwords/wordlists' => [
'summary' => 'List available XKCD wordlists',
'description' => 'Returns available language wordlists for passphrase generation.',
'successCode' => 200,
'errorCode' => 500,
],
'/api/v1/health' => [
'summary' => 'Health check',
'description' => 'Returns the health status of the API service.',
'successCode' => 200,
'errorCode' => 500,
],
'/api/v1/locales/{lang}' => [
'summary' => 'Fetch locale data',
'description' => 'Returns the translation resource for a given locale code.',
'successCode' => 200,
'errorCode' => 404,
],
];
return $metadata[$routePath] ?? [
'summary' => 'API route',
'description' => 'Generated from the public Slim route registry.',
'successCode' => 200,
'errorCode' => 500,
];
}
/**
* @return array<string, array<string, mixed>>
*/
private static function responses(string $routePath, int $successCode, int $errorCode): array
{
return [
(string) $successCode => [
'description' => 'Successful response',
'content' => [
'application/json' => [
'schema' => self::successSchemaFor($routePath),
],
],
],
(string) $errorCode => [
'description' => 'Error response',
'content' => [
'application/json' => [
'schema' => [
'$ref' => '#/components/schemas/ErrorResponse',
],
],
],
],
];
}
private static function successSchemaFor(string $routePath): array
{
return match ($routePath) {
'/api/v1/passwords' => [
'type' => 'object',
'properties' => [
'success' => ['type' => 'boolean', 'example' => true],
'data' => [
'type' => 'object',
'properties' => [
'types' => ['type' => 'array', 'items' => ['type' => 'string']],
],
],
'timestamp' => ['type' => 'string', 'format' => 'date-time'],
'message' => ['type' => 'string', 'example' => 'Available password generation types'],
],
'required' => ['success', 'data', 'timestamp'],
],
'/api/v1/passwords/generate' => [
'$ref' => '#/components/schemas/PasswordGenerationSuccess',
],
'/api/v1/passwords/wordlists' => [
'$ref' => '#/components/schemas/WordlistsSuccess',
],
'/api/v1/health' => [
'$ref' => '#/components/schemas/HealthSuccess',
],
'/api/v1/locales/{lang}' => [
'$ref' => '#/components/schemas/LocaleSuccess',
],
default => [
'type' => 'object',
'additionalProperties' => true,
],
};
}
}
+214
View File
@@ -0,0 +1,214 @@
<?php
declare(strict_types=1);
namespace App\Api;
use App\Managers\PasswordGeneratorManager;
use App\Utils\ConfigLoader;
use App\Utils\ResponseHelper;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Log\LoggerInterface;
class PasswordController
{
public function __construct(
private PasswordGeneratorManager $generatorManager,
private ResponseHelper $responseHelper,
private LoggerInterface $logger
) {}
public function getPasswordTypes(Request $request, Response $response): Response
{
try {
$types = $this->generatorManager->getAvailableTypes();
$this->logger->info('Password types requested', ['count' => count($types)]);
return $this->responseHelper->successResponse(
$response,
['types' => $types],
'Available password generation types'
);
} catch (\Exception $e) {
$this->logger->error('Error fetching password types', ['error' => $e->getMessage()]);
return $this->responseHelper->errorResponse(
$response,
'Failed to fetch password types',
500
);
}
}
public function getGenerators(Request $request, Response $response): Response
{
try {
$definitions = array_values($this->generatorManager->getGeneratorDefinitions());
$appConfig = ConfigLoader::load();
$rouletteIntervalSeconds = (int)($appConfig['generator']['roulette_interval_seconds'] ?? 3);
$rouletteIntervalSeconds = max(1, min(30, $rouletteIntervalSeconds));
$this->logger->info('Generator definitions requested', ['count' => count($definitions)]);
return $this->responseHelper->successResponse(
$response,
[
'generators' => $definitions,
'settings' => [
'roulette_interval_seconds' => $rouletteIntervalSeconds,
],
],
'Available password generators'
);
} catch (\Exception $e) {
$this->logger->error('Error fetching generator definitions', ['error' => $e->getMessage()]);
return $this->responseHelper->errorResponse(
$response,
'Failed to fetch generator definitions',
500
);
}
}
/**
* NEW: Get available wordlists for XKCD generator
*/
public function getAvailableWordlists(Request $request, Response $response): Response
{
try {
$xkcdGenerator = $this->generatorManager->getGenerator('xkcdpass');
$availableLanguages = $xkcdGenerator->getAvailableLanguages();
// Filter to only show available wordlists
$availableWordlists = array_filter($availableLanguages, function($lang) {
return $lang['available'];
});
// If no wordlists are available, provide default language metadata
if (empty($availableWordlists)) {
$this->logger->warning('No wordlist files found, using default language metadata');
// Provide basic language info even without files
$defaultLanguages = [
'en' => [
'name' => 'English (Default)',
'description' => 'Built-in English word list',
'file' => 'eff_large_wordlist_en.txt',
'available' => false,
'fallback' => true
],
'de' => [
'name' => 'German (Default)',
'description' => 'Built-in German word list',
'file' => 'german_common_words_de.txt',
'available' => false,
'fallback' => true
]
];
$availableWordlists = $defaultLanguages;
}
$this->logger->info('Wordlists requested', [
'available_count' => count($availableWordlists),
'languages' => array_keys($availableWordlists)
]);
return $this->responseHelper->successResponse(
$response,
[
'wordlists' => $availableWordlists,
'count' => count($availableWordlists)
],
'Available wordlists for XKCD password generation'
);
} catch (\Exception $e) {
$this->logger->error('Error fetching wordlists', ['error' => $e->getMessage()]);
return $this->responseHelper->errorResponse(
$response,
'Failed to fetch available wordlists',
500
);
}
}
public function generatePasswords(Request $request, Response $response): Response
{
try {
$data = $request->getParsedBody();
$type = $data['type'] ?? 'high-entropy';
$count = max(1, min(50, (int)($data['count'] ?? 1)));
$options = $data['options'] ?? [];
// Validate required parameters
if (empty($type)) {
return $this->responseHelper->errorResponse(
$response,
'Password type is required',
400
);
}
$result = $this->generatorManager->generate($type, $count, $options);
$this->logger->info('Passwords generated', [
'type' => $type,
'count' => $count,
'entropy' => $result['entropy_bits'],
'ip' => $this->getClientIp($request)
]);
return $this->responseHelper->successResponse(
$response,
$result,
"Generated {$count} password(s) of type '{$type}'"
);
} catch (\InvalidArgumentException $e) {
$this->logger->warning('Invalid password generation request', [
'error' => $e->getMessage(),
'ip' => $this->getClientIp($request)
]);
return $this->responseHelper->errorResponse(
$response,
$e->getMessage(),
400
);
} catch (\Exception $e) {
$this->logger->error('Error generating passwords', [
'error' => $e->getMessage(),
'ip' => $this->getClientIp($request)
]);
return $this->responseHelper->errorResponse(
$response,
'Failed to generate passwords',
500
);
}
}
private function getClientIp(Request $request): string
{
$serverParams = $request->getServerParams();
if (!empty($serverParams['HTTP_CLIENT_IP'])) {
return $serverParams['HTTP_CLIENT_IP'];
} elseif (!empty($serverParams['HTTP_X_FORWARDED_FOR'])) {
return explode(',', $serverParams['HTTP_X_FORWARDED_FOR'])[0];
} elseif (!empty($serverParams['REMOTE_ADDR'])) {
return $serverParams['REMOTE_ADDR'];
}
return '127.0.0.1';
}
}
@@ -0,0 +1,469 @@
<?php
declare(strict_types=1);
namespace App\Generators;
use InvalidArgumentException;
use RuntimeException;
use Psr\Log\LoggerInterface;
use App\Utils\ConfigLoader;
use App\Services\Logging\LoggingHelper;
use PHPUnit\TextUI\XmlConfiguration\Logging\Logging;
/**
* Abstract base class for password generators
* Implements common functionality including i18n key derivation and validation
*
* @package App\Generators
*/
abstract class AbstractPasswordGenerator implements PasswordGeneratorInterface
{
protected ?LoggerInterface $logger = null;
protected array $config = [];
public function __construct(?LoggerInterface $logger = null)
{
$this->logger = $logger;
$this->loadConfiguration();
}
/**
* Load configuration from config/config.json.
*/
protected function loadConfiguration(): void
{
try {
$this->config = ConfigLoader::load();
LoggingHelper::debug($this->logger, $this->getId(), 'Configuration loaded');
} catch (\Exception $e) {
LoggingHelper::warning(
$this->logger,
$this->getId(),
'Failed to load config',
['error' => $e->getMessage()]
);
$this->config = [];
}
}
/**
* Get configuration value using dot notation with safe nested access
*
* Provides a convenient way to access nested configuration arrays using
* dot notation (e.g., 'llm.providers.openai.display_name'). Returns a
* default value if the path doesn't exist, preventing errors from missing keys.
*
* This is particularly useful for:
* - LLM provider configuration: $this->getConfigValue('llm.providers.openai')
* - Locale settings: $this->getConfigValue('locale.default', 'en')
* - Logging levels: $this->getConfigValue('logging.level', 'info')
* - Any nested configuration structure
*
* @param string $path Configuration path using dot notation (e.g., 'key1.key2.key3')
* @param mixed $default Fallback value if path doesn't exist (default: null)
* @return mixed The configuration value at the path, or $default if not found
*
* @throws RuntimeException If $this->config is not loaded or initialized
*
* @example
* // Get a configuration value with fallback
* $lang = $this->getConfigValue('locale.default', 'en');
*
* // Get nested LLM configuration
* $provider = $this->getConfigValue('llm.providers.openai', null);
*
* // Get logging level (defaults to 'info' if not set)
* $level = $this->getConfigValue('logging.level', 'info');
*
* // Get a boolean value safely
* $debugMode = $this->getConfigValue('app.debug', false);
*
* // Navigate deep into configuration
* $displayName = $this->getConfigValue('llm.providers.openai.display_name', 'OpenAI');
*/
protected function getConfigValue(string $path, mixed $default = null): mixed
{
// Guard: Ensure config is loaded
if ($this->config === null) {
if ($this->logger) {
$this->logger->warning(
$this->getId() . ': getConfigValue called but config not loaded',
['requested_path' => $path]
);
}
return $default;
}
// Handle empty path
if (empty($path)) {
return $this->config;
}
// Split path by dots to get individual keys
$keys = explode('.', $path);
// Start with the full config array
$current = $this->config;
// Traverse through each key in the path
foreach ($keys as $key) {
// Validate key is not empty
if (empty($key)) {
LoggingHelper::warning(
$this->logger,
$this->getId(),
'getConfigValue received empty key in path',
['path' => $path]
);
return $default;
}
// Check if current value is an array and has the key
if (is_array($current)) {
if (isset($current[$key])) {
// Move to the next level
$current = $current[$key];
} else {
// Key not found in this level
LoggingHelper::debug(
$this->logger,
$this->getId(),
'getConfigValue key not found in path',
['key' => $key, 'path' => $path]
);
return $default;
}
} else {
// Current value is not an array, can't navigate further
LoggingHelper::debug(
$this->logger,
$this->getId(),
'getConfigValue cannot navigate non-array value',
['key' => $key, 'path' => $path]
);
return $default;
}
}
// Successfully retrieved the value at the path
LoggingHelper::debug(
$this->logger,
$this->getId(),
'getConfigValue retrieved value',
['path' => $path, 'type' => gettype($current)]
);
return $current;
}
/**
* Get generator name i18n key (programmatically derived)
*/
public function getNameKey(): string
{
return $this->getId() . '_name';
}
/**
* Get generator description i18n key (programmatically derived)
*/
public function getDescriptionKey(): string
{
return $this->getId() . '_description';
}
/**
* Get i18n key for option label (programmatically derived)
*/
public function getOptionLabelKey(string $optionName): string
{
return $this->getId() . '_option_' . $optionName . '_label';
}
/**
* Get i18n key for option description (programmatically derived)
*/
public function getOptionDescriptionKey(string $optionName): string
{
return $this->getId() . '_option_' . $optionName . '_description';
}
/**
* Get i18n key for option group title (programmatically derived)
*/
public function getOptionGroupTitleKey(string $groupName): string
{
return $this->getId() . '_options_group_' . $groupName . '_title';
}
/**
* Get i18n key for select option choice (programmatically derived)
*/
public function getOptionChoiceLabelKey(string $optionName, string $choiceValue): string
{
return $this->getId() . '_option_' . $optionName . '_choice_' . $choiceValue;
}
/**
* Get i18n key for validation error (programmatically derived)
*/
public function getValidationErrorKey(string $optionName): string
{
return $this->getId() . '_option_' . $optionName . '_validation_error';
}
/**
* Get all i18n keys used by this generator
*
* Automatically collects all keys from generator metadata and options
*/
public function getAllI18nKeys(): array
{
$keys = [];
// Generator metadata keys
$keys[] = $this->getNameKey();
$keys[] = $this->getDescriptionKey();
$optionDefs = $this->getOptionDefinitions();
$groups = [];
foreach ($optionDefs as $optionName => $definition) {
// Option label and description keys
$keys[] = $this->getOptionLabelKey($optionName);
$keys[] = $this->getOptionDescriptionKey($optionName);
$keys[] = $this->getValidationErrorKey($optionName);
// Group title key (collect unique groups)
$group = $definition['group'] ?? 'basic';
if (!in_array($group, $groups)) {
$groups[] = $group;
$keys[] = $this->getOptionGroupTitleKey($group);
}
// Select option choice keys (if translateChoices is true)
if ($definition['type'] === 'select' && ($definition['translateChoices'] ?? true)) {
$choices = $definition['choices'] ?? [];
foreach ($choices as $choice) {
$value = is_array($choice) ? $choice['value'] : $choice;
$keys[] = $this->getOptionChoiceLabelKey($optionName, $value);
}
}
}
return $keys;
}
/**
* Get default options from definitions
*/
public function getDefaultOptions(): array
{
$defaults = [];
foreach ($this->getOptionDefinitions() as $name => $definition) {
$defaults[$name] = $definition['default'] ?? null;
}
return $defaults;
}
/**
* Get complete option schema with derived i18n keys
*/
public function getOptionSchema(): array
{
$schema = [];
$optionDefs = $this->getOptionDefinitions();
foreach ($optionDefs as $name => $definition) {
$option = [
'name' => $name,
'type' => $definition['type'],
'labelKey' => $this->getOptionLabelKey($name),
'descriptionKey' => $this->getOptionDescriptionKey($name),
'default' => $definition['default'] ?? null,
'required' => $definition['required'] ?? false,
'group' => $definition['group'] ?? 'basic',
'groupTitleKey' => $this->getOptionGroupTitleKey($definition['group'] ?? 'basic'),
'validationErrorKey' => $this->getValidationErrorKey($name)
];
// Type-specific properties
switch ($definition['type']) {
case 'integer':
if (isset($definition['min'])) $option['min'] = $definition['min'];
if (isset($definition['max'])) $option['max'] = $definition['max'];
break;
case 'string':
if (isset($definition['minLength'])) $option['minLength'] = $definition['minLength'];
if (isset($definition['maxLength'])) $option['maxLength'] = $definition['maxLength'];
if (isset($definition['pattern'])) $option['pattern'] = $definition['pattern'];
break;
case 'select':
$translateChoices = $definition['translateChoices'] ?? true;
$option['translateChoices'] = $translateChoices;
$formattedChoices = [];
foreach ($definition['choices'] ?? [] as $choice) {
if (is_array($choice)) {
$value = $choice['value'];
$label = $choice['label'];
} else {
$value = $choice;
$label = $choice;
}
$formattedChoices[] = [
'value' => $value,
'label' => $label,
'labelKey' => $translateChoices
? $this->getOptionChoiceLabelKey($name, $value)
: null
];
}
$option['choices'] = $formattedChoices;
break;
}
$schema[] = $option;
}
return $schema;
}
/**
* Validate options against definitions
*/
public function validateOptions(array $options): array
{
$definitions = $this->getOptionDefinitions();
$validated = $this->getDefaultOptions();
foreach ($definitions as $name => $definition) {
// Skip if option not provided and not required
if (!isset($options[$name])) {
if ($definition['required'] ?? false) {
throw new InvalidArgumentException(
"Required option '{$name}' is missing"
);
}
continue;
}
$value = $options[$name];
// Type-specific validation
switch ($definition['type']) {
case 'integer':
if (!is_int($value) && !is_numeric($value)) {
throw new InvalidArgumentException(
"Option '{$name}' must be an integer"
);
}
$value = (int)$value;
if (isset($definition['min']) && $value < $definition['min']) {
throw new InvalidArgumentException(
"Option '{$name}' must be at least {$definition['min']}"
);
}
if (isset($definition['max']) && $value > $definition['max']) {
throw new InvalidArgumentException(
"Option '{$name}' must be at most {$definition['max']}"
);
}
break;
case 'boolean':
if (!is_bool($value) && !in_array($value, [0, 1, '0', '1'], true)) {
throw new InvalidArgumentException(
"Option '{$name}' must be a boolean"
);
}
$value = (bool)$value;
break;
case 'string':
if (!is_string($value)) {
throw new InvalidArgumentException(
"Option '{$name}' must be a string"
);
}
$length = strlen($value);
if (isset($definition['minLength']) && $length < $definition['minLength']) {
throw new InvalidArgumentException(
"Option '{$name}' must be at least {$definition['minLength']} characters"
);
}
if (isset($definition['maxLength']) && $length > $definition['maxLength']) {
throw new InvalidArgumentException(
"Option '{$name}' must be at most {$definition['maxLength']} characters"
);
}
if (isset($definition['pattern']) && !preg_match($definition['pattern'], $value)) {
throw new InvalidArgumentException(
"Option '{$name}' does not match required pattern"
);
}
break;
case 'select':
if (!is_string($value)) {
throw new InvalidArgumentException(
"Option '{$name}' must be a string"
);
}
$validValues = array_map(function ($choice) {
return is_array($choice) ? $choice['value'] : $choice;
}, $definition['choices'] ?? []);
if (!in_array($value, $validValues, true)) {
throw new InvalidArgumentException(
"Option '{$name}' must be one of: " . implode(', ', $validValues)
);
}
break;
}
$validated[$name] = $value;
}
return $validated;
}
/**
* Parse wordlist entry that may contain word and explanation separated by tab
* Returns array with 'word' and 'explanation' keys
*/
protected function parseWordlistEntry(string $entry): array
{
$parts = explode("\t", $entry, 2);
return [
'word' => trim($parts[0]),
'explanation' => isset($parts[1]) ? trim($parts[1]) : null
];
}
/**
* Format multiple word explanations for display
*/
protected function formatExplanations(array $wordExplanations): ?string
{
$explanations = array_filter(
array_column($wordExplanations, 'explanation')
);
if (empty($explanations)) {
return null;
}
return implode(' — ', $explanations);
}
}
+317
View File
@@ -0,0 +1,317 @@
<?php
declare(strict_types=1);
/**
* PHP fallback implementation for pwgen logic (pronounceable passwords).
*
* Based on NIST FIPS 181 "Automated Password Generator" standard
* and inspired by roderik/pwgen-php (https://github.com/roderik/pwgen-php)
*
* Original pwgen concept:
* Copyright (c) 2001 Theodore Ts'o <tytso@alum.mit.edu>
*
* PHP implementation credits:
* Copyright (c) 2012 Roderik van der Veer
* BSD License (see https://github.com/roderik/pwgen-php/blob/master/LICENSE)
* Original source: https://github.com/roderik/pwgen-php
*
* Modernized for PHP 8+ by Password Generator Project, 2025.
* This class provides a fallback for pwgen-style password generation when
* the native pwgen executable is not available.
*
* Based on NIST FIPS 181 - Federal Information Processing Standards Publication 181
* "Automated Password Generator" (1993)
*/
class PwGenPhpNg
{
// Character classification flags (based on NIST FIPS 181)
private const VOWEL = 0x01;
private const CONSONANT = 0x02;
private const DIPHTHONG = 0x04;
private const NOT_FIRST = 0x08;
// Unit definitions with pronunciability rules
private array $elements = [
'a' => ['flags' => self::VOWEL, 'weight' => 4],
'ae' => ['flags' => self::VOWEL | self::DIPHTHONG, 'weight' => 1],
'ah' => ['flags' => self::VOWEL | self::DIPHTHONG, 'weight' => 1],
'ai' => ['flags' => self::VOWEL | self::DIPHTHONG, 'weight' => 1],
'b' => ['flags' => self::CONSONANT, 'weight' => 2],
'c' => ['flags' => self::CONSONANT, 'weight' => 2],
'ch' => ['flags' => self::CONSONANT | self::DIPHTHONG, 'weight' => 1],
'd' => ['flags' => self::CONSONANT, 'weight' => 2],
'e' => ['flags' => self::VOWEL, 'weight' => 4],
'ee' => ['flags' => self::VOWEL | self::DIPHTHONG, 'weight' => 1],
'ei' => ['flags' => self::VOWEL | self::DIPHTHONG, 'weight' => 1],
'f' => ['flags' => self::CONSONANT, 'weight' => 2],
'g' => ['flags' => self::CONSONANT, 'weight' => 2],
'gh' => ['flags' => self::CONSONANT | self::DIPHTHONG | self::NOT_FIRST, 'weight' => 1],
'h' => ['flags' => self::CONSONANT, 'weight' => 2],
'i' => ['flags' => self::VOWEL, 'weight' => 4],
'ie' => ['flags' => self::VOWEL | self::DIPHTHONG, 'weight' => 1],
'j' => ['flags' => self::CONSONANT, 'weight' => 1],
'k' => ['flags' => self::CONSONANT, 'weight' => 2],
'l' => ['flags' => self::CONSONANT, 'weight' => 2],
'm' => ['flags' => self::CONSONANT, 'weight' => 2],
'n' => ['flags' => self::CONSONANT, 'weight' => 2],
'ng' => ['flags' => self::CONSONANT | self::DIPHTHONG | self::NOT_FIRST, 'weight' => 1],
'o' => ['flags' => self::VOWEL, 'weight' => 4],
'oh' => ['flags' => self::VOWEL | self::DIPHTHONG, 'weight' => 1],
'oo' => ['flags' => self::VOWEL | self::DIPHTHONG, 'weight' => 1],
'p' => ['flags' => self::CONSONANT, 'weight' => 2],
'ph' => ['flags' => self::CONSONANT | self::DIPHTHONG, 'weight' => 1],
'qu' => ['flags' => self::CONSONANT | self::DIPHTHONG, 'weight' => 1],
'r' => ['flags' => self::CONSONANT, 'weight' => 2],
'rh' => ['flags' => self::CONSONANT | self::DIPHTHONG, 'weight' => 1],
's' => ['flags' => self::CONSONANT, 'weight' => 2],
'sh' => ['flags' => self::CONSONANT | self::DIPHTHONG, 'weight' => 1],
't' => ['flags' => self::CONSONANT, 'weight' => 2],
'th' => ['flags' => self::CONSONANT | self::DIPHTHONG, 'weight' => 1],
'u' => ['flags' => self::VOWEL, 'weight' => 4],
'v' => ['flags' => self::CONSONANT, 'weight' => 1],
'w' => ['flags' => self::CONSONANT, 'weight' => 2],
'wh' => ['flags' => self::CONSONANT | self::DIPHTHONG, 'weight' => 1],
'x' => ['flags' => self::CONSONANT | self::NOT_FIRST, 'weight' => 1],
'y' => ['flags' => self::VOWEL, 'weight' => 2],
'z' => ['flags' => self::CONSONANT, 'weight' => 1],
];
private array $vowels = [];
private array $consonants = [];
public function __construct()
{
$this->initializePhonemes();
}
/**
* Initialize phoneme arrays based on element classification
*/
private function initializePhonemes(): void
{
foreach ($this->elements as $phoneme => $properties) {
if ($properties['flags'] & self::VOWEL) {
for ($i = 0; $i < $properties['weight']; $i++) {
$this->vowels[] = $phoneme;
}
}
if ($properties['flags'] & self::CONSONANT) {
for ($i = 0; $i < $properties['weight']; $i++) {
$this->consonants[] = $phoneme;
}
}
}
}
/**
* Generate pronounceable passwords using pwgen logic
*
* @param int $length Minimum length of password
* @param int $count Number of passwords to generate
* @param bool $includeNumbers Include numbers in passwords
* @param bool $includeCapitals Include capital letters
* @return array Array of generated passwords
*/
public function generate(int $length = 8, int $count = 1, bool $includeNumbers = true, bool $includeCapitals = true): array
{
if ($length < 4) {
$length = 4; // Minimum for pronounceable passwords
}
$passwords = [];
for ($i = 0; $i < $count; $i++) {
$password = $this->generatePassword($length, $includeNumbers, $includeCapitals);
$passwords[] = $password;
}
return $passwords;
}
/**
* Generate a single pronounceable password
*/
private function generatePassword(int $length, bool $includeNumbers, bool $includeCapitals): string
{
$password = '';
$currentLength = 0;
$lastWasVowel = false;
$syllableLength = 0;
$maxAttempts = 100;
$attempts = 0;
while ($currentLength < $length && $attempts < $maxAttempts) {
$attempts++;
// Start new syllable with consonant, then alternate
if ($syllableLength === 0 || ($syllableLength > 0 && $lastWasVowel)) {
// Choose consonant
$phoneme = $this->selectConsonant($currentLength === 0);
$lastWasVowel = false;
} else {
// Choose vowel
$phoneme = $this->selectVowel();
$lastWasVowel = true;
}
// Check if adding this phoneme exceeds length
if ($currentLength + strlen($phoneme) > $length) {
// Try to find a shorter phoneme or end the password
if ($lastWasVowel) {
$phoneme = $this->selectVowel(1); // Single character vowel
} else {
$phoneme = $this->selectConsonant(false, 1); // Single character consonant
}
if ($currentLength + strlen($phoneme) > $length) {
break; // Can't fit anything more
}
}
$password .= $phoneme;
$currentLength += strlen($phoneme);
$syllableLength++;
// End syllable after 2-4 phonemes
if ($syllableLength >= 2 && random_int(0, 2) === 0) {
$syllableLength = 0;
$lastWasVowel = false;
}
}
// Apply post-processing
if ($includeCapitals) {
$password = $this->addCapitalization($password);
}
if ($includeNumbers && strlen($password) < $length) {
$password = $this->addNumbers($password, $length);
}
return $password;
}
/**
* Select a consonant phoneme
*/
private function selectConsonant(bool $isFirst = false, int $maxLength = PHP_INT_MAX): string
{
$candidates = [];
foreach ($this->consonants as $consonant) {
$element = $this->elements[$consonant];
// Skip if not allowed at beginning
if ($isFirst && ($element['flags'] & self::NOT_FIRST)) {
continue;
}
// Skip if too long
if (strlen($consonant) > $maxLength) {
continue;
}
$candidates[] = $consonant;
}
if (empty($candidates)) {
return 'b'; // Fallback
}
return $candidates[random_int(0, count($candidates) - 1)];
}
/**
* Select a vowel phoneme
*/
private function selectVowel(int $maxLength = PHP_INT_MAX): string
{
$candidates = [];
foreach ($this->vowels as $vowel) {
if (strlen($vowel) <= $maxLength) {
$candidates[] = $vowel;
}
}
if (empty($candidates)) {
return 'a'; // Fallback
}
return $candidates[random_int(0, count($candidates) - 1)];
}
/**
* Add capitalization to password (typically first letter)
*/
private function addCapitalization(string $password): string
{
if (strlen($password) === 0) {
return $password;
}
// Capitalize first letter
return ucfirst(strtolower($password));
}
/**
* Add numbers to password if there's remaining length
*/
private function addNumbers(string $password, int $targetLength): string
{
$remaining = $targetLength - strlen($password);
if ($remaining <= 0) {
return $password;
}
// Add 1-2 numbers
$numbersToAdd = min($remaining, random_int(1, 2));
for ($i = 0; $i < $numbersToAdd; $i++) {
$password .= (string)random_int(0, 9);
}
return $password;
}
/**
* Check if a password meets basic pronounceability rules
*/
public function isPronounceablePassword(string $password): bool
{
if (strlen($password) < 3) {
return false;
}
$vowelCount = 0;
$consonantCount = 0;
// Simple heuristic: check for reasonable vowel/consonant distribution
for ($i = 0; $i < strlen($password); $i++) {
$char = strtolower($password[$i]);
if (in_array($char, ['a', 'e', 'i', 'o', 'u', 'y'])) {
$vowelCount++;
} elseif (ctype_alpha($char)) {
$consonantCount++;
}
}
// Must have at least one vowel and one consonant
// And vowels shouldn't be more than 60% of letters
$totalLetters = $vowelCount + $consonantCount;
return $vowelCount >= 1 && $consonantCount >= 1 &&
($totalLetters === 0 || $vowelCount / $totalLetters <= 0.6);
}
/**
* Get available phoneme elements (for debugging/inspection)
*/
public function getPhonemeCount(): array
{
return [
'vowels' => count(array_unique($this->vowels)),
'consonants' => count(array_unique($this->consonants)),
'total_elements' => count($this->elements)
];
}
}
+462
View File
@@ -0,0 +1,462 @@
<?php
declare(strict_types=1);
namespace App\Generators;
use Psr\Log\LoggerInterface;
use App\Services\Logging\LoggingHelper;
/**
* Authentic Diceware Password Generator
*
* Implements the true EFF Diceware method using cryptographically
* secure dice roll simulation. Focused on maximum security and
* authentic Diceware methodology with educational features.
*
*/
class DicewareGenerator extends AbstractPasswordGenerator
{
private const GENERATOR_ID = 'diceware';
private const DICEWARE_KEYSPACE = 7776; // 6^5
private array $wordlist = [];
private array $lastDiceRolls = []; // Store for display
private int $effectiveWordlistSize = 0;
public function __construct(?LoggerInterface $logger = null)
{
parent::__construct($logger);
$this->loadWordlist();
}
public function getId(): string
{
return self::GENERATOR_ID;
}
public function getVersion(): string
{
return '2.0.0';
}
public function getName(): string
{
return 'Diceware';
}
public function getDescription(): string
{
return 'Authentic EFF Diceware method using cryptographically secure dice rolls for maximum security';
}
/**
* Centralized option definitions
*/
public function getOptionDefinitions(): array
{
$options = [
'words' => [
'type' => 'integer',
'default' => 6,
'min' => 4,
'max' => 12,
'required' => false,
'group' => 'basic',
'description' => 'Number of words to include in the passphrase (4-12 recommended for security).'
],
'show_dice_rolls' => [
'type' => 'boolean',
'default' => false,
'required' => false,
'group' => 'advanced',
'description' => 'Whether to display the dice rolls used to select each word (educational purpose).'
],
'uppercase_first' => [
'type' => 'boolean',
'default' => false,
'required' => false,
'group' => 'advanced',
'description' => 'Capitalize the first word only (traditional Diceware style).'
]
];
return $options;
}
public function generate(int $count = 1, array $options = []): array
{
$options = array_merge($this->getDefaultOptions(), $options);
$results = [];
for ($i = 0; $i < $count; $i++) {
$result = $this->generateSingle($options);
$results[] = $result;
}
return $results;
}
private function generateSingle(array $options): array
{
$wordCount = $options['words'];
$this->lastDiceRolls = [];
$selectedWords = [];
$diceRollsDisplay = [];
for ($i = 0; $i < $wordCount; $i++) {
// True Diceware: 5 dice rolls per word
$diceRoll = $this->rollSecureDice(5);
$index = implode('', $diceRoll);
$this->lastDiceRolls[] = $diceRoll;
$diceRollsDisplay[] = $index;
if (isset($this->wordlist[$index])) {
$wordEntry = $this->wordlist[$index];
if (is_array($wordEntry)) {
$selectedWords[] = $wordEntry; // Word with explanation
} else {
$selectedWords[] = ['word' => $wordEntry, 'explanation' => null];
}
} else {
// Fallback to random selection if index not found
$randomIndex = array_rand($this->wordlist);
$wordEntry = $this->wordlist[$randomIndex];
$selectedWords[] = is_array($wordEntry)
? $wordEntry
: ['word' => $wordEntry, 'explanation' => null];
}
}
// Extract words for password
$words = array_map(fn($entry) => $entry['word'], $selectedWords);
// Optional: Capitalize first word only (traditional Diceware style)
if ($options['uppercase_first']) {
$words[0] = ucfirst($words[0]);
for ($i = 1; $i < count($words); $i++) {
$words[$i] = strtolower($words[$i]);
}
}
// Diceware standard: space separation (no randomization)
$password = implode(' ', $words);
$result = [
'password' => $password,
'wordEntries' => $selectedWords
];
// Add dice rolls and security info if requested
if ($options['show_dice_rolls']) {
$result['dice_rolls'] = $diceRollsDisplay;
$result['security_info'] = $this->calculateDicewareEntropy($wordCount);
}
return $result;
}
/**
* Cryptographically secure dice rolling
* Uses true randomness for authentic Diceware methodology
*/
private function rollSecureDice(int $diceCount): array
{
$rolls = [];
for ($i = 0; $i < $diceCount; $i++) {
$rolls[] = random_int(1, 6);
}
return $rolls;
}
/**
* Calculate Diceware entropy and security metrics
*/
private function calculateDicewareEntropy(int $wordCount): array
{
// Use effective candidate count loaded at runtime.
$wordsInList = max(1, $this->effectiveWordlistSize);
$entropy = $wordCount * log($wordsInList, 2);
return [
'entropy_bits' => round($entropy, 1),
'words_in_list' => $wordsInList,
'combinations' => number_format(pow($wordsInList, $wordCount)),
'security_level' => $this->getSecurityLevel($entropy),
'crack_time_estimate' => $this->getCrackTimeEstimate($entropy)
];
}
private function getSecurityLevel(float $entropy): string
{
if ($entropy >= 80) return 'Excellent (suitable for high-value accounts)';
if ($entropy >= 65) return 'Strong (suitable for most uses)';
if ($entropy >= 50) return 'Adequate (basic security)';
return 'Weak (not recommended)';
}
private function getCrackTimeEstimate(float $entropy): string
{
// Assuming 1 trillion guesses per second
$combinations = pow(2, $entropy);
$guessesPerSecond = 1e12;
$secondsToHack = $combinations / (2 * $guessesPerSecond); // Average time
if ($secondsToHack < 60) return 'Less than 1 minute';
if ($secondsToHack < 3600) return round($secondsToHack / 60) . ' minutes';
if ($secondsToHack < 86400) return round($secondsToHack / 3600) . ' hours';
if ($secondsToHack < 31536000) return round($secondsToHack / 86400) . ' days';
if ($secondsToHack < 31536000000) return round($secondsToHack / 31536000) . ' years';
return 'Millions of years';
}
private function selectWords(int $count): array
{
// This helper remains available for the current diceware flow; generation is handled by the main path below.
if (empty($this->wordlist)) {
throw new \RuntimeException('Diceware wordlist is not loaded');
}
$wordEntries = [];
for ($i = 0; $i < $count; $i++) {
$diceRoll = $this->rollSecureDice(5);
$index = $this->diceRollToIndex($diceRoll);
if (isset($this->wordlist[$index])) {
$wordEntry = $this->wordlist[$index];
$wordEntries[] = is_array($wordEntry) ? $wordEntry : ['word' => $wordEntry, 'explanation' => null];
} else {
$randomIndex = array_rand($this->wordlist);
$wordEntry = $this->wordlist[$randomIndex];
$wordEntries[] = is_array($wordEntry) ? $wordEntry : ['word' => $wordEntry, 'explanation' => null];
}
}
return $wordEntries;
}
private function diceRollToIndex(array $rolls): string
{
return implode('', $rolls);
}
/**
* Load EFF Diceware wordlist with explanation support
* Supports both simple words and word\texplanation format
*/
private function loadWordlist(): void
{
$candidates = [];
// Preferred native Diceware-format file (11111 word).
$primaryPath = __DIR__ . '/../../data/wordlists/eff_large_wordlist.txt';
$this->collectWordsFromFile($primaryPath, $candidates);
// Augment with repository wordlists to ensure robust candidate space.
$augmentPaths = [
__DIR__ . '/../../data/wordlists/eff_large_wordlist_en.txt',
__DIR__ . '/../../data/wordlists/german_common_words_de.txt',
__DIR__ . '/../../data/wordlists/french_common_words_fr.txt',
__DIR__ . '/../../data/wordlists/italian_common_words_it.txt',
__DIR__ . '/../../data/wordlists/spanish_common_words_es.txt',
__DIR__ . '/../../data/wordlists/swiss_german_slaengikon.txt',
];
foreach ($augmentPaths as $path) {
$this->collectWordsFromFile($path, $candidates);
}
if (empty($candidates)) {
$this->loadFallbackWordlist();
$this->effectiveWordlistSize = count($this->wordlist);
return;
}
$candidateList = array_values($candidates);
$candidateCount = count($candidateList);
$keys = $this->generateDicewareKeys();
$this->wordlist = [];
foreach ($keys as $index => $diceKey) {
$this->wordlist[$diceKey] = $candidateList[$index % $candidateCount];
}
$this->effectiveWordlistSize = min($candidateCount, self::DICEWARE_KEYSPACE);
}
/**
* Calculate accurate entropy for Diceware generation
*/
public function calculateEntropyForOptions(array $options): float
{
$breakdown = $this->getEntropyBreakdownForOptions($options);
return (float) ($breakdown['entropy_bits'] ?? 0.0);
}
/**
* Detailed entropy contributors for API transparency.
*/
public function getEntropyBreakdownForOptions(array $options): array
{
$validated = $this->validateOptions($options);
$words = max(1, (int) ($validated['words'] ?? 6));
// Candidate space reflects the loaded wordlist quality.
$wordlistSize = max(1, $this->effectiveWordlistSize);
$wordEntropyBits = $words * log((float) $wordlistSize, 2);
return [
'entropy_model' => 'diceware-combinatorial-v1',
'model_confidence' => $wordlistSize >= self::DICEWARE_KEYSPACE ? 'high' : 'medium',
'requested_words' => $words,
'wordlist_size' => $wordlistSize,
'word_entropy_bits' => round($wordEntropyBits, 1),
'separator_entropy_bits' => 0.0,
'number_entropy_bits' => 0.0,
'entropy_bits' => round($wordEntropyBits, 1),
];
}
/**
* Load fallback wordlist with explanations
*/
private function loadFallbackWordlist(): void
{
// Fallback wordlist with explanations for demonstration
$fallbackWords = [
'Aardvark' => 'nocturnal mammal that feeds on ants',
'absurd' => 'wildly unreasonable or illogical',
'accrue' => 'accumulate or receive over time',
'Acme' => 'the highest point of achievement',
'adrift' => 'floating without being steered',
'Adult' => 'fully grown person',
'afflict' => 'cause pain or suffering to',
'ahead' => 'in front or in advance',
'aimless' => 'without purpose or direction',
'Algae' => 'simple plants found in water',
'allow' => 'permit or give permission',
'alone' => 'on one\'s own without others',
'amuse' => 'provide entertainment or cause laughter',
'Angel' => 'spiritual being or very kind person',
'Ankle' => 'joint connecting foot and leg',
'Apathy' => 'lack of interest or enthusiasm',
'aqua' => 'light blue-green color like water',
'Arcade' => 'covered passage with shops',
'ardent' => 'passionate or enthusiastic',
'Area' => 'region or extent of space',
'argue' => 'give reasons for or against something',
'Ark' => 'large boat or chest',
'armed' => 'equipped with weapons',
'Armor' => 'protective covering or clothing',
'Army' => 'organized military force',
'around' => 'on every side or in the vicinity',
'Arrow' => 'projectile shot from a bow',
'Arson' => 'crime of deliberately setting fire',
'Art' => 'creative expression or skill',
'ascend' => 'go up or climb higher',
'ask' => 'request information or something',
'Aspect' => 'particular part or feature',
'Atom' => 'smallest unit of matter',
'Attic' => 'space under the roof',
'Avatar' => 'representation of a person online',
'avoid' => 'keep away from or prevent',
'awake' => 'not sleeping or alert',
'Award' => 'prize given for achievement',
'aware' => 'conscious or informed',
'away' => 'at a distance from',
'Axis' => 'imaginary line through center',
'Backup' => 'copy kept for security',
'Badge' => 'symbol of authority or achievement',
'Bagel' => 'ring-shaped bread roll',
'Balance' => 'even distribution of weight',
'Ball' => 'round object used in games',
'Bamboo' => 'fast-growing woody grass',
'Banana' => 'yellow curved tropical fruit',
'Band' => 'group or strip of material',
'Banner' => 'flag or sign for display',
'bring' => 'take or carry to a place'
];
// Convert to Diceware index format for consistency
$index = 11111;
foreach ($fallbackWords as $word => $explanation) {
$this->wordlist[(string)$index] = [
'word' => $word,
'explanation' => $explanation
];
$index++;
}
$this->effectiveWordlistSize = count($this->wordlist);
}
/**
* Collect word entries from a wordlist file and deduplicate by lowercase word.
*/
private function collectWordsFromFile(string $path, array &$target): void
{
if (!file_exists($path) || !is_readable($path)) {
return;
}
$content = file_get_contents($path);
if ($content === false) {
return;
}
$lines = explode("\n", $content);
foreach ($lines as $line) {
$line = trim($line);
if ($line === '' || str_starts_with($line, '#')) {
continue;
}
// Supports classic Diceware lines: 12345 word[\texplanation]
if (preg_match('/^(\d{5})\s+(.+)$/', $line, $matches)) {
$entry = $this->parseWordlistEntry(trim($matches[2]));
} else {
$entry = $this->parseWordlistEntry($line);
}
$word = trim((string) ($entry['word'] ?? ''));
if ($word === '') {
continue;
}
$key = strtolower($word);
if (!isset($target[$key])) {
$target[$key] = [
'word' => $word,
'explanation' => $entry['explanation'] ?? null,
];
}
}
}
/**
* Generate all valid 5-dice key strings from 11111 to 66666.
*
* @return array<int, string>
*/
private function generateDicewareKeys(): array
{
$keys = [];
for ($a = 1; $a <= 6; $a++) {
for ($b = 1; $b <= 6; $b++) {
for ($c = 1; $c <= 6; $c++) {
for ($d = 1; $d <= 6; $d++) {
for ($e = 1; $e <= 6; $e++) {
$keys[] = (string) ($a . $b . $c . $d . $e);
}
}
}
}
}
return $keys;
}
}
+194
View File
@@ -0,0 +1,194 @@
<?php
declare(strict_types=1);
namespace App\Generators;
use Psr\Log\LoggerInterface;
/**
* High Entropy Password Generator
*
* Generates cryptographically secure random passwords with customizable
* character sets based on user preferences.
*/
class HighEntropyGenerator extends AbstractPasswordGenerator
{
private const GENERATOR_ID = 'high_entropy';
private const LOWERCASE = 'abcdefghijklmnopqrstuvwxyz';
private const UPPERCASE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
private const NUMBERS = '0123456789';
private const SYMBOLS = '!@#$%^&*()_+-=[]{}|;:,.<>?';
public function __construct(?LoggerInterface $logger = null)
{
parent::__construct($logger);
}
public function getId(): string
{
return self::GENERATOR_ID;
}
public function getVersion(): string
{
return '1.1.0';
}
public function getName(): string
{
return 'High Entropy';
}
public function getDescription(): string
{
return 'Cryptographically secure random passwords with customizable character sets';
}
/**
* Centralized option definitions
*/
public function getOptionDefinitions(): array
{
$options = [
'length' => [
'type' => 'integer',
'default' => 16,
'min' => 8,
'max' => 128,
'required' => false,
'group' => 'basic',
'description' => 'Total length of the generated password'
],
'include_uppercase' => [
'type' => 'boolean',
'default' => true,
'required' => false,
'group' => 'character_set',
'description' => 'Include uppercase letters (A-Z)'
],
'include_lowercase' => [
'type' => 'boolean',
'default' => true,
'required' => false,
'group' => 'character_set',
'description' => 'Include lowercase letters (a-z)'
],
'include_numbers' => [
'type' => 'boolean',
'default' => true,
'required' => false,
'group' => 'character_set',
'description' => 'Include numbers (0-9)'
],
'include_symbols' => [
'type' => 'boolean',
'default' => true,
'required' => false,
'group' => 'character_set',
'description' => 'Include symbols (e.g., !@#$%)'
],
];
return $options;
}
public function generate(int $count = 1, array $options = []): array
{
$options = array_merge($this->getDefaultOptions(), $options);
$passwords = [];
for ($i = 0; $i < $count; $i++) {
$passwords[] = $this->generateSingle($options);
}
return $passwords;
}
private function generateSingle(array $options): string
{
$characterSet = $this->buildCharacterSet($options);
$length = $options['length'];
if (empty($characterSet)) {
throw new \RuntimeException('No character types selected');
}
// Generate password ensuring at least one character from each selected type
$password = $this->generateWithGuaranteedTypes($characterSet, $length, $options);
// Shuffle the password to randomize position of guaranteed characters
return $this->shuffleString($password);
}
private function buildCharacterSet(array $options): string
{
$characterSet = '';
if ($options['include_lowercase'] ?? false) {
$characterSet .= self::LOWERCASE;
}
if ($options['include_uppercase'] ?? false) {
$characterSet .= self::UPPERCASE;
}
if ($options['include_numbers'] ?? false) {
$characterSet .= self::NUMBERS;
}
if ($options['include_symbols'] ?? false) {
$characterSet .= self::SYMBOLS;
}
return $characterSet;
}
private function generateWithGuaranteedTypes(string $characterSet, int $length, array $options): string
{
$password = '';
$guaranteedChars = [];
// Add at least one character from each selected type
if ($options['include_lowercase'] ?? false) {
$guaranteedChars[] = self::LOWERCASE[random_int(0, strlen(self::LOWERCASE) - 1)];
}
if ($options['include_uppercase'] ?? false) {
$guaranteedChars[] = self::UPPERCASE[random_int(0, strlen(self::UPPERCASE) - 1)];
}
if ($options['include_numbers'] ?? false) {
$guaranteedChars[] = self::NUMBERS[random_int(0, strlen(self::NUMBERS) - 1)];
}
if ($options['include_symbols'] ?? false) {
$guaranteedChars[] = self::SYMBOLS[random_int(0, strlen(self::SYMBOLS) - 1)];
}
// Add guaranteed characters
$password = implode('', $guaranteedChars);
// Fill remaining length with random characters from full set
$remainingLength = $length - count($guaranteedChars);
for ($i = 0; $i < $remainingLength; $i++) {
$password .= $characterSet[random_int(0, strlen($characterSet) - 1)];
}
return $password;
}
private function shuffleString(string $string): string
{
$characters = str_split($string);
// Fisher-Yates shuffle using cryptographically secure random
for ($i = count($characters) - 1; $i > 0; $i--) {
$j = random_int(0, $i);
[$characters[$i], $characters[$j]] = [$characters[$j], $characters[$i]];
}
return implode('', $characters);
}
}
@@ -0,0 +1,82 @@
<?php
declare(strict_types=1);
namespace App\Generators\Interfaces;
/**
* Interface for generator option schema definitions
*
* All option types must implement this interface to ensure
* consistent metadata and i18n support across generators.
*
* @package App\Generators\Interfaces
*/
interface GeneratorOptionInterface
{
/**
* Get the option name (internal identifier)
* @return string
*/
public function getName(): string;
/**
* Get the option type (integer, boolean, string, select)
* @return string
*/
public function getType(): string;
/**
* Get i18n key for the option label
* @return string
*/
public function getLabelKey(): string;
/**
* Get i18n key for the option description/help text
* @return string
*/
public function getDescriptionKey(): string;
/**
* Get the default value for this option
* @return mixed
*/
public function getDefault(): mixed;
/**
* Check if this option is required
* @return bool
*/
public function isRequired(): bool;
/**
* Get the option group (basic, advanced, character_set, etc.)
* @return string
*/
public function getGroup(): string;
/**
* Get i18n key for the group title
* @return string
*/
public function getGroupTitleKey(): string;
/**
* Convert option to array format for API responses
* @return array
*/
public function toArray(): array;
/**
* Validate a value against this option's constraints
* @param mixed $value
* @return bool
*/
public function validate(mixed $value): bool;
/**
* Get validation error message i18n key
* @return string
*/
public function getValidationErrorKey(): string;
}
+328
View File
@@ -0,0 +1,328 @@
<?php
declare(strict_types=1);
namespace App\Generators;
use Psr\Log\LoggerInterface;
/**
* Apple-Style Passkey Generator
*
* Generates pronounceable passwords using syllable patterns with
* integrated character substitutions, similar to Apple Keychain suggestions.
*
* REFACTORING CHANGES APPLIED:
* - Step 5: Removed non-functional add_numbers and add_symbols options
* - Focus on syllable-based generation with integrated substitutions
* - Apple-style formatting with character replacements (o→0, i→1, etc.)
*
* FIXES APPLIED:
* - Removed calculateEntropy method that conflicted with parent signature
* - Fixed generate() method to return simple string array (not objects)
*/
class PasskeyGenerator extends AbstractPasswordGenerator
{
private const GENERATOR_ID = 'passkey';
private const CONSONANTS = 'bcdfghjklmnpqrstvwxyz';
private const VOWELS = 'aeiou';
// Common consonant pairs that sound natural together
private const CONSONANT_PAIRS = [
'bl',
'br',
'ch',
'cl',
'cr',
'dr',
'fl',
'fr',
'gl',
'gr',
'pl',
'pr',
'sc',
'sh',
'sk',
'sl',
'sm',
'sn',
'sp',
'st',
'sw',
'th',
'tr',
'tw',
'wh',
'wr'
];
// Syllable patterns for natural pronunciation
private const PATTERNS = [
'cv', // consonant-vowel (most common)
'cvc', // consonant-vowel-consonant
'ccv', // consonant-consonant-vowel
'cvcv', // consonant-vowel-consonant-vowel (longer syllables)
'vc', // vowel-consonant (occasional)
];
public function __construct(?LoggerInterface $logger = null)
{
parent::__construct($logger);
}
public function getId(): string
{
return self::GENERATOR_ID;
}
public function getVersion(): string
{
return '1.2.0';
}
public function getName(): string
{
return 'Passkey';
}
public function getDescription(): string
{
return 'Apple-style pronounceable passwords with integrated character substitutions';
}
/**
* Centralized option definitions
*/
public function getOptionDefinitions(): array
{
$options = [
'syllables' => [
'type' => 'integer',
'default' => 6,
'min' => 4,
'max' => 12,
'required' => false,
'group' => 'basic',
'description' => 'Number of pronounceable syllables in the passkey'
]
];
return $options;
}
/**
* FIXED: Return simple array of password strings (not objects)
* This matches the expected format for both API and frontend
*/
public function generate(int $count = 1, array $options = []): array
{
$options = array_merge($this->getDefaultOptions(), $options);
$passwords = [];
for ($i = 0; $i < $count; $i++) {
$passwords[] = $this->generateSingle($options);
}
return $passwords;
}
private function generateSingle(array $options): string
{
$syllableCount = $options['syllables'];
// Generate pronounceable syllables
$syllables = $this->generateSyllables($syllableCount);
// Apply Apple-style formatting with integrated character substitutions
$password = $this->formatAppleStyle($syllables);
return $password;
}
/**
* Generate pronounceable syllables using various patterns
*/
private function generateSyllables(int $count): array
{
$syllables = [];
for ($i = 0; $i < $count; $i++) {
// Select a random syllable pattern
$pattern = self::PATTERNS[random_int(0, count(self::PATTERNS) - 1)];
$syllable = $this->generateSyllableByPattern($pattern);
$syllables[] = $syllable;
}
return $syllables;
}
/**
* Generate a syllable following a specific pattern
*/
private function generateSyllableByPattern(string $pattern): string
{
$syllable = '';
$i = 0;
while ($i < strlen($pattern)) {
$char = $pattern[$i];
if ($char === 'c') {
// Check if we can use a consonant pair
if ($i === 0 && random_int(1, 4) === 1) { // 25% chance
$pair = self::CONSONANT_PAIRS[random_int(0, count(self::CONSONANT_PAIRS) - 1)];
$syllable .= $pair;
// Skip next consonant if it exists in pattern
if ($i + 1 < strlen($pattern) && $pattern[$i + 1] === 'c') {
$i++; // Skip the next 'c' since we used a pair
}
} else {
$consonant = self::CONSONANTS[random_int(0, strlen(self::CONSONANTS) - 1)];
$syllable .= $consonant;
}
} else if ($char === 'v') {
$vowel = self::VOWELS[random_int(0, strlen(self::VOWELS) - 1)];
$syllable .= $vowel;
}
$i++;
}
return $syllable;
}
/**
* Apply Apple-style formatting with integrated character substitutions
*/
private function formatAppleStyle(array $syllables): string
{
$password = implode('', $syllables);
// Apple-style character substitutions (looks similar, maintains pronounceability)
$substitutions = [
'o' => '0', // o looks like 0
'i' => '1', // i looks like 1
'a' => '@', // a looks like @
's' => '$', // s looks like $
'e' => '3', // e looks like 3
];
// Apply 1-2 substitutions randomly for Apple-like feel (reduced from 1-3)
$maxSubstitutions = min(2, max(1, strlen($password) / 4)); // More conservative
$substitutionCount = random_int(1, $maxSubstitutions);
$applied = 0;
// Shuffle substitutions for randomness
$availableSubstitutions = $substitutions;
$keys = array_keys($availableSubstitutions);
shuffle($keys);
foreach ($keys as $letter) {
if ($applied >= $substitutionCount) break;
$replacement = $substitutions[$letter];
// Find all positions of this letter
$positions = [];
for ($i = 0; $i < strlen($password); $i++) {
if (strtolower($password[$i]) === $letter) { // Case insensitive search
$positions[] = $i;
}
}
if (!empty($positions)) {
// Replace one random occurrence of this letter
$randomPosition = $positions[random_int(0, count($positions) - 1)];
$password[$randomPosition] = $replacement;
$applied++;
}
}
// Apple-style: Capitalize first letter
$password = ucfirst($password);
return $password;
}
/**
* Calculate entropy for syllable-based generation
*/
public function calculateEntropyForOptions(array $options): float
{
$breakdown = $this->getEntropyBreakdownForOptions($options);
return (float) ($breakdown['entropy_bits'] ?? 0.0);
}
/**
* Detailed entropy contributors for API transparency.
*/
public function getEntropyBreakdownForOptions(array $options): array
{
$validated = $this->validateOptions($options);
$syllables = max(1, (int) ($validated['syllables'] ?? 6));
// Heuristic estimate based on average pronounceable-syllable branching.
$combinationsPerSyllable = 150.0;
$baseEntropyBits = $syllables * log($combinationsPerSyllable, 2);
// Character substitutions add a small, heuristic bonus.
$substitutionMultiplier = 1.2;
$substitutionEntropyBits = log($substitutionMultiplier, 2);
$totalEntropyBits = $baseEntropyBits + $substitutionEntropyBits;
return [
'entropy_model' => 'passkey-syllable-heuristic-v1',
'model_confidence' => 'medium',
'requested_syllables' => $syllables,
'estimated_combinations_per_syllable' => $combinationsPerSyllable,
'substitution_multiplier' => $substitutionMultiplier,
'syllable_entropy_bits' => round($baseEntropyBits, 1),
'substitution_entropy_bits' => round($substitutionEntropyBits, 1),
'entropy_bits' => round($totalEntropyBits, 1),
];
}
/**
* Get example passkeys for documentation
*/
public function getExamples(): array
{
return [
'Short (4 syllables)' => $this->generate(3, ['syllables' => 4]),
'Medium (6 syllables)' => $this->generate(3, ['syllables' => 6]),
'Long (8 syllables)' => $this->generate(3, ['syllables' => 8]),
];
}
/**
* Get pronunciation guide for a generated password
*/
public function getPronunciationGuide(string $password): string
{
// Remove numbers and symbols for pronunciation
$pronounceable = preg_replace('/[0-9@$3]/', '', $password);
// Add hyphens between likely syllable breaks
$guide = '';
$vowelPattern = '/[aeiou]/i';
$lastWasVowel = false;
for ($i = 0; $i < strlen($pronounceable); $i++) {
$char = $pronounceable[$i];
$isVowel = preg_match($vowelPattern, $char);
// Add hyphen before consonants that follow vowels (syllable breaks)
if (!$isVowel && $lastWasVowel && $i > 0) {
$guide .= '-';
}
$guide .= strtolower($char);
$lastWasVowel = $isVowel;
}
return $guide;
}
}
@@ -0,0 +1,178 @@
<?php
declare(strict_types=1);
namespace App\Generators;
use App\Generators\Interfaces\GeneratorOptionInterface;
/**
* Interface for all password generators with centralized option definitions
* and programmatic i18n key derivation
*
* All generators must define their options in getOptionDefinitions() and
* i18n keys will be automatically derived from the generator ID and option names.
*
* @package App\Generators
*/
interface PasswordGeneratorInterface
{
/**
* Generate passwords
*
* @param int $count Number of passwords to generate
* @param array $options Generation options
* @return array Array of password data structures
*/
public function generate(int $count = 1, array $options = []): array;
/**
* Get generator ID (internal identifier)
* Used as base for all i18n keys
*
* @return string Generator ID (e.g., 'pwgen', 'xkcdpass')
*/
public function getId(): string;
/**
* Get generator version
*
* @return string Version string (e.g., '3.1.0')
*/
public function getVersion(): string;
/**
* Get option definitions (centralized configuration)
*
* All option metadata, constraints, and defaults in one place.
* I18n keys will be automatically derived from generator ID and option names.
*
* Example:
* [
* 'length' => [
* 'type' => 'integer',
* 'default' => 8,
* 'min' => 4,
* 'max' => 32,
* 'required' => false,
* 'group' => 'basic'
* ],
* 'separator' => [
* 'type' => 'string',
* 'default' => '',
* 'maxLength' => 10,
* 'group' => 'advanced'
* ],
* 'wordlist' => [
* 'type' => 'select',
* 'default' => 'en',
* 'choices' => $this->getAvailableWordlists(),
* 'translateChoices' => false, // Don't translate wordlist names
* 'group' => 'basic'
* ]
* ]
*
* @return array Associative array of option definitions
*/
public function getOptionDefinitions(): array;
/**
* Get generator name i18n key (automatically derived)
*
* Pattern: {generator_id}_name
*
* @return string i18n key
*/
public function getNameKey(): string;
/**
* Get generator description i18n key (automatically derived)
*
* Pattern: {generator_id}_description
*
* @return string i18n key
*/
public function getDescriptionKey(): string;
/**
* Get i18n key for an option label (automatically derived)
*
* Pattern: {generator_id}_option_{option_name}_label
*
* @param string $optionName Option name
* @return string i18n key
*/
public function getOptionLabelKey(string $optionName): string;
/**
* Get i18n key for an option description (automatically derived)
*
* Pattern: {generator_id}_option_{option_name}_description
*
* @param string $optionName Option name
* @return string i18n key
*/
public function getOptionDescriptionKey(string $optionName): string;
/**
* Get i18n key for an option group title (automatically derived)
*
* Pattern: {generator_id}_options_group_{group_name}_title
*
* @param string $groupName Group name
* @return string i18n key
*/
public function getOptionGroupTitleKey(string $groupName): string;
/**
* Get i18n key for a select option choice (automatically derived)
*
* Pattern: {generator_id}_option_{option_name}_choice_{choice_value}
*
* @param string $optionName Option name
* @param string $choiceValue Choice value
* @return string i18n key
*/
public function getOptionChoiceLabelKey(string $optionName, string $choiceValue): string;
/**
* Get i18n key for validation error (automatically derived)
*
* Pattern: {generator_id}_option_{option_name}_validation_error
*
* @param string $optionName Option name
* @return string i18n key
*/
public function getValidationErrorKey(string $optionName): string;
/**
* Get all i18n keys used by this generator (for translation extraction)
*
* Returns array of all keys that need translations
*
* @return array Array of i18n keys
*/
public function getAllI18nKeys(): array;
/**
* Get default options (derived from option definitions)
*
* @return array Associative array of option_name => default_value
*/
public function getDefaultOptions(): array;
/**
* Validate options against definitions
*
* @param array $options Options to validate
* @return array Validated and sanitized options
* @throws \InvalidArgumentException If validation fails
*/
public function validateOptions(array $options): array;
/**
* Get complete option schema for API (with derived i18n keys)
*
* @return array Array of option metadata with all i18n keys
*/
public function getOptionSchema(): array;
}
@@ -0,0 +1,666 @@
<?php
declare(strict_types=1);
namespace App\Generators;
use App\Services\LLM\LLMManager;
use Psr\Log\LoggerInterface;
/**
* Phrase Abbreviator Generator v1.3
*
* Generates passwords from sentences by extracting and combining first letters
* and other word characters based on customizable rules.
*
* Supports:
* - Single or dual character extraction per word
* - Optional random number addition (1-4 digits)
* - AI-generated sentences via LLM integration
* - Localized number word conversion from translation files
* - Multiple language support for number words
*
* @package App\Generators
*/
class PhraseAbbreviatorGenerator extends AbstractPasswordGenerator
{
private const GENERATOR_ID = 'phraseabbreviator';
private const DEFAULT_SENTENCE = 'The quick brown fox jumps over the lazy dog.';
private const VOWELS = 'aeiouyAEIOUY';
private const CONSONANTS = 'bcdfghjklmnpqrstvwxzBCDFGHJKLMNPQRSTVWXZ';
private ?LLMManager $llmManager = null;
/**
* Constructor with optional LLM Manager and logger
*/
public function __construct(?LLMManager $llmManager = null, ?LoggerInterface $logger = null)
{
parent::__construct($logger); // ✅ Calls parent::__construct()
$this->llmManager = $llmManager;
}
public function getId(): string
{
return self::GENERATOR_ID;
}
public function getVersion(): string
{
return '1.3.0';
}
public function getName(): string
{
return 'Phrase Abbreviator';
}
public function getDescription(): string
{
return 'Generates memorable passwords from sentence fragments and word abbreviations';
}
/**
* Get option definitions
*/
public function getOptionDefinitions(): array
{
$options = [
'sentence' => [
'type' => 'string',
'default' => self::DEFAULT_SENTENCE,
'minLength' => 1,
'maxLength' => 500,
'required' => false, // Not required if using AI
'group' => 'basic',
'description' => 'User-supplied sentence (minimum 8 words, must start with capital letter)'
],
'character_extraction' => [
'type' => 'select',
'default' => 'single',
'choices' => [
['value' => 'single', 'label' => 'First letter only'],
['value' => 'dual', 'label' => 'First letter + next vowel/consonant']
],
'translateChoices' => true,
'required' => false,
'group' => 'basic',
'description' => 'Method for extracting characters from each word'
],
'language' => [
'type' => 'select',
'default' => 'en',
'choices' => $this->getLanguageChoices(),
'translateChoices' => false,
'required' => false,
'group' => 'basic',
'description' => 'Language for number word conversion'
],
'add_number' => [
'type' => 'boolean',
'default' => false,
'required' => false,
'group' => 'advanced',
'description' => 'Add random digits to the password'
],
'number_digits' => [
'type' => 'integer',
'default' => 2,
'min' => 1,
'max' => 4,
'required' => false,
'group' => 'advanced',
'description' => 'How many random digits to add (1-4)'
],
'number_position' => [
'type' => 'select',
'default' => 'append',
'choices' => [
['value' => 'prepend', 'label' => 'Before'],
['value' => 'append', 'label' => 'After']
],
'translateChoices' => true,
'required' => false,
'group' => 'advanced',
'description' => 'Place number before or after the password'
]
];
// Add AI options if LLM manager is available
if ($this->llmManager && $this->llmManager->hasAvailableProvider()) {
$options['use_ai_sentence'] = [
'type' => 'boolean',
'default' => false,
'required' => false,
'group' => 'ai',
'description' => 'Let AI generate a sentence from your prompt'
];
$options['ai_prompt'] = [
'type' => 'string',
'default' => 'Generate a creative, memorable sentence about technology.',
'maxLength' => 500,
'required' => false,
'group' => 'ai',
'description' => 'Prompt for AI to generate a memorable sentence (minimum 12 words)'
];
// Get available LLM providers
$providers = $this->llmManager->getAvailableProviders();
if (!empty($providers)) {
$providerChoices = [];
foreach ($providers as $name => $info) {
$providerChoices[] = [
'value' => $name,
'label' => $info['display_name']
];
}
$options['ai_provider'] = [
'type' => 'select',
'default' => $this->llmManager->getDefaultProviderName(),
'choices' => $providerChoices,
'translateChoices' => false,
'required' => false,
'group' => 'ai',
'description' => 'Which LLM provider to use for sentence generation'
];
// Get models for default provider
$defaultProvider = $this->llmManager->getDefaultProvider();
$modelChoices = [];
foreach ($defaultProvider->getAvailableModels() as $value => $label) {
$modelChoices[] = ['value' => $value, 'label' => $label];
}
$options['ai_model'] = [
'type' => 'select',
'default' => $defaultProvider->getDefaultModel(),
'choices' => $modelChoices,
'translateChoices' => false,
'required' => false,
'group' => 'ai',
'description' => 'Which AI model to use'
];
}
}
return $options;
}
/**
* Get language choices from configuration
*/
private function getLanguageChoices(): array
{
$languages = $this->config['locale']['languages'] ?? [
'en' => 'English',
'de' => 'Deutsch',
'fr' => 'Français',
'es' => 'Español',
'it' => 'Italiano'
];
$choices = [];
foreach ($languages as $code => $name) {
$choices[] = ['value' => $code, 'label' => $name];
}
return $choices;
}
public function validateOptions(array $options): array
{
$normalized = $options;
if (!($normalized['use_ai_sentence'] ?? false) && isset($normalized['sentence']) && empty(trim((string) $normalized['sentence']))) {
$normalized['sentence'] = self::DEFAULT_SENTENCE;
}
$validated = parent::validateOptions($normalized);
if (!($validated['use_ai_sentence'] ?? false) && empty(trim((string) ($validated['sentence'] ?? '')))) {
$validated['sentence'] = self::DEFAULT_SENTENCE;
}
return $validated;
}
/**
* Generate passwords from phrases
*/
public function generate(int $count = 1, array $options = []): array
{
// Validate options
$options = $this->validateOptions($options);
// Validate count
if ($count < 1 || $count > 100) {
throw new InvalidArgumentException('Count must be between 1 and 100');
}
if ($this->logger && $this->isLogLevel('debug')) {
$this->logger->debug('PhraseAbbreviator: starting generation', [
'count' => $count,
'extraction_type' => $options['character_extraction'],
'use_ai' => $options['use_ai_sentence'] ?? false
]);
}
$passwords = [];
for ($i = 0; $i < $count; $i++) {
try {
// Get sentence
if ($options['use_ai_sentence'] ?? false) {
if (!$this->llmManager) {
throw new RuntimeException('LLM manager not configured');
}
$sentence = $this->generateAiSentence($options);
} else {
$sentence = $options['sentence'];
if (empty($sentence)) {
throw new InvalidArgumentException(
'Sentence is required when not using AI generation'
);
}
}
// Validate sentence
$this->validateSentence($sentence, $options['use_ai_sentence'] ?? false);
// Replace number words with digits
$processedSentence = $this->replaceNumberWords($sentence, $options['language']);
// Generate password from sentence
$password = $this->generatePasswordFromSentence(
$processedSentence,
$options['character_extraction'],
$options['language']
);
// Add optional number
if ($options['add_number']) {
$password = $this->addRandomNumber(
$password,
$options['number_digits'],
$options['number_position']
);
}
$passwords[] = [
'password' => $password,
'method' => 'phrase_abbreviator',
'source_sentence' => $sentence,
'extraction_type' => $options['character_extraction']
];
} catch (\Exception $e) {
if ($this->logger) {
$this->logger->error('PhraseAbbreviator: generation iteration failed', [
'iteration' => $i + 1,
'error' => $e->getMessage()
]);
}
throw $e;
}
}
if ($this->logger && $this->isLogLevel('info')) {
$this->logger->info('PhraseAbbreviator: generation complete', [
'count' => count($passwords)
]);
}
return $passwords;
}
/**
* Validate sentence according to rules
*/
private function validateSentence(string $sentence, bool $isAiGenerated): void
{
$minWords = $isAiGenerated ? 12 : 8;
if (strlen(trim($sentence)) === 0) {
throw new InvalidArgumentException('Sentence cannot be empty');
}
if ($sentence[0] !== strtoupper($sentence[0])) {
throw new InvalidArgumentException(
'Sentence must start with a capital letter'
);
}
if (!preg_match('/[A-Z]/', $sentence)) {
throw new InvalidArgumentException(
'Sentence must contain at least one capitalized letter'
);
}
$words = preg_split('/\s+/', trim($sentence), -1, PREG_SPLIT_NO_EMPTY);
$wordCount = count($words);
if ($wordCount < $minWords) {
throw new InvalidArgumentException(
"Sentence must contain at least {$minWords} words (found {$wordCount})"
);
}
}
/**
* Generate sentence using LLM
*/
private function generateAiSentence(array $options): string
{
$provider = $options['ai_provider'] ?? null;
$model = $options['ai_model'] ?? null;
$prompt = $options['ai_prompt'] ?? 'Generate a creative, memorable sentence.';
// Ensure minimum 12 words
$fullPrompt = $prompt . " The sentence must contain at least 12 words.";
if ($this->logger && $this->isLogLevel('debug')) {
$this->logger->debug('PhraseAbbreviator: requesting AI-generated sentence', [
'provider' => $provider,
'model' => $model
]);
}
try {
$sentence = $this->llmManager->generateText(
$fullPrompt,
$provider,
['model' => $model]
);
// Clean up sentence
$sentence = trim($sentence);
$sentence = rtrim($sentence, '.');
$sentence = ucfirst($sentence);
if ($this->logger && $this->isLogLevel('debug')) {
$this->logger->debug('PhraseAbbreviator: AI sentence generated', [
'word_count' => count(preg_split('/\s+/', $sentence))
]);
}
return $sentence;
} catch (\Exception $e) {
if ($this->logger) {
$this->logger->error('PhraseAbbreviator: AI generation failed', [
'error' => $e->getMessage()
]);
}
throw new RuntimeException(
"Failed to generate sentence from AI: {$e->getMessage()}"
);
}
}
/**
* Generate password from sentence
*/
private function generatePasswordFromSentence(
string $sentence,
string $extractionType,
string $language
): string {
// Split into words
$words = preg_split('/\s+/', trim($sentence), -1, PREG_SPLIT_NO_EMPTY);
$passwordChars = [];
foreach ($words as $word) {
// Remove leading/trailing punctuation but keep internal
$cleanWord = $word;
$leadingPunctuation = '';
$trailingPunctuation = '';
// Extract trailing punctuation
while (
strlen($cleanWord) > 0 &&
!ctype_alnum($cleanWord[strlen($cleanWord) - 1]) &&
$cleanWord[strlen($cleanWord) - 1] !== "'"
) {
$trailingPunctuation = substr($cleanWord, -1) . $trailingPunctuation;
$cleanWord = substr($cleanWord, 0, -1);
}
// Extract leading punctuation
while (strlen($cleanWord) > 0 && !ctype_alnum($cleanWord[0])) {
$leadingPunctuation .= $cleanWord[0];
$cleanWord = substr($cleanWord, 1);
}
if (strlen($cleanWord) === 0) {
continue;
}
// Extract characters based on type
if ($extractionType === 'dual') {
$chars = $this->extractDualCharacters($cleanWord);
} else {
$chars = $cleanWord[0]; // Single character (first letter)
}
$passwordChars[] = $leadingPunctuation . $chars . $trailingPunctuation;
}
return implode('', $passwordChars);
}
/**
* Extract dual characters from word
*
* Word starts with vowel: first letter + first consonant
* Word starts with consonant: first letter + first vowel
* Fallback (no vowel/consonant found): first two letters
*/
private function extractDualCharacters(string $word): string
{
if (strlen($word) < 2) {
return $word;
}
$firstChar = $word[0];
if ($this->isVowel($firstChar)) {
// Word starts with vowel: first letter + first consonant
for ($i = 1; $i < strlen($word); $i++) {
if ($this->isConsonant($word[$i])) {
return $firstChar . $word[$i];
}
}
// No consonant found, return first two letters
return substr($word, 0, 2);
} else {
// Word starts with consonant: first letter + first vowel
for ($i = 1; $i < strlen($word); $i++) {
if ($this->isVowel($word[$i])) {
return $firstChar . $word[$i];
}
}
// No vowel found, return first two letters
return substr($word, 0, 2);
}
}
/**
* Check if character is a vowel
*/
private function isVowel(string $char): bool
{
return strpos(self::VOWELS, $char) !== false;
}
/**
* Check if character is a consonant
*/
private function isConsonant(string $char): bool
{
return strpos(self::CONSONANTS, $char) !== false;
}
/**
* Replace number words using translations
*
* Loads number words from translation file and replaces all occurrences
* in the sentence with their digit equivalents
*/
private function replaceNumberWords(string $sentence, string $language): string
{
// Load number words from translation file
$numberWords = $this->loadNumberWordsFromTranslations($language);
if (empty($numberWords)) {
if ($this->logger && $this->isLogLevel('debug')) {
$this->logger->debug('PhraseAbbreviator: no number words found for language', [
'language' => $language
]);
}
return $sentence;
}
// Replace each number word with its digit equivalent
foreach ($numberWords as $word => $digit) {
// Create regex pattern for word boundary matching
// This ensures 'one' doesn't match inside 'stone'
$pattern = '/\b' . preg_quote($word, '/') . '\b/i';
// Replace all occurrences (case-insensitive)
$sentence = preg_replace($pattern, $digit, $sentence);
}
return $sentence;
}
/**
* Load number words from translation file
*
* Searches translation file for keys matching pattern: phraseabbreviator_number_{name}
* Where {name} is: zero, one, two, three... twenty
*/
private function loadNumberWordsFromTranslations(string $language): array
{
// Construct path to locale file
$localeDir = __DIR__ . '/../../locales';
$localeFile = "{$localeDir}/{$language}.json";
// Return empty array if file doesn't exist
if (!file_exists($localeFile)) {
if ($this->logger) {
$this->logger->warning('PhraseAbbreviator: translation file not found', [
'language' => $language,
'file' => $localeFile
]);
}
return [];
}
// Load JSON file
$content = file_get_contents($localeFile);
if ($content === false) {
if ($this->logger) {
$this->logger->warning('PhraseAbbreviator: failed to read translation file', [
'file' => $localeFile
]);
}
return [];
}
// Decode JSON
$translations = json_decode($content, true);
if ($translations === null || !is_array($translations)) {
if ($this->logger) {
$this->logger->warning('PhraseAbbreviator: failed to decode translation file', [
'file' => $localeFile
]);
}
return [];
}
// Extract number words
$numberWords = [];
$prefix = 'phraseabbreviator_number_';
foreach ($translations as $key => $value) {
// Check if key starts with prefix
if (str_starts_with($key, $prefix)) {
// Extract digit name from key
// Example: 'phraseabbreviator_number_one' -> 'one'
$digitName = substr($key, strlen($prefix));
// Convert digit name to actual digit
$digit = $this->digitFromKeyName($digitName);
// Add to mapping if digit was successfully converted
if ($digit !== null) {
$numberWords[$value] = $digit;
}
}
}
if ($this->logger && $this->isLogLevel('debug')) {
$this->logger->debug('PhraseAbbreviator: loaded number words', [
'language' => $language,
'count' => count($numberWords)
]);
}
return $numberWords;
}
/**
* Convert digit key name to digit string
*
* Maps word names to their numeric equivalents
*/
private function digitFromKeyName(string $keyName): ?string
{
// Map of key names to digits
$digitMap = [
'zero' => '0',
'one' => '1',
'two' => '2',
'three' => '3',
'four' => '4',
'five' => '5',
'six' => '6',
'seven' => '7',
'eight' => '8',
'nine' => '9',
'ten' => '10',
'eleven' => '11',
'twelve' => '12',
'thirteen' => '13',
'fourteen' => '14',
'fifteen' => '15',
'sixteen' => '16',
'seventeen' => '17',
'eighteen' => '18',
'nineteen' => '19',
'twenty' => '20'
];
return $digitMap[$keyName] ?? null;
}
/**
* Add random number to password
*/
private function addRandomNumber(
string $password,
int $digits,
string $position
): string {
$max = (int)str_repeat('9', $digits);
$number = random_int(0, $max);
$paddedNumber = str_pad((string)$number, $digits, '0', STR_PAD_LEFT);
if ($position === 'prepend') {
return $paddedNumber . $password;
} else {
return $password . $paddedNumber;
}
}
}
+621
View File
@@ -0,0 +1,621 @@
<?php
declare(strict_types=1);
namespace App\Generators;
use Psr\Log\LoggerInterface;
use App\Services\Logging\LoggingHelper;
/**
* PWGen Enhanced Generator v3.2
*
* Generates pronounceable passwords with intelligent syllable-based separation
* based on phonetic patterns (VC, CV, VCC, CVC, VVC, CVV).
*
* Features:
* - Native pwgen executable support with comprehensive options
* - PHP fallback with phonetic syllable separation using PwGenPhpNg
* - Intelligent syllable splitting based on vowel/consonant patterns
* - Random separator selection for EACH separator position
* - Automatic number placement at syllable endings
* - No leading/trailing separators
*
* @package App\Generators
*/
class PwgenGenerator extends AbstractPasswordGenerator
{
private const GENERATOR_ID = 'pwgen';
private const MIN_SYLLABLE_LENGTH = 2;
private const MAX_SYLLABLE_LENGTH = 4;
private const VOWELS = 'aeiouAEIOU';
private ?object $pwgenFallback = null;
/**
* Constructor with optional logger
*/
public function __construct(?LoggerInterface $logger = null)
{
parent::__construct($logger);
}
public function getId(): string
{
return self::GENERATOR_ID;
}
public function getVersion(): string
{
return '2.1.0';
}
public function getName(): string
{
return 'PWGen Enhanced';
}
public function getDescription(): string
{
return 'Generates pronounceable passwords with intelligent syllable-based separation using phonetic patterns.';
}
/**
* Get option definitions
*/
public function getOptionDefinitions(): array
{
$options = [
'length' => [
'type' => 'integer',
'default' => 8,
'min' => 4,
'max' => 32,
'required' => false,
'group' => 'basic',
'description' => 'Total length of the generated password'
],
'separator' => [
'type' => 'string',
'default' => '',
'maxLength' => 10,
'required' => false,
'group' => 'advanced',
'description' => 'Characters to use as separators between syllables. If multiple characters are provided, one will be chosen at random for each separator position.'
],
'include_numbers' => [
'type' => 'boolean',
'default' => true,
'required' => false,
'group' => 'character_set',
'description' => 'Whether to include numbers in the generated password'
],
'include_capitals' => [
'type' => 'boolean',
'default' => true,
'required' => false,
'group' => 'character_set',
'description' => 'Whether to include capital letters in the generated password'
],
'secure_mode' => [
'type' => 'boolean',
'default' => false,
'required' => false,
'group' => 'advanced',
'description' => 'Whether to use secure mode (fully random) generation'
],
'no_ambiguous' => [
'type' => 'boolean',
'default' => false,
'required' => false,
'group' => 'advanced',
'description' => 'Whether to exclude ambiguous characters (like 0/O, 1/l, etc.) from the generated password'
]
];
return $options;
}
public function validateOptions(array $options): array
{
return parent::validateOptions($options);
}
/**
* Generate passwords
*/
public function generate(int $count = 1, array $options = []): array
{
// Validate options using parent class method
$options = $this->validateOptions($options);
$length = $options['length'];
$separator = $options['separator'];
if ($this->logger && $this->isLogLevel('debug')) {
$this->logger->debug('Pwgen: starting generation', [
'count' => $count,
'length' => $length,
'has_separator' => !empty($separator)
]);
}
LoggingHelper::debug($this->logger, $this->getId(), 'Message');
try {
// If separator is provided, use syllable splitting; otherwise use standard generation
if (!empty($separator)) {
return $this->generateWithSyllableSeparation($length, $count, $separator, $options);
} else {
// Use native pwgen or PHP fallback without separation
if ($this->isPwgenCommandAvailable()) {
return $this->generateWithNativePwgen($length, $count, $options);
} else {
return $this->generateWithPhpFallback($length, $count, $options);
}
}
} catch (\Exception $e) {
if ($this->logger) {
$this->logger->error('Pwgen: generation failed', [
'error' => $e->getMessage()
]);
}
throw new RuntimeException(
"PWGen password generation failed: " . $e->getMessage(),
0,
$e
);
}
}
/**
* Generate passwords using native pwgen executable
*/
private function generateWithNativePwgen(int $length, int $count, array $options): array
{
$command = ['pwgen'];
// Add pwgen flags based on options
if ($options['include_capitals']) {
$command[] = '-c'; // Include capital letters
} else {
$command[] = '-A'; // No capitals
}
if ($options['include_numbers']) {
$command[] = '-n'; // Include numbers
} else {
$command[] = '-0'; // No numbers
}
if ($options['secure_mode']) {
$command[] = '-s'; // Secure (fully random)
}
if ($options['no_ambiguous']) {
$command[] = '-B'; // No ambiguous characters
}
// Add length and count
$command[] = (string)$length;
$command[] = (string)$count;
// Execute command
$result = shell_exec(escapeshellcmd(implode(' ', $command)));
if ($result === null || $result === '') {
throw new RuntimeException('PWGen command execution failed');
}
$passwords = array_filter(explode("\n", trim($result)));
if ($this->logger && $this->isLogLevel('debug')) {
$this->logger->debug('Pwgen: native pwgen generated', ['count' => count($passwords)]);
}
// Return array of password objects
return array_map(function ($password) {
return [
'password' => $password,
'method' => 'native_pwgen'
];
}, $passwords);
}
/**
* Generate passwords with PHP fallback (no separation)
*/
private function generateWithPhpFallback(int $length, int $count, array $options): array
{
$this->initializePwgenFallback();
$passwords = [];
for ($i = 0; $i < $count; $i++) {
// Generate password using PHP fallback
$generatedPasswords = $this->pwgenFallback->generate(
$length,
1,
$options['include_numbers'],
$options['include_capitals']
);
$passwords[] = [
'password' => $generatedPasswords[0],
'method' => 'php_fallback'
];
}
if ($this->logger && $this->isLogLevel('debug')) {
$this->logger->debug('Pwgen: PHP fallback generated', ['count' => count($passwords)]);
}
return $passwords;
}
/**
* Generate passwords with intelligent syllable separation
*/
private function generateWithSyllableSeparation(int $length, int $count, string $separator, array $options): array
{
$passwords = [];
for ($i = 0; $i < $count; $i++) {
// Generate base password first (without separator)
$baseLength = $length;
if ($this->isPwgenCommandAvailable() && !$options['secure_mode']) {
// Use native pwgen for base password
$result = $this->generateWithNativePwgen($baseLength, 1, $options);
$basePassword = trim($result[0]['password']);
} else {
// Use PHP fallback
$this->initializePwgenFallback();
$generated = $this->pwgenFallback->generate(
$baseLength,
1,
$options['include_numbers'],
$options['include_capitals']
);
$basePassword = $generated[0];
}
// Split into phonetic syllables
$syllables = $this->splitIntoPhoneticSyllables($basePassword);
// Join syllables with random separator selection for each position
$finalPassword = $this->joinSyllablesWithRandomSeparators($syllables, $separator);
// Trim to target length if needed (but preserve complete syllables when possible)
if (strlen($finalPassword) > $length) {
$finalPassword = $this->trimToLength($finalPassword, $length, $separator);
}
// Extract used separators for display
$usedSeparators = $this->extractUsedSeparators($finalPassword, $syllables);
$passwords[] = [
'password' => $finalPassword,
'syllables' => $syllables,
'separator_used' => implode('', array_unique($usedSeparators)),
'syllable_count' => count($syllables),
'method' => 'syllable_separated'
];
}
if ($this->logger && $this->isLogLevel('info')) {
$this->logger->info('Pwgen: syllable-separated passwords generated', [
'count' => count($passwords),
'separator' => $separator
]);
}
return $passwords;
}
/**
* Join syllables with random separator selection for EACH separator position
*
* Ensures no leading or trailing separators
*/
private function joinSyllablesWithRandomSeparators(array $syllables, string $separatorChars): string
{
if (count($syllables) === 0) {
return '';
}
if (count($syllables) === 1) {
return $syllables[0]; // No separator needed for single syllable
}
// Get available separator characters (excluding spaces)
$availableChars = str_split($separatorChars);
$availableChars = array_filter($availableChars, fn($char) => $char !== ' ');
if (empty($availableChars)) {
// Fallback to dash if no valid separators
$availableChars = ['-'];
}
$result = '';
$syllableCount = count($syllables);
for ($i = 0; $i < $syllableCount; $i++) {
// Add the syllable
$result .= $syllables[$i];
// Add random separator BETWEEN syllables (not after last)
if ($i < $syllableCount - 1) {
// Select random separator for THIS position
$randomSeparator = $availableChars[array_rand($availableChars)];
$result .= $randomSeparator;
}
}
return $result;
}
/**
* Trim password to target length while preserving structure
*/
private function trimToLength(string $password, int $targetLength, string $separatorChars): string
{
if (strlen($password) <= $targetLength) {
return $password;
}
// Simple trim, ensuring no trailing separator
$trimmed = substr($password, 0, $targetLength);
// Remove trailing separator if present
$separatorArray = str_split($separatorChars);
$separatorArray = array_filter($separatorArray, fn($char) => $char !== ' ');
while (strlen($trimmed) > 0 && in_array(substr($trimmed, -1), $separatorArray)) {
$trimmed = substr($trimmed, 0, -1);
}
return $trimmed;
}
/**
* Extract separators actually used in the password
*/
private function extractUsedSeparators(string $password, array $syllables): array
{
$used = [];
// Simple extraction: find chars that aren't in any syllable
$syllableChars = implode('', $syllables);
foreach (str_split($password) as $char) {
if (strpos($syllableChars, $char) === false && !in_array($char, $used)) {
$used[] = $char;
}
}
return $used;
}
/**
* Split password into phonetic syllables based on vowel/consonant patterns
*
* Preferred patterns (longer syllables first):
* - CVV (Consonant-Vowel-Vowel)
* - VVC (Vowel-Vowel-Consonant)
* - CVC (Consonant-Vowel-Consonant)
* - VCC (Vowel-Consonant-Consonant)
* - CV (Consonant-Vowel)
* - VC (Vowel-Consonant)
* - Numbers preferably at syllable end
*/
private function splitIntoPhoneticSyllables(string $password): array
{
$syllables = [];
$currentSyllable = '';
$chars = str_split($password);
$length = count($chars);
$i = 0;
while ($i < $length) {
$char = $chars[$i];
$currentSyllable .= $char;
// Check if current syllable should end
$shouldEnd = false;
$syllableLen = strlen($currentSyllable);
// Minimum syllable length check
if ($syllableLen >= self::MIN_SYLLABLE_LENGTH) {
// Look ahead to decide
$pattern = $this->getPhoneticPattern($currentSyllable);
$nextChar = ($i + 1 < $length) ? $chars[$i + 1] : null;
// End syllable if:
// 1. We have a complete pattern (CVV, CVC, etc.)
// 2. Maximum length reached
// 3. Number at end of syllable
// 4. Good breaking point
if ($syllableLen >= self::MAX_SYLLABLE_LENGTH) {
$shouldEnd = true;
} elseif ($this->isNumber($char) && $nextChar && !$this->isNumber($nextChar)) {
// Number at end, good break point
$shouldEnd = true;
} elseif ($this->isCompletePattern($pattern) && $syllableLen >= 3) {
// Have a good 3+ char pattern
$shouldEnd = true;
} elseif ($syllableLen >= 2 && $nextChar === null) {
// End of password
$shouldEnd = true;
}
}
if ($shouldEnd && strlen($currentSyllable) >= self::MIN_SYLLABLE_LENGTH) {
$syllables[] = $currentSyllable;
$currentSyllable = '';
}
$i++;
}
// Add remaining characters
if (!empty($currentSyllable)) {
if (count($syllables) > 0 && strlen($currentSyllable) < self::MIN_SYLLABLE_LENGTH) {
// Merge short remainder with last syllable
$syllables[count($syllables) - 1] .= $currentSyllable;
} else {
$syllables[] = $currentSyllable;
}
}
return $syllables;
}
/**
* Get phonetic pattern of a string (V=vowel, C=consonant, N=number)
*/
private function getPhoneticPattern(string $str): string
{
$pattern = '';
foreach (str_split($str) as $char) {
if ($this->isNumber($char)) {
$pattern .= 'N';
} elseif ($this->isVowel($char)) {
$pattern .= 'V';
} else {
$pattern .= 'C';
}
}
return $pattern;
}
/**
* Check if pattern is complete/good for syllable breaking
*/
private function isCompletePattern(string $pattern): bool
{
$goodPatterns = ['CVV', 'VVC', 'CVC', 'VCC', 'CVN', 'VCN', 'CVCN'];
return in_array($pattern, $goodPatterns);
}
/**
* Check if character is a vowel
*/
private function isVowel(string $char): bool
{
return strpos(self::VOWELS, $char) !== false;
}
/**
* Check if character is a number
*/
private function isNumber(string $char): bool
{
return is_numeric($char);
}
/**
* Check if native pwgen command is available
*/
private function isPwgenCommandAvailable(): bool
{
static $available = null;
if ($available === null) {
$which = shell_exec('which pwgen 2>/dev/null');
$available = !empty($which) && is_executable(trim($which));
// Alternative check for Windows
if (!$available && PHP_OS_FAMILY === 'Windows') {
$where = shell_exec('where pwgen 2>nul');
$available = !empty($where);
}
}
return $available;
}
/**
* Initialize PHP fallback if not already done
*
* Uses the PwGenPhpNg class at src/Generators/Classes/PwGenPhpNg.class.php
*/
private function initializePwgenFallback(): void
{
if ($this->pwgenFallback === null) {
require_once __DIR__ . '/Classes/PwGenPhpNg.class.php';
$this->pwgenFallback = new \PwGenPhpNg();
if ($this->logger && $this->isLogLevel('debug')) {
$this->logger->debug('Pwgen: PHP fallback initialized');
}
}
}
/**
* Calculate entropy estimate for current PWGen option set.
*/
public function calculateEntropyForOptions(array $options): float
{
$breakdown = $this->getEntropyBreakdownForOptions($options);
return (float) ($breakdown['entropy_bits'] ?? 0.0);
}
/**
* Detailed entropy contributors for API transparency.
*/
public function getEntropyBreakdownForOptions(array $options): array
{
$validated = $this->validateOptions($options);
$length = max(1, (int) ($validated['length'] ?? 8));
$secureMode = (bool) ($validated['secure_mode'] ?? false);
$includeNumbers = (bool) ($validated['include_numbers'] ?? true);
$includeCapitals = (bool) ($validated['include_capitals'] ?? true);
$noAmbiguous = (bool) ($validated['no_ambiguous'] ?? false);
$separatorChars = str_replace(' ', '', (string) ($validated['separator'] ?? ''));
$charsetSize = 26; // lowercase always available
if ($includeCapitals) {
$charsetSize += 26;
}
if ($includeNumbers) {
$charsetSize += 10;
}
$symbolEntropyBits = 0.0;
if ($separatorChars !== '') {
$separatorAlphabet = count(array_unique(str_split($separatorChars)));
$separatorSlotsEstimate = max(1, (int) floor($length / 3));
if ($separatorAlphabet > 1) {
$symbolEntropyBits = $separatorSlotsEstimate * log((float) $separatorAlphabet, 2);
}
}
if ($noAmbiguous) {
// Approximate reduced alphabet due to ambiguity filtering.
$charsetSize = max(24, (int) floor($charsetSize * 0.85));
}
// Secure mode approximates random selection over active charset.
$baseEntropyBits = $length * log((float) $charsetSize, 2);
// Pronounceable mode has structure constraints; apply conservative penalty.
$structurePenaltyBits = $secureMode ? 0.0 : (0.35 * $length);
$totalEntropyBits = max(0.0, $baseEntropyBits - $structurePenaltyBits + $symbolEntropyBits);
return [
'entropy_model' => 'pwgen-structured-heuristic-v1',
'model_confidence' => $secureMode ? 'medium' : 'low',
'length' => $length,
'secure_mode' => $secureMode,
'include_numbers' => $includeNumbers,
'include_capitals' => $includeCapitals,
'no_ambiguous' => $noAmbiguous,
'estimated_charset_size' => $charsetSize,
'base_entropy_bits' => round($baseEntropyBits, 1),
'structure_penalty_bits' => round($structurePenaltyBits, 1),
'separator_entropy_bits' => round($symbolEntropyBits, 1),
'entropy_bits' => round($totalEntropyBits, 1),
];
}
}
+878
View File
@@ -0,0 +1,878 @@
<?php
declare(strict_types=1);
namespace App\Generators;
use App\Services\LLM\LLMManager;
use Psr\Log\LoggerInterface;
/**
* XKCD Pass Generator v2.4
*
* Generates memorable passphrases from curated wordlists.
* Supports traditional wordlist selection or AI-generated custom wordlists via LLM.
*
* Features:
* - Multiple language wordlists
* - Intelligent word filtering (length, spaces)
* - Random separator selection for each position
* - Optional number insertion
* - Entropy calculation
* - AI-powered custom wordlist generation
*
* @package App\Generators
*/
class XkcdPassGenerator extends AbstractPasswordGenerator
{
private const GENERATOR_ID = 'xkcdpass';
private const DEFAULT_WORDLISTS_DIR = 'data/wordlists';
private array $wordlists = [];
private array $availableWordlists = [];
private array $wordlistFileMap = [];
private ?LLMManager $llmManager = null;
/**
* Constructor with optional LLM Manager and logger
*/
public function __construct(?LLMManager $llmManager = null, ?LoggerInterface $logger = null)
{
parent::__construct($logger);
$this->llmManager = $llmManager;
$this->initializeWordlists();
}
public function getId(): string
{
return self::GENERATOR_ID;
}
public function getVersion(): string
{
return '2.4.0';
}
public function getName(): string
{
return 'XKCD Pass';
}
public function getDescription(): string
{
return 'Memorable passphrases using word combinations with enhanced filtering and separator options';
}
public function getAvailableLanguages(): array
{
$languages = [];
foreach ($this->availableWordlists as $code => $label) {
$filename = $this->getWordlistFilename($code);
$path = $this->getWordlistPath() . '/' . $filename;
$languages[$code] = [
'name' => $label,
'description' => 'Wordlist for ' . $label,
'file' => $filename,
'available' => file_exists($path) && is_readable($path),
'fallback' => false,
];
}
return $languages;
}
/**
* Get option definitions with AI wordlist support
*/
public function getOptionDefinitions(): array
{
$options = [
'words' => [
'type' => 'integer',
'default' => 4,
'min' => 2,
'max' => 8,
'required' => false,
'group' => 'basic',
'description' => 'Number of words to include in the passphrase'
],
'wordlist' => [
'type' => 'select',
'default' => 'en',
'choices' => $this->getAvailableWordlistChoices(),
'translateChoices' => false,
'required' => false,
'group' => 'basic',
'description' => 'Wordlist to use for generating the passphrase'
],
'separator' => [
'type' => 'string',
'default' => '-',
'maxLength' => 5,
'required' => false,
'group' => 'basic',
'description' => 'Separator character(s) between words. If multiple, one is randomly selected for each position'
],
'max_word_length' => [
'type' => 'integer',
'default' => 0,
'min' => 0,
'max' => 30,
'required' => false,
'group' => 'advanced',
'description' => 'Maximum length for words (0 = no limit)'
],
'space_handling' => [
'type' => 'select',
'default' => 'allow',
'choices' => [
['value' => 'allow', 'label' => 'Allow'],
['value' => 'skip', 'label' => 'Skip'],
['value' => 'modify', 'label' => 'Modify']
],
'translateChoices' => true,
'required' => false,
'group' => 'advanced',
'description' => 'How to handle words containing spaces: allow as-is, skip them, or modify to underscores'
],
'add_number' => [
'type' => 'boolean',
'default' => false,
'required' => false,
'group' => 'advanced',
'description' => 'Add random number to the passphrase'
],
'number_position' => [
'type' => 'select',
'default' => 'end',
'choices' => [
['value' => 'beginning', 'label' => 'Beginning'],
['value' => 'end', 'label' => 'End'],
['value' => 'random', 'label' => 'Random']
],
'translateChoices' => true,
'required' => false,
'group' => 'advanced',
'description' => 'Where to place the random number'
]
];
// Add AI wordlist generation options if LLM available
if ($this->llmManager && $this->llmManager->hasAvailableProvider()) {
$options['use_ai_wordlist'] = [
'type' => 'boolean',
'default' => false,
'required' => false,
'group' => 'ai',
'description' => 'Generate custom wordlist using AI'
];
$options['ai_wordlist_prompt'] = [
'type' => 'string',
'default' => '',
'maxLength' => 500,
'required' => false,
'group' => 'ai',
'description' => 'Complete the prompt: "Create a list of X words of at least 5 characters, which..."'
];
// Get available LLM providers
$providers = $this->llmManager->getAvailableProviders();
if (!empty($providers)) {
$providerChoices = [];
foreach ($providers as $name => $info) {
$providerChoices[] = [
'value' => $name,
'label' => $info['display_name']
];
}
$options['ai_provider'] = [
'type' => 'select',
'default' => $this->llmManager->getDefaultProviderName(),
'choices' => $providerChoices,
'translateChoices' => false,
'required' => false,
'group' => 'ai',
'description' => 'Which LLM provider to use'
];
}
}
return $options;
}
/**
* Get wordlist choices for UI
*/
private function getAvailableWordlistChoices(): array
{
$choices = [];
foreach ($this->availableWordlists as $code => $name) {
$choices[] = [
'value' => $code,
'label' => $name
];
}
return $choices;
}
public function validateOptions(array $options): array
{
$normalized = $options;
if (isset($normalized['wordlist']) && is_string($normalized['wordlist']) && trim($normalized['wordlist']) === '') {
$normalized['wordlist'] = 'en';
}
$validated = parent::validateOptions($normalized);
if (empty(trim((string) ($validated['wordlist'] ?? '')))) {
$validated['wordlist'] = 'en';
}
return $validated;
}
/**
* Generate passphrases
*/
public function generate(int $count = 1, array $options = []): array
{
// Validate options using parent class method
$options = $this->validateOptions($options);
if ($count < 1 || $count > 100) {
throw new InvalidArgumentException('Count must be between 1 and 100');
}
if ($this->logger && $this->isLogLevel('debug')) {
$this->logger->debug('XkcdPass: starting generation', [
'count' => $count,
'use_ai' => $options['use_ai_wordlist'] ?? false
]);
}
$results = [];
for ($i = 0; $i < $count; $i++) {
try {
// Check if using AI-generated wordlist
if ($options['use_ai_wordlist'] ?? false) {
if (!$this->llmManager) {
throw new RuntimeException('LLM manager not configured for AI wordlist generation');
}
$wordlist = $this->generateAiWordlist(
$options['words'],
$options['ai_wordlist_prompt'] ?? '',
$options['ai_provider'] ?? null
);
} else {
$wordlist = $this->getWordlist($options['wordlist']);
}
$results[] = $this->generateSingle($wordlist, $options);
} catch (\Exception $e) {
if ($this->logger) {
$this->logger->error('XkcdPass: generation iteration failed', [
'iteration' => $i + 1,
'error' => $e->getMessage()
]);
}
throw $e;
}
}
if ($this->logger && $this->isLogLevel('info')) {
$this->logger->info('XkcdPass: generation complete', [
'count' => count($results)
]);
}
return $results;
}
/**
* Generate wordlist using LLM
*
* Calculates total words needed: count × words_per_password
* Sends request to LLM to generate that many words
*/
private function generateAiWordlist(
int $wordCount,
string $userPrompt,
?string $provider
): array {
// Build complete prompt
$basePrompt = "Create a list of $wordCount words of at least 5 characters";
if (!empty($userPrompt)) {
// User completes: "which..."
$fullPrompt = $basePrompt . ", which " . trim($userPrompt);
} else {
$fullPrompt = $basePrompt . ", which are memorable and diverse";
}
$fullPrompt .= ". Return ONLY the words, one per line, without numbers or any formatting.";
if ($this->logger && $this->isLogLevel('debug')) {
$this->logger->debug('XkcdPass: requesting AI wordlist', [
'word_count' => $wordCount,
'provider' => $provider
]);
}
try {
// Call LLM
$response = $this->llmManager->generateText($fullPrompt, $provider);
// Parse response
$words = $this->parseWordList($response, $wordCount);
if (count($words) < $wordCount) {
throw new RuntimeException(
"LLM returned only " . count($words) . " words (expected $wordCount)"
);
}
// Convert to wordlist format
$wordlist = [];
foreach (array_slice($words, 0, $wordCount) as $word) {
$wordlist[] = [
'word' => strtolower($word),
'explanation' => 'AI-generated'
];
}
if ($this->logger && $this->isLogLevel('debug')) {
$this->logger->debug('XkcdPass: AI wordlist generated', [
'count' => count($wordlist)
]);
}
return $wordlist;
} catch (\Exception $e) {
if ($this->logger) {
$this->logger->error('XkcdPass: AI wordlist generation failed', [
'error' => $e->getMessage()
]);
}
throw new RuntimeException(
"Failed to generate wordlist with LLM: {$e->getMessage()}"
);
}
}
/**
* Parse LLM response into clean word list
*/
private function parseWordList(string $response, int $expectedCount): array
{
$lines = preg_split('/[\r\n]+/', trim($response));
$words = [];
foreach ($lines as $line) {
if (count($words) >= $expectedCount) {
break;
}
$line = trim($line);
// Skip empty lines
if (empty($line)) {
continue;
}
// Remove leading numbers/bullets (e.g., "1. word", "- word")
$line = preg_replace('/^[\d\-\.\)\s]+/', '', $line);
$line = trim($line);
// Extract first word only
$parts = preg_split('/\s+/', $line);
$word = strtolower(trim($parts[0]));
// Validate word: at least 5 chars, only alphabetic
if (strlen($word) >= 5 && preg_match('/^[a-z]+$/', $word)) {
$words[] = $word;
}
}
return $words;
}
/**
* Generate single passphrase
*/
private function generateSingle(array $wordlist, array $options): array
{
if (empty($wordlist)) {
throw new RuntimeException('Wordlist is empty');
}
// Select random words with filtering
$wordEntries = $this->selectWords($options['words'], $wordlist, $options);
// Process words (apply space handling, length filtering, etc.)
$words = [];
$processedWordEntries = [];
foreach ($wordEntries as $entry) {
$processedEntry = $this->processWordEntry($entry, $options);
if ($processedEntry !== null) {
$words[] = $processedEntry['word'];
$processedWordEntries[] = $processedEntry;
}
}
// Try to get more words if filtering removed too many
if (count($words) < $options['words']) {
$additionalNeeded = $options['words'] - count($words);
$additionalEntries = $this->selectWords($additionalNeeded * 3, $wordlist, $options);
foreach ($additionalEntries as $entry) {
if (count($words) >= $options['words']) {
break;
}
$processedEntry = $this->processWordEntry($entry, $options);
if ($processedEntry !== null) {
$words[] = $processedEntry['word'];
$processedWordEntries[] = $processedEntry;
}
}
}
if (count($words) < $options['words']) {
throw new RuntimeException(
'Not enough suitable words found with current filtering options'
);
}
// Trim to requested count
$words = array_slice($words, 0, $options['words']);
$processedWordEntries = array_slice($processedWordEntries, 0, $options['words']);
// Add number if requested
if ($options['add_number']) {
$words = $this->addNumberToWords($words, $options['number_position']);
}
// Join with separators
$password = $this->joinWordsWithRandomSeparators($words, $options['separator']);
return [
'password' => $password,
'method' => 'xkcdpass',
'word_count' => count($words),
'wordEntries' => $processedWordEntries,
'entropy' => $this->calculateEntropyForOptions($options)
];
}
/**
* Process word entry with filtering options
*/
private function processWordEntry(array $entry, array $options): ?array
{
$word = $entry['word'];
$explanation = $entry['explanation'] ?? '';
// Apply word length filter
if ($options['max_word_length'] > 0 && strlen($word) > $options['max_word_length']) {
return null;
}
// Apply space handling
if (strpos($word, ' ') !== false) {
switch ($options['space_handling']) {
case 'skip':
return null;
case 'modify':
$word = str_replace(' ', '_', $word);
break;
case 'allow':
default:
// Keep as-is
break;
}
}
return [
'word' => $word,
'explanation' => $explanation
];
}
/**
* Select random words from wordlist
*/
private function selectWords(int $count, array $wordlist, array $options): array
{
// Pre-filter based on options
$filtered = [];
foreach ($wordlist as $entry) {
$word = $entry['word'];
// Check length constraint
if ($options['max_word_length'] > 0 && strlen($word) > $options['max_word_length']) {
continue;
}
// Check space handling
if ($options['space_handling'] === 'skip' && strpos($word, ' ') !== false) {
continue;
}
$filtered[] = $entry;
}
// Fall back to original if filtering removed everything
if (empty($filtered)) {
$filtered = $wordlist;
}
// Shuffle and return
shuffle($filtered);
return array_slice($filtered, 0, min($count * 2, count($filtered)));
}
/**
* Join words with random separator selection for each position
*/
private function joinWordsWithRandomSeparators(array $words, string $separatorSet): string
{
if (count($words) <= 1) {
return implode('', $words);
}
// Clean separator set
$cleanSeparators = str_replace(' ', '', $separatorSet);
if (empty($cleanSeparators)) {
return implode('', $words);
}
$result = $words[0];
for ($i = 1; $i < count($words); $i++) {
// Select random separator if multiple available
if (strlen($cleanSeparators) === 1) {
$separator = $cleanSeparators;
} else {
$chars = str_split($cleanSeparators);
$separator = $chars[array_rand($chars)];
}
$result .= $separator . $words[$i];
}
return $result;
}
/**
* Add random number to words
*/
private function addNumberToWords(array $words, string $position): array
{
$number = (string)random_int(10, 9999);
switch ($position) {
case 'beginning':
array_unshift($words, $number);
break;
case 'random':
if (count($words) > 1) {
array_splice($words, random_int(0, count($words)), 0, [$number]);
} else {
$words[] = $number;
}
break;
case 'end':
default:
$words[] = $number;
}
return $words;
}
/**
* Calculate XKCD entropy from actual option-constrained candidate space.
*/
public function calculateEntropyForOptions(array $options): float
{
$breakdown = $this->getEntropyBreakdownForOptions($options);
return (float) ($breakdown['entropy_bits'] ?? 0.0);
}
/**
* Return detailed entropy contributors for transparent API/UI reporting.
*/
public function getEntropyBreakdownForOptions(array $options): array
{
$normalized = $this->normalizeEntropyOptions($options);
$requestedWords = max(1, (int) ($normalized['words'] ?? 4));
$wordlistCode = (string) ($normalized['wordlist'] ?? 'en');
$wordlistEntries = $this->getWordlist($wordlistCode);
$totalCandidates = count($wordlistEntries);
$effectiveCandidates = 0;
foreach ($wordlistEntries as $entry) {
$word = is_array($entry) ? (string) ($entry['word'] ?? '') : (string) $entry;
if ($word === '') {
continue;
}
if ($normalized['max_word_length'] > 0 && strlen($word) > $normalized['max_word_length']) {
continue;
}
if ($normalized['space_handling'] === 'skip' && strpos($word, ' ') !== false) {
continue;
}
$effectiveCandidates++;
}
if ($effectiveCandidates <= 0) {
$effectiveCandidates = max(1, $totalCandidates);
}
$wordEntropyBits = $requestedWords * log((float) $effectiveCandidates, 2);
$separatorSet = str_replace(' ', '', (string) ($normalized['separator'] ?? '-'));
$separatorAlphabet = count(array_unique(str_split($separatorSet)));
$separatorSlots = max(0, $requestedWords - 1);
$separatorEntropyBits = ($separatorAlphabet > 1 && $separatorSlots > 0)
? ($separatorSlots * log((float) $separatorAlphabet, 2))
: 0.0;
$numberEntropyBits = 0.0;
if ((bool) ($normalized['add_number'] ?? false)) {
// random_int(10, 9999) includes 9,990 possible values.
$numberEntropyBits = log(9990.0, 2);
if (($normalized['number_position'] ?? 'end') === 'random') {
$numberEntropyBits += log((float) ($requestedWords + 1), 2);
}
}
$totalEntropyBits = $wordEntropyBits + $separatorEntropyBits + $numberEntropyBits;
return [
'entropy_model' => 'xkcd-candidate-space-v1',
'model_confidence' => 'high',
'wordlist' => $wordlistCode,
'requested_words' => $requestedWords,
'wordlist_total_candidates' => $totalCandidates,
'effective_wordlist_candidates' => $effectiveCandidates,
'max_word_length' => (int) $normalized['max_word_length'],
'space_handling' => (string) ($normalized['space_handling'] ?? 'allow'),
'separator_alphabet_size' => $separatorAlphabet,
'separator_slots' => $separatorSlots,
'number_inserted' => (bool) ($normalized['add_number'] ?? false),
'number_position' => (string) ($normalized['number_position'] ?? 'end'),
'word_entropy_bits' => round($wordEntropyBits, 1),
'separator_entropy_bits' => round($separatorEntropyBits, 1),
'number_entropy_bits' => round($numberEntropyBits, 1),
'entropy_bits' => round($totalEntropyBits, 1),
];
}
/**
* Normalize option aliases used across API/frontend payloads.
*/
private function normalizeEntropyOptions(array $options): array
{
$normalized = $options;
if (!isset($normalized['wordlist']) && isset($normalized['language'])) {
$normalized['wordlist'] = $normalized['language'];
}
if (!isset($normalized['add_number']) && isset($normalized['addNumber'])) {
$normalized['add_number'] = $normalized['addNumber'];
}
return $this->validateOptions($normalized);
}
/**
* Initialize available wordlists
*/
private function initializeWordlists(): void
{
$defaultLabels = [
'en' => 'EFF Large Wordlist (English)',
'de' => 'German Common Words',
'fr' => 'French Common Words',
'it' => 'Italian Common Words',
'es' => 'Spanish Common Words'
];
$defaultFileMap = [
'en' => 'eff_large_wordlist_en.txt',
'de' => 'german_common_words_de.txt',
'fr' => 'french_common_words_fr.txt',
'it' => 'italian_common_words_it.txt',
'es' => 'spanish_common_words_es.txt'
];
$configuredWordlists = $this->getConfigValue('generator.xkcd.wordlists', null);
if (is_array($configuredWordlists) && !empty($configuredWordlists)) {
$labels = [];
$files = [];
foreach ($configuredWordlists as $code => $definition) {
if (!is_string($code) || trim($code) === '' || !is_array($definition)) {
continue;
}
$label = $definition['label'] ?? null;
$file = $definition['file'] ?? null;
if (!is_string($label) || trim($label) === '' || !is_string($file) || trim($file) === '') {
continue;
}
$normalizedCode = trim($code);
$labels[$normalizedCode] = trim($label);
$files[$normalizedCode] = trim($file);
}
if (!empty($labels) && !empty($files)) {
$this->availableWordlists = $labels;
$this->wordlistFileMap = $files;
}
}
if (empty($this->availableWordlists) || empty($this->wordlistFileMap)) {
$this->availableWordlists = $defaultLabels;
$this->wordlistFileMap = $defaultFileMap;
}
if ($this->logger && $this->isLogLevel('debug')) {
$this->logger->debug('XkcdPass: wordlists initialized', [
'count' => count($this->availableWordlists),
'configured' => !empty($configuredWordlists ?? null)
]);
}
}
/**
* Load wordlist from file
*/
private function getWordlist(string $code): array
{
if (isset($this->wordlists[$code])) {
return $this->wordlists[$code];
}
// Load wordlist
$this->loadWordlist($code);
return $this->wordlists[$code] ?? $this->getFallbackWordlist();
}
/**
* Load wordlist from file
*/
private function loadWordlist(string $code): void
{
$filename = $this->getWordlistFilename($code);
$path = $this->getWordlistPath() . '/' . $filename;
if (!file_exists($path) || !is_readable($path)) {
if ($this->logger) {
$this->logger->warning('XkcdPass: wordlist file not found', [
'code' => $code,
'path' => $path
]);
}
$this->wordlists[$code] = [];
return;
}
$content = file_get_contents($path);
if ($content === false) {
if ($this->logger) {
$this->logger->warning('XkcdPass: failed to read wordlist file', [
'path' => $path
]);
}
$this->wordlists[$code] = [];
return;
}
$lines = array_filter(
array_map('trim', explode("\n", $content)),
fn($line) => !empty($line) && strlen($line) >= 3
);
$processed = [];
foreach ($lines as $line) {
$parsed = $this->parseWordlistEntry($line);
if (!empty($parsed['word'])) {
$processed[] = $parsed;
}
}
$this->wordlists[$code] = !empty($processed) ? $processed : [];
if ($this->logger && $this->isLogLevel('debug')) {
$this->logger->debug('XkcdPass: wordlist loaded', [
'code' => $code,
'count' => count($this->wordlists[$code])
]);
}
}
/**
* Get fallback wordlist
*/
private function getFallbackWordlist(): array
{
return [
['word' => 'correct', 'explanation' => ''],
['word' => 'horse', 'explanation' => ''],
['word' => 'battery', 'explanation' => ''],
['word' => 'staple', 'explanation' => ''],
['word' => 'python', 'explanation' => ''],
['word' => 'monkey', 'explanation' => ''],
['word' => 'pencil', 'explanation' => ''],
['word' => 'cloud', 'explanation' => ''],
['word' => 'mountain', 'explanation' => ''],
['word' => 'rainbow', 'explanation' => '']
];
}
/**
* Get wordlist filename
*/
private function getWordlistFilename(string $code): string
{
return $this->wordlistFileMap[$code] ?? ($this->wordlistFileMap['en'] ?? 'eff_large_wordlist_en.txt');
}
/**
* Get wordlist directory
*/
private function getWordlistPath(): string
{
$configuredPath = $this->getConfigValue('generator.xkcd.wordlists_path', self::DEFAULT_WORDLISTS_DIR);
$safePath = is_string($configuredPath) && trim($configuredPath) !== ''
? trim($configuredPath)
: self::DEFAULT_WORDLISTS_DIR;
if (str_starts_with($safePath, '/')) {
return $safePath;
}
return dirname(__DIR__, 2) . '/' . ltrim($safePath, '/');
}
}
+586
View File
@@ -0,0 +1,586 @@
<?php
declare(strict_types=1);
namespace App\Managers;
use App\Generators\HighEntropyGenerator;
use App\Services\LLM\LLMManager;
use App\Utils\ConfigLoader;
class PasswordGeneratorManager
{
private array $generators = [];
private ?LLMManager $llmManager = null;
public function __construct()
{
// Load config
$config = $this->loadConfig();
// Initialize LLM Manager
if (isset($config['llm'])) {
$this->llmManager = new LLMManager($config['llm']);
}
// Register generators
$this->registerGenerators();
}
private function loadConfig(): array
{
try {
$config = ConfigLoader::load();
} catch (\Throwable $e) {
$config = [];
}
$provider = $_ENV['LLM_DEFAULT_PROVIDER'] ?? null;
if (is_string($provider) && $provider !== '') {
$config['llm']['default_provider'] = $provider;
}
$apiKeyEnvMap = [
'openai' => 'OPENAI_API_KEY',
'claude' => 'CLAUDE_API_KEY',
'gemini' => 'GEMINI_API_KEY',
'perplexity' => 'PERPLEXITY_API_KEY',
];
foreach ($apiKeyEnvMap as $providerName => $envVar) {
$apiKey = $_ENV[$envVar] ?? null;
if (is_string($apiKey) && $apiKey !== '') {
$config['llm']['api_keys'][$providerName] = $apiKey;
}
}
return is_array($config) ? $config : [];
}
private function registerGenerators(): void
{
$this->generators = [
'high-entropy' => new \App\Generators\HighEntropyGenerator(),
'xkcdpass' => new \App\Generators\XkcdPassGenerator($this->llmManager),
'diceware' => new \App\Generators\DicewareGenerator(),
'pwgen' => new \App\Generators\PwgenGenerator(),
'passkey' => new \App\Generators\PasskeyGenerator(),
'phrase-abbreviator' => new \App\Generators\PhraseAbbreviatorGenerator($this->llmManager),
];
}
public function getAvailableTypes(): array
{
$types = [];
foreach ($this->generators as $key => $generator) {
$types[] = [
'id' => $key,
'name' => $generator->getName(),
'description' => $generator->getDescription(),
];
}
return $types;
}
public function getGeneratorDefinitions(): array
{
$definitions = [];
foreach ($this->generators as $id => $generator) {
$optionDefinitions = [];
foreach ($generator->getOptionDefinitions() as $optionName => $definition) {
$group = $definition['group'] ?? 'basic';
$label = $definition['label'] ?? $this->humanizeOptionName($optionName);
$description = $definition['description'] ?? '';
$option = [
'name' => $optionName,
'type' => $definition['type'],
'label' => $label,
'description' => $description,
'labelKey' => $id . '_option_' . $optionName,
'descriptionKey' => $id . '_option_' . $optionName . '_description',
'default' => $definition['default'] ?? null,
'required' => $definition['required'] ?? false,
'group' => $group,
'groupTitleKey' => $id . '_options_group_' . $group . '_title',
'validationErrorKey' => $id . '_option_' . $optionName . '_validation_error',
];
if (isset($definition['min'])) {
$option['min'] = $definition['min'];
}
if (isset($definition['max'])) {
$option['max'] = $definition['max'];
}
if (isset($definition['minLength'])) {
$option['minLength'] = $definition['minLength'];
}
if (isset($definition['maxLength'])) {
$option['maxLength'] = $definition['maxLength'];
}
if (isset($definition['pattern'])) {
$option['pattern'] = $definition['pattern'];
}
if (isset($definition['translateChoices'])) {
$option['translateChoices'] = $definition['translateChoices'];
}
if (isset($definition['choices'])) {
$formattedChoices = [];
foreach ($definition['choices'] as $choice) {
if (is_array($choice)) {
$value = $choice['value'];
$label = $choice['label'];
} else {
$value = $choice;
$label = $choice;
}
$formattedChoices[] = [
'value' => $value,
'label' => $label,
'labelKey' => $id . '_option_' . $optionName . '_choice_' . $value,
];
}
$option['choices'] = $formattedChoices;
}
$optionDefinitions[] = $option;
}
$definitions[$id] = [
'id' => $id,
'name' => $generator->getName(),
'description' => $generator->getDescription(),
'nameKey' => $id . '_title',
'descriptionKey' => $id . '_description',
'version' => $generator->getVersion(),
'options' => $optionDefinitions,
];
}
return $definitions;
}
private function humanizeOptionName(string $optionName): string
{
$humanized = preg_replace('/[_-]+/', ' ', $optionName);
$humanized = preg_replace('/(?<!^)([A-Z])/', ' $1', (string) $humanized);
return trim(ucwords((string) $humanized));
}
public function getGenerator(string $type)
{
if (!isset($this->generators[$type])) {
throw new \InvalidArgumentException("Unknown generator type: $type");
}
return $this->generators[$type];
}
/**
* Enhanced generate method with proper data extraction
*/
public function generate(string $type, int $count, array $options): array
{
$generator = $this->getGenerator($type);
$results = $generator->generate($count, $options);
// Initialize response data
$passwords = [];
$explanations = [];
$diceRolls = [];
$securityInfo = null;
// Process each result and extract all nested data
for ($i = 0; $i < count($results); $i++) {
$result = $results[$i];
if (is_array($result) && isset($result['password'])) {
// Extract password
$passwords[] = $result['password'];
// Extract explanations from wordEntries
$explanation = null;
if (!empty($result['wordEntries'])) {
$explanationTexts = [];
foreach ($result['wordEntries'] as $entry) {
if (is_array($entry) && isset($entry['explanation']) && !empty($entry['explanation'])) {
$explanationTexts[] = $entry['explanation'];
}
}
if (!empty($explanationTexts)) {
$explanation = implode(' – ', $explanationTexts);
}
}
$explanations[] = $explanation;
// Extract dice rolls (for Diceware)
if (!empty($result['dice_rolls'])) {
$diceRolls[] = $result['dice_rolls'];
}
// Extract security info (for Diceware)
if (!empty($result['security_info'])) {
$securityInfo = $result['security_info'];
}
} else {
// Simple string-only format
$passwords[] = is_string($result) ? $result : (string)$result;
$explanations[] = null;
}
}
// Calculate accurate entropy and optional model breakdown
$entropyAnalysis = $this->calculateEntropyAnalysis($generator, $options, $passwords[0] ?? '');
$entropyBits = $entropyAnalysis['entropy_bits'];
// Build response
$response = [
'passwords' => $passwords,
'entropy_bits' => $entropyBits,
'generator_info' => $generator->getName() . ' generator',
'options_used' => $options
];
// FIXED: Only add explanations if any exist and are not null
$validExplanations = array_filter($explanations, fn($exp) => !empty($exp));
if (!empty($validExplanations)) {
// Preserve array indices to match passwords
$response['explanations'] = $explanations;
}
// FIXED: Only add dice rolls if any exist
if (!empty($diceRolls)) {
$response['dice_rolls'] = $diceRolls;
}
// FIXED: Add security info if exists
if ($securityInfo !== null) {
$response['security_info'] = $securityInfo;
}
$assessment = $this->buildSecurityAssessment(
$entropyBits,
$generator->getName(),
$securityInfo,
$passwords[0] ?? '',
$entropyAnalysis['details'] ?? []
);
$response['security_assessment'] = $assessment['security_assessment'];
$response['raw_technical_details'] = $assessment['raw_technical_details'];
return $response;
}
private function buildSecurityAssessment(float $entropyBits, string $generatorName, ?array $securityInfo, string $password, array $entropyDetails = []): array
{
$rawDetails = [
'entropy_bits' => round($entropyBits, 1),
'generator' => $generatorName,
'length' => $password !== '' ? strlen($password) : 0,
];
if (!empty($entropyDetails)) {
$rawDetails['entropy_breakdown'] = $entropyDetails;
}
if (is_array($securityInfo)) {
$rawDetails = array_merge($rawDetails, $securityInfo);
}
$securityLevel = $this->normalizeSecurityLevel($rawDetails['security_level'] ?? '', $entropyBits);
$rawDetails['security_level'] = $securityLevel;
return [
'security_assessment' => $this->getSecurityAssessmentText($securityLevel, $entropyBits),
'raw_technical_details' => $rawDetails,
];
}
private function normalizeSecurityLevel(string $securityLevel, float $entropyBits): string
{
$normalized = strtolower(trim($securityLevel));
if ($normalized === '' || $normalized === 'none') {
return $this->classifyEntropy($entropyBits);
}
if (str_contains($normalized, 'excellent') || str_contains($normalized, 'very strong')) {
return 'Very strong';
}
if (str_contains($normalized, 'strong')) {
return 'Strong';
}
if (str_contains($normalized, 'adequate')) {
return 'Adequate';
}
if (str_contains($normalized, 'weak')) {
return 'Weak';
}
return $this->classifyEntropy($entropyBits);
}
private function classifyEntropy(float $entropyBits): string
{
if ($entropyBits >= 128) {
return 'Very strong';
}
if ($entropyBits >= 96) {
return 'Strong';
}
if ($entropyBits >= 64) {
return 'Adequate';
}
if ($entropyBits >= 40) {
return 'Weak';
}
return 'Very weak';
}
private function getSecurityAssessmentText(string $securityLevel, float $entropyBits): string
{
$level = trim($securityLevel) !== '' ? $securityLevel : $this->classifyEntropy($entropyBits);
$messages = [
'Very strong' => 'Very strong — this password has excellent entropy and is suitable for high-value accounts.',
'Strong' => 'Strong — this password has good entropy and is suitable for most uses.',
'Adequate' => 'Adequate — this password is acceptable for ordinary use but can be improved.',
'Weak' => 'Weak — this password is relatively easy to brute-force and should be replaced.',
'Very weak' => 'Very weak — this password offers very little protection.',
];
return $messages[$level] ?? 'Security assessment unavailable.';
}
public function validateOptions(string $type, array $options): array
{
$generator = $this->getGenerator($type);
return $generator->validateOptions($options);
}
public function getDefaultOptions(string $type): array
{
$generator = $this->getGenerator($type);
return $generator->getDefaultOptions();
}
/**
* Accurate entropy calculation using generator-specific logic
*/
private function calculateAccurateEntropy($generator, array $options, string $samplePassword): float
{
// Try generator-specific calculation first
if (method_exists($generator, 'calculateEntropyForOptions')) {
return $generator->calculateEntropyForOptions($options);
}
// Fallback to type-specific calculations
$generatorName = strtolower($generator->getName());
switch ($generatorName) {
case 'xkcd pass':
case 'xkcdpass':
return $this->calculateXkcdEntropy($options);
case 'diceware':
return $this->calculateDicewareEntropy($options);
case 'passkey':
return $this->calculatePasskeyEntropy($options);
case 'high entropy':
return $this->calculateHighEntropyEntropy($options);
case 'pwgen':
return $this->calculatePwgenEntropy($options, $samplePassword);
default:
return $this->calculateGenericEntropy($samplePassword);
}
}
/**
* Calculate entropy and expose optional generator-level entropy details.
*/
private function calculateEntropyAnalysis($generator, array $options, string $samplePassword): array
{
$details = [];
if (method_exists($generator, 'getEntropyBreakdownForOptions')) {
try {
$details = $generator->getEntropyBreakdownForOptions($options);
} catch (\Throwable $e) {
$details = [];
}
}
$entropyBits = $this->calculateAccurateEntropy($generator, $options, $samplePassword);
if (isset($details['entropy_bits']) && is_numeric($details['entropy_bits'])) {
$entropyBits = (float) $details['entropy_bits'];
}
return [
'entropy_bits' => round($entropyBits, 1),
'details' => $details,
];
}
/**
* Calculate XKCD passphrase entropy with filtering effects
*/
private function calculateXkcdEntropy(array $options): float
{
$words = $options['words'] ?? 4;
$addNumber = $options['addNumber'] ?? false;
$language = $options['language'] ?? 'en';
// Base wordlist sizes by language
$wordlistSizes = [
'en' => 7776, // EFF Large wordlist
'de' => 5000, // Estimated German words
'fr' => 5000, // Estimated French words
'it' => 4000, // Estimated Italian words
'es' => 4000, // Estimated Spanish words
'ch' => 3000, // Estimated Swiss German words
];
$baseWordlistSize = $wordlistSizes[$language] ?? 7776;
// Apply filtering penalties
$effectiveWordlistSize = $baseWordlistSize;
// Word length filter penalty
if (isset($options['max_word_length']) && $options['max_word_length'] > 0) {
$maxLength = $options['max_word_length'];
if ($maxLength <= 4) {
$effectiveWordlistSize *= 0.3; // Very restrictive
} elseif ($maxLength <= 6) {
$effectiveWordlistSize *= 0.6; // Moderately restrictive
} elseif ($maxLength <= 8) {
$effectiveWordlistSize *= 0.85; // Slightly restrictive
}
}
// Space handling penalty
if (isset($options['space_handling']) && $options['space_handling'] === 'skip') {
$effectiveWordlistSize *= 0.95; // ~5% of words contain spaces
}
// Ensure reasonable minimum
$effectiveWordlistSize = max($effectiveWordlistSize, 50);
// Calculate word entropy
$wordEntropy = $words * log($effectiveWordlistSize, 2);
// Add number entropy if used
$numberEntropy = 0;
if ($addNumber) {
$numberEntropy = log(90, 2); // 2-digit numbers: 10-99 = 90 possibilities
}
return round($wordEntropy + $numberEntropy, 1);
}
/**
* Calculate Diceware entropy
*/
private function calculateDicewareEntropy(array $options): float
{
$words = $options['words'] ?? 6;
// Diceware standard: 7776 words (6^5)
$entropy = $words * log(7776, 2);
return round($entropy, 1);
}
/**
* Calculate Passkey entropy
*/
private function calculatePasskeyEntropy(array $options): float
{
$syllables = $options['syllables'] ?? 6;
// Estimate combinations per syllable
// CV: 21×5=105, CVC: 21×5×21=2205, average ≈ 150
$combinationsPerSyllable = 150;
// Character substitutions add ~20% entropy
$substitutionMultiplier = 1.2;
$totalCombinations = pow($combinationsPerSyllable, $syllables) * $substitutionMultiplier;
return round(log($totalCombinations, 2), 1);
}
/**
* Calculate High Entropy password entropy
*/
private function calculateHighEntropyEntropy(array $options): float
{
$length = $options['length'] ?? 16;
// Count character set size
$charsetSize = 0;
if ($options['include_lowercase'] ?? true) $charsetSize += 26;
if ($options['include_uppercase'] ?? true) $charsetSize += 26;
if ($options['include_numbers'] ?? true) $charsetSize += 10;
if ($options['include_symbols'] ?? true) $charsetSize += 32;
// Ensure at least one character type
$charsetSize = max($charsetSize, 26);
return round($length * log($charsetSize, 2), 1);
}
/**
* Calculate PWGen entropy estimate
*/
private function calculatePwgenEntropy(array $options, string $samplePassword): float
{
$type = $options['type'] ?? '16-char';
// PWGen entropy estimates based on pattern complexity
$entropyMap = [
'8-char' => 35.0,
'12-char' => 45.0,
'16-char' => 55.0
];
return $entropyMap[$type] ?? $this->calculateGenericEntropy($samplePassword);
}
/**
* Generic entropy calculation for unknown generators
*/
private function calculateGenericEntropy(string $password): float
{
if (empty($password)) {
return 0.0;
}
$length = strlen($password);
// Analyze character set used
$hasLower = preg_match('/[a-z]/', $password);
$hasUpper = preg_match('/[A-Z]/', $password);
$hasDigit = preg_match('/[0-9]/', $password);
$hasSymbol = preg_match('/[^a-zA-Z0-9\s]/', $password);
$charsetSize = 0;
if ($hasLower) $charsetSize += 26;
if ($hasUpper) $charsetSize += 26;
if ($hasDigit) $charsetSize += 10;
if ($hasSymbol) $charsetSize += 32; // Estimate
$charsetSize = max($charsetSize, 26); // Minimum alphabet
return round($length * log($charsetSize, 2), 1);
}
}
+31
View File
@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace App\Middleware;
use App\Utils\SecurityConfig;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\MiddlewareInterface as Middleware;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
class CorsMiddleware implements Middleware
{
public function process(Request $request, RequestHandler $handler): Response
{
$response = $handler->handle($request);
$origin = SecurityConfig::resolveCorsOrigin($_ENV['APP_ENV'] ?? 'production');
if ($origin === '') {
$response = $response->withoutHeader('Access-Control-Allow-Origin');
} else {
$response = $response->withHeader('Access-Control-Allow-Origin', $origin);
}
return $response
->withHeader('Access-Control-Allow-Headers', $_ENV['CORS_HEADERS'] ?? 'Content-Type,Authorization')
->withHeader('Access-Control-Allow-Methods', $_ENV['CORS_METHODS'] ?? 'GET,POST,OPTIONS')
->withHeader('Access-Control-Max-Age', '3600');
}
}
+27
View File
@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace App\Middleware;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\MiddlewareInterface as Middleware;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
class JsonResponseMiddleware implements Middleware
{
public function process(Request $request, RequestHandler $handler): Response
{
$response = $handler->handle($request);
// Add JSON content type for API routes
if (strpos($request->getUri()->getPath(), '/api/') === 0) {
if (!$response->hasHeader('Content-Type')) {
$response = $response->withHeader('Content-Type', 'application/json');
}
}
return $response;
}
}
+86
View File
@@ -0,0 +1,86 @@
<?php
declare(strict_types=1);
namespace App\Middleware;
use App\Utils\SecurityConfig;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\MiddlewareInterface as Middleware;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
class RateLimitMiddleware implements Middleware
{
private const CACHE_PREFIX = 'rate_limit_';
private int $maxRequests;
private int $timeWindow = 60; // seconds
public function __construct()
{
$this->maxRequests = SecurityConfig::resolveRateLimit($_ENV['APP_ENV'] ?? 'production', 120);
}
public function process(Request $request, RequestHandler $handler): Response
{
$clientIp = $this->getClientIp($request);
$cacheKey = self::CACHE_PREFIX . $clientIp;
// Simple in-memory rate limiting for demo purposes
// In production, use Redis or database
static $requests = [];
$now = time();
$windowStart = $now - $this->timeWindow;
// Clean old requests
if (isset($requests[$cacheKey])) {
$requests[$cacheKey] = array_filter(
$requests[$cacheKey],
fn($timestamp) => $timestamp > $windowStart
);
} else {
$requests[$cacheKey] = [];
}
// Check if limit exceeded
if (count($requests[$cacheKey]) >= $this->maxRequests) {
$response = new \Slim\Psr7\Response();
$response->getBody()->write(json_encode([
'error' => 'Rate limit exceeded',
'message' => "Maximum {$this->maxRequests} requests per minute allowed"
]));
return $response
->withStatus(429)
->withHeader('Content-Type', 'application/json')
->withHeader('Retry-After', $this->timeWindow);
}
// Add current request
$requests[$cacheKey][] = $now;
$response = $handler->handle($request);
// Add rate limit headers
$remaining = max(0, $this->maxRequests - count($requests[$cacheKey]));
return $response
->withHeader('X-RateLimit-Limit', (string) $this->maxRequests)
->withHeader('X-RateLimit-Remaining', (string) $remaining)
->withHeader('X-RateLimit-Reset', (string) ($now + $this->timeWindow));
}
private function getClientIp(Request $request): string
{
$serverParams = $request->getServerParams();
if (!empty($serverParams['HTTP_CLIENT_IP'])) {
return $serverParams['HTTP_CLIENT_IP'];
} elseif (!empty($serverParams['HTTP_X_FORWARDED_FOR'])) {
return $serverParams['HTTP_X_FORWARDED_FOR'];
} elseif (!empty($serverParams['REMOTE_ADDR'])) {
return $serverParams['REMOTE_ADDR'];
}
return '127.0.0.1';
}
}
+106
View File
@@ -0,0 +1,106 @@
<?php
declare(strict_types=1);
namespace App\Services\LLM;
use RuntimeException;
/**
* Abstract LLM Provider Base Class
*
* Provides common functionality for all LLM providers
*/
abstract class AbstractLLMProvider implements LLMProviderInterface
{
protected string $apiKey;
protected int $timeout;
protected int $maxTokens;
protected float $temperature;
public function __construct(array $config = [])
{
$this->apiKey = $config['api_key'] ?? '';
$this->timeout = $config['timeout'] ?? 30;
$this->maxTokens = $config['max_tokens'] ?? 500;
$this->temperature = $config['temperature'] ?? 0.7;
}
/**
* Check if provider is available (has API key)
*/
public function isAvailable(): bool
{
return !empty($this->apiKey);
}
/**
* Make HTTP request to API
*/
protected function makeRequest(
string $url,
array $headers,
array $data,
string $method = 'POST'
): array {
$options = [
'http' => [
'method' => $method,
'header' => $this->buildHeaders($headers),
'content' => json_encode($data),
'timeout' => $this->timeout,
'ignore_errors' => true
]
];
$context = stream_context_create($options);
$response = @file_get_contents($url, false, $context);
if ($response === false) {
throw new RuntimeException(
"Failed to connect to {$this->getName()} API"
);
}
// Get HTTP response code
$httpCode = 200;
if (isset($http_response_header[0])) {
preg_match('/\d{3}/', $http_response_header[0], $matches);
$httpCode = (int)($matches[0] ?? 200);
}
$result = json_decode($response, true);
if ($httpCode >= 400) {
$errorMsg = $this->extractErrorMessage($result);
throw new RuntimeException(
"{$this->getName()} API error (HTTP {$httpCode}): {$errorMsg}"
);
}
return $result ?? [];
}
/**
* Build HTTP headers array into string
*/
private function buildHeaders(array $headers): string
{
$headerLines = [];
foreach ($headers as $key => $value) {
$headerLines[] = "{$key}: {$value}";
}
return implode("\r\n", $headerLines);
}
/**
* Extract error message from API response
*/
protected function extractErrorMessage(array $response): string
{
// Try common error message fields
return $response['error']['message']
?? $response['error']
?? $response['message']
?? 'Unknown error';
}
}
+150
View File
@@ -0,0 +1,150 @@
<?php
declare(strict_types=1);
namespace App\Services\LLM;
use App\Services\LLM\Providers\OpenAIProvider;
use App\Services\LLM\Providers\ClaudeProvider;
use App\Services\LLM\Providers\GeminiProvider;
use App\Services\LLM\Providers\PerplexityProvider;
use RuntimeException;
/**
* LLM Manager
*
* Central service for managing multiple LLM providers
*/
class LLMManager
{
private array $providers = [];
private string $defaultProvider;
private array $config;
public function __construct(array $config = [])
{
$this->config = $config;
$this->defaultProvider = $config['default_provider'] ?? 'openai';
$this->initializeProviders();
}
/**
* Initialize all LLM providers
*/
private function initializeProviders(): void
{
$apiKeys = $this->config['api_keys'] ?? [];
$llmConfig = $this->config;
// OpenAI
if (!empty($apiKeys['openai'])) {
$this->providers['openai'] = new OpenAIProvider([
'api_key' => $apiKeys['openai'],
'timeout' => $llmConfig['timeout'] ?? 30,
'max_tokens' => $llmConfig['max_tokens'] ?? 500,
'temperature' => $llmConfig['temperature'] ?? 0.7
]);
}
// Claude
if (!empty($apiKeys['claude'])) {
$this->providers['claude'] = new ClaudeProvider([
'api_key' => $apiKeys['claude'],
'timeout' => $llmConfig['timeout'] ?? 30,
'max_tokens' => $llmConfig['max_tokens'] ?? 500,
'temperature' => $llmConfig['temperature'] ?? 0.7
]);
}
// Gemini
if (!empty($apiKeys['gemini'])) {
$this->providers['gemini'] = new GeminiProvider([
'api_key' => $apiKeys['gemini'],
'timeout' => $llmConfig['timeout'] ?? 30,
'max_tokens' => $llmConfig['max_tokens'] ?? 500,
'temperature' => $llmConfig['temperature'] ?? 0.7
]);
}
// Perplexity
if (!empty($apiKeys['perplexity'])) {
$this->providers['perplexity'] = new PerplexityProvider([
'api_key' => $apiKeys['perplexity'],
'timeout' => $llmConfig['timeout'] ?? 30,
'max_tokens' => $llmConfig['max_tokens'] ?? 500,
'temperature' => $llmConfig['temperature'] ?? 0.7
]);
}
}
/**
* Get provider by name
*/
public function getProvider(string $name): LLMProviderInterface
{
if (!isset($this->providers[$name])) {
throw new RuntimeException("LLM provider '{$name}' not available or not configured");
}
return $this->providers[$name];
}
/**
* Get default provider
*/
public function getDefaultProvider(): LLMProviderInterface
{
return $this->getProvider($this->defaultProvider);
}
/**
* Generate text using specified or default provider
*/
public function generateText(
string $prompt,
?string $provider = null,
array $options = []
): string {
$llm = $provider ? $this->getProvider($provider) : $this->getDefaultProvider();
return $llm->generateText($prompt, $options);
}
/**
* Get list of available providers
*/
public function getAvailableProviders(): array
{
$available = [];
foreach ($this->providers as $name => $provider) {
if ($provider->isAvailable()) {
$available[$name] = [
'name' => $name,
'display_name' => ucfirst($name),
'models' => $provider->getAvailableModels(),
'default_model' => $provider->getDefaultModel()
];
}
}
return $available;
}
/**
* Check if any provider is available
*/
public function hasAvailableProvider(): bool
{
foreach ($this->providers as $provider) {
if ($provider->isAvailable()) {
return true;
}
}
return false;
}
/**
* Get default provider name
*/
public function getDefaultProviderName(): string
{
return $this->defaultProvider;
}
}
+42
View File
@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace App\Services\LLM;
/**
* LLM Provider Interface
*
* Defines the contract that all LLM provider implementations must follow
*/
interface LLMProviderInterface
{
/**
* Get provider name
*/
public function getName(): string;
/**
* Generate text completion from prompt
*
* @param string $prompt User prompt
* @param array $options Additional options (model, temperature, max_tokens, etc.)
* @return string Generated text
* @throws \RuntimeException If API call fails
*/
public function generateText(string $prompt, array $options = []): string;
/**
* Check if provider is configured and available
*/
public function isAvailable(): bool;
/**
* Get default model for this provider
*/
public function getDefaultModel(): string;
/**
* Get available models for this provider
*/
public function getAvailableModels(): array;
}
@@ -0,0 +1,71 @@
<?php
declare(strict_types=1);
namespace App\Services\LLM\Providers;
use App\Services\LLM\AbstractLLMProvider;
use RuntimeException;
/**
* Claude Provider (Anthropic)
*/
class ClaudeProvider extends AbstractLLMProvider
{
private const API_URL = 'https://api.anthropic.com/v1/messages';
public function getName(): string
{
return 'claude';
}
public function getDefaultModel(): string
{
return 'claude-3-sonnet-20240229';
}
public function getAvailableModels(): array
{
return [
'claude-3-opus-20240229' => 'Claude 3 Opus',
'claude-3-sonnet-20240229' => 'Claude 3 Sonnet',
'claude-3-haiku-20240307' => 'Claude 3 Haiku'
];
}
public function generateText(string $prompt, array $options = []): string
{
if (!$this->isAvailable()) {
throw new RuntimeException('Claude API key not configured');
}
$model = $options['model'] ?? $this->getDefaultModel();
$temperature = $options['temperature'] ?? $this->temperature;
$maxTokens = $options['max_tokens'] ?? $this->maxTokens;
$data = [
'model' => $model,
'max_tokens' => $maxTokens,
'temperature' => $temperature,
'messages' => [
[
'role' => 'user',
'content' => $prompt
]
]
];
$headers = [
'Content-Type' => 'application/json',
'x-api-key' => $this->apiKey,
'anthropic-version' => '2023-06-01'
];
$response = $this->makeRequest(self::API_URL, $headers, $data);
if (!isset($response['content'][0]['text'])) {
throw new RuntimeException('Invalid Claude API response format');
}
return trim($response['content'][0]['text']);
}
}
@@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
namespace App\Services\LLM\Providers;
use App\Services\LLM\AbstractLLMProvider;
use RuntimeException;
/**
* Gemini Provider (Google)
*/
class GeminiProvider extends AbstractLLMProvider
{
private const API_URL = 'https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent';
public function getName(): string
{
return 'gemini';
}
public function getDefaultModel(): string
{
return 'gemini-pro';
}
public function getAvailableModels(): array
{
return [
'gemini-pro' => 'Gemini Pro',
'gemini-1.5-pro' => 'Gemini 1.5 Pro',
'gemini-1.5-flash' => 'Gemini 1.5 Flash'
];
}
public function generateText(string $prompt, array $options = []): string
{
if (!$this->isAvailable()) {
throw new RuntimeException('Gemini API key not configured');
}
$model = $options['model'] ?? $this->getDefaultModel();
$temperature = $options['temperature'] ?? $this->temperature;
$maxTokens = $options['max_tokens'] ?? $this->maxTokens;
$url = str_replace('{model}', $model, self::API_URL) . "?key={$this->apiKey}";
$data = [
'contents' => [
[
'parts' => [
['text' => $prompt]
]
]
],
'generationConfig' => [
'temperature' => $temperature,
'maxOutputTokens' => $maxTokens
]
];
$headers = [
'Content-Type' => 'application/json'
];
$response = $this->makeRequest($url, $headers, $data);
if (!isset($response['candidates'][0]['content']['parts'][0]['text'])) {
throw new RuntimeException('Invalid Gemini API response format');
}
return trim($response['candidates'][0]['content']['parts'][0]['text']);
}
}
@@ -0,0 +1,71 @@
<?php
declare(strict_types=1);
namespace App\Services\LLM\Providers;
use App\Services\LLM\AbstractLLMProvider;
use RuntimeException;
/**
* OpenAI Provider (GPT models)
*/
class OpenAIProvider extends AbstractLLMProvider
{
private const API_URL = 'https://api.openai.com/v1/chat/completions';
public function getName(): string
{
return 'openai';
}
public function getDefaultModel(): string
{
return 'gpt-3.5-turbo';
}
public function getAvailableModels(): array
{
return [
'gpt-3.5-turbo' => 'GPT-3.5 Turbo',
'gpt-4' => 'GPT-4',
'gpt-4-turbo' => 'GPT-4 Turbo',
'gpt-4o' => 'GPT-4 Omni'
];
}
public function generateText(string $prompt, array $options = []): string
{
if (!$this->isAvailable()) {
throw new RuntimeException('OpenAI API key not configured');
}
$model = $options['model'] ?? $this->getDefaultModel();
$temperature = $options['temperature'] ?? $this->temperature;
$maxTokens = $options['max_tokens'] ?? $this->maxTokens;
$data = [
'model' => $model,
'messages' => [
[
'role' => 'user',
'content' => $prompt
]
],
'max_tokens' => $maxTokens,
'temperature' => $temperature
];
$headers = [
'Content-Type' => 'application/json',
'Authorization' => "Bearer {$this->apiKey}"
];
$response = $this->makeRequest(self::API_URL, $headers, $data);
if (!isset($response['choices'][0]['message']['content'])) {
throw new RuntimeException('Invalid OpenAI API response format');
}
return trim($response['choices'][0]['message']['content']);
}
}
@@ -0,0 +1,71 @@
<?php
declare(strict_types=1);
namespace App\Services\LLM\Providers;
use App\Services\LLM\AbstractLLMProvider;
use RuntimeException;
/**
* Perplexity Provider
*/
class PerplexityProvider extends AbstractLLMProvider
{
private const API_URL = 'https://api.perplexity.ai/chat/completions';
public function getName(): string
{
return 'perplexity';
}
public function getDefaultModel(): string
{
return 'llama-3-sonar-small-32k-online';
}
public function getAvailableModels(): array
{
return [
'llama-3-sonar-small-32k-online' => 'Llama 3 Sonar Small (32k)',
'llama-3-sonar-large-32k-online' => 'Llama 3 Sonar Large (32k)',
'llama-3-8b-instruct' => 'Llama 3 8B Instruct',
'llama-3-70b-instruct' => 'Llama 3 70B Instruct'
];
}
public function generateText(string $prompt, array $options = []): string
{
if (!$this->isAvailable()) {
throw new RuntimeException('Perplexity API key not configured');
}
$model = $options['model'] ?? $this->getDefaultModel();
$temperature = $options['temperature'] ?? $this->temperature;
$maxTokens = $options['max_tokens'] ?? $this->maxTokens;
$data = [
'model' => $model,
'messages' => [
[
'role' => 'user',
'content' => $prompt
]
],
'max_tokens' => $maxTokens,
'temperature' => $temperature
];
$headers = [
'Content-Type' => 'application/json',
'Authorization' => "Bearer {$this->apiKey}"
];
$response = $this->makeRequest(self::API_URL, $headers, $data);
if (!isset($response['choices'][0]['message']['content'])) {
throw new RuntimeException('Invalid Perplexity API response format');
}
return trim($response['choices'][0]['message']['content']);
}
}
+363
View File
@@ -0,0 +1,363 @@
<?php
declare(strict_types=1);
namespace App\Services\Logging;
use Psr\Log\LoggerInterface;
/**
* LoggingHelper - Centralized logging utility
*
* Provides static methods for logging with automatic null-safety and
* context injection. Eliminates the need for repeated if statements
* checking logger availability and log levels.
*
* All logging methods:
* - Accept optional logger (null-safe)
* - Inject generator context automatically
* - Support additional context data
* - Are static (no instantiation needed)
*
* @example
* LoggingHelper::debug($this->logger, $this->getId(), 'Starting generation');
* LoggingHelper::info($this->logger, 'DicewareGenerator', 'Complete', ['count' => 5]);
* LoggingHelper::warning($this->logger, 'Pwgen', 'Using fallback', $context);
* LoggingHelper::error($this->logger, 'XkcdPass', 'Failed', ['error' => $e->getMessage()]);
*
* @package App\Services\Logging
*/
class LoggingHelper
{
/**
* PSR-3 severity levels for reference
* Lower numbers = more verbose (more messages logged)
* Higher numbers = less verbose (fewer messages logged)
*/
private const LOG_LEVELS = [
'debug' => 0, // Most verbose - development messages
'info' => 1, // Informational - important milestones
'notice' => 2, // Normal but significant
'warning' => 3, // Warning - potential issues
'error' => 4, // Error - something went wrong
'critical' => 5, // Critical - system in trouble
'alert' => 6, // Alert - immediate action needed
'emergency' => 7, // Emergency - system unusable
];
/**
* Log a DEBUG message
*
* DEBUG level is for detailed development information.
* Only logged when LOG_LEVEL is set to 'debug'.
*
* @param LoggerInterface|null $logger Logger instance (null-safe)
* @param string $generatorId Generator identifier for context
* @param string $message The log message
* @param array $context Additional contextual data
*
* @example
* LoggingHelper::debug($logger, 'DicewareGenerator', 'Starting generation', [
* 'count' => 5,
* 'words' => 6
* ]);
*/
public static function debug(
?LoggerInterface $logger,
string $generatorId,
string $message,
array $context = []
): void {
self::logWithContext($logger, 'debug', $generatorId, $message, $context);
}
/**
* Log an INFO message
*
* INFO level is for informational messages about important events.
* Logged when LOG_LEVEL is 'debug' or 'info' or higher.
*
* @param LoggerInterface|null $logger Logger instance (null-safe)
* @param string $generatorId Generator identifier for context
* @param string $message The log message
* @param array $context Additional contextual data
*
* @example
* LoggingHelper::info($logger, 'DicewareGenerator', 'Generation complete', [
* 'count' => 5,
* 'duration_ms' => 145
* ]);
*/
public static function info(
?LoggerInterface $logger,
string $generatorId,
string $message,
array $context = []
): void {
self::logWithContext($logger, 'info', $generatorId, $message, $context);
}
/**
* Log a WARNING message
*
* WARNING level is for potentially problematic situations.
* Logged when LOG_LEVEL is 'warning' or higher (less verbose).
*
* @param LoggerInterface|null $logger Logger instance (null-safe)
* @param string $generatorId Generator identifier for context
* @param string $message The log message
* @param array $context Additional contextual data
*
* @example
* LoggingHelper::warning($logger, 'Pwgen', 'Command not found, using fallback', [
* 'command' => 'pwgen',
* 'fallback' => 'php_random'
* ]);
*/
public static function warning(
?LoggerInterface $logger,
string $generatorId,
string $message,
array $context = []
): void {
self::logWithContext($logger, 'warning', $generatorId, $message, $context);
}
/**
* Log an ERROR message
*
* ERROR level is for error conditions.
* Always logged (unless logger is null).
*
* @param LoggerInterface|null $logger Logger instance (null-safe)
* @param string $generatorId Generator identifier for context
* @param string $message The log message
* @param array $context Additional contextual data
*
* @example
* LoggingHelper::error($logger, 'XkcdPass', 'Generation failed', [
* 'error' => 'Invalid word count',
* 'provided_count' => 200
* ]);
*/
public static function error(
?LoggerInterface $logger,
string $generatorId,
string $message,
array $context = []
): void {
self::logWithContext($logger, 'error', $generatorId, $message, $context);
}
/**
* Log a CRITICAL message
*
* CRITICAL level is for critical errors.
* Always logged (unless logger is null).
*
* @param LoggerInterface|null $logger Logger instance (null-safe)
* @param string $generatorId Generator identifier for context
* @param string $message The log message
* @param array $context Additional contextual data
*
* @example
* LoggingHelper::critical($logger, 'PasswordManager', 'Configuration loading failed');
*/
public static function critical(
?LoggerInterface $logger,
string $generatorId,
string $message,
array $context = []
): void {
self::logWithContext($logger, 'critical', $generatorId, $message, $context);
}
/**
* Core logging implementation with context injection
*
* This method:
* 1. Checks if logger is available (null-safe)
* 2. Injects generator context into all messages
* 3. Routes to appropriate PSR-3 log method
*
* @param LoggerInterface|null $logger Logger instance
* @param string $level Log level (debug, info, warning, error, critical)
* @param string $generatorId Generator identifier for context
* @param string $message Log message
* @param array $context Additional context data
*/
private static function logWithContext(
?LoggerInterface $logger,
string $level,
string $generatorId,
string $message,
array $context = []
): void {
// Guard: No logger available - silently return
if ($logger === null) {
return;
}
// Inject generator context into all messages
$fullContext = array_merge(
['generator' => $generatorId],
$context
);
// Route to appropriate PSR-3 logging method
match ($level) {
'debug' => $logger->debug($message, $fullContext),
'info' => $logger->info($message, $fullContext),
'notice' => $logger->notice($message, $fullContext),
'warning' => $logger->warning($message, $fullContext),
'error' => $logger->error($message, $fullContext),
'critical' => $logger->critical($message, $fullContext),
'alert' => $logger->alert($message, $fullContext),
'emergency' => $logger->emergency($message, $fullContext),
default => $logger->info($message, $fullContext)
};
}
/**
* Check if logging at a specific level is enabled
*
* Useful when building expensive log messages (e.g., serialization, formatting).
* Allows conditional construction of message data.
*
* Note: This is a reference helper. Most PSR-3 loggers handle this internally.
*
* @param string $configuredLevel Current configured log level from config
* @param string $checkLevel Level to check
* @return bool True if message at checkLevel would be logged
*
* @example
* if (LoggingHelper::isLevelEnabled($configLevel, 'debug')) {
* $expensiveDebugData = buildComplexDebugInfo();
* LoggingHelper::debug($logger, $id, 'Data', ['debug' => $expensiveDebugData]);
* }
*/
public static function isLevelEnabled(
string $configuredLevel,
string $checkLevel
): bool {
$configValue = self::LOG_LEVELS[$configuredLevel] ?? self::LOG_LEVELS['info'];
$checkValue = self::LOG_LEVELS[$checkLevel] ?? self::LOG_LEVELS['info'];
return $checkValue >= $configValue;
}
}
/**
* USAGE EXAMPLES
* ═════════════════════════════════════════════════════════════════════════════
*/
// Example 1: In AbstractPasswordGenerator.php
/*
protected function loadConfiguration(): void
{
try {
$this->config = json_decode(file_get_contents('config/config.json'), true, 512, JSON_THROW_ON_ERROR);
LoggingHelper::debug($this->logger, $this->getId(), 'Configuration loaded');
} catch (\Exception $e) {
LoggingHelper::warning($this->logger, $this->getId(),
'Failed to load config', ['error' => $e->getMessage()]);
$this->config = [];
}
}
*/
// Example 2: In DicewareGenerator.php
/*
public function generate(int $count = 1, array $options = []): array
{
$options = $this->validateOptions($options);
LoggingHelper::debug($this->logger, $this->getId(),
'Starting generation', ['count' => $count]);
$passwords = [];
for ($i = 0; $i < $count; $i++) {
$words = $this->selectRandomWords($options['words']);
$passwords[] = implode('-', $words);
}
LoggingHelper::info($this->logger, $this->getId(),
'Generation complete', ['count' => count($passwords)]);
return $passwords;
}
*/
// Example 3: In PwgenGenerator.php
/*
private function generateWithNativePwgen(int $length, int $count): array
{
try {
$command = "pwgen -S {$length} {$count}";
$result = shell_exec(escapeshellcmd($command));
if ($result === null) {
throw new RuntimeException('PWGen command failed');
}
LoggingHelper::debug($this->logger, $this->getId(),
'Native pwgen generated', ['count' => $count]);
return array_filter(explode("\n", trim($result)));
} catch (\Exception $e) {
LoggingHelper::warning($this->logger, $this->getId(),
'Native pwgen failed, using fallback', [
'error' => $e->getMessage(),
'fallback' => 'php_random'
]);
throw new RuntimeException(
"PWGen execution failed: " . $e->getMessage(),
0,
$e
);
}
}
*/
// Example 4: In XkcdPassGenerator.php
/*
private function generateAiWordlist(int $wordCount, string $prompt): array
{
try {
LoggingHelper::debug($this->logger, $this->getId(),
'Requesting AI wordlist', ['word_count' => $wordCount]);
$response = $this->llmManager->generateText($prompt);
$words = $this->parseWordList($response, $wordCount);
if (count($words) < $wordCount) {
throw new RuntimeException(
"LLM returned only " . count($words) . " words"
);
}
LoggingHelper::info($this->logger, $this->getId(),
'AI wordlist generated', ['count' => count($words)]);
return $words;
} catch (\Exception $e) {
LoggingHelper::error($this->logger, $this->getId(),
'AI wordlist generation failed', [
'error' => $e->getMessage(),
'requested_count' => $wordCount
]);
throw new RuntimeException(
"Failed to generate wordlist: " . $e->getMessage(),
0,
$e
);
}
}
*/
?>
+37
View File
@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace App\Utils;
use RuntimeException;
use JsonException;
class ConfigLoader
{
public static function load(?string $configPath = null): array
{
$path = $configPath ?? dirname(__DIR__, 2) . '/config/config.json';
if (!is_file($path)) {
throw new RuntimeException('Configuration file not found at ' . $path);
}
$json = file_get_contents($path);
if ($json === false) {
throw new RuntimeException('Unable to read configuration file at ' . $path);
}
try {
$decoded = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
throw new RuntimeException('Invalid JSON in configuration file at ' . $path, 0, $e);
}
if (!is_array($decoded)) {
throw new RuntimeException('Configuration root must decode to an object at ' . $path);
}
return $decoded;
}
}
+59
View File
@@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
namespace App\Utils;
use Psr\Http\Message\ResponseInterface as Response;
class ResponseHelper
{
public function successResponse(
Response $response,
array $data = [],
?string $message = null // FIXED: Explicit nullable type
): Response {
$responseData = [
'success' => true,
'data' => $data,
'timestamp' => date('c')
];
if ($message !== null) {
$responseData['message'] = $message;
}
$response->getBody()->write(json_encode($responseData, JSON_UNESCAPED_UNICODE));
return $response->withHeader('Content-Type', 'application/json');
}
public function errorResponse(
Response $response,
string $message,
int $statusCode = 400,
?string $errorCode = null
): Response {
$responseData = [
'success' => false,
'error' => [
'message' => $message,
'code' => $errorCode ?? 'ERROR',
'status' => $statusCode
],
'timestamp' => date('c')
];
$response->getBody()->write(json_encode($responseData, JSON_UNESCAPED_UNICODE));
return $response
->withHeader('Content-Type', 'application/json')
->withStatus($statusCode);
}
public function jsonResponse(Response $response, array $data, int $statusCode = 200): Response
{
$response->getBody()->write(json_encode($data, JSON_UNESCAPED_UNICODE));
return $response
->withHeader('Content-Type', 'application/json')
->withStatus($statusCode);
}
}
+52
View File
@@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
namespace App\Utils;
use Monolog\Logger;
final class SecurityConfig
{
public static function resolveCorsOrigin(?string $environment = null): string
{
$configuredOrigin = trim((string) ($_ENV['CORS_ORIGIN'] ?? ''));
if ($configuredOrigin !== '') {
return $configuredOrigin;
}
$env = strtolower((string) ($environment ?? ($_ENV['APP_ENV'] ?? 'production')));
if (in_array($env, ['development', 'local', 'dev'], true)) {
return 'http://localhost:8080';
}
return '';
}
public static function resolveRateLimit(?string $environment = null, int $fallback = 120): int
{
if (isset($_ENV['RATE_LIMIT']) && is_numeric($_ENV['RATE_LIMIT'])) {
return (int) $_ENV['RATE_LIMIT'];
}
return max(1, $fallback);
}
public static function resolveLogLevel(?string $configuredLevel = null): int
{
$level = strtoupper((string) ($configuredLevel ?? ($_ENV['LOG_LEVEL'] ?? 'warning')));
return match ($level) {
'DEBUG' => Logger::DEBUG,
'INFO' => Logger::INFO,
'NOTICE' => Logger::NOTICE,
'WARNING' => Logger::WARNING,
'ERROR' => Logger::ERROR,
'CRITICAL' => Logger::CRITICAL,
'ALERT' => Logger::ALERT,
'EMERGENCY' => Logger::EMERGENCY,
default => Logger::WARNING,
};
}
}
+52
View File
@@ -0,0 +1,52 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title data-i18n="app_title">Password Generator</title>
<link rel="stylesheet" href="../assets/css/style.css">
</head>
<body>
<header class="header">
<div class="container">
<h1>🔐 <span data-i18n="app_title">Password Generator</span></h1>
<a href="/docs">📚 <span data-i18n="api_documentation">API Documentation</a></a>
<div class="language-selector">
<label for="language-select" data-i18n="language">Language:</label>
<select id="language-select">
<option value="en">English</option>
<option value="de">Deutsch</option>
<option value="de_CH">Schwyzerdütsch</option>
<option value="fr">Français</option>
<option value="it">Italiano</option>
<option value="es">Español</option>
<option value="pt">Portugues</option>
<option value="ja">日本語</option>
<option value="zh">中文</option>
</select>
</div>
</div>
</header>
<main class="main-container">
<div class="loading" id="initial-loading">
<div class="loading-spinner"></div>
<p data-i18n="loading_generators">Loading password generators...</p>
</div>
<div id="generators-container" class="generators-grid" style="display: none;">
<!-- Password generator cards will be inserted here by JavaScript -->
</div>
</main>
<footer class="footer">
<p>
<span data-i18n="footer_text">Footer Text</span>
<a href="/docs" data-i18n="api_documentation">API Documentation</a>
</p>
</footer>
<script src="/assets/js/app.js"></script>
</body>
</html>
+225
View File
@@ -0,0 +1,225 @@
<?php
declare(strict_types=1);
use App\Generators\HighEntropyGenerator;
use App\Generators\PasswordGeneratorInterface;
use App\Generators\PhraseAbbreviatorGenerator;
use App\Generators\PwgenGenerator;
use App\Managers\PasswordGeneratorManager;
use PHPUnit\Framework\TestCase;
final class GeneratorValidationTest extends TestCase
{
public function testDefaultGenerationWorksAcrossSupportedModes(): void
{
$manager = new PasswordGeneratorManager();
foreach ([
'high-entropy',
'xkcdpass',
'diceware',
'pwgen',
'passkey',
'phrase-abbreviator',
] as $type) {
$defaults = $manager->getDefaultOptions($type);
$result = $manager->generate($type, 1, $defaults);
$this->assertArrayHasKey('passwords', $result);
$this->assertNotEmpty($result['passwords']);
$this->assertNotSame('', trim((string) $result['passwords'][0]));
}
}
public function testHighEntropyRejectsAllFalseCharacterSets(): void
{
$this->expectException(\RuntimeException::class);
(new HighEntropyGenerator())->generate(1, [
'length' => 16,
'include_lowercase' => false,
'include_uppercase' => false,
'include_numbers' => false,
'include_symbols' => false,
]);
}
public function testPhraseAbbreviatorUsesFallbackSentenceWhenMissing(): void
{
$generator = new PhraseAbbreviatorGenerator();
$validated = $generator->validateOptions(['sentence' => '']);
$this->assertNotSame('', $validated['sentence']);
$this->assertSame('The quick brown fox jumps over the lazy dog.', $validated['sentence']);
}
public function testGeneratorDefinitionsMatchFrontendMetadataContract(): void
{
$manager = new PasswordGeneratorManager();
$definitions = $manager->getGeneratorDefinitions();
$this->assertNotEmpty($definitions);
$this->assertArrayHasKey('high-entropy', $definitions);
$highEntropy = $definitions['high-entropy'];
$this->assertSame('high-entropy_title', $highEntropy['nameKey']);
$this->assertSame('high-entropy_description', $highEntropy['descriptionKey']);
$this->assertNotEmpty($highEntropy['options']);
$this->assertSame('high-entropy_option_length', $highEntropy['options'][0]['labelKey']);
}
public function testGeneratedPasswordsExposeSecurityAssessmentAndEntropyDetails(): void
{
$manager = new PasswordGeneratorManager();
$result = $manager->generate('diceware', 1, [
'words' => 6,
'show_dice_rolls' => true,
]);
$this->assertArrayHasKey('passwords', $result);
$this->assertArrayHasKey('security_assessment', $result);
$this->assertNotSame('', trim((string) $result['security_assessment']));
$this->assertArrayHasKey('raw_technical_details', $result);
$this->assertArrayHasKey('entropy_bits', $result['raw_technical_details']);
$this->assertGreaterThan(0, $result['raw_technical_details']['entropy_bits']);
}
public function testPwgenGeneratorDoesNotExposeRemovedTypePresetOption(): void
{
$generator = new PwgenGenerator();
$options = $generator->getOptionDefinitions();
$this->assertArrayNotHasKey('type', $options);
$this->assertSame('custom', $generator->validateOptions([])['type'] ?? 'custom');
}
public function testXkcdEntropyReflectsWordLengthFiltering(): void
{
$manager = new PasswordGeneratorManager();
$unfiltered = $manager->generate('xkcdpass', 1, [
'words' => 4,
'wordlist' => 'en',
'separator' => '-',
'max_word_length' => 0,
'space_handling' => 'allow',
'add_number' => false,
]);
$shortFiltered = $manager->generate('xkcdpass', 1, [
'words' => 4,
'wordlist' => 'en',
'separator' => '-',
'max_word_length' => 4,
'space_handling' => 'allow',
'add_number' => false,
]);
$this->assertGreaterThan($shortFiltered['entropy_bits'], $unfiltered['entropy_bits']);
}
public function testXkcdResponseIncludesEntropyBreakdownDetails(): void
{
$manager = new PasswordGeneratorManager();
$result = $manager->generate('xkcdpass', 1, [
'words' => 4,
'wordlist' => 'en',
'separator' => '-_',
'max_word_length' => 6,
'space_handling' => 'skip',
'add_number' => true,
'number_position' => 'random',
]);
$this->assertArrayHasKey('raw_technical_details', $result);
$this->assertArrayHasKey('entropy_breakdown', $result['raw_technical_details']);
$breakdown = $result['raw_technical_details']['entropy_breakdown'];
$this->assertSame('xkcd-candidate-space-v1', $breakdown['entropy_model']);
$this->assertArrayHasKey('effective_wordlist_candidates', $breakdown);
$this->assertGreaterThan(0, $breakdown['effective_wordlist_candidates']);
$this->assertArrayHasKey('separator_entropy_bits', $breakdown);
$this->assertArrayHasKey('number_entropy_bits', $breakdown);
}
public function testGeneratorsExposeEntropyModelConfidenceInBreakdown(): void
{
$manager = new PasswordGeneratorManager();
$cases = [
'diceware' => ['words' => 6],
'passkey' => ['syllables' => 6],
'pwgen' => ['length' => 12, 'separator' => '-_', 'include_numbers' => true, 'include_capitals' => true],
];
foreach ($cases as $type => $options) {
$result = $manager->generate($type, 1, $options);
$this->assertArrayHasKey('raw_technical_details', $result);
$this->assertArrayHasKey('entropy_breakdown', $result['raw_technical_details']);
$this->assertArrayHasKey('entropy_model', $result['raw_technical_details']['entropy_breakdown']);
$this->assertArrayHasKey('model_confidence', $result['raw_technical_details']['entropy_breakdown']);
$this->assertContains(
$result['raw_technical_details']['entropy_breakdown']['model_confidence'],
['high', 'medium', 'low']
);
}
}
public function testPwgenAndXkcdCurrentOptionsProduceExpectedBehavior(): void
{
$manager = new PasswordGeneratorManager();
$pwgen16 = (string) $manager->generate('pwgen', 1, ['length' => 16])['passwords'][0];
$pwgen12 = (string) $manager->generate('pwgen', 1, ['length' => 12])['passwords'][0];
$pwgen8 = (string) $manager->generate('pwgen', 1, ['length' => 8])['passwords'][0];
$this->assertSame(16, strlen($pwgen16));
$this->assertSame(12, strlen($pwgen12));
$this->assertSame(8, strlen($pwgen8));
$xkcdGerman = (string) $manager->generate('xkcdpass', 1, [
'wordlist' => 'de',
'words' => 4,
'separator' => '-',
'add_number' => true,
'number_position' => 'end',
])['passwords'][0];
$xkcdEnglish = (string) $manager->generate('xkcdpass', 1, [
'wordlist' => 'en',
'words' => 4,
'separator' => '-',
'add_number' => true,
'number_position' => 'end',
])['passwords'][0];
$xkcdSlaengikon = (string) $manager->generate('xkcdpass', 1, [
'wordlist' => 'ch',
'words' => 4,
'separator' => '_',
'add_number' => true,
'number_position' => 'end',
])['passwords'][0];
$this->assertSame(1, preg_match('/\d/', $xkcdGerman));
$this->assertSame(1, preg_match('/\d/', $xkcdEnglish));
$this->assertSame(1, preg_match('/\d/', $xkcdSlaengikon));
}
public function testDicewareUsesLargeEffectiveCandidateSpace(): void
{
$manager = new PasswordGeneratorManager();
$result = $manager->generate('diceware', 1, [
'words' => 6,
'show_dice_rolls' => true,
]);
$this->assertArrayHasKey('raw_technical_details', $result);
$this->assertArrayHasKey('entropy_breakdown', $result['raw_technical_details']);
$this->assertArrayHasKey('wordlist_size', $result['raw_technical_details']['entropy_breakdown']);
$this->assertGreaterThanOrEqual(7000, $result['raw_technical_details']['entropy_breakdown']['wordlist_size']);
}
}
+36
View File
@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
use App\Api\OpenApiGenerator;
use PHPUnit\Framework\TestCase;
final class OpenApiGeneratorTest extends TestCase
{
public function testGenerateIncludesPublicApiRoutes(): void
{
$spec = OpenApiGenerator::generate('http://localhost:8080');
$this->assertSame('3.0.3', $spec['openapi']);
$this->assertArrayHasKey('/api/v1/passwords', $spec['paths']);
$this->assertArrayHasKey('/api/v1/passwords/generate', $spec['paths']);
$this->assertArrayHasKey('/api/v1/health', $spec['paths']);
$this->assertArrayHasKey('/api/v1/locales/{lang}', $spec['paths']);
$this->assertSame('http://localhost:8080', $spec['servers'][0]['url']);
$this->assertSame('boolean', $spec['paths']['/api/v1/passwords']['get']['responses']['200']['content']['application/json']['schema']['properties']['success']['type']);
$this->assertArrayHasKey('types', $spec['paths']['/api/v1/passwords']['get']['responses']['200']['content']['application/json']['schema']['properties']['data']['properties']);
$this->assertSame('#/components/schemas/PasswordGenerationSuccess', $spec['paths']['/api/v1/passwords/generate']['post']['responses']['200']['content']['application/json']['schema']['$ref']);
$passwordResponseData = $spec['components']['schemas']['PasswordGenerationSuccess']['properties']['data']['properties'];
$this->assertArrayHasKey('security_assessment', $passwordResponseData);
$this->assertArrayHasKey('raw_technical_details', $passwordResponseData);
$this->assertArrayHasKey('entropy_breakdown', $passwordResponseData['raw_technical_details']['properties']);
$localeEnum = $spec['paths']['/api/v1/locales/{lang}']['get']['parameters'][0]['schema']['enum'];
$this->assertContains('ja', $localeEnum);
$this->assertContains('de_CH', $localeEnum);
$this->assertNotContains('js', $localeEnum);
$this->assertNotContains('ch', $localeEnum);
}
}
+23
View File
@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
use App\Utils\SecurityConfig;
use PHPUnit\Framework\TestCase;
final class ProductionSecurityConfigTest extends TestCase
{
public function testDevelopmentDefaultsKeepLocalhostCorsAndDebugLogging(): void
{
$this->assertSame('http://localhost:8080', SecurityConfig::resolveCorsOrigin('development'));
$this->assertSame(100, SecurityConfig::resolveRateLimit('development', 100));
$this->assertSame(100, SecurityConfig::resolveLogLevel('debug'));
}
public function testProductionDefaultsRejectWildcardOriginsAndUseRestrictedLogging(): void
{
$this->assertSame('', SecurityConfig::resolveCorsOrigin('production'));
$this->assertSame(120, SecurityConfig::resolveRateLimit('production', 120));
$this->assertSame(300, SecurityConfig::resolveLogLevel('warning'));
}
}
+48
View File
@@ -0,0 +1,48 @@
<?php
// scripts/generate-docs.php
class DocumentationGenerator
{
private array $languages = [
'de' => 'Deutsch',
'fr' => 'Français',
'es' => 'Español',
'it' => 'Italiano',
'ja' => '日本語',
'zh' => '中文'
];
public function generateAllLanguages(): void
{
$baseTemplate = file_get_contents(__DIR__ . '/../docs/index.html');
foreach ($this->languages as $code => $name) {
$translated = $this->translateContent($baseTemplate, $code);
file_put_contents(__DIR__ . "/../docs/docs-{$code}.html", $translated);
echo "Generated docs-{$code}.html\n";
}
}
private function translateContent(string $content, string $lang): string
{
// Load translations from JSON files
$translations = json_decode(
file_get_contents(__DIR__ . "/../locales/{$lang}.json"),
true
);
// Replace content with translations
$content = str_replace('lang="en"', "lang=\"{$lang}\"", $content);
// Replace specific content sections
foreach ($translations as $key => $value) {
$content = str_replace("{{$key}}", $value, $content);
}
return $content;
}
}
// Usage
$generator = new DocumentationGenerator();
$generator->generateAllLanguages();
+256
View File
@@ -0,0 +1,256 @@
# Translation Management Tools
## 📝 Overview
This directory contains tools and utilities for managing translations across the Password Generator application. These tools help identify, extract, and maintain translatable strings across HTML, JavaScript, and PHP files.
## 🛠️ Tools Provided
### 1. **extract-translations.php**
Advanced PHP-based translation extractor that scans your codebase and automatically generates/updates translation files.
**Features:**
- ✅ Scans HTML, JavaScript, and PHP files
- ✅ Extracts `data-i18n` attributes
- ✅ Finds `this.t()` calls in JavaScript
- ✅ Identifies hardcoded strings
- ✅ Generates translation keys automatically
- ✅ Updates existing translation files
- ✅ Provides basic translations for common strings
- ✅ Preserves consistent key structure across locales
**Usage:**
```bash
php extract-translations.php
```
### 2. **update-translations.sh**
Shell script for quick translation file updates and scanning.
**Features:**
- ✅ Fast scanning with grep/sed
- ✅ Updates all language files at once
- ✅ Shows translation coverage statistics
- ✅ Identifies hardcoded strings
- ✅ Color-coded output
**Usage:**
```bash
chmod +x update-translations.sh
./update-translations.sh
```
## 📋 Current Translation Status
### Supported Languages
- 🇺🇸 **English (en)** - Base language
- 🇩🇪 **German (de)** - Partially translated
- 🇫🇷 **French (fr)** - Partially translated
- 🇮🇹 **Italian (it)** - Placeholders only
- 🇪🇸 **Spanish (es)** - Placeholders only
- 🇯🇵 **Japanese (ja)** - Placeholders only
- 🇨🇳 **Chinese (zh)** - Placeholders only
### Translation Keys Identified
The tools have identified these categories of translatable strings:
#### **UI Elements**
- `generate` - Generate button text
- `copy` - Copy button text
- `language` - Language selector label
- `options` - Options section header
- `loading` - Loading messages
- `error` - Error messages
- `success` - Success messages
#### **Password Generator Types**
- `high_entropy` - High Entropy generator name
- `xkcd_pass` - XKCD Pass generator name
- `diceware` - Diceware generator name
- `pwgen` - PWGen generator name
- `passkey` - Passkey generator name
#### **API Documentation**
- `api_documentation` - API Documentation link
- `download` - Download button text
- `interactive_api_explorer` - API Explorer heading
- `openapi_json` - OpenAPI JSON description
- `postman_collection` - Postman Collection description
## 🔧 How to Use These Tools
### **Step 1: Run the Extractor**
```bash
# Use the PHP extractor for comprehensive analysis
php extract-translations.php
# OR use the shell script for quick updates
./update-translations.sh
```
### **Step 2: Review Generated Files**
Check the `locales/` directory for:
- Updated JSON files for each language
- Any missing or placeholder translation entries that still need review
### **Step 3: Fix Hardcoded Strings**
**HTML Files:**
```html
<!-- BEFORE -->
<button>Generate</button>
<label>Language:</label>
<!-- AFTER -->
<button data-i18n="generate">Generate</button>
<label data-i18n="language">Language:</label>
```
**JavaScript Files:**
```javascript
// BEFORE
showNotification('Copied to clipboard!');
generateBtn.innerHTML = 'Generating...';
// AFTER
showNotification(this.t('copied_to_clipboard'));
generateBtn.innerHTML = this.t('generating');
```
### **Step 4: Translate Placeholder Strings**
Replace placeholder entries in language files:
```json
{
"generate": "Generate (TODO: de)", // ← Replace this
"copy": "Kopieren" // ✅ Already translated
}
```
With proper translations:
```json
{
"generate": "Generieren", // ✅ Properly translated
"copy": "Kopieren" // ✅ Already translated
}
```
## 📊 Translation Statistics
Run the tools to get current statistics:
```
📊 Translation Status Report
============================
en: 87/87 (100%) translated
de: 45/87 (52%) translated
fr: 32/87 (37%) translated
it: 15/87 (17%) translated
es: 15/87 (17%) translated
ja: 0/87 (0%) translated
zh: 0/87 (0%) translated
```
## 🎯 Quick Win Translations
### **High Priority Strings (Visible to Users)**
1. `generate` → "Generate" button
2. `copy` → "Copy" button
3. `language` → Language selector
4. `loading_generators` → Loading message
5. `api_documentation` → Header link
6. `back_to_generator` → Navigation link
### **Common Translations Already Provided**
**German (de):**
- `generate` → "Generieren"
- `copy` → "Kopieren"
- `language` → "Sprache"
- `password_generator` → "Passwort-Generator"
**French (fr):**
- `generate` → "Générer"
- `copy` → "Copier"
- `language` → "Langue"
- `password_generator` → "Générateur de mots de passe"
## 🚨 Common Issues & Solutions
### **Issue: Missing data-i18n attributes**
**Solution:** Add `data-i18n="key"` to HTML elements
```html
<h1 data-i18n="app_title">Password Generator</h1>
```
### **Issue: Hardcoded JavaScript strings**
**Solution:** Use `this.t()` method
```javascript
const message = this.t('copied_to_clipboard');
```
### **Issue: Translation not appearing**
**Solution:** Check that:
1. Translation key exists in JSON file
2. `data-i18n` attribute is correct
3. `updateUI()` is called after content changes
## 📈 Improvement Roadmap
### **Phase 1: Core UI (Current)**
- ✅ Main navigation
- ✅ Generator cards
- ✅ Button text
- ✅ Form labels
### **Phase 2: API Documentation**
- 🔄 Swagger page content
- 🔄 Download descriptions
- 🔄 Example text
### **Phase 3: Advanced Features**
- ⏳ Error messages
- ⏳ Validation text
- ⏳ Help tooltips
## 💡 Best Practices
### **Translation Keys**
- Use `snake_case` format
- Be descriptive but concise
- Group related keys with prefixes:
- `error_*` for error messages
- `button_*` for button text
- `label_*` for form labels
### **String Extraction**
- Run tools regularly during development
- Check for new hardcoded strings after UI changes
- Update translations before releases
### **Quality Assurance**
- Test UI in multiple languages
- Check for text overflow in different languages
- Verify special characters display correctly
---
## 🚀 Getting Started
1. **Run the extractor:**
```bash
php extract-translations.php
```
2. **Fix the highest-priority strings first**
3. **Test in multiple languages:**
```bash
# Change language in the UI dropdown
# Or visit: http://localhost:8080/?lang=de
```
4. **Iterate and improve**
Your translation management system is now ready to ensure a fully internationalized Password Generator application! 🌍✨
+6
View File
@@ -0,0 +1,6 @@
<?php
$output = [];
exec('which pwgen 2>/dev/null', $output, $returnVar);
var_dump($output, $returnVar);
echo "pwgen available: " . ($returnVar === 0 && !empty($output[0]) ? 'yes' : 'no') . "\n";
+723
View File
@@ -0,0 +1,723 @@
<?php
/**
* Translation Key Extraction Tool v3.1
*
* Comprehensive translation key extraction with multiple sources:
* 1. Generator API-driven keys (from generator definitions)
* 2. JavaScript files ($this.t() calls)
* 3. PHP files (static i18n keys)
* 4. Backup of existing translation keys
*
* Output format: JSON only
*
* Usage:
* php bin/extract-translations.php [options]
*
* Options:
* --output=file Output file path
* --context Include context information
* --template Generate template with placeholders
* --language=en Language for template generation
* --backup Create backup of existing translations
* --merge Merge with existing translations
* --js-path=path Path to JavaScript files (default: public/assets/js)
* --php-path=path Path to PHP files (default: src)
*
* Examples:
* php bin/extract-translations.php --output=locales/keys.json
* php bin/extract-translations.php --backup --merge --output=locales/en.json
* php bin/extract-translations.php --context --output=locales/keys-context.json
*
* @author Password Generator Project
* @version 3.1.0
*/
declare(strict_types=1);
require __DIR__ . '/../vendor/autoload.php';
use App\Managers\PasswordGeneratorManager;
/**
* Main extraction class
*/
class TranslationKeyExtractor
{
private PasswordGeneratorManager $generatorManager;
private array $collectedKeys = [];
private array $keysWithContext = [];
private array $existingTranslations = [];
private array $stats = [
'generator_keys' => 0,
'js_keys' => 0,
'php_keys' => 0,
'existing_keys' => 0,
'new_keys' => 0,
'obsolete_keys' => 0
];
public function __construct(PasswordGeneratorManager $generatorManager)
{
$this->generatorManager = $generatorManager;
}
/**
* Extract all i18n keys from all sources
*/
public function extractAll(array $options = []): array
{
$jsPath = $options['js_path'] ?? 'public/assets/js';
$phpPath = $options['php_path'] ?? 'src';
$existingFile = $options['existing_file'] ?? null;
echo "=== Extraction Sources ===\n";
echo "JavaScript path: {$jsPath}\n";
echo "PHP path: {$phpPath}\n";
if ($existingFile && file_exists($existingFile)) {
echo "Existing translations: {$existingFile}\n";
$this->loadExistingTranslations($existingFile);
}
echo "\n";
// Extract from generators (API-driven)
echo "=== Extracting from Password Generators ===\n";
$this->extractFromGenerators();
// Extract from JavaScript files
echo "\n=== Extracting from JavaScript Files ===\n";
$this->extractFromJavaScriptFiles($jsPath);
// Extract from PHP files
echo "\n=== Extracting from PHP Files ===\n";
$this->extractFromPhpFiles($phpPath);
// Compare with existing
if (!empty($this->existingTranslations)) {
echo "\n=== Comparing with Existing Translations ===\n";
$this->compareWithExisting();
}
// Sort keys alphabetically
ksort($this->collectedKeys);
ksort($this->keysWithContext);
return [
'keys' => $this->collectedKeys,
'keysWithContext' => $this->keysWithContext,
'stats' => $this->getStatistics(),
'existingTranslations' => $this->existingTranslations
];
}
/**
* Load existing translations for comparison/merging
*/
private function loadExistingTranslations(string $file): void
{
try {
if (!str_ends_with($file, '.json')) {
throw new Exception('Only JSON format is supported');
}
$content = file_get_contents($file);
$this->existingTranslations = json_decode($content, true) ?? [];
// Remove comment keys
$this->existingTranslations = array_filter(
$this->existingTranslations,
fn($key) => !str_starts_with($key, '_comment'),
ARRAY_FILTER_USE_KEY
);
$this->stats['existing_keys'] = count($this->existingTranslations);
echo "✅ Loaded {$this->stats['existing_keys']} existing translations\n";
} catch (Exception $e) {
echo "⚠️ Failed to load existing translations: {$e->getMessage()}\n";
}
}
/**
* Extract keys from password generators using API-driven approach
*/
private function extractFromGenerators(): void
{
$generators = $this->generatorManager->getAvailableTypes();
foreach ($generators as $generator) {
$this->extractFromGenerator($generator);
}
}
/**
* Extract keys from a single generator
*/
private function extractFromGenerator($generator): void
{
$generatorId = $generator->getId();
echo "📦 {$generatorId}...\n";
// Get all keys using the generator's built-in method
$keys = $generator->getAllI18nKeys();
// Add keys with context information
foreach ($keys as $key) {
$this->addKey($key, 'generator', $generatorId);
}
$this->stats['generator_keys'] += count($keys);
echo " → Found " . count($keys) . " keys\n";
}
/**
* Extract i18n keys from JavaScript files
* Looks for: this.t('key'), $this.t('key'), app.t('key')
*/
private function extractFromJavaScriptFiles(string $basePath): void
{
if (!is_dir($basePath)) {
echo "⚠️ JavaScript path not found: {$basePath}\n";
return;
}
$files = $this->findFiles($basePath, ['js']);
echo "Found " . count($files) . " JavaScript files\n";
foreach ($files as $file) {
$this->extractFromJavaScriptFile($file);
}
}
/**
* Extract keys from a single JavaScript file
*/
private function extractFromJavaScriptFile(string $file): void
{
$content = file_get_contents($file);
$filename = basename($file);
// Pattern for this.t('key'), $this.t('key'), this.t("key")
$patterns = [
'/(?:this|\\$this)\\.t\\([\'"]([^\'"]+)[\'"]\\)/',
'/app\\.t\\([\'"]([^\'"]+)[\'"]\\)/',
'/t\\([\'"]([^\'"]+)[\'"]\\)/', // Simple t() calls
];
$foundKeys = [];
foreach ($patterns as $pattern) {
if (preg_match_all($pattern, $content, $matches)) {
$foundKeys = array_merge($foundKeys, $matches[1]);
}
}
$foundKeys = array_unique($foundKeys);
if (!empty($foundKeys)) {
echo " 📄 {$filename}: " . count($foundKeys) . " keys\n";
foreach ($foundKeys as $key) {
$this->addKey($key, 'javascript', $filename);
}
$this->stats['js_keys'] += count($foundKeys);
}
}
/**
* Extract i18n keys from PHP files
* Looks for static string keys in specific patterns
*/
private function extractFromPhpFiles(string $basePath): void
{
if (!is_dir($basePath)) {
echo "⚠️ PHP path not found: {$basePath}\n";
return;
}
$files = $this->findFiles($basePath, ['php']);
echo "Found " . count($files) . " PHP files\n";
foreach ($files as $file) {
$this->extractFromPhpFile($file);
}
}
/**
* Extract keys from a single PHP file
* Looks for static keys in comments or constants
*/
private function extractFromPhpFile(string $file): void
{
$content = file_get_contents($file);
$filename = basename($file);
// Pattern for static keys in comments like: // i18n: key_name
$patterns = [
'/\\/\\/\\s*i18n:\\s*([a-z0-9_]+)/i',
'/\\/\\*\\*.*?@i18n\\s+([a-z0-9_]+).*?\\*\\//s',
// Add more patterns as needed
];
$foundKeys = [];
foreach ($patterns as $pattern) {
if (preg_match_all($pattern, $content, $matches)) {
$foundKeys = array_merge($foundKeys, $matches[1]);
}
}
$foundKeys = array_unique($foundKeys);
if (!empty($foundKeys)) {
echo " 📄 {$filename}: " . count($foundKeys) . " keys\n";
foreach ($foundKeys as $key) {
$this->addKey($key, 'php', $filename);
}
$this->stats['php_keys'] += count($foundKeys);
}
}
/**
* Find files with specific extensions recursively
*/
private function findFiles(string $dir, array $extensions): array
{
$files = [];
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS)
);
foreach ($iterator as $file) {
if ($file->isFile()) {
$ext = strtolower($file->getExtension());
if (in_array($ext, $extensions)) {
$files[] = $file->getPathname();
}
}
}
return $files;
}
/**
* Add a key to the collection with context
*/
private function addKey(string $key, string $source, string $sourceDetail = ''): void
{
// Initialize with existing translation if available
if (!isset($this->collectedKeys[$key])) {
$this->collectedKeys[$key] = $this->existingTranslations[$key] ?? '';
}
// Add or update context
if (!isset($this->keysWithContext[$key])) {
$this->keysWithContext[$key] = [
'key' => $key,
'sources' => [],
'type' => $this->getKeyType($key),
'description' => $this->getKeyDescription($key)
];
}
// Add source information
$this->keysWithContext[$key]['sources'][] = [
'type' => $source,
'detail' => $sourceDetail
];
}
/**
* Get key type from key pattern
*/
private function getKeyType(string $key): string
{
if (str_ends_with($key, '_name')) {
return 'generator_metadata';
}
if (str_ends_with($key, '_description')) {
return 'generator_metadata';
}
if (str_contains($key, '_option_') && str_ends_with($key, '_label')) {
return 'option_label';
}
if (str_contains($key, '_option_') && str_ends_with($key, '_description')) {
return 'option_description';
}
if (str_contains($key, '_option_') && str_contains($key, '_choice_')) {
return 'option_choice_label';
}
if (str_ends_with($key, '_validation_error')) {
return 'validation_error';
}
if (str_contains($key, '_options_group_') && str_ends_with($key, '_title')) {
return 'group_title';
}
return 'general';
}
/**
* Get key description
*/
private function getKeyDescription(string $key): string
{
$type = $this->getKeyType($key);
switch ($type) {
case 'generator_metadata':
return str_ends_with($key, '_name') ? 'Generator display name' : 'Generator description';
case 'option_label':
preg_match('/_option_(.+)_label$/', $key, $matches);
return 'Label for option: ' . ($matches[1] ?? 'unknown');
case 'option_description':
preg_match('/_option_(.+)_description$/', $key, $matches);
return 'Description for option: ' . ($matches[1] ?? 'unknown');
case 'option_choice_label':
preg_match('/_option_(.+)_choice_(.+)$/', $key, $matches);
return 'Choice label for: ' . ($matches[1] ?? 'unknown') . ' = ' . ($matches[2] ?? 'unknown');
case 'validation_error':
preg_match('/_option_(.+)_validation_error$/', $key, $matches);
return 'Validation error for: ' . ($matches[1] ?? 'unknown');
case 'group_title':
preg_match('/_options_group_(.+)_title$/', $key, $matches);
return 'Group title for: ' . ($matches[1] ?? 'unknown');
default:
return 'General UI text';
}
}
/**
* Compare extracted keys with existing translations
*/
private function compareWithExisting(): void
{
$extractedKeys = array_keys($this->collectedKeys);
$existingKeys = array_keys($this->existingTranslations);
$newKeys = array_diff($extractedKeys, $existingKeys);
$obsoleteKeys = array_diff($existingKeys, $extractedKeys);
$this->stats['new_keys'] = count($newKeys);
$this->stats['obsolete_keys'] = count($obsoleteKeys);
if (!empty($newKeys)) {
echo "🆕 New keys: " . count($newKeys) . "\n";
foreach (array_slice($newKeys, 0, 10) as $key) {
echo " + {$key}\n";
}
if (count($newKeys) > 10) {
echo " ... and " . (count($newKeys) - 10) . " more\n";
}
}
if (!empty($obsoleteKeys)) {
echo "\n⚠️ Obsolete keys (not found in code): " . count($obsoleteKeys) . "\n";
foreach (array_slice($obsoleteKeys, 0, 10) as $key) {
echo " - {$key}\n";
}
if (count($obsoleteKeys) > 10) {
echo " ... and " . (count($obsoleteKeys) - 10) . " more\n";
}
}
}
/**
* Get extraction statistics
*/
private function getStatistics(): array
{
$byType = [];
$bySource = [];
foreach ($this->keysWithContext as $keyData) {
$type = $keyData['type'];
$byType[$type] = ($byType[$type] ?? 0) + 1;
foreach ($keyData['sources'] as $source) {
$sourceType = $source['type'];
$bySource[$sourceType] = ($bySource[$sourceType] ?? 0) + 1;
}
}
return array_merge($this->stats, [
'total_keys' => count($this->collectedKeys),
'by_type' => $byType,
'by_source' => $bySource
]);
}
/**
* Create backup of existing translations
*/
public function createBackup(string $file): bool
{
if (!file_exists($file)) {
return false;
}
$backupFile = $file . '.backup.' . date('Y-m-d_His');
if (copy($file, $backupFile)) {
echo "💾 Created backup: {$backupFile}\n";
return true;
}
return false;
}
/**
* Export keys to JSON format
*/
public function exportToJson(): string
{
return json_encode($this->collectedKeys, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
}
/**
* Export keys with context to JSON format
*/
public function exportToJsonWithContext(): string
{
return json_encode([
'keys' => $this->collectedKeys,
'context' => array_values($this->keysWithContext),
'stats' => $this->getStatistics()
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
}
/**
* Generate template translation file with placeholders
*/
public function generateTemplate(string $language = 'en'): string
{
$template = [];
$lastGenerator = '';
foreach ($this->keysWithContext as $keyData) {
$key = $keyData['key'];
// Add section comments for generators
if (str_contains($key, '_') && !str_starts_with($key, '_comment')) {
$generatorId = explode('_', $key)[0];
if ($generatorId !== $lastGenerator) {
$template["_comment_{$generatorId}"] = strtoupper($generatorId) . " Generator";
$lastGenerator = $generatorId;
}
}
// Use existing translation or generate placeholder
$value = $this->existingTranslations[$key] ?? $this->generatePlaceholder($keyData);
$template[$key] = $value;
}
return json_encode($template, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
}
/**
* Generate placeholder text for a key
*/
private function generatePlaceholder(array $keyData): string
{
$type = $keyData['type'];
$key = $keyData['key'];
// Extract meaningful part from key
if (preg_match('/_option_([^_]+)_/', $key, $matches)) {
$field = str_replace('_', ' ', $matches[1]);
$field = ucwords($field);
} else {
$field = 'unknown';
}
switch ($type) {
case 'generator_metadata':
return str_ends_with($key, '_name')
? 'Generator Name'
: 'Generator description text';
case 'option_label':
return $field;
case 'option_description':
return "Description for {$field}";
case 'option_choice_label':
if (preg_match('/_choice_([^_]+)$/', $key, $matches)) {
return ucfirst(str_replace('_', ' ', $matches[1]));
}
return ucfirst($field);
case 'validation_error':
return "Invalid value for {$field}";
case 'group_title':
return $field . " Options";
default:
return '';
}
}
}
/**
* CLI Interface
*/
class CLI
{
private array $options = [];
public function __construct(array $argv)
{
$this->parseArguments($argv);
}
private function parseArguments(array $argv): void
{
for ($i = 1; $i < count($argv); $i++) {
if (str_starts_with($argv[$i], '--')) {
$parts = explode('=', substr($argv[$i], 2), 2);
$this->options[$parts[0]] = $parts[1] ?? true;
}
}
}
public function getOption(string $name, $default = null)
{
return $this->options[$name] ?? $default;
}
public function hasOption(string $name): bool
{
return isset($this->options[$name]);
}
}
/**
* Main execution
*/
try {
echo "=============================================\n";
echo "Translation Key Extraction Tool v3.1\n";
echo "JSON Format Only\n";
echo "=============================================\n\n";
$cli = new CLI($argv);
// Get options
$output = $cli->getOption('output');
$withContext = $cli->hasOption('context');
$template = $cli->hasOption('template');
$language = $cli->getOption('language', 'en');
$backup = $cli->hasOption('backup');
$merge = $cli->hasOption('merge');
$jsPath = $cli->getOption('js-path', 'public/assets/js');
$phpPath = $cli->getOption('php-path', 'src');
// Initialize generator manager
$generatorManager = new PasswordGeneratorManager();
// Extract keys
$extractor = new TranslationKeyExtractor($generatorManager);
$extractOptions = [
'js_path' => $jsPath,
'php_path' => $phpPath
];
// Load existing translations if merging or backing up
if (($merge || $backup) && $output && file_exists($output)) {
$extractOptions['existing_file'] = $output;
// Create backup if requested
if ($backup) {
$extractor->createBackup($output);
}
}
$result = $extractor->extractAll($extractOptions);
// Display statistics
echo "\n=== Extraction Statistics ===\n";
echo "Total keys: {$result['stats']['total_keys']}\n";
echo "Generator keys: {$result['stats']['generator_keys']}\n";
echo "JavaScript keys: {$result['stats']['js_keys']}\n";
echo "PHP keys: {$result['stats']['php_keys']}\n";
if ($result['stats']['existing_keys'] > 0) {
echo "Existing keys: {$result['stats']['existing_keys']}\n";
echo "New keys: {$result['stats']['new_keys']}\n";
echo "Obsolete keys: {$result['stats']['obsolete_keys']}\n";
}
echo "\nBy type:\n";
foreach ($result['stats']['by_type'] as $type => $count) {
echo " - {$type}: {$count}\n";
}
echo "\nBy source:\n";
foreach ($result['stats']['by_source'] as $source => $count) {
echo " - {$source}: {$count}\n";
}
// Generate output
$outputContent = '';
if ($template) {
echo "\n=== Generating template file ===\n";
$outputContent = $extractor->generateTemplate($language);
} else {
echo "\n=== Generating JSON output ===\n";
$outputContent = $withContext
? $extractor->exportToJsonWithContext()
: $extractor->exportToJson();
}
// Save or display
if ($output) {
file_put_contents($output, $outputContent);
echo "✅ Saved to: {$output}\n";
} else {
echo "\n=== Output ===\n";
echo $outputContent;
echo "\n";
}
echo "\n✅ Extraction complete!\n";
} catch (Exception $e) {
echo "ERROR: {$e->getMessage()}\n";
echo $e->getTraceAsString() . "\n";
echo "\nUsage:\n";
echo " php bin/extract-translations.php [options]\n\n";
echo "Options:\n";
echo " --output=file Output file path (JSON only)\n";
echo " --context Include context information\n";
echo " --template Generate template with placeholders\n";
echo " --language=en Language for template generation\n";
echo " --backup Create backup of existing translations\n";
echo " --merge Merge with existing translations\n";
echo " --js-path=path Path to JavaScript files (default: public/assets/js)\n";
echo " --php-path=path Path to PHP files (default: src)\n\n";
echo "Examples:\n";
echo " php bin/extract-translations.php --output=locales/keys.json\n";
echo " php bin/extract-translations.php --backup --merge --output=locales/en.json\n";
echo " php bin/extract-translations.php --context --output=locales/keys-context.json\n";
echo " php bin/extract-translations.php --template --output=locales/en-template.json\n";
exit(1);
}
+18
View File
@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
require __DIR__ . '/../vendor/autoload.php';
use App\Api\OpenApiGenerator;
$baseUrl = $_ENV['APP_PUBLIC_BASE_URL'] ?? $_ENV['APP_URL'] ?? 'http://localhost:8080';
if (isset($argv[1])) {
$baseUrl = $argv[1];
}
$destination = __DIR__ . '/../public/pwgenerator-swagger-spec.json';
OpenApiGenerator::write($baseUrl, $destination);
echo "OpenAPI spec generated at {$destination}\n";
echo "Base URL: {$baseUrl}\n";
+63
View File
@@ -0,0 +1,63 @@
/*
Used to collect words and their definitions from the Swiss German Slängikon page.
https://zuri.net/de/zurich/slängikon.htm
*/
$(document).ready(function() {
var output = [];
$('h2').each(function() {
var heading = $(this).text().trim();
var headingElem = this; // Native DOM element
var nextSibling = headingElem.nextSibling;
var listText = '';
while (nextSibling) {
if (nextSibling.nodeType === 3) { // Text node
var text = nextSibling.textContent.trim();
if (text) {
listText += text + ' ';
}
} else if (nextSibling.nodeType === 1 && (nextSibling.tagName === 'HR' || nextSibling.tagName === 'H2')) {
break; // Stop at <hr> or next <h2>
}
nextSibling = nextSibling.nextSibling;
}
// Process the collected listText
if (listText) {
var words = listText.split(/ ?\| ?/); // Split by ' | ' or '|'
words.forEach(function(word) {
word = word.trim();
if (!word) return;
// Extract content in parentheses at the end
var explanation = '';
var match = word.match(/\s*\(([^)]+)\)$/);
if (match) {
explanation = match[1].trim();
word = word.replace(/\s*\([^)]+\)$/, '').trim();
}
// Remove leading articles 'e ', 'es ', 'en '
word = word.replace(/^(e |es |en |d |de |s )\b/i, '');
// Construct definition: heading + (explanation if present)
var definition = heading;
if (explanation) {
definition += ' (' + explanation + ')';
}
// Add to output
output.push(word + '\t' + definition);
});
}
});
// Output the list to console, one word per line
if (output.length > 0) {
var outputText = output.join('\n');
console.log(outputText);
// Copy to clipboard
navigator.clipboard.writeText(outputText).then(function() {
console.log('Output successfully copied to clipboard.');
}).catch(function(err) {
console.error('Failed to copy to clipboard: ', err);
console.log('You can manually copy the output from the console above.');
});
} else {
console.log('No words found. Please ensure jQuery is loaded and verify the page structure.');
}
});
+229
View File
@@ -0,0 +1,229 @@
#!/bin/bash
# Translation Update Script
# Automatically extracts and updates translation strings
set -e
echo "🌍 Translation Update Utility"
echo "============================="
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Configuration
LOCALES_DIR="../locales"
TEMPLATES_DIR="../templates"
ASSETS_DIR="../assets"
SRC_DIR="../src"
# Create locales directory if it doesn't exist
mkdir -p "$LOCALES_DIR"
echo -e "${BLUE}📁 Scanning for untranslated strings...${NC}"
# Find all HTML files and extract data-i18n attributes
echo -e "${YELLOW}🔍 Scanning HTML templates...${NC}"
find $TEMPLATES_DIR -name "*.html" -exec grep -oE 'data-i18n="[^"]*"' {} \; | \
sed 's/data-i18n="//g' | sed 's/"//g' | sort | uniq > /tmp/html_keys.txt
find $TEMPLATES_DIR -name "*.html" -exec grep -oE 'data-i18n-placeholder="[^"]*"' {} \; | \
sed 's/data-i18n-placeholder="//g' | sed 's/"//g' | sort | uniq >> /tmp/html_keys.txt
find $TEMPLATES_DIR -name "*.html" -exec grep -oE 'data-i18n-title="[^"]*"' {} \; | \
sed 's/data-i18n-title="//g' | sed 's/"//g' | sort | uniq >> /tmp/html_keys.txt
# Find all PHP files and extract data-i18n attributes
echo -e "${YELLOW}🔍 Scanning PHP templates...${NC}"
find $SRC_DIR -name "*.php" -exec grep -oE 'data-i18n="[^"]*"' {} \; | \
sed 's/data-i18n="//g' | sed 's/"//g' | sort | uniq > /tmp/php_keys.txt
find $SRC_DIR -name "*.php" -exec grep -oE 'data-i18n-placeholder="[^"]*"' {} \; | \
sed 's/data-i18n-placeholder="//g' | sed 's/"//g' | sort | uniq >> /tmp/php_keys.txt
find $SRC_DIR -name "*.php" -exec grep -oE 'data-i18n-title="[^"]*"' {} \; | \
sed 's/data-i18n-title="//g' | sed 's/"//g' | sort | uniq >> /tmp/php_keys.txt
# Find JavaScript files and extract this.t() calls
echo -e "${YELLOW}🔍 Scanning JavaScript files...${NC}"
find $ASSETS_DIR -name "*.js" -exec grep -oE "this\.t\('[^']*'\)" {} \; | \
sed "s/this\.t('//g" | sed "s/')//g" | sort | uniq > /tmp/js_keys.txt
find $ASSETS_DIR -name "*.js" -exec grep -oE 'this\.t\("[^"]*"\)' {} \; | \
sed 's/this\.t("//g' | sed 's/")//g' | sort | uniq >> /tmp/js_keys.txt
# Combine all keys
cat /tmp/html_keys.txt /tmp/js_keys.txt /tmp/php_keys.txt | sort | uniq > /tmp/all_keys.txt
echo -e "${BLUE}📊 Found $(cat /tmp/all_keys.txt | wc -l) unique translation keys${NC}"
# Function to create/update language file
update_language_file() {
local lang=$1
local file="$LOCALES_DIR/$lang.json"
echo -e "${YELLOW}📝 Updating $lang.json...${NC}"
# Load existing translations or create empty object
if [ -f "$file" ]; then
cp "$file" "/tmp/${lang}_backup.json"
existing_keys=$(cat "$file" | jq -r 'keys[]' 2>/dev/null || echo "")
else
echo "{}" > "$file"
existing_keys=""
fi
# Create temporary file with all required keys
echo "{" > "/tmp/${lang}_new.json"
first=true
while IFS= read -r key; do
if [ ! -z "$key" ]; then
# Check if key already exists
existing_value=$(cat "$file" | jq -r ".[\"$key\"] // empty" 2>/dev/null)
if [ ! -z "$existing_value" ] && [ "$existing_value" != "null" ]; then
# Keep existing translation
value="$existing_value"
else
# Add new key with placeholder or translation
case $lang in
"en")
# Convert snake_case to human readable
value=$(echo "$key" | sed 's/_/ /g' | sed 's/\b\(.\)/\u\1/g')
;;
"de")
case $key in
"generate") value="Generieren" ;;
"copy") value="Kopieren" ;;
"language") value="Sprache" ;;
"options") value="Optionen" ;;
"password_generator") value="Passwort-Generator" ;;
"api_documentation") value="API-Dokumentation" ;;
"loading") value="Wird geladen..." ;;
"error") value="Fehler" ;;
"success") value="Erfolgreich" ;;
"download") value="Herunterladen" ;;
"back") value="Zurück" ;;
"home") value="Startseite" ;;
"settings") value="Einstellungen" ;;
*) value="$(echo "$key" | sed 's/_/ /g' | sed 's/\b\(.\)/\u\1/g') (DE)" ;;
esac
;;
"fr")
case $key in
"generate") value="Générer" ;;
"copy") value="Copier" ;;
"language") value="Langue" ;;
"options") value="Options" ;;
"password_generator") value="Générateur de mots de passe" ;;
"api_documentation") value="Documentation API" ;;
"loading") value="Chargement..." ;;
"error") value="Erreur" ;;
"success") value="Succès" ;;
"download") value="Télécharger" ;;
"back") value="Retour" ;;
"home") value="Accueil" ;;
"settings") value="Paramètres" ;;
*) value="$(echo "$key" | sed 's/_/ /g' | sed 's/\b\(.\)/\u\1/g') (FR)" ;;
esac
;;
*)
value="$(echo "$key" | sed 's/_/ /g' | sed 's/\b\(.\)/\u\1/g') (TODO: $lang)"
;;
esac
fi
# Add to JSON
if [ "$first" = true ]; then
first=false
else
echo "," >> "/tmp/${lang}_new.json"
fi
# Escape quotes in value
escaped_value=$(echo "$value" | sed 's/"/\\"/g')
echo " \"$key\": \"$escaped_value\"" >> "/tmp/${lang}_new.json"
fi
done < /tmp/all_keys.txt
echo "" >> "/tmp/${lang}_new.json"
echo "}" >> "/tmp/${lang}_new.json"
# Format JSON nicely
cat "/tmp/${lang}_new.json" | jq '.' > "$file"
# Count translations
total_keys=$(cat /tmp/all_keys.txt | wc -l)
translated_keys=$(cat "$file" | jq -r 'to_entries[] | select(.value | test("\\(TODO:|\\(DE\\)|\\(FR\\)|\\(IT\\)|\\(ES\\)|\\(JA\\)|\\(ZH\\))") | not) | .key' | wc -l)
echo -e "${GREEN}✅ Updated $lang.json: $translated_keys/$total_keys translated${NC}"
}
# Update all language files
languages=("en" "de" "fr" "it" "es" "ja" "zh")
for lang in "${languages[@]}"; do
update_language_file "$lang"
done
# Generate report
echo ""
echo -e "${BLUE}📊 Translation Status Report${NC}"
echo "============================"
for lang in "${languages[@]}"; do
file="$LOCALES_DIR/$lang.json"
if [ -f "$file" ]; then
total=$(cat /tmp/all_keys.txt | wc -l)
translated=$(cat "$file" | jq -r 'to_entries[] | select(.value | test("\\(TODO:|\\(DE\\)|\\(FR\\)|\\(IT\\)|\\(ES\\)|\\(JA\\)|\\(ZH\\))") | not) | .key' | wc -l 2>/dev/null || echo "0")
percentage=$((translated * 100 / total))
echo -e "$lang: $translated/$total ($percentage%) translated"
fi
done
# Find hardcoded strings that need data-i18n attributes
echo ""
echo -e "${YELLOW}🔍 Finding hardcoded strings that need localization...${NC}"
# Check HTML files for hardcoded text
echo -e "${BLUE}HTML files with potential hardcoded text:${NC}"
find $TEMPLATES_DIR -name "*.html" -exec grep -l '[>][A-Z][^<]*[a-z][^<]*[<]' {} \; | while read file; do
echo " 📄 $file"
grep -oE '>[A-Z][^<]*[a-z][^<]*<' "$file" | sed 's/^>//; s/<$//' | head -5 | while read text; do
if [[ ! "$text" =~ ^(GET|POST|PUT|DELETE|HTTP|JSON|API|URL|ID|OK|CSS|JS|HTML)$ ]]; then
echo " → \"$text\" (needs data-i18n)"
fi
done
done
# Check JavaScript files for hardcoded strings
echo -e "${BLUE}JavaScript files with potential hardcoded text:${NC}"
find $ASSETS_DIR -name "*.js" -exec grep -l "'[A-Z][^']*[a-z][^']*'" {} \; | while read file; do
echo " 📄 $file"
grep -oE "'[A-Z][^']*[a-z][^']*'" "$file" | sed "s/^'//; s/'$//" | head -5 | while read text; do
if [[ ! "$text" =~ ^(GET|POST|PUT|DELETE|HTTP|JSON|API|URL|ID|OK|CSS|JS|HTML|Content-Type|application/json)$ ]]; then
echo " → \"$text\" (needs this.t())"
fi
done
done
# Generate migration instructions
echo ""
echo -e "${GREEN}📋 Next Steps:${NC}"
echo "1. Review the generated translation files in $LOCALES_DIR/"
echo "2. Replace placeholder translations (TODO: lang) with proper translations"
echo "3. Add data-i18n attributes to hardcoded HTML strings"
echo "4. Replace hardcoded JavaScript strings with this.t() calls"
echo "5. Test the application in different languages"
# Clean up temporary files
rm -f /tmp/html_keys.txt /tmp/js_keys.txt /tmp/all_keys.txt /tmp/*_new.json
echo ""
echo -e "${GREEN}✅ Translation update completed!${NC}"
@@ -0,0 +1,156 @@
# Wiktionary Random Word Generator - Verfeinerte Version
Eine **hochoptimierte Version** des Wiktionary Random Word Generators mit verbesserter Definition-Extraktion und erweiterten Filteroptionen.
## 🔧 Neue Verfeinerungen
### Saubere Definition-Extraktion
- **Entfernt führende Nummerierungen**: `1]`, `[2]`, `3)` werden automatisch entfernt
- **Bereinigt Formatierungen**: Entfernt `:`, `•`, `>`, `(`, `"` am Anfang
- **Intelligente Kleinschreibung**: Definitionen beginnen mit Kleinbuchstaben
- **Bessere Lesbarkeit**: Cleaner, natürlicher Text ohne Wiki-Markup
### Zahlen-Filter (Neu)
- **`--exclude-numbers` Option**: Schließt Wörter mit Ziffern aus
- **Intelligente Erkennung**: Filtert `5-mal`, `Jahr2024`, `3D-Drucker`
- **Erhält Wörter**: `dreimal`, `hundert`, `eins` bleiben erlaubt
- **Flexible Nutzung**: Optional aktivierbar per Parameter
## 📋 Verwendung
### Standard (Empfohlen)
```bash
# 50 deutsche Wörter mit sauberen Definitionen
python wiktionary_random_refined.py -c 50 -l de
```
### Mit Zahlen-Ausschluss
```bash
# Keine Wörter mit Ziffern (5-mal, Jahr2024, etc.)
python wiktionary_random_refined.py -c 30 --exclude-numbers
# Kombiniert mit Wortart-Filter
python wiktionary_random_refined.py -c 25 --include-pos verb adjektiv --exclude-numbers
```
### Erweiterte Kombinationen
```bash
# Englische Wörter, Namen erlaubt, aber keine Zahlen
python wiktionary_random_refined.py -c 40 -l en --include-names --exclude-numbers
# Nur Substantive und Verben, ohne Zahlen
python wiktionary_random_refined.py -c 35 --include-pos substantiv verb --exclude-numbers
```
## 🎯 Definition-Bereinigung (Beispiele)
### Vorher → Nachher
```
1] eine große Pflanze mit Stamm und Ästen
→ eine große Pflanze mit Stamm und Ästen
[2] übertragen: etwas Großes und Stabiles
→ übertragen: etwas Großes und Stabiles
: [1] positive Eigenschaft oder Zustand
→ positive Eigenschaft oder Zustand
• erste Bedeutung: schön anzusehen
→ erste Bedeutung: schön anzusehen
(1) Gebäude zum Wohnen
→ gebäude zum Wohnen
```
## 🔢 Zahlen-Filter (Beispiele)
### Ausgeschlossen (mit --exclude-numbers)
- `5-mal` → ❌ (enthält Ziffer)
- `Jahr2024` → ❌ (enthält Ziffer)
- `3D-Drucker` → ❌ (enthält Ziffer)
- `42fach` → ❌ (enthält Ziffer)
### Erlaubt
- `dreimal` → ✅ (keine Ziffern)
- `hundert` → ✅ (ausgeschrieben)
- `eins` → ✅ (ausgeschrieben)
- `Baum` → ✅ (keine Zahlen)
## 🔧 Alle Parameter
| Parameter | Kurz | Beschreibung | Standard |
|-----------|------|--------------|----------|
| `--count` | `-c` | Anzahl Wörter | 50 |
| `--language` | `-l` | Sprache (de/en/fr/es/it) | de |
| `--output` | `-o` | Output-Datei | refined_wordlist.csv |
| `--include-pos` | - | Wortarten-Filter | adjektiv verb substantiv |
| `--include-names` | - | Namen einschließen | False |
| `--exclude-numbers` | - | **NEU**: Zahlen ausschließen | False |
| `--verbose` | `-v` | Ausführliche Ausgabe | - |
## 📊 Beispiel-Output
```
🎲 Wiktionary Random Word Generator (Verfeinerte Version)
===========================================================================
Sprache: DE
Anzahl: 25
Wortarten: adjektiv, verb, substantiv (Standard)
Namen einschließen: Nein
Zahlen ausschließen: Ja
Output: refined_wordlist.csv
===========================================================================
Sammle Wörter aus Kategorien: adjektiv, verb, substantiv
Lade adjektiv aus Kategorie:Adjektiv (Deutsch)...
→ 167 Wörter gefunden
Lade verb aus Kategorie:Verb (Deutsch)...
→ 234 Wörter gefunden
Lade substantiv aus Kategorie:Substantiv (Deutsch)...
→ 445 Wörter gefunden
Gesamt gesammelt: 846 Wörter
Verarbeite Kandidaten...
1/25: schön
2/25: laufen
3/25: Baum
[...]
25/25: denken
✅ 25 Wörter erfolgreich generiert!
💾 Wortliste gespeichert in: refined_wordlist.csv
📖 Beispiele aus der generierten Liste:
-----------------------------------------------------------------
1. schön → ansprechend und angenehm in der Erscheinung...
2. laufen → sich zu Fuß mit schnellen Schritten fortbewegen...
3. Baum → großes, meist mehrjähriges Gewächs mit Stamm...
4. intelligent → geistig begabt, klug und aufgeweckt...
5. sprechen → mündlich kommunizieren, Worte äußern...
... und 20 weitere Wörter
```
## 🚀 Verbesserungen gegenüber vorherigen Versionen
### Definition-Qualität
1. **Sauberer Text**: Keine störenden `1]`, `[2]` Präfixe mehr
2. **Natürliche Sprache**: Definitionen lesen sich flüssiger
3. **Konsistente Formatierung**: Einheitlicher Stil ohne Wiki-Markup
4. **Bessere UX**: Professionellere, verwendbare Ausgabe
### Filter-Präzision
1. **Zahlen-Kontrolle**: Optionaler Ausschluss numerischer Begriffe
2. **Flexible Konfiguration**: Alle Filter kombinierbar
3. **Intelligente Erkennung**: Unterscheidet Ziffern vs. ausgeschriebene Zahlen
4. **Mehrschichtiger Ansatz**: Basis- + Kategorie- + Zahlen-Filter
### Performance-Optimierungen
1. **Effizientere Regex**: Optimierte Pattern für Definition-Bereinigung
2. **Reduzierte API-Calls**: Weniger Kategorien-Checks durch Vorfilterung
3. **Bessere Kandidaten-Auswahl**: Höhere Erfolgsquote bei finaler Auswahl
Das verfeinerte Script ist besonders geeignet für:
- **Professionelle Anwendungen**: Saubere, verwendbare Definitionen
- **Sprachlernen**: Klare, verständliche Erklärungen ohne Formatierungs-Chaos
- **Datenqualität**: Konsistente, strukturierte Ausgabe
- **Flexible Filterung**: Präzise Kontrolle über Inhaltstypen
+418
View File
@@ -0,0 +1,418 @@
meiden transitiv, gehoben: etwas dafür tun, einer Person oder Sache nicht zu begegnen
sturmgepeitscht gehoben: vom Sturm heftig bewegt, aufgewirbelt und/oder geschleudert werdend
Tonkünstlerin gehoben, Musik: Künstlerin, die musikalische Werke schafft
duress gehoben, meist Rechtssprache: Nötigung, Zwang
Wohlgestalt gehoben veraltend: schöne, ansprechende Gestalt
Gezweig gehoben: Gesamtheit aller Zweige (einer Hecke, eines Baumes oder Ähnlichem)
hinabwerfen transitiv; gehoben: von (hier) oben nach (dort) unten werfen (weg vom Sprechenden)
Verblichener gehoben: männliche Person, die gestorben ist
aufkündigen einen Vertrag/eine Vertragsbeziehung beenden
straßauf, straßab adverbiell; gehoben: auf/in vielen Straßen, durch mehrere Straßen
entströmen intransitiv, unpersönlich, gehoben: aus etwas in Strömen herauskommen
durchkreuzen transitiv: Wörter, Buchstaben in einem Text mit Kreuzen (×) unleserlich, ungültig machen
nimmermüde gehoben: nicht ermüdend, unermüdlich
Heermeister gehoben oder veraltet: Oberbefehlshaber (eines Teils) der Streitkräfte, eines Heeres
Frieden kein Plural: vertraglich gesichertes Miteinander verschiedener Staaten, das durch Abwesenheit von Gewalt (und speziell von Krieg) gekennzeichnet ist
späterhin gehoben: zu einem Zeitpunkt, der nach einem Ereignis liegt, auf etwas folgt, noch in der Zukunft liegt
Stachel im Fleisch gehoben: immerwährende eindringliche Erinnerung an etwas
Tondichterin gehoben, Musik: Künstlerin, die musikalische Werke schafft
Verstorbene gehoben: weibliche Person, die nicht mehr am Leben, sondern tot ist
übersonnt gehoben; auch übertragen: von der Sonne hell erleuchtet, von Sonnenstrahlen beschienen
engen transitiv, selten, gehoben: etwas schmäler machen
hungrig ein Gefühl des Hungers verspürend, Hunger habend
Dotation gehoben: finanzielle und/oder materielle Zuwendung
emporgehen nach oben gehen
verstummen intransitiv, von Personen: (plötzlich) aufhören, die Stimme zu gebrauchen
unflätig gehoben, abwertend: so, dass es nicht den üblichen Regeln des guten Benehmens oder Anstandes entspricht
Widerfahrnis veraltet, gehoben, sonst fachsprachlich, Psychologie: dasjenige (nicht Beeinflussbare), was (einem, jemandem) widerfährt
Fittich poetisch, gehoben: Körperteil, das einem Tier das Fliegen ermöglicht
Trottoir österreichisch gehoben, schweizerisch, süddeutsch, sonst veraltend oder landschaftlich: (durch eine Bordsteinkante von der Fahrbahn abgegrenzter, erhöhter) Weg für Fußgänger und Fußgängerinnen, der entlang einer Straße führt
durchpflügen transitiv: eine Fläche sehr stark und gründlich mit dem Pflug bearbeiten
in Flammen aufgehen gehoben: durch Feuer vollständig zerstört werden, verbrennen
ekel gehoben, sonst veraltend: (moralisch oder physisch) Ekel erregend
Bildnerin veraltet: weibliche Person, die eine Person oder Sache formt, ausbildet
guter Dinge in einer heiteren, vergnügten Stimmung
bisweilen gehoben: unterschiedlich häufig, mehr oder minder oft
herniederbrennen intransitiv, gehoben, Hilfsverb haben, von der Sonne: große Hitze, gleißendes Licht nach unten ausstrahlen
beargwöhnen gehoben: misstrauisch betrachten
partizipieren gehoben: an etwas teilhaben; an etwas seinen Anteil haben
zurücksenden transitiv; gehoben: etwas wieder an einen bestimmten Ort/zu einer bestimmten Person gelangen lassen
dahinwelken intransitiv; gehoben: vom blühenden in einen welken Zustand übergehen
anbequemen reflexiv, veraltend, sonst gehoben: sich mit seinem Verhalten, seinen Handlungen an jemandem oder etwas orientieren, sich danach richten
epochal gehoben: von großer Bedeutung, bedeutsam für eine Epoche
heimführen jemanden in dessen Zuhause bringen
ausharren intransitiv; gehoben: (trotz Widrigkeiten) mit Geduld abwarten, warten
emporwirbeln transitiv, Hilfsverb haben: kleine Teilchen (zum Beispiel Sand, Schnee) so in Bewegung versetzen, dass sie wild drehend, ungeordnet nach oben fliegen
pasteur gehoben: Hirt, Senner
Landesvater gehoben, historisch: Fürst eines Landes
Tonschöpfer gehoben, Musik: Künstler, der musikalische Werke schafft
zurückbegehren transitiv, gehoben: etwas wieder in seinem Besitz, bei sich haben wollen
Tonmaler gehoben, Musik: Künstler, der musikalische Werke schafft
erfließen österreichisch, Amtssprache, gehoben: daraus folgen, sich ergeben, entstehen
einmütig gehoben: hinsichtlich der Meinung übereinstimmend, völlig gleich
Lustbarkeit gehoben, veraltend: Veranstaltung, die jemand zur Unterhaltung, zur Vergnügung besucht; allgemein auch: etwas, was Freude bereiten soll
Ansinnen gehoben: oft als Zumutung empfundenes Ersuchen, Gesuch, Bitte
Dämmer gehoben: (Tages-)Licht, das allmählich hell beziehungsweise dunkel wird
hinabgehen intransitiv, gehoben: nach unten gehen
wohlauf gehoben: gesund, in guter Verfassung
hinausschauen regional: aus einem bestimmten Bereich, von einer bestimmten Stelle (hier drinnen) in einen anderen Bereich, an eine andere Stelle (dort draußen) blicken (schauen) (in Richtung vom Sprecher weg)
äquivalent gehoben: gleichwertig, entsprechend
Anverwandter gehoben: Person, die zur Verwandtschaft gehört
den Tod finden gehoben: infolge von etwas (Unfall, Krankheit, Ermordung oder dergleichen) aufhören zu leben
mit doppelter Zunge sprechen gehoben: Unwahres oder Widersprüchliches sagen; heuchlerisch reden; fuchsschwänzen
Verelendung gehoben: das Geraten in sehr schlechte Lebensumstände
obzwar gehoben, veraltend: leitet einen Konzessivsatz ein, der ein Gegenargument gegen das zuvor Gesagte enthält; obwohl
Bacchus huldigen gehoben, euphemistisch: Wein trinken
Edelmut gehoben: edle Gesinnung, die egoistische Motive hintenan stellt
hoffärtig gehoben abwertend: besonders hochmütig, andere Personen verletzend
sonach gehoben: eine Folgerung einleitend: folglich, also
Todespein gehoben: tödliche Qualen, Schmerzen
weiterreichen transitiv, gehoben: etwas, das man von jemandem erhalten hat, einem anvertraut wurde, einem anderen (händisch) geben, in dessen Verantwortung überstellen
Mund Anatomie: Öffnung im unteren Teil des (hauptsächlich menschlichen) Gesichtes, hauptsächlich zur Nahrungsaufnahme und zur Lautbildung genutzt
Notschrei gehoben, veraltend: lauter Ruf nach Hilfe aus einer Notsituation
nicken intransitiv: (zur Bejahung, Zustimmung, als Geste des Verstehens, als Gruß oder dergleichen) den Kopf kurz hintereinander senken und heben
herabkommen intransitiv, gehoben: (von dort oben) nach (hier) unten kommen (in Richtung zum Sprechenden)
voranschreiten vor jemand anders gehen
Anverwandte gehoben: weibliche Person, die zur Verwandtschaft gehört
betören gehoben: jemanden faszinieren, in sich verliebt machen
Unnatur gehoben: etwas, das der Natur/der natürlichen Veranlagung nicht gemäß ist
winden transitiv: etwas drehen, wickeln
durchtosen transitiv, gehoben, veraltend: mit heftiger, wilder Bewegung und dröhnendem Geräusch durchqueren
feilbieten transitiv, gehoben: etwas zum Kauf anbieten
nachgerade umgangssprachlich, mit Betonung auf Ende/Konsequenz eines Geschehens/Prozesses: endlich, letztlich
edelgeboren veraltet gehoben: von vornehmer/adliger Herkunft
mit gespaltener Zunge reden gehoben: Unwahres oder Widersprüchliches sagen, heuchlerisch reden, fuchsschwänzen
emporarbeiten sich unter Anstrengungen, Mühen von einer unten gelegenen Stelle nach oben bewegen (vom Sprecher weg)
anfliegen in einem Luftfahrzeug, mit Flügeln, durch die Luft näher kommen
auserlesen gehoben: qualitativ besonders gut/hochwertig
goldig selten, gehoben: die Farbe und den Glanz von Gold habend (auch durch Vergolden)
fernhin gehoben: in weitem Umkreis, bis in weite Ferne
Abgeschiedener gehoben: (kürzlich) verstorbene (männliche) Person
Wehe veraltet gehoben: seelischer Schmerz
Beilager gehoben veraltet: sexuelle Vereinigung
antizipieren gehoben, bildungssprachlich: etwas erkennen, bevor es eingetreten ist
mit gespaltener Zunge sprechen gehoben: Unwahres oder Widersprüchliches sagen; heuchlerisch reden; fuchsschwänzen
Trottoirfläche österreichisch gehoben, schweizerisch, süddeutsch, sonst veraltend oder landschaftlich: befestigte und meist gepflasterte Fläche am Rand einer Straße, die für Fußgänger vorgesehen ist
Kronjuwel kostbares Schmuckstück, das sich im Besitz des Herrscherhauses befindet
gürten gehoben: jemandem/sich einen Gürtel oder eine andere Art Riemen oder Band umlegen, umschnallen; etwas mit einem Gürtel oder Ähnlichem versehen
leichten Herzens gehoben: ohne dass es jemandem Anstrengung, Sorgen bereitet, jemanden Überwindung kostet, ohne Bedenken, Zweifel; sehr gern; völlig unbekümmert
ersterben aufhören
empordienen reflexiv, gehoben: sich durch Fleiß, beständiges Handeln/Tätigsein im Beruf oder Ähnliches nach und nach stufenweise in eine höhergestellte Position bringen
Augenweite gehoben oder veraltet: mit den Augen einsehbare Entfernung
aufblühen intransitiv: sich blühend öffnen, die Blüten (langsam) auseinanderfalten; anfangen, sich zu voller Blüte zu entfalten, allmählich in Blüte kommen
abdarben transitiv, gehoben, sich etwas abdarben: sich etwas (bis zum Grad des Notleidens) entziehen (um es für Anderweitiges aufzusparen)
tagen gehoben: Tag werden
anreihen transitiv: (etwas) einer bereits vorhandenen Reihe hinzufügen
aufbrechen auch bildlich: etwas, das verschlossen ist, mit Gewalt öffnen
geizen mit der Präposition „mit“: in übertriebener, unangemessener Weise sparen, geizig mit etwas sein
umfriedigen gehoben, selten: einrahmend, abgrenzend umgeben
niederstrecken transitiv; gehoben, auch übertragen: einen Menschen oder ein Tier schwer verletzen oder töten und so zu Boden bringen
ausgleiten intransitiv; auch übertragen: auf einer glatten Fläche aus dem Gleichgewicht kommen, keinen Halt finden (und hinfallen)
distinguiert gehoben: vornehm; sich durch betont gepflegtes Auftreten oder Ähnliches von anderen abhebend
Kunde jemand, der bei einem bestimmten Geschäft einkauft; der Käufer einer Ware; derjenige, der eine Dienstleistung in Anspruch nimmt; jeder, der für etwas zahlt (auch wenn die Leistung an einen Dritten geht)
Allgütiger gehoben: derjenige, der allgütig ist: Gott
Gemahl gehoben: der vermählte Mann
die Hülle und Fülle gehoben: in großer Menge, im Überfluss
Freveltat gehoben: einzelne Handlung, die einen Frevel gegen etwas darstellt; frevelhafte Tat
beschatten gehoben: jemandem/etwas einen Schatten verleihen
schmachbeladen gehoben: sehr erniedrigt, in seiner Würde stark herabgesetzt, in seinem Stolz sehr verletzt, Schande auf sich geladen habend
entschwinden intransitiv, gehoben: sich von etwas entfernen
allein als Adjektiv: für sich, getrennt, einsam, einzeln sein; nur; ohne Beistand, Zeugen, Anwesenheit von anderen Personen, ohne Hilfe sein
zurückverlangen von jemandem die Rückgabe von etwas fordern, das einem gehört
ergötzlich gehoben: in unterhaltsamer Art und Weise Gefallen/Vergnügen bereitend
erkühnen reflexiv, gehoben: so wagemutig und frech (kühn) sein, dass man etwas eigentlich Unerlaubtes/Unzulässiges versucht
dergestalt gehoben: derart, dermaßen
anfechten die Gültigkeit/Rechtmäßigkeit einer Entscheidung anzweifeln und dann dagegen vorgehen
über Gebühr gehoben: über das angemessene Maß hinausgehend, mehr als erforderlich
Hall gehoben: akustisch wahrnehmbares, unspezifisches Ereignis
fortrühren gehoben, reflexiv, meistens verneint: sich wegbewegen; sich vom eigenen Aufenthaltsort entfernen
Inbrunst gehoben: starke Hingabe an ein Gefühl oder an ein Vorhaben
anspeien transitiv, gehoben: jemanden oder etwas (absichtlich) mit Spucke oder Erbrochenem beschmutzen
Begräbnisstätte gehoben: Stelle, wo jemand begraben liegt
Land der Morgenröte poetisch, gehoben: Bezeichnung und Selbstbezeichnung für China
Beute durch Diebstahl, Raub oder Plünderung angeeignete Güter
Diskutant gehoben: Person, die sich an einer Diskussion beteiligt
bedürfen gehoben, mit Genitiv: etwas benötigen, etwas brauchen, auf etwas angewiesen sein
entsühnen transitiv, auch reflexiv, gehoben: jemanden, sich durch Sühne von Sünde, Schuld befreien
Degout gehoben: starke Abneigung gegen jemanden oder etwas
entzünden transitiv: in Brand setzen
Quisquilie gehoben, meist Plural: Kleinigkeit, Nichtigkeit, Bagatelle
äffen transitiv, gehoben, veraltete Bedeutung: jemanden durch Unwahrheiten irreführen, täuschen
hernieder gehoben: aus der Richtung von irgendwo oben nach unten, meist zum Sprecher, Erzähler oder Handelnden hin
so weit das Auge reicht gehoben: so weit man blicken/sehen kann; überall
einstmals gehoben: früher, vor langer Zeit
sinnesfroh gehoben: mit Sinnenfreude; sich am sinnlichen Genuss erfreuend
nachforschen intransitiv: sich intensiv mit dem Sammeln von Informationen über etwas beschäftigen
abspeisen transitiv, umgangssprachlich: jemandes Hunger stillen
umfrieden gehoben: einrahmend, abgrenzend umgeben
Wegesrand gehoben: seitliche Grenze eines Weges
rapide gehoben, oft bildlich: äußerst schnell
nachkommen intransitiv: jemandem, einer Sache nach einiger Zeit folgen; einige Zeit später hinterherkommen; auf etwas (später) als Konsequenz folgen
zuwider gehoben, jemandem ist etwas zuwider: Ablehnung und unangenehme Gefühle hervorrufend
divinatorisch gehoben: die Fähigkeit zur Voraussage von Ereignissen besitzend
wohlgemut gehoben: heiter gestimmt
Affront gehoben: öffentlicher verbaler oder schriftlicher Angriff bzw. Beschuldigung
zurücksehnen reflexiv: wieder an einen bestimmten Ort/in eine bestimmte Zeit/zu einer bestimmten Person zurückversetzt sein wollen
zuinnerst sehr tief in der Gedanken- und Gefühlswelt einer Person
zu Tode kommen gehoben: infolge von Unfall, Krankheit, Ermordung oder dergleichen aufhören zu leben
erwürgen jemanden ermorden, indem man ihm (mit den Händen) den Hals zusammendrückt, um die Luftzufuhr abzuschneiden, sodass das Blut nicht mehr zum Kopf zirkulieren kann
überstrahlen transitiv, gehoben: auf der gesamten Fläche bestrahlen
mitnichten veraltend, gehoben: auf keinen Fall, in keiner Weise
fortsterben intransitiv, gehoben oder veraltete Bedeutung: (allmählich, aber auch plötzlich) aufhören zu leben
zurückbeben intransitiv, gehoben oder veraltete Bedeutung: mit einer kurzen, heftigen Bewegung vor etwas oder jemandem zurückweichen
mit doppelter Zunge reden gehoben: Unwahres oder Widersprüchliches sagen, heuchlerisch reden, fuchsschwänzen
spartanisch keine Steigerung: das antike Sparta betreffend
Dolmetsch besonders in Österreich: Person, die (gesprochene) Texte in eine andere Sprache übersetzt
Unflat gehoben, veraltend: abstoßend wirkender Schmutz
ausweinen reflexiv: (bei jemandem) Erleichterung suchen durch Weinen, sich jemandem anvertrauen, jemandem von seinen Sorgen erzählen
vorwalten gehoben, veraltend: vorhanden sein; wirksam sein
seligen Angedenkens veraltet bis veraltend, sonst gehoben verhüllend: Beistellung, die die erwähnte Person als verstorben kennzeichnet
Reich der Mitte poetisch, gehoben, literatursprachlich: Bezeichnung und Selbstbezeichnung für China; wörtliche Übersetzung der chinesischen Schriftzeichen: zhong guo; wird insbesondere historisch und außerhalb Chinas in Poesie und Prosa verwendet
Strahlkraft gehoben: starke Wirkung auf andere
hinausgeleiten transitiv; gehoben: jemanden (von hier drinnen) nach dort draußen begleiten/führen (weg vom Sprechenden)
umwittern veraltet: mit schlechtem Wetter oder Unwetter umgeben
hoffnungsfroh gehoben: der Zukunft zuversichtlich entgegensehend
Zutrunk gehoben veraltet: das Erheben eines (mit Alkohol gefüllten) Glases, um einer Person die Ehre zu erweisen oder ihr Gutes zu wünschen
Akteur gehoben: Person, die handelt, die etwas tut
ungesättigt gehoben: Hunger verspürend
Weh gehoben: seelischer Schmerz
martervoll gehoben: mit großen Schmerzen, nach schmerzhaften Qualen
vermessen übermäßig ehrgeizig und deshalb riskant
heißen genannt werden, den Namen haben
Sinnenrausch gehoben: rauschhafter Zustand der Sinne
Mehrer gehoben: Person, die eine größere Menge von etwas anhäuft
dahinschwinden intransitiv, gehoben, unpersönlich: (allmählich) weniger werden
voreinst gehoben oder veraltet: vor langer/längerer Zeit
niederlassen veraltende Bedeutung: einen Gegenstand senken/auf den Boden legen
einhellig gehoben: ohne Gegenstimmen
bekleiden transitiv, reflexiv: mit Bekleidung versehen
Blattkrone gehoben: Baumkrone
widereinander gehoben, von Personen oder Dingen: im Widerstreit zueinander, in einem konkurrierenden Verhältnis zueinander
Mondesfinsternis Astronomie, Österreich, gehoben: Ereignis, bei dem der Mond durch den Kernschatten der Erde wandert
Gezweige gehoben: Gesamtheit aller Zweige (einer Hecke, eines Baumes oder Ähnliches)
Apotheose Religion, gehoben: die Erklärung eines Menschen zu einer Gottheit
Œuvre gehoben, bildungssprachlich: Gesamtheit aller Einzelwerke eines Künstlers
bejahrt gehoben: in vorgerücktem Alter
mondhell gehoben: durch das vom Erdmond reflektierte Licht erhellt
Ichsucht gehoben, veraltend: Haltung, die darauf aus ist, sich selbst immer zuerst zu berücksichtigen
gebrechen gehoben, unpersönlich, jemandem (Dativ) gebricht es an etwas (Dativ); persönlich nur veraltend: fehlen, mangeln
Furor gehoben: Wildheit, Raserei
Bringerin veraltend, gehoben: Überbringerin von etwas
Billigkeit Eigenschaft, preisgünstig zu sein
abdorren gehoben: trocken werden (und dadurch absterben)
Lüsternheit gehoben: Zustand, lüstern/sexuell begehrlich zu sein
Buhle gehoben, veraltet: Geliebter
aufzehren transitiv, auch übertragen: etwas vollständig verzehren, aufbrauchen
landauf, landab adverbiell; gehoben: im ganzen Land
kundmachen transitiv, österreichische Amtssprache, sonst gehoben: öffentlich bekanntgeben
affirmativ gehoben: Zustimmung/Affirmation erkennen lassend
meinige gehoben, veraltend: Nebenform von mein
Leichnam gehoben: Körper eines Verstorbenen
rotwangig gehoben: mit roten Wangen
blicklos gehoben: mit offenen Augen, aber nichts wahrnehmend
delegitimieren gehoben: jemanden oder etwas rechtlich nicht anerkennen; die Legitimation von etwas oder jemandem aufheben
ephemerisch gehoben: nur einen Tag dauernd, nur kurze Zeit bestehend, rasch vorübergehend
fortan gehoben: ab jetzt, von jetzt an, ab sofort, in Zukunft
entbehren transitiv, gehoben: das Nichtvorhandensein einer Person beziehungsweise einer Sache als persönlichen Mangel empfindend erdulden müssen
grosso modo gehoben: insgesamt betrachtet
nottun gehoben, noch landschaftlich: nötig, vonnöten sein
Aberwitz gehoben: etwas völlig (absurd) Unsinniges; Handeln oder Verhalten, das abwegig, unvernünftig, bisweilen zudem gefährlich ist
junonisch gehoben: sehr schön (wie die Göttin Juno)
unziemlich gehoben: den sozialen Normen nicht entsprechend
anbefehlen jemandem etwas ausdrücklich befehlen, dringend anraten
zurückgelangen intransitiv, gehoben: wieder an einem bestimmten Ort/bei einem bestimmten Ziel eintreffen, erneut zu diesem kommen
guten Mutes sein gehoben: in einer fröhlichen Stimmung sein; voller Zuversicht sein
Sturmwind dichterisch, gehoben: mit großer Wucht einhergehender, sehr starker Wind; Sturm
freund gehoben veraltend: freundlich gegenüber jemandem/etwas eingestellt, Zuneigung gegenüber jemandem/etwas hegend
aufrühren durch Umrühren etwas nach oben steigen lassen
hinuntergeleiten transitiv, gehoben: (jemanden) von einem höhergelegenen Ort nach unten führen, begleiten (vom Sprecher weg)
alert gehoben: munter, aufgeweckt, rege, frisch
kontrafaktisch gehoben: gegen das Gängige und Gewohnte gerichtet
ausatmen intransitiv: die Atemluft ausstoßen
jüngsthin gehoben oder veraltet: vor Kurzem, vor nicht langer Zeit
Himmelsbote dichterisch, gehoben: zwischen Himmel und Erde verkehrendes übernatürliches Wesen als Überbringer göttlicher Botschaften
sonnenbeschienen gehoben: im Sonnenschein befindlich
dahinsterben intransitiv, gehoben: (allmählich, aber auch plötzlich) aufhören zu leben
Tondichter gehoben, Musik: Künstler, der musikalische Werke schafft
entraten veraltet, gehoben: auf etwas Verzicht üben, ohne etwas zurechtkommen
seimig veraltet, gehoben: so, dass es nur langsam und schwer fließt; von eher zäher Konsistenz
Segen bringend gehoben: Positives bewirkend, Vorteile für viele bietend
Gepflogenheit gehoben: eine nicht verbindliche Übereinkunft, die durch häufiges Wiederholen zur Konvention geworden ist
Dulderin gehoben: weibliche Person, die etwas duldet/erträgt
zurückbegeben reflexiv, gehoben: den Ort wieder aufsuchen, von dem man kommt, aufgebrochen ist
Nebelmeer gehoben, dichterisch: dicht und weit wallender Nebel
Strauß (kunstvoll gestaltete, häufig gebundene) Zusammenstellung von abgeschnittenen oder gepflückten Blumen, Halmgräsern, Zweigen oder dergleichen
Tonschöpferin gehoben, Musik: Künstlerin, die musikalische Werke schafft
weh gehoben: erfüllt, geprägt von Kummer, Leid, (seelischem) Schmerz, Weh
herzgeliebt gehoben: sehr, innig geliebt
Hort dichterisch, sonst veraltet: (vor allem aus Gold beziehungsweise goldenen Gegenständen zusammengesetzter) Schatz
Abendvisite Arztbesuch während der Abendzeit
rechten intransitiv oder mit Präpositionalobjekt; gehoben: sich (verbal) mit jemandem über oder um etwas streiten (um sein Recht zu behaupten); für sein Recht einstehen, sein Recht von jemandem fordern
anschicken reflexiv, gehoben: sich dazu bereit machen, etwas zu tun; im Begriff sein, etwas zu tun
lüstern begierig nach etwas
eingeben jemandem etwas (vor allem eine Arznei in einer bestimmten festgesetzten Menge) zum Hinunterschlucken langsam in den Mund geben, jemanden etwas vorsichtig einnehmen lassen
flocken intransitiv, Hilfsverb haben, Hilfsverb sein, gehoben: Flocken bilden; flockig werden
Abendglanz gehoben: glänzendes und strahlendes Licht während der Abendstunden, insbesondere in Verbindung mit einem schönen Abendhimmel
abkargen reflexiv, veraltet, gehoben: etwas, meist Geld, unter Verzicht auf anderes für die spätere Verwendung ansammeln
ohne Zahl gehoben: sehr viele; in solch einer Fülle, die sich nicht zählen lässt
entschlagen mit Genitiv, veraltet, selten, gehoben, reflexiv: sich innerlich frei machen von einer Sache, sich von etwas befreien, auf etwas verzichten
reparabel gehoben: so beschaffen, dass es repariert werden kann
aberhundert gehoben: mehrere hundert, noch einmal hundert
ein Kind des Todes sein gehoben: sterben müssen; (angesichts einer Todesgefahr) bald sterben werden
Sukkus Österreich, gehoben: Zusammenfassung des Wesentlichen; Ergebnis
hineilen zu einem bestimmten Ort laufen; sich schnell wegbewegen
Fürsorger veraltend: Person, die sich im Auftrag einer Fürsorgeeinrichtung beruflich um Hilfsbedürftige kümmert
verhehlen transitiv, gehoben: jemandem etwas (besonders Gefühle und Gedanken) verheimlichen, verbergen
Mildtätigkeit gehoben: Bereitschaft, sich um in Not befindliche Menschen zu kümmern oder (meist finanziell) großzügig zu unterstützen
zuweilen gehoben: zu bestimmten Zeiten, aber nicht regelmäßig; für eine Weile und später wieder für eine Weile
Walplatz gehoben oder veraltet: Platz, an dem ein kriegerischer Kampf stattfindet/stattgefunden hat
Mägdlein dichterisch, gehoben oder veraltet: junge weibliche Person
erdreisten reflexiv, gehoben: sich etwas herausnehmen, etwas Unerwünschtes tun
profund gehoben: gründlich, tiefgreifend, ganz umfassend
wehret den Anfängen gehoben: Mahnung, bereits frühzeitig gegen (für die Gesellschaft) gefährliche Entwicklungen, Strömungen vorzugehen
augenscheinlich gehoben: (auf den ersten Blick) klar erkennbar, klar ersichtlich
anmutlos gehoben veraltet: ohne jegliche Grazie, Eleganz
Applikation veraltete Bedeutung: ein Gesuch, eine Bittschrift
dem Bacchus huldigen gehoben, euphemistisch: Wein trinken
damasten attributiv, gehoben: aus Damast gemacht
segenbringend gehoben: Positives bewirkend, Vorteile für viele bietend
entfliehen intransitiv: aus einer Gefahr, aus einer Zwangslage entkommen
ein Raub der Flammen werden gehoben: durch Feuer vollständig zerstört werden, verbrennen
einstweilen gehoben: fürs Erste, bis auf Weiteres
dürsten intransitiv, gehoben: Durst haben
Mägdelein dichterisch gehoben oder veraltet: junge weibliche Person
Verstorbener gehoben: Person, die verstorben ist
gottigkeit österreichisch, gehoben, veraltet, zur Beteuerung, parenthetisch verwendet: gewissermaßen, sozusagen, gleichsam, wirklich, bei Gott, will sagen
hintanhalten gehoben, sonst besonders Österreich: (durch entsprechende Maßnahmen) bewirken, dass etwas (für eine Weile) nicht geschehen kann, von jemandem (für eine Weile) nicht getan werden kann
röten transitiv, gehoben: etwas rot färben, rot erscheinen lassen
anrühren jemand oder etwas anfassen, berühren
verdämmern intransitiv, gehoben: untätig sein, nicht teilnehmen, keine Anteilnahme, kein Mitgefühl zeigen
anwandeln transitiv, gehoben: (jemanden) unvermittelt erfassen, (jemandem) plötzlich in den Sinn kommen (eine Empfindung, Erinnerung, Idee oder Anderes)
Ungestüm gehoben: überraschende, nicht logisch erklärbare Wildheit
zagen intransitiv, gehoben: ängstlich zögern, bangen
wild Botanik, Zoologie, Gegenwort zu zahm oder veredelt: nicht gezähmt; frei in der Natur vorkommend; nicht veredelt
türein, türaus adverbiell; gehoben: durch eine Tür hinein und (durch dieselbe oder eine andere) wieder hinaus; von einem Raum, Zimmer, einer Amtsstube, Haustür (oder dergleichen) zur nächsten
erquicklich gehoben: so, dass es Freude, Gefallen bereitet, erweckt
Hymnus Keine Definition gefunden
Toilette eine sanitäre Vorrichtung oder Anlage zur Aufnahme von Körperausscheidungen
schweren Herzens gehoben: so dass es jemandem Anstrengung, Sorgen bereitet, jemanden Überwindung kostet; mit Bedenken, Zweifel; sehr ungern; ganz bekümmert; im Widerspruch zu jemandes Gefühl
einstens vor langer/längerer Zeit
immerdar gehoben, poetisch: zu jedem Zeitpunkt in der Zukunft
andachtsvoll gehoben: in Andacht versunken, am Gebet/Gottesdienst/einem spirituellen Inhalt innerlich beteiligt
temperieren die Temperatur von etwas so verändern, wie sie gewünscht wird/wie sie als angenehm/passend empfunden wird
unerfindlich gehoben: sich nicht erklären lassend, rätselhaft erscheinend
in Rauch und Flammen aufgehen gehoben: durch Feuer vollständig zerstört werden, verbrennen
Stachel im Fleische gehoben: immerwährende eindringliche Erinnerung an etwas
Disputant gehoben: Person, die sich an einem Disput beteiligt
Dafürhalten gehoben: Einstellung, dass etwas richtig/zutreffend ist
Küchlein gehoben, veraltet: Jungtier von Geflügel
anempfinden reflexiv, gehoben: sich in jemandes Gefühlswelt hineinversetzen und sich dessen Empfindungen zu eigen machen
unter Waffen stehen gehoben: bewaffnet (und kampfbereit) sein
schärfen transitiv: etwas scharf oder schärfer machen
au contraire gehoben: im Gegenteil
fehlschießen gehoben: einen Schuss abgeben, der das Ziel verfehlt
überliefern etwas, beispielsweise eine Geschichte, einen Brauch, an die Nachkommen weitererzählen/weitergeben
Dolmetschin besonders in Österreich: weibliche Person, die (gesprochene) Texte in eine andere Sprache übersetzt
Dunkel gehoben: lichtarmer oder völlig lichtloser Zustand, in dem sich etwas befindet oder der etwas auszeichnet
bleiern aus Blei bestehend; hergestellt aus Blei
Blutzoll gehoben: Verluste bei bewaffneten Konflikten
Silberbart gehoben: Bart mit hellem, silbrig schimmerndem Grauton
Paradiesgarten Religion: Ort, an den selige Menschen nach dem Tod kommen
Angedenken veraltet: kleiner Gegenstand, der Erinnerungen an ein bestimmtes Ereignis oder eine bestimmte Person hervorrufen soll
zerscherben gehoben: in einzelne Teile (Scherben) zerbrechen (lassen)
Begebnis gehoben oder veraltet: etwas, was geschieht/passiert/sich begibt
Ruhestatt gehoben: Platz/Stelle der Bestattung
zwischenein gehoben, zeitlich: in unregelmäßigen zeitlichen Abständen (ein Geschehen kurz unterbrechend)
Aperçu gehoben: geistreiche Bemerkung
hinwegfegen Hilfsverb sein: schnell über etwas hinwegbewegen
gelüsten unpersönlich, gehoben oder scherzhaft: großes Verlangen nach etwas haben, Lust auf etwas verspüren
blutenden Herzens gehoben: so dass es jemandem Anstrengung, Sorgen bereitet, jemanden Überwindung kostet; mit Bedenken, Zweifel; sehr ungern; ganz bekümmert; im Widerspruch zu jemandes Gefühl
Ich-Sucht gehoben, veraltend: Haltung, die darauf aus ist, sich selbst immer zuerst zu berücksichtigen
nunmehrig österreichisch, sonst gehoben: jetzt stattfindend; sich auf die unmittelbare Gegenwart beziehend
herabwerfen transitiv; gehoben: von (dort) oben nach (hier) unten werfen (in Richtung zum Sprechenden)
kundgeben transitiv, gehoben: etwas öffentlich bekannt geben/machen, jemanden etwas wissen lassen
hinzuaddieren transitiv, gehoben: (etwas) zu einer bekannten Zahl rechnerisch hinzufügen
dartun gehoben: etwas erläuternd darlegen, etwas zum Ausdruck bringen
einbekennen österreichisch, sonst gehoben: eingestehen
Sinnesverwirrung gehoben: (vorübergehende) falsche Interpretation von Sinneseindrücken (zum Beispiel durch Drogenkonsum oder Krankheit)
Spross Botanik: junger Trieb an einer Pflanze, neu wachsender Teil einer Pflanze
auslesen etwas aus einer Menge desselben heraussuchen und entfernen (weil es unbrauchbar [geworden] ist)
behagen intransitiv; gehoben, unpersönlich, mit Dativ: ein wohliges, still vergnügliches Gefühl bereiten
Brüderpaar gehoben: zwei Brüder
Schimpf gehoben, meist Singular: Verletzung der Selbstachtung
sinnenfroh gehoben: mit Sinnenfreude; sich am sinnlichen Genuss erfreuend
abschreiten transitiv, Hilfsverb haben oder Hilfsverb sein: eine Strecke zur Kontrolle, prüfend oder feierlich entlanggehen
Otterngezücht gehoben abwertend übertragen: boshafte, verachtenswerte Menschen
Sprachmittler gehoben: Person, die Text aus einer anderen Sprache übersetzt
davonstieben intransitiv, gehoben, von einer Gruppe oder Menge: rasch, fluchtartig, sich (in verschiedene Richtungen) zerstreuend, weglaufen
Bildner veraltet: jemand, der eine Person oder Sache formt, ausbildet
Disput gehoben: Gespräch oder (seltener schriftlicher) Gedankenaustausch zwischen zwei oder mehr Personen, mit dem Ziel, die jeweilige Gegenseite von den eigenen Argumenten zu überzeugen
nächtlicherweile gehoben: während es Nacht ist, in der Nacht, bei Nacht
wehklagen gehoben: seelische Not äußern
Blütenschnee dichterisch oder gehoben; übertragen: Menge weißer/weißlicher Blüten (die wie Schnee langsam niederfallen, sich wie dieser auf dem Boden ausgebreitet haben, …)
Grundgütiger gehoben, meist als Ausruf: der einzige, allmächtige, unfehlbare Herr
obschon gehoben: einräumende Subjunktion
Band dasjenige, was verbindet oder verknüpft
prostituieren gehoben, pejorativ: etwas Anrüchiges, Unpassendes, Verwerfliches begehen oder jemandem vorwerfen
schlaftrunken gehoben: noch vom Schlaf benommen; durch Müdigkeit hervorgerufenes Gefühl, als sei man betrunken
unlängst gehoben: vor nicht allzu langer Zeit
allerorten gehoben oder veraltet: an jedem Ort, überall
abschweifen gehoben: von einer Richtung abweichen, die Richtung ändern
Berufsethos gehoben: moralisches Bewusstsein, verantwortungsbewusste Einstellung einer Berufsgruppe hinsichtlich der einzuhaltenden Pflichten, Normen bei der Ausübung des Berufs
zerrinnen intransitiv, gehoben: flüssig werden und zerfließen
Wegscheide gehoben: Stelle, an der sich ein Weg gabelt
einherschreiten gehoben: vor jemandes Augen eher langsam und gemessenen Schrittes umhergehen oder vorbeigehen
traun gehoben veraltet: in der Tat, wirklich, fürwahr, wahrlich
Fels kein Plural: zusammenhängende Masse festen Gesteins (Singular 1)
hintanstehen intransitiv; gehoben: sich zugunsten von jemand/etwas anderem zurückhalten; zugunsten von jemand/etwas anderem auf jemanden/etwas verzichten; zugunsten von jemand/etwas anderem unberücksichtigt, vernachlässigt werden
kundtun gehoben, veraltend: etwas anderen mitteilen
unveräußerlich so, dass es nicht beseitigt/aberkannt werden darf/kann
ichsüchtig gehoben: übermäßig auf die eigene Person fixiert, nur zum eigenen Vorteil handelnd
benehmen reflexiv: sich verhalten, sich auf bestimmte Weise in der Umwelt zeigen
Plazet gehoben: Erklärung des Einverständnisses
durchschneiden transitiv, gehoben: einen Raum oder eine Fläche in einer geraden Linie durchqueren
verwinden gehoben: psychisch über etwas hinwegkommen
innewohnen intransitiv, gehoben, mit Dativ: fester Bestandteil sein von etwas; grundsätzlich dazugehören; typische Charakteristik von etwas sein
vulgo bildungssprachlich: im Allgemeinen/auch unter der Bezeichnung x geläufig (oft mit abwertendem, drastischem Ton)
Disputantin gehoben: weibliche Person, die sich an einem Disput beteiligt
herüberbemühen transitiv, gehoben: jemanden darum ersuchen, die Mühe auf sich zu nehmen, um aus einiger Entfernung zum Sprecher hin zu kommen
erharren transitiv, selten, gehoben: auf etwas Ersehntes warten
süffisant gehoben: selbstgefällig, genüsslich überlegene Distanz zeigend, ein Gefühl von meist geistiger Überlegenheit zur Schau tragend, spöttisch-überheblich
Maiennacht dichterisch, gehoben: Nacht im Mai
Jüngling gehoben: junger, fast erwachsener Mann
hinfallen intransitiv: (mit Wucht) zu Boden, auf die Erde fallen
mondbeschienen dichterisch, gehoben: im Mondschein befindlich
woselbst veraltend, gehoben: dort an diesem Ort, an dieser Stelle
einbegreifen gehoben: auch beinhalten, dazunehmen
jüngstens gehoben oder veraltet: vor Kurzem, vor nicht langer Zeit
wegleugnen transitiv; umgangssprachlich: (etwas, eine Sache) durch Leugnen bereinigen, endgültig beseitigen
Aufschwung allgemein, gehoben: mehr Schwung, Auftrieb, Fortschritt
Erlesenheit gehoben, ohne Plural: außerordentlich gute, exquisite Qualität
preiswürdig gehoben: lobenswert, hervorragend • Diese Bedeutungsangabe sollte ergänzt werden.
Ferne große räumliche Distanz
Zauberbann dichterisch und gehoben, auch übertragen: auf jemanden oder etwas Einfluss nehmende, unwiderstehliche Kraft eines Zaubers
dahingehen intransitiv; gehoben: jemanden, etwas gehend passieren, sich an jemanden, etwas gehend vorbeibewegen; (ohne erkennbares, festes Ziel) von einer Stelle zu einer anderen gehen
davongehen sich zu Fuß von einem bestimmten Ort/vom Sprechenden entfernen
Flucht rasches Entfliehen von einer Sache, die Gefahr oder Unglück verheißt
zwiefältig gehoben, veraltend: zweimal so oft oder so viel
untersagen transitiv, gehoben: ein Verbot aussprechen
Tonkünstler gehoben, Musik: Künstler, der musikalische Werke schafft
Selbst gehoben: die eigene Person in ihrer Gesamtheit
gotikeit österreichisch, gehoben, veraltet, zur Beteuerung, parenthetisch verwendet: gewissermaßen, sozusagen, gleichsam, wirklich, bei Gott, will sagen
Menetekel gehoben: (geheimnisvolles) warnendes Vorzeichen eines drohenden Unheils, ernstes Warnzeichen
bewegen die Lage oder Stellung von etwas im Raum verändern; etwas von einem Ort zum anderen schaffen
Placet gehoben: Erklärung des Einverständnisses
Morgentoilette gehoben, kein Plural: Körperreinigung, Körperpflege und Ankleiden am Morgen
Diskutantin gehoben: weibliche Person, die sich an einer Diskussion beteiligt
schöpfen etwas aus einer großen Menge Flüssigkeit entnehmen
zurückgeleiten transitiv, gehoben: jemanden oder etwas wieder dorthin geleiten, wo er/sie/es zuvor hergekommen ist
gesunden nach einer Krankheit wieder gesund werden
abhold gehoben: abgeneigt, ungünstig
anheben ein wenig in die Höhe heben
soupieren intransitiv, gehoben: festlich/in großem Rahmen/mit Gästen zu Abend essen
leidlich gehoben: gerade noch ausreichend, akzeptabel
Bedenken tragen gehoben: noch unentschlossen sein, zagen, zaudern; Vorbehalte (gegen etwas) haben
geifern intransitiv: Speichel aus dem Mund fließen lassen
widerfahren gehoben; intransitiv, unpersönlich, mit Dativ: (jemandem) geschehen (besonders etwas Unausweichliches, scheinbar vom Schicksal Bestimmtes)
Verblichene gehoben: weibliche Person, die gestorben ist
zupasskommen gehoben: für jemanden gerade recht kommen, jemandem äußerst gelegen kommen, jemandem sehr willkommen sein
1 meiden transitiv, gehoben: etwas dafür tun, einer Person oder Sache nicht zu begegnen
2 sturmgepeitscht gehoben: vom Sturm heftig bewegt, aufgewirbelt und/oder geschleudert werdend
3 Tonkünstlerin gehoben, Musik: Künstlerin, die musikalische Werke schafft
4 duress gehoben, meist Rechtssprache: Nötigung, Zwang
5 Wohlgestalt gehoben veraltend: schöne, ansprechende Gestalt
6 Gezweig gehoben: Gesamtheit aller Zweige (einer Hecke, eines Baumes oder Ähnlichem)
7 hinabwerfen transitiv; gehoben: von (hier) oben nach (dort) unten werfen (weg vom Sprechenden)
8 Verblichener gehoben: männliche Person, die gestorben ist
9 aufkündigen einen Vertrag/eine Vertragsbeziehung beenden
10 straßauf, straßab adverbiell; gehoben: auf/in vielen Straßen, durch mehrere Straßen
11 entströmen intransitiv, unpersönlich, gehoben: aus etwas in Strömen herauskommen
12 durchkreuzen transitiv: Wörter, Buchstaben in einem Text mit Kreuzen (×) unleserlich, ungültig machen
13 nimmermüde gehoben: nicht ermüdend, unermüdlich
14 Heermeister gehoben oder veraltet: Oberbefehlshaber (eines Teils) der Streitkräfte, eines Heeres
15 Frieden kein Plural: vertraglich gesichertes Miteinander verschiedener Staaten, das durch Abwesenheit von Gewalt (und speziell von Krieg) gekennzeichnet ist
16 späterhin gehoben: zu einem Zeitpunkt, der nach einem Ereignis liegt, auf etwas folgt, noch in der Zukunft liegt
17 Stachel im Fleisch gehoben: immerwährende eindringliche Erinnerung an etwas
18 Tondichterin gehoben, Musik: Künstlerin, die musikalische Werke schafft
19 Verstorbene gehoben: weibliche Person, die nicht mehr am Leben, sondern tot ist
20 übersonnt gehoben; auch übertragen: von der Sonne hell erleuchtet, von Sonnenstrahlen beschienen
21 engen transitiv, selten, gehoben: etwas schmäler machen
22 hungrig ein Gefühl des Hungers verspürend, Hunger habend
23 Dotation gehoben: finanzielle und/oder materielle Zuwendung
24 emporgehen nach oben gehen
25 verstummen intransitiv, von Personen: (plötzlich) aufhören, die Stimme zu gebrauchen
26 unflätig gehoben, abwertend: so, dass es nicht den üblichen Regeln des guten Benehmens oder Anstandes entspricht
27 Widerfahrnis veraltet, gehoben, sonst fachsprachlich, Psychologie: dasjenige (nicht Beeinflussbare), was (einem, jemandem) widerfährt
28 Fittich poetisch, gehoben: Körperteil, das einem Tier das Fliegen ermöglicht
29 Trottoir österreichisch gehoben, schweizerisch, süddeutsch, sonst veraltend oder landschaftlich: (durch eine Bordsteinkante von der Fahrbahn abgegrenzter, erhöhter) Weg für Fußgänger und Fußgängerinnen, der entlang einer Straße führt
30 durchpflügen transitiv: eine Fläche sehr stark und gründlich mit dem Pflug bearbeiten
31 in Flammen aufgehen gehoben: durch Feuer vollständig zerstört werden, verbrennen
32 ekel gehoben, sonst veraltend: (moralisch oder physisch) Ekel erregend
33 Bildnerin veraltet: weibliche Person, die eine Person oder Sache formt, ausbildet
34 guter Dinge in einer heiteren, vergnügten Stimmung
35 bisweilen gehoben: unterschiedlich häufig, mehr oder minder oft
36 herniederbrennen intransitiv, gehoben, Hilfsverb haben, von der Sonne: große Hitze, gleißendes Licht nach unten ausstrahlen
37 beargwöhnen gehoben: misstrauisch betrachten
38 partizipieren gehoben: an etwas teilhaben; an etwas seinen Anteil haben
39 zurücksenden transitiv; gehoben: etwas wieder an einen bestimmten Ort/zu einer bestimmten Person gelangen lassen
40 dahinwelken intransitiv; gehoben: vom blühenden in einen welken Zustand übergehen
41 anbequemen reflexiv, veraltend, sonst gehoben: sich mit seinem Verhalten, seinen Handlungen an jemandem oder etwas orientieren, sich danach richten
42 epochal gehoben: von großer Bedeutung, bedeutsam für eine Epoche
43 heimführen jemanden in dessen Zuhause bringen
44 ausharren intransitiv; gehoben: (trotz Widrigkeiten) mit Geduld abwarten, warten
45 emporwirbeln transitiv, Hilfsverb haben: kleine Teilchen (zum Beispiel Sand, Schnee) so in Bewegung versetzen, dass sie wild drehend, ungeordnet nach oben fliegen
46 pasteur gehoben: Hirt, Senner
47 Landesvater gehoben, historisch: Fürst eines Landes
48 Tonschöpfer gehoben, Musik: Künstler, der musikalische Werke schafft
49 zurückbegehren transitiv, gehoben: etwas wieder in seinem Besitz, bei sich haben wollen
50 Tonmaler gehoben, Musik: Künstler, der musikalische Werke schafft
51 erfließen österreichisch, Amtssprache, gehoben: daraus folgen, sich ergeben, entstehen
52 einmütig gehoben: hinsichtlich der Meinung übereinstimmend, völlig gleich
53 Lustbarkeit gehoben, veraltend: Veranstaltung, die jemand zur Unterhaltung, zur Vergnügung besucht; allgemein auch: etwas, was Freude bereiten soll
54 Ansinnen gehoben: oft als Zumutung empfundenes Ersuchen, Gesuch, Bitte
55 Dämmer gehoben: (Tages-)Licht, das allmählich hell beziehungsweise dunkel wird
56 hinabgehen intransitiv, gehoben: nach unten gehen
57 wohlauf gehoben: gesund, in guter Verfassung
58 hinausschauen regional: aus einem bestimmten Bereich, von einer bestimmten Stelle (hier drinnen) in einen anderen Bereich, an eine andere Stelle (dort draußen) blicken (schauen) (in Richtung vom Sprecher weg)
59 äquivalent gehoben: gleichwertig, entsprechend
60 Anverwandter gehoben: Person, die zur Verwandtschaft gehört
61 den Tod finden gehoben: infolge von etwas (Unfall, Krankheit, Ermordung oder dergleichen) aufhören zu leben
62 mit doppelter Zunge sprechen gehoben: Unwahres oder Widersprüchliches sagen; heuchlerisch reden; fuchsschwänzen
63 Verelendung gehoben: das Geraten in sehr schlechte Lebensumstände
64 obzwar gehoben, veraltend: leitet einen Konzessivsatz ein, der ein Gegenargument gegen das zuvor Gesagte enthält; obwohl
65 Bacchus huldigen gehoben, euphemistisch: Wein trinken
66 Edelmut gehoben: edle Gesinnung, die egoistische Motive hintenan stellt
67 hoffärtig gehoben abwertend: besonders hochmütig, andere Personen verletzend
68 sonach gehoben: eine Folgerung einleitend: folglich, also
69 Todespein gehoben: tödliche Qualen, Schmerzen
70 weiterreichen transitiv, gehoben: etwas, das man von jemandem erhalten hat, einem anvertraut wurde, einem anderen (händisch) geben, in dessen Verantwortung überstellen
71 Mund Anatomie: Öffnung im unteren Teil des (hauptsächlich menschlichen) Gesichtes, hauptsächlich zur Nahrungsaufnahme und zur Lautbildung genutzt
72 Notschrei gehoben, veraltend: lauter Ruf nach Hilfe aus einer Notsituation
73 nicken intransitiv: (zur Bejahung, Zustimmung, als Geste des Verstehens, als Gruß oder dergleichen) den Kopf kurz hintereinander senken und heben
74 herabkommen intransitiv, gehoben: (von dort oben) nach (hier) unten kommen (in Richtung zum Sprechenden)
75 voranschreiten vor jemand anders gehen
76 Anverwandte gehoben: weibliche Person, die zur Verwandtschaft gehört
77 betören gehoben: jemanden faszinieren, in sich verliebt machen
78 Unnatur gehoben: etwas, das der Natur/der natürlichen Veranlagung nicht gemäß ist
79 winden transitiv: etwas drehen, wickeln
80 durchtosen transitiv, gehoben, veraltend: mit heftiger, wilder Bewegung und dröhnendem Geräusch durchqueren
81 feilbieten transitiv, gehoben: etwas zum Kauf anbieten
82 nachgerade umgangssprachlich, mit Betonung auf Ende/Konsequenz eines Geschehens/Prozesses: endlich, letztlich
83 edelgeboren veraltet gehoben: von vornehmer/adliger Herkunft
84 mit gespaltener Zunge reden gehoben: Unwahres oder Widersprüchliches sagen, heuchlerisch reden, fuchsschwänzen
85 emporarbeiten sich unter Anstrengungen, Mühen von einer unten gelegenen Stelle nach oben bewegen (vom Sprecher weg)
86 anfliegen in einem Luftfahrzeug, mit Flügeln, durch die Luft näher kommen
87 auserlesen gehoben: qualitativ besonders gut/hochwertig
88 goldig selten, gehoben: die Farbe und den Glanz von Gold habend (auch durch Vergolden)
89 fernhin gehoben: in weitem Umkreis, bis in weite Ferne
90 Abgeschiedener gehoben: (kürzlich) verstorbene (männliche) Person
91 Wehe veraltet gehoben: seelischer Schmerz
92 Beilager gehoben veraltet: sexuelle Vereinigung
93 antizipieren gehoben, bildungssprachlich: etwas erkennen, bevor es eingetreten ist
94 mit gespaltener Zunge sprechen gehoben: Unwahres oder Widersprüchliches sagen; heuchlerisch reden; fuchsschwänzen
95 Trottoirfläche österreichisch gehoben, schweizerisch, süddeutsch, sonst veraltend oder landschaftlich: befestigte und meist gepflasterte Fläche am Rand einer Straße, die für Fußgänger vorgesehen ist
96 Kronjuwel kostbares Schmuckstück, das sich im Besitz des Herrscherhauses befindet
97 gürten gehoben: jemandem/sich einen Gürtel oder eine andere Art Riemen oder Band umlegen, umschnallen; etwas mit einem Gürtel oder Ähnlichem versehen
98 leichten Herzens gehoben: ohne dass es jemandem Anstrengung, Sorgen bereitet, jemanden Überwindung kostet, ohne Bedenken, Zweifel; sehr gern; völlig unbekümmert
99 ersterben aufhören
100 empordienen reflexiv, gehoben: sich durch Fleiß, beständiges Handeln/Tätigsein im Beruf oder Ähnliches nach und nach stufenweise in eine höhergestellte Position bringen
101 Augenweite gehoben oder veraltet: mit den Augen einsehbare Entfernung
102 aufblühen intransitiv: sich blühend öffnen, die Blüten (langsam) auseinanderfalten; anfangen, sich zu voller Blüte zu entfalten, allmählich in Blüte kommen
103 abdarben transitiv, gehoben, sich etwas abdarben: sich etwas (bis zum Grad des Notleidens) entziehen (um es für Anderweitiges aufzusparen)
104 tagen gehoben: Tag werden
105 anreihen transitiv: (etwas) einer bereits vorhandenen Reihe hinzufügen
106 aufbrechen auch bildlich: etwas, das verschlossen ist, mit Gewalt öffnen
107 geizen mit der Präposition „mit“: in übertriebener, unangemessener Weise sparen, geizig mit etwas sein
108 umfriedigen gehoben, selten: einrahmend, abgrenzend umgeben
109 niederstrecken transitiv; gehoben, auch übertragen: einen Menschen oder ein Tier schwer verletzen oder töten und so zu Boden bringen
110 ausgleiten intransitiv; auch übertragen: auf einer glatten Fläche aus dem Gleichgewicht kommen, keinen Halt finden (und hinfallen)
111 distinguiert gehoben: vornehm; sich durch betont gepflegtes Auftreten oder Ähnliches von anderen abhebend
112 Kunde jemand, der bei einem bestimmten Geschäft einkauft; der Käufer einer Ware; derjenige, der eine Dienstleistung in Anspruch nimmt; jeder, der für etwas zahlt (auch wenn die Leistung an einen Dritten geht)
113 Allgütiger gehoben: derjenige, der allgütig ist: Gott
114 Gemahl gehoben: der vermählte Mann
115 die Hülle und Fülle gehoben: in großer Menge, im Überfluss
116 Freveltat gehoben: einzelne Handlung, die einen Frevel gegen etwas darstellt; frevelhafte Tat
117 beschatten gehoben: jemandem/etwas einen Schatten verleihen
118 schmachbeladen gehoben: sehr erniedrigt, in seiner Würde stark herabgesetzt, in seinem Stolz sehr verletzt, Schande auf sich geladen habend
119 entschwinden intransitiv, gehoben: sich von etwas entfernen
120 allein als Adjektiv: für sich, getrennt, einsam, einzeln sein; nur; ohne Beistand, Zeugen, Anwesenheit von anderen Personen, ohne Hilfe sein
121 zurückverlangen von jemandem die Rückgabe von etwas fordern, das einem gehört
122 ergötzlich gehoben: in unterhaltsamer Art und Weise Gefallen/Vergnügen bereitend
123 erkühnen reflexiv, gehoben: so wagemutig und frech (kühn) sein, dass man etwas eigentlich Unerlaubtes/Unzulässiges versucht
124 dergestalt gehoben: derart, dermaßen
125 anfechten die Gültigkeit/Rechtmäßigkeit einer Entscheidung anzweifeln und dann dagegen vorgehen
126 über Gebühr gehoben: über das angemessene Maß hinausgehend, mehr als erforderlich
127 Hall gehoben: akustisch wahrnehmbares, unspezifisches Ereignis
128 fortrühren gehoben, reflexiv, meistens verneint: sich wegbewegen; sich vom eigenen Aufenthaltsort entfernen
129 Inbrunst gehoben: starke Hingabe an ein Gefühl oder an ein Vorhaben
130 anspeien transitiv, gehoben: jemanden oder etwas (absichtlich) mit Spucke oder Erbrochenem beschmutzen
131 Begräbnisstätte gehoben: Stelle, wo jemand begraben liegt
132 Land der Morgenröte poetisch, gehoben: Bezeichnung und Selbstbezeichnung für China
133 Beute durch Diebstahl, Raub oder Plünderung angeeignete Güter
134 Diskutant gehoben: Person, die sich an einer Diskussion beteiligt
135 bedürfen gehoben, mit Genitiv: etwas benötigen, etwas brauchen, auf etwas angewiesen sein
136 entsühnen transitiv, auch reflexiv, gehoben: jemanden, sich durch Sühne von Sünde, Schuld befreien
137 Degout gehoben: starke Abneigung gegen jemanden oder etwas
138 entzünden transitiv: in Brand setzen
139 Quisquilie gehoben, meist Plural: Kleinigkeit, Nichtigkeit, Bagatelle
140 äffen transitiv, gehoben, veraltete Bedeutung: jemanden durch Unwahrheiten irreführen, täuschen
141 hernieder gehoben: aus der Richtung von irgendwo oben nach unten, meist zum Sprecher, Erzähler oder Handelnden hin
142 so weit das Auge reicht gehoben: so weit man blicken/sehen kann; überall
143 einstmals gehoben: früher, vor langer Zeit
144 sinnesfroh gehoben: mit Sinnenfreude; sich am sinnlichen Genuss erfreuend
145 nachforschen intransitiv: sich intensiv mit dem Sammeln von Informationen über etwas beschäftigen
146 abspeisen transitiv, umgangssprachlich: jemandes Hunger stillen
147 umfrieden gehoben: einrahmend, abgrenzend umgeben
148 Wegesrand gehoben: seitliche Grenze eines Weges
149 rapide gehoben, oft bildlich: äußerst schnell
150 nachkommen intransitiv: jemandem, einer Sache nach einiger Zeit folgen; einige Zeit später hinterherkommen; auf etwas (später) als Konsequenz folgen
151 zuwider gehoben, jemandem ist etwas zuwider: Ablehnung und unangenehme Gefühle hervorrufend
152 divinatorisch gehoben: die Fähigkeit zur Voraussage von Ereignissen besitzend
153 wohlgemut gehoben: heiter gestimmt
154 Affront gehoben: öffentlicher verbaler oder schriftlicher Angriff bzw. Beschuldigung
155 zurücksehnen reflexiv: wieder an einen bestimmten Ort/in eine bestimmte Zeit/zu einer bestimmten Person zurückversetzt sein wollen
156 zuinnerst sehr tief in der Gedanken- und Gefühlswelt einer Person
157 zu Tode kommen gehoben: infolge von Unfall, Krankheit, Ermordung oder dergleichen aufhören zu leben
158 erwürgen jemanden ermorden, indem man ihm (mit den Händen) den Hals zusammendrückt, um die Luftzufuhr abzuschneiden, sodass das Blut nicht mehr zum Kopf zirkulieren kann
159 überstrahlen transitiv, gehoben: auf der gesamten Fläche bestrahlen
160 mitnichten veraltend, gehoben: auf keinen Fall, in keiner Weise
161 fortsterben intransitiv, gehoben oder veraltete Bedeutung: (allmählich, aber auch plötzlich) aufhören zu leben
162 zurückbeben intransitiv, gehoben oder veraltete Bedeutung: mit einer kurzen, heftigen Bewegung vor etwas oder jemandem zurückweichen
163 mit doppelter Zunge reden gehoben: Unwahres oder Widersprüchliches sagen, heuchlerisch reden, fuchsschwänzen
164 spartanisch keine Steigerung: das antike Sparta betreffend
165 Dolmetsch besonders in Österreich: Person, die (gesprochene) Texte in eine andere Sprache übersetzt
166 Unflat gehoben, veraltend: abstoßend wirkender Schmutz
167 ausweinen reflexiv: (bei jemandem) Erleichterung suchen durch Weinen, sich jemandem anvertrauen, jemandem von seinen Sorgen erzählen
168 vorwalten gehoben, veraltend: vorhanden sein; wirksam sein
169 seligen Angedenkens veraltet bis veraltend, sonst gehoben verhüllend: Beistellung, die die erwähnte Person als verstorben kennzeichnet
170 Reich der Mitte poetisch, gehoben, literatursprachlich: Bezeichnung und Selbstbezeichnung für China; wörtliche Übersetzung der chinesischen Schriftzeichen: zhong guo; wird insbesondere historisch und außerhalb Chinas in Poesie und Prosa verwendet
171 Strahlkraft gehoben: starke Wirkung auf andere
172 hinausgeleiten transitiv; gehoben: jemanden (von hier drinnen) nach dort draußen begleiten/führen (weg vom Sprechenden)
173 umwittern veraltet: mit schlechtem Wetter oder Unwetter umgeben
174 hoffnungsfroh gehoben: der Zukunft zuversichtlich entgegensehend
175 Zutrunk gehoben veraltet: das Erheben eines (mit Alkohol gefüllten) Glases, um einer Person die Ehre zu erweisen oder ihr Gutes zu wünschen
176 Akteur gehoben: Person, die handelt, die etwas tut
177 ungesättigt gehoben: Hunger verspürend
178 Weh gehoben: seelischer Schmerz
179 martervoll gehoben: mit großen Schmerzen, nach schmerzhaften Qualen
180 vermessen übermäßig ehrgeizig und deshalb riskant
181 heißen genannt werden, den Namen haben
182 Sinnenrausch gehoben: rauschhafter Zustand der Sinne
183 Mehrer gehoben: Person, die eine größere Menge von etwas anhäuft
184 dahinschwinden intransitiv, gehoben, unpersönlich: (allmählich) weniger werden
185 voreinst gehoben oder veraltet: vor langer/längerer Zeit
186 niederlassen veraltende Bedeutung: einen Gegenstand senken/auf den Boden legen
187 einhellig gehoben: ohne Gegenstimmen
188 bekleiden transitiv, reflexiv: mit Bekleidung versehen
189 Blattkrone gehoben: Baumkrone
190 widereinander gehoben, von Personen oder Dingen: im Widerstreit zueinander, in einem konkurrierenden Verhältnis zueinander
191 Mondesfinsternis Astronomie, Österreich, gehoben: Ereignis, bei dem der Mond durch den Kernschatten der Erde wandert
192 Gezweige gehoben: Gesamtheit aller Zweige (einer Hecke, eines Baumes oder Ähnliches)
193 Apotheose Religion, gehoben: die Erklärung eines Menschen zu einer Gottheit
194 Œuvre gehoben, bildungssprachlich: Gesamtheit aller Einzelwerke eines Künstlers
195 bejahrt gehoben: in vorgerücktem Alter
196 mondhell gehoben: durch das vom Erdmond reflektierte Licht erhellt
197 Ichsucht gehoben, veraltend: Haltung, die darauf aus ist, sich selbst immer zuerst zu berücksichtigen
198 gebrechen gehoben, unpersönlich, jemandem (Dativ) gebricht es an etwas (Dativ); persönlich nur veraltend: fehlen, mangeln
199 Furor gehoben: Wildheit, Raserei
200 Bringerin veraltend, gehoben: Überbringerin von etwas
201 Billigkeit Eigenschaft, preisgünstig zu sein
202 abdorren gehoben: trocken werden (und dadurch absterben)
203 Lüsternheit gehoben: Zustand, lüstern/sexuell begehrlich zu sein
204 Buhle gehoben, veraltet: Geliebter
205 aufzehren transitiv, auch übertragen: etwas vollständig verzehren, aufbrauchen
206 landauf, landab adverbiell; gehoben: im ganzen Land
207 kundmachen transitiv, österreichische Amtssprache, sonst gehoben: öffentlich bekanntgeben
208 affirmativ gehoben: Zustimmung/Affirmation erkennen lassend
209 meinige gehoben, veraltend: Nebenform von mein
210 Leichnam gehoben: Körper eines Verstorbenen
211 rotwangig gehoben: mit roten Wangen
212 blicklos gehoben: mit offenen Augen, aber nichts wahrnehmend
213 delegitimieren gehoben: jemanden oder etwas rechtlich nicht anerkennen; die Legitimation von etwas oder jemandem aufheben
214 ephemerisch gehoben: nur einen Tag dauernd, nur kurze Zeit bestehend, rasch vorübergehend
215 fortan gehoben: ab jetzt, von jetzt an, ab sofort, in Zukunft
216 entbehren transitiv, gehoben: das Nichtvorhandensein einer Person beziehungsweise einer Sache als persönlichen Mangel empfindend erdulden müssen
217 grosso modo gehoben: insgesamt betrachtet
218 nottun gehoben, noch landschaftlich: nötig, vonnöten sein
219 Aberwitz gehoben: etwas völlig (absurd) Unsinniges; Handeln oder Verhalten, das abwegig, unvernünftig, bisweilen zudem gefährlich ist
220 junonisch gehoben: sehr schön (wie die Göttin Juno)
221 unziemlich gehoben: den sozialen Normen nicht entsprechend
222 anbefehlen jemandem etwas ausdrücklich befehlen, dringend anraten
223 zurückgelangen intransitiv, gehoben: wieder an einem bestimmten Ort/bei einem bestimmten Ziel eintreffen, erneut zu diesem kommen
224 guten Mutes sein gehoben: in einer fröhlichen Stimmung sein; voller Zuversicht sein
225 Sturmwind dichterisch, gehoben: mit großer Wucht einhergehender, sehr starker Wind; Sturm
226 freund gehoben veraltend: freundlich gegenüber jemandem/etwas eingestellt, Zuneigung gegenüber jemandem/etwas hegend
227 aufrühren durch Umrühren etwas nach oben steigen lassen
228 hinuntergeleiten transitiv, gehoben: (jemanden) von einem höhergelegenen Ort nach unten führen, begleiten (vom Sprecher weg)
229 alert gehoben: munter, aufgeweckt, rege, frisch
230 kontrafaktisch gehoben: gegen das Gängige und Gewohnte gerichtet
231 ausatmen intransitiv: die Atemluft ausstoßen
232 jüngsthin gehoben oder veraltet: vor Kurzem, vor nicht langer Zeit
233 Himmelsbote dichterisch, gehoben: zwischen Himmel und Erde verkehrendes übernatürliches Wesen als Überbringer göttlicher Botschaften
234 sonnenbeschienen gehoben: im Sonnenschein befindlich
235 dahinsterben intransitiv, gehoben: (allmählich, aber auch plötzlich) aufhören zu leben
236 Tondichter gehoben, Musik: Künstler, der musikalische Werke schafft
237 entraten veraltet, gehoben: auf etwas Verzicht üben, ohne etwas zurechtkommen
238 seimig veraltet, gehoben: so, dass es nur langsam und schwer fließt; von eher zäher Konsistenz
239 Segen bringend gehoben: Positives bewirkend, Vorteile für viele bietend
240 Gepflogenheit gehoben: eine nicht verbindliche Übereinkunft, die durch häufiges Wiederholen zur Konvention geworden ist
241 Dulderin gehoben: weibliche Person, die etwas duldet/erträgt
242 zurückbegeben reflexiv, gehoben: den Ort wieder aufsuchen, von dem man kommt, aufgebrochen ist
243 Nebelmeer gehoben, dichterisch: dicht und weit wallender Nebel
244 Strauß (kunstvoll gestaltete, häufig gebundene) Zusammenstellung von abgeschnittenen oder gepflückten Blumen, Halmgräsern, Zweigen oder dergleichen
245 Tonschöpferin gehoben, Musik: Künstlerin, die musikalische Werke schafft
246 weh gehoben: erfüllt, geprägt von Kummer, Leid, (seelischem) Schmerz, Weh
247 herzgeliebt gehoben: sehr, innig geliebt
248 Hort dichterisch, sonst veraltet: (vor allem aus Gold beziehungsweise goldenen Gegenständen zusammengesetzter) Schatz
249 Abendvisite Arztbesuch während der Abendzeit
250 rechten intransitiv oder mit Präpositionalobjekt; gehoben: sich (verbal) mit jemandem über oder um etwas streiten (um sein Recht zu behaupten); für sein Recht einstehen, sein Recht von jemandem fordern
251 anschicken reflexiv, gehoben: sich dazu bereit machen, etwas zu tun; im Begriff sein, etwas zu tun
252 lüstern begierig nach etwas
253 eingeben jemandem etwas (vor allem eine Arznei in einer bestimmten festgesetzten Menge) zum Hinunterschlucken langsam in den Mund geben, jemanden etwas vorsichtig einnehmen lassen
254 flocken intransitiv, Hilfsverb haben, Hilfsverb sein, gehoben: Flocken bilden; flockig werden
255 Abendglanz gehoben: glänzendes und strahlendes Licht während der Abendstunden, insbesondere in Verbindung mit einem schönen Abendhimmel
256 abkargen reflexiv, veraltet, gehoben: etwas, meist Geld, unter Verzicht auf anderes für die spätere Verwendung ansammeln
257 ohne Zahl gehoben: sehr viele; in solch einer Fülle, die sich nicht zählen lässt
258 entschlagen mit Genitiv, veraltet, selten, gehoben, reflexiv: sich innerlich frei machen von einer Sache, sich von etwas befreien, auf etwas verzichten
259 reparabel gehoben: so beschaffen, dass es repariert werden kann
260 aberhundert gehoben: mehrere hundert, noch einmal hundert
261 ein Kind des Todes sein gehoben: sterben müssen; (angesichts einer Todesgefahr) bald sterben werden
262 Sukkus Österreich, gehoben: Zusammenfassung des Wesentlichen; Ergebnis
263 hineilen zu einem bestimmten Ort laufen; sich schnell wegbewegen
264 Fürsorger veraltend: Person, die sich im Auftrag einer Fürsorgeeinrichtung beruflich um Hilfsbedürftige kümmert
265 verhehlen transitiv, gehoben: jemandem etwas (besonders Gefühle und Gedanken) verheimlichen, verbergen
266 Mildtätigkeit gehoben: Bereitschaft, sich um in Not befindliche Menschen zu kümmern oder (meist finanziell) großzügig zu unterstützen
267 zuweilen gehoben: zu bestimmten Zeiten, aber nicht regelmäßig; für eine Weile und später wieder für eine Weile
268 Walplatz gehoben oder veraltet: Platz, an dem ein kriegerischer Kampf stattfindet/stattgefunden hat
269 Mägdlein dichterisch, gehoben oder veraltet: junge weibliche Person
270 erdreisten reflexiv, gehoben: sich etwas herausnehmen, etwas Unerwünschtes tun
271 profund gehoben: gründlich, tiefgreifend, ganz umfassend
272 wehret den Anfängen gehoben: Mahnung, bereits frühzeitig gegen (für die Gesellschaft) gefährliche Entwicklungen, Strömungen vorzugehen
273 augenscheinlich gehoben: (auf den ersten Blick) klar erkennbar, klar ersichtlich
274 anmutlos gehoben veraltet: ohne jegliche Grazie, Eleganz
275 Applikation veraltete Bedeutung: ein Gesuch, eine Bittschrift
276 dem Bacchus huldigen gehoben, euphemistisch: Wein trinken
277 damasten attributiv, gehoben: aus Damast gemacht
278 segenbringend gehoben: Positives bewirkend, Vorteile für viele bietend
279 entfliehen intransitiv: aus einer Gefahr, aus einer Zwangslage entkommen
280 ein Raub der Flammen werden gehoben: durch Feuer vollständig zerstört werden, verbrennen
281 einstweilen gehoben: fürs Erste, bis auf Weiteres
282 dürsten intransitiv, gehoben: Durst haben
283 Mägdelein dichterisch gehoben oder veraltet: junge weibliche Person
284 Verstorbener gehoben: Person, die verstorben ist
285 gottigkeit österreichisch, gehoben, veraltet, zur Beteuerung, parenthetisch verwendet: gewissermaßen, sozusagen, gleichsam, wirklich, bei Gott, will sagen
286 hintanhalten gehoben, sonst besonders Österreich: (durch entsprechende Maßnahmen) bewirken, dass etwas (für eine Weile) nicht geschehen kann, von jemandem (für eine Weile) nicht getan werden kann
287 röten transitiv, gehoben: etwas rot färben, rot erscheinen lassen
288 anrühren jemand oder etwas anfassen, berühren
289 verdämmern intransitiv, gehoben: untätig sein, nicht teilnehmen, keine Anteilnahme, kein Mitgefühl zeigen
290 anwandeln transitiv, gehoben: (jemanden) unvermittelt erfassen, (jemandem) plötzlich in den Sinn kommen (eine Empfindung, Erinnerung, Idee oder Anderes)
291 Ungestüm gehoben: überraschende, nicht logisch erklärbare Wildheit
292 zagen intransitiv, gehoben: ängstlich zögern, bangen
293 wild Botanik, Zoologie, Gegenwort zu zahm oder veredelt: nicht gezähmt; frei in der Natur vorkommend; nicht veredelt
294 türein, türaus adverbiell; gehoben: durch eine Tür hinein und (durch dieselbe oder eine andere) wieder hinaus; von einem Raum, Zimmer, einer Amtsstube, Haustür (oder dergleichen) zur nächsten
295 erquicklich gehoben: so, dass es Freude, Gefallen bereitet, erweckt
296 Hymnus Keine Definition gefunden
297 Toilette eine sanitäre Vorrichtung oder Anlage zur Aufnahme von Körperausscheidungen
298 schweren Herzens gehoben: so dass es jemandem Anstrengung, Sorgen bereitet, jemanden Überwindung kostet; mit Bedenken, Zweifel; sehr ungern; ganz bekümmert; im Widerspruch zu jemandes Gefühl
299 einstens vor langer/längerer Zeit
300 immerdar gehoben, poetisch: zu jedem Zeitpunkt in der Zukunft
301 andachtsvoll gehoben: in Andacht versunken, am Gebet/Gottesdienst/einem spirituellen Inhalt innerlich beteiligt
302 temperieren die Temperatur von etwas so verändern, wie sie gewünscht wird/wie sie als angenehm/passend empfunden wird
303 unerfindlich gehoben: sich nicht erklären lassend, rätselhaft erscheinend
304 in Rauch und Flammen aufgehen gehoben: durch Feuer vollständig zerstört werden, verbrennen
305 Stachel im Fleische gehoben: immerwährende eindringliche Erinnerung an etwas
306 Disputant gehoben: Person, die sich an einem Disput beteiligt
307 Dafürhalten gehoben: Einstellung, dass etwas richtig/zutreffend ist
308 Küchlein gehoben, veraltet: Jungtier von Geflügel
309 anempfinden reflexiv, gehoben: sich in jemandes Gefühlswelt hineinversetzen und sich dessen Empfindungen zu eigen machen
310 unter Waffen stehen gehoben: bewaffnet (und kampfbereit) sein
311 schärfen transitiv: etwas scharf oder schärfer machen
312 au contraire gehoben: im Gegenteil
313 fehlschießen gehoben: einen Schuss abgeben, der das Ziel verfehlt
314 überliefern etwas, beispielsweise eine Geschichte, einen Brauch, an die Nachkommen weitererzählen/weitergeben
315 Dolmetschin besonders in Österreich: weibliche Person, die (gesprochene) Texte in eine andere Sprache übersetzt
316 Dunkel gehoben: lichtarmer oder völlig lichtloser Zustand, in dem sich etwas befindet oder der etwas auszeichnet
317 bleiern aus Blei bestehend; hergestellt aus Blei
318 Blutzoll gehoben: Verluste bei bewaffneten Konflikten
319 Silberbart gehoben: Bart mit hellem, silbrig schimmerndem Grauton
320 Paradiesgarten Religion: Ort, an den selige Menschen nach dem Tod kommen
321 Angedenken veraltet: kleiner Gegenstand, der Erinnerungen an ein bestimmtes Ereignis oder eine bestimmte Person hervorrufen soll
322 zerscherben gehoben: in einzelne Teile (Scherben) zerbrechen (lassen)
323 Begebnis gehoben oder veraltet: etwas, was geschieht/passiert/sich begibt
324 Ruhestatt gehoben: Platz/Stelle der Bestattung
325 zwischenein gehoben, zeitlich: in unregelmäßigen zeitlichen Abständen (ein Geschehen kurz unterbrechend)
326 Aperçu gehoben: geistreiche Bemerkung
327 hinwegfegen Hilfsverb sein: schnell über etwas hinwegbewegen
328 gelüsten unpersönlich, gehoben oder scherzhaft: großes Verlangen nach etwas haben, Lust auf etwas verspüren
329 blutenden Herzens gehoben: so dass es jemandem Anstrengung, Sorgen bereitet, jemanden Überwindung kostet; mit Bedenken, Zweifel; sehr ungern; ganz bekümmert; im Widerspruch zu jemandes Gefühl
330 Ich-Sucht gehoben, veraltend: Haltung, die darauf aus ist, sich selbst immer zuerst zu berücksichtigen
331 nunmehrig österreichisch, sonst gehoben: jetzt stattfindend; sich auf die unmittelbare Gegenwart beziehend
332 herabwerfen transitiv; gehoben: von (dort) oben nach (hier) unten werfen (in Richtung zum Sprechenden)
333 kundgeben transitiv, gehoben: etwas öffentlich bekannt geben/machen, jemanden etwas wissen lassen
334 hinzuaddieren transitiv, gehoben: (etwas) zu einer bekannten Zahl rechnerisch hinzufügen
335 dartun gehoben: etwas erläuternd darlegen, etwas zum Ausdruck bringen
336 einbekennen österreichisch, sonst gehoben: eingestehen
337 Sinnesverwirrung gehoben: (vorübergehende) falsche Interpretation von Sinneseindrücken (zum Beispiel durch Drogenkonsum oder Krankheit)
338 Spross Botanik: junger Trieb an einer Pflanze, neu wachsender Teil einer Pflanze
339 auslesen etwas aus einer Menge desselben heraussuchen und entfernen (weil es unbrauchbar [geworden] ist)
340 behagen intransitiv; gehoben, unpersönlich, mit Dativ: ein wohliges, still vergnügliches Gefühl bereiten
341 Brüderpaar gehoben: zwei Brüder
342 Schimpf gehoben, meist Singular: Verletzung der Selbstachtung
343 sinnenfroh gehoben: mit Sinnenfreude; sich am sinnlichen Genuss erfreuend
344 abschreiten transitiv, Hilfsverb haben oder Hilfsverb sein: eine Strecke zur Kontrolle, prüfend oder feierlich entlanggehen
345 Otterngezücht gehoben abwertend übertragen: boshafte, verachtenswerte Menschen
346 Sprachmittler gehoben: Person, die Text aus einer anderen Sprache übersetzt
347 davonstieben intransitiv, gehoben, von einer Gruppe oder Menge: rasch, fluchtartig, sich (in verschiedene Richtungen) zerstreuend, weglaufen
348 Bildner veraltet: jemand, der eine Person oder Sache formt, ausbildet
349 Disput gehoben: Gespräch oder (seltener schriftlicher) Gedankenaustausch zwischen zwei oder mehr Personen, mit dem Ziel, die jeweilige Gegenseite von den eigenen Argumenten zu überzeugen
350 nächtlicherweile gehoben: während es Nacht ist, in der Nacht, bei Nacht
351 wehklagen gehoben: seelische Not äußern
352 Blütenschnee dichterisch oder gehoben; übertragen: Menge weißer/weißlicher Blüten (die wie Schnee langsam niederfallen, sich wie dieser auf dem Boden ausgebreitet haben, …)
353 Grundgütiger gehoben, meist als Ausruf: der einzige, allmächtige, unfehlbare Herr
354 obschon gehoben: einräumende Subjunktion
355 Band dasjenige, was verbindet oder verknüpft
356 prostituieren gehoben, pejorativ: etwas Anrüchiges, Unpassendes, Verwerfliches begehen oder jemandem vorwerfen
357 schlaftrunken gehoben: noch vom Schlaf benommen; durch Müdigkeit hervorgerufenes Gefühl, als sei man betrunken
358 unlängst gehoben: vor nicht allzu langer Zeit
359 allerorten gehoben oder veraltet: an jedem Ort, überall
360 abschweifen gehoben: von einer Richtung abweichen, die Richtung ändern
361 Berufsethos gehoben: moralisches Bewusstsein, verantwortungsbewusste Einstellung einer Berufsgruppe hinsichtlich der einzuhaltenden Pflichten, Normen bei der Ausübung des Berufs
362 zerrinnen intransitiv, gehoben: flüssig werden und zerfließen
363 Wegscheide gehoben: Stelle, an der sich ein Weg gabelt
364 einherschreiten gehoben: vor jemandes Augen eher langsam und gemessenen Schrittes umhergehen oder vorbeigehen
365 traun gehoben veraltet: in der Tat, wirklich, fürwahr, wahrlich
366 Fels kein Plural: zusammenhängende Masse festen Gesteins (Singular 1)
367 hintanstehen intransitiv; gehoben: sich zugunsten von jemand/etwas anderem zurückhalten; zugunsten von jemand/etwas anderem auf jemanden/etwas verzichten; zugunsten von jemand/etwas anderem unberücksichtigt, vernachlässigt werden
368 kundtun gehoben, veraltend: etwas anderen mitteilen
369 unveräußerlich so, dass es nicht beseitigt/aberkannt werden darf/kann
370 ichsüchtig gehoben: übermäßig auf die eigene Person fixiert, nur zum eigenen Vorteil handelnd
371 benehmen reflexiv: sich verhalten, sich auf bestimmte Weise in der Umwelt zeigen
372 Plazet gehoben: Erklärung des Einverständnisses
373 durchschneiden transitiv, gehoben: einen Raum oder eine Fläche in einer geraden Linie durchqueren
374 verwinden gehoben: psychisch über etwas hinwegkommen
375 innewohnen intransitiv, gehoben, mit Dativ: fester Bestandteil sein von etwas; grundsätzlich dazugehören; typische Charakteristik von etwas sein
376 vulgo bildungssprachlich: im Allgemeinen/auch unter der Bezeichnung x geläufig (oft mit abwertendem, drastischem Ton)
377 Disputantin gehoben: weibliche Person, die sich an einem Disput beteiligt
378 herüberbemühen transitiv, gehoben: jemanden darum ersuchen, die Mühe auf sich zu nehmen, um aus einiger Entfernung zum Sprecher hin zu kommen
379 erharren transitiv, selten, gehoben: auf etwas Ersehntes warten
380 süffisant gehoben: selbstgefällig, genüsslich überlegene Distanz zeigend, ein Gefühl von meist geistiger Überlegenheit zur Schau tragend, spöttisch-überheblich
381 Maiennacht dichterisch, gehoben: Nacht im Mai
382 Jüngling gehoben: junger, fast erwachsener Mann
383 hinfallen intransitiv: (mit Wucht) zu Boden, auf die Erde fallen
384 mondbeschienen dichterisch, gehoben: im Mondschein befindlich
385 woselbst veraltend, gehoben: dort an diesem Ort, an dieser Stelle
386 einbegreifen gehoben: auch beinhalten, dazunehmen
387 jüngstens gehoben oder veraltet: vor Kurzem, vor nicht langer Zeit
388 wegleugnen transitiv; umgangssprachlich: (etwas, eine Sache) durch Leugnen bereinigen, endgültig beseitigen
389 Aufschwung allgemein, gehoben: mehr Schwung, Auftrieb, Fortschritt
390 Erlesenheit gehoben, ohne Plural: außerordentlich gute, exquisite Qualität
391 preiswürdig gehoben: lobenswert, hervorragend • Diese Bedeutungsangabe sollte ergänzt werden.
392 Ferne große räumliche Distanz
393 Zauberbann dichterisch und gehoben, auch übertragen: auf jemanden oder etwas Einfluss nehmende, unwiderstehliche Kraft eines Zaubers
394 dahingehen intransitiv; gehoben: jemanden, etwas gehend passieren, sich an jemanden, etwas gehend vorbeibewegen; (ohne erkennbares, festes Ziel) von einer Stelle zu einer anderen gehen
395 davongehen sich zu Fuß von einem bestimmten Ort/vom Sprechenden entfernen
396 Flucht rasches Entfliehen von einer Sache, die Gefahr oder Unglück verheißt
397 zwiefältig gehoben, veraltend: zweimal so oft oder so viel
398 untersagen transitiv, gehoben: ein Verbot aussprechen
399 Tonkünstler gehoben, Musik: Künstler, der musikalische Werke schafft
400 Selbst gehoben: die eigene Person in ihrer Gesamtheit
401 gotikeit österreichisch, gehoben, veraltet, zur Beteuerung, parenthetisch verwendet: gewissermaßen, sozusagen, gleichsam, wirklich, bei Gott, will sagen
402 Menetekel gehoben: (geheimnisvolles) warnendes Vorzeichen eines drohenden Unheils, ernstes Warnzeichen
403 bewegen die Lage oder Stellung von etwas im Raum verändern; etwas von einem Ort zum anderen schaffen
404 Placet gehoben: Erklärung des Einverständnisses
405 Morgentoilette gehoben, kein Plural: Körperreinigung, Körperpflege und Ankleiden am Morgen
406 Diskutantin gehoben: weibliche Person, die sich an einer Diskussion beteiligt
407 schöpfen etwas aus einer großen Menge Flüssigkeit entnehmen
408 zurückgeleiten transitiv, gehoben: jemanden oder etwas wieder dorthin geleiten, wo er/sie/es zuvor hergekommen ist
409 gesunden nach einer Krankheit wieder gesund werden
410 abhold gehoben: abgeneigt, ungünstig
411 anheben ein wenig in die Höhe heben
412 soupieren intransitiv, gehoben: festlich/in großem Rahmen/mit Gästen zu Abend essen
413 leidlich gehoben: gerade noch ausreichend, akzeptabel
414 Bedenken tragen gehoben: noch unentschlossen sein, zagen, zaudern; Vorbehalte (gegen etwas) haben
415 geifern intransitiv: Speichel aus dem Mund fließen lassen
416 widerfahren gehoben; intransitiv, unpersönlich, mit Dativ: (jemandem) geschehen (besonders etwas Unausweichliches, scheinbar vom Schicksal Bestimmtes)
417 Verblichene gehoben: weibliche Person, die gestorben ist
418 zupasskommen gehoben: für jemanden gerade recht kommen, jemandem äußerst gelegen kommen, jemandem sehr willkommen sein
+1
View File
@@ -0,0 +1 @@
Wort Definition Sprache Zeitstempel
1 Wort Definition Sprache Zeitstempel
@@ -0,0 +1,101 @@
Wort Definition Sprache Zeitstempel
abbummeln eine zuvor außerhalb der regulären/vereinbarten Arbeitszeiten geleistete Arbeit (Mehrarbeit) durch Freizeit anstatt Bezahlung ausgleichen de 2025-09-29 18:26:54
abschussbereit vorbereitet sein, zu schießen oder etwas abzuschießen de 2025-09-29 18:26:27
Aalfischerei das gewerbliche, wirtschaftliche Betreiben des Aalfangs de 2025-09-29 18:27:02
abendlich zum Abend gehörend; am Abend, abends stattfindend de 2025-09-29 18:26:37
abhandenkommen verloren gehen, nicht mehr auffindbar sein de 2025-09-29 18:26:51
abfieseln mit den Zähnen, mit den Fingern oder mit einem Messer Fleisch (gründlich) von den Knochen lösen de 2025-09-29 18:26:27
A-Saite musik]]:'' Saite, die auf den Ton A oder a gestimmt ist de 2025-09-29 18:26:26
abgelegen weit weg und schlecht zugänglich de 2025-09-29 18:26:06
abhorreszieren abscheu gegen etwas oder jemanden empfinden de 2025-09-29 18:26:10
abackern etwas durch Bearbeitung der Erde absondern und somit aus dem Besitz einer anderen Person entziehen de 2025-09-29 18:26:07
abdikativ sich auf eine Abdikation beziehend oder sie bewirkend de 2025-09-29 18:27:03
ablandig geographie]], Hydrologie, Meteorologie, Klimatologie:'' hinsichtlich auf Luftströmungen und Meeresströmungen bezogen: vom Lande wegführend de 2025-09-29 18:26:43
abendfüllend den gesamten oder zumindest einen Großteil des Abends andauernd de 2025-09-29 18:26:20
abkarten eine heimliche Vereinbarung treffen, etwas heimlich zu jemandes Nachteil aushandeln de 2025-09-29 18:27:08
abdüsen sich schnell von einem Ort entfernen, meist mithilfe eines Fortbewegungsmittels de 2025-09-29 18:26:13
a-Schwa vokal, der zwischen einem [a] und einem [ə] (Schwa) liegt und im Deutschen nur unbetont vorkommt de 2025-09-29 18:26:46
abchecken überprüfen]], kontrollieren de 2025-09-29 18:27:09
abhörsicher von Räumen oder Geräten:'' vor Abhöraktionen geschützt; so beschaffen, dass es nicht abgehört werden kann de 2025-09-29 18:26:24
abhauen etwas abschlagen, abtrennen (starke und schwache Konjugation: hieb/haute ab und abgehauen/abgehaut) de 2025-09-29 18:27:07
abbestellen etwas abbestellen:'' eine Bestellung rückgängig machen, etwas kündigen de 2025-09-29 18:26:11
abkrüppeln sterben, abkratzen, verrecken de 2025-09-29 18:26:45
abhinfallen veraltet; bairisch, südalemannisch:'' vom Sprecher weg fallen de 2025-09-29 18:26:36
abbehalten (eine Kopfbedeckung) nach dem Abnehmen (vorerst) nicht erneut aufsetzen de 2025-09-29 18:26:44
abgemergelt in schlechter körperlicher Verfassung, durch Arbeit erschöpft de 2025-09-29 18:26:34
abflachen flach(er) machen de 2025-09-29 18:26:28
aboral vom Mund weg gelegen de 2025-09-29 18:26:32
abdreschen stroh ausdreschen, um das Getreide zu erhalten; das Getreide aus dem Stroh dreschen de 2025-09-29 18:26:50
abbussen süddeutsch, österreichisch, umgangssprachlich:'' über und über mit Küssen bedecken de 2025-09-29 18:26:49
abern einwände anbringen, erheben de 2025-09-29 18:27:06
aasfräßig sich von Aas ernährend de 2025-09-29 18:26:40
Aalstechen art des Aalfanges mittels Stechen nach den Aalen mit Aalspeeren de 2025-09-29 18:26:42
Aalmöwe im Ostatlantik und den nordeuropäischen Küsten lebender Barschartiger, de 2025-09-29 18:27:10
abenteuerhungrig übertragen:'' hungrig auf Abenteuer; ein Abenteuer vermissend, erwartend, sich nach einem Abenteuer sehnend de 2025-09-29 18:26:30
Aalbaum veraltet]], Botanik:'' Geißblattgewächs mit eierförmigen Blättern, gelben Blüten und roten, für den Menschen ungenießbaren Früchten de 2025-09-29 18:27:02
abendländisch das Abendland betreffend, zu ihm gehörend, charakteristisch für das Abendland de 2025-09-29 18:26:47
abfretten sich abmühen de 2025-09-29 18:27:04
abgelehnt zurückgewiesen, nicht genehmigt de 2025-09-29 18:26:26
abfohlen ein Jungtier (Fohlen) zur Welt bringen de 2025-09-29 18:26:09
abgrätschen mit gestreckten und gespreizten Beinen von einem Turngerät springen, Abgang von einem Gerät mit einer Grätsche de 2025-09-29 18:27:05
abfieren etwas am Tau herunterlassen de 2025-09-29 18:26:34
abkömmlich nicht zwingend benötigt; verzichtbar, unnotwendig, disponibel de 2025-09-29 18:27:06
abrufbar so, dass man Zugriff darauf hat; so, dass einem etwas zur Verfügung steht de 2025-09-29 18:26:32
abgekämpft erschöpft von sehr großer Anstrengung de 2025-09-29 18:26:30
abgerissen in einem schlechten Zustand befindlich, zerlumpt, mitgenommen de 2025-09-29 18:27:00
abbimsen zur Glättung/Reinigung mit einem Bimsstein abreiben de 2025-09-29 18:26:58
abberufen jemanden von seinem Amt oder seiner Funktion entheben de 2025-09-29 18:26:55
abbitten jemanden für etwas um Verzeihung bitten de 2025-09-29 18:26:29
Aakerbeere mehrjährige Pflanze mit leuchtend roten bis purpurnen Blüten und roten, essbaren Früchten, die im Norden Eurasiens und in Nordamerika vorkommt () de 2025-09-29 18:26:41
abfetzen etwas (in Fetzen) nach unten reißen de 2025-09-29 18:27:08
abalienieren entäußern, veräußern de 2025-09-29 18:26:38
abgegrenzt voneinander getrennt de 2025-09-29 18:26:57
Aalmolch familie]] der Schwanzlurche, die aalförmig gestreckt sind de 2025-09-29 18:26:52
abjustieren abgleichen]] de 2025-09-29 18:26:16
Aalsuppe suppe aus Aal de 2025-09-29 18:26:07
abbellen (zum Beispiel eine Rede) laut, aber unverständlich, mit heiserer Stimme vortragen de 2025-09-29 18:26:50
abjudizieren jemandem etwas (gerichtlich) aberkennen, absprechen de 2025-09-29 18:26:48
Aalenium geologie]]:'' geologisch unterste Stufe des Mittleren Jura Braunjura de 2025-09-29 18:26:59
Aalspeer gerät]], das einem Speer ähnelt, jedoch über mehrere Zinken verfügt und zum Aalstechen benutzt wird de 2025-09-29 18:26:54
aasfressend aas fressend, sich von toten Organismen ernährend de 2025-09-29 18:26:29
abblitzen von jemandem zurückgewiesen werden, jemandem gegenüber keine Chance zur Kommunikation haben de 2025-09-29 18:26:22
abenteuersüchtig mit einem übersteigerten Verlangen nach Abenteuern de 2025-09-29 18:26:25
aargauisch den Kanton Aargau oder dessen Bewohner betreffend, aus dem Aargau kommend de 2025-09-29 18:26:20
Aalfischer person, die Aale fängt de 2025-09-29 18:26:42
abschlägig ein Ansinnen verweigernd de 2025-09-29 18:27:01
abfechten jemandem etwas durch eine Kampfhandlung abgewinnen de 2025-09-29 18:26:18
abgasentgiftet so beschaffen, dass keine giftigen Abgase vorhanden sind de 2025-09-29 18:26:45
abdecken eine Decke entfernen de 2025-09-29 18:26:19
abfiedeln etwas mit einem stumpfen Schneidwerkzeug schneidend entfernen de 2025-09-29 18:26:12
abnorm von der Norm oder dem Üblichen (Normalen) abweichend de 2025-09-29 18:26:21
abblassen farblich]] weniger intensiv werden, blass werden de 2025-09-29 18:26:53
Aap kleines Stagsegel auf Segelschiffen de 2025-09-29 18:26:35
abgefuckt jargon]], derb, vulgär:'' in schlechtem, üblem Zustand befindlich de 2025-09-29 18:26:08
abdorren trocken werden (und dadurch absterben) de 2025-09-29 18:26:08
abgelenkt geistig]] abwesend, nicht auf eine Sache konzentriert de 2025-09-29 18:26:33
abduschen etwas oder sich durch Duschen reinigen de 2025-09-29 18:26:12
Aalrutte knochenfisch aus der Familie der Quappen de 2025-09-29 18:26:24
abfangen jemanden oder etwas auf seinem Weg aufhalten de 2025-09-29 18:27:03
abteilungsübergreifend mehr als eine Abteilung betreffend; mehrere Abteilungen umfassend de 2025-09-29 18:26:36
absichtlich die Auswirkung seiner Handlung wollend de 2025-09-29 18:26:23
abducken den Schlag eines Gegners durch Ducken ins Leere gehen lassen de 2025-09-29 18:26:39
aalähnlich wie ein Aal, an einen Aal erinnernd de 2025-09-29 18:26:48
abbaken den Fahrweg mit Baken oder eingeschlagenen Pfählen markieren de 2025-09-29 18:26:59
abfallarm so beschaffen, dass wenig Abfall entsteht de 2025-09-29 18:26:58
abhandeln mit jemandem über etwas verhandeln, sprechen, mit dem Ziel, dass man von ihm etwas bekommt de 2025-09-29 18:27:05
abbalzen die Balz beenden de 2025-09-29 18:26:33
abmarschbereit zum Abmarsch bereit de 2025-09-29 18:26:13
abkanzeln mit harschen Worten, von oben herab, kritisieren de 2025-09-29 18:26:31
abbreiten mit einem Breithammer (in bestimmtem Maße) breit schlagen de 2025-09-29 18:26:55
abbrüchig so beschaffen, dass es leicht abbricht de 2025-09-29 18:26:17
abdingbar durch andere Vereinbarungen abänderbar de 2025-09-29 18:26:57
abkommen veraltet]]:'' von (etwas, jemandem) stammen, auf (etwas, jemanden) zurückgehen de 2025-09-29 18:26:15
abborgen jemandem etwas abborgen:'' mit dem Versprechen der baldigen Rückgabe vorübergehend in seinen Besitz nehmen de 2025-09-29 18:27:02
abflusslos keinen Abfluss habend de 2025-09-29 18:26:59
abgasarm nur eine relativ kleine Menge an (schädlichen) Abgasen ausstoßend de 2025-09-29 18:26:18
abgaunern jemandem etwas durch List, Betrug, Täuschung widerrechtlich wegnehmen de 2025-09-29 18:27:05
abkindern einen Ehekredit durch das Zeugen von Kindern tilgen de 2025-09-29 18:26:39
abermalig nochmalig, wiederholt de 2025-09-29 18:26:17
Aalstreif zoologie]]:'' schwarzer Streifen längs auf dem Rücken eines Tieres, meist eines Pferdes de 2025-09-29 18:26:47
abachen sich bei etwas bis zur Erschöpfung anstrengen und ächzen de 2025-09-29 18:26:56
abasinisch auf das Volk der Abasiner/Abasinen bezogen, von ihm stammend, zu ihm gehörend de 2025-09-29 18:26:25
1 Wort Definition Sprache Zeitstempel
2 abbummeln eine zuvor außerhalb der regulären/vereinbarten Arbeitszeiten geleistete Arbeit (Mehrarbeit) durch Freizeit anstatt Bezahlung ausgleichen de 2025-09-29 18:26:54
3 abschussbereit vorbereitet sein, zu schießen oder etwas abzuschießen de 2025-09-29 18:26:27
4 Aalfischerei das gewerbliche, wirtschaftliche Betreiben des Aalfangs de 2025-09-29 18:27:02
5 abendlich zum Abend gehörend; am Abend, abends stattfindend de 2025-09-29 18:26:37
6 abhandenkommen verloren gehen, nicht mehr auffindbar sein de 2025-09-29 18:26:51
7 abfieseln mit den Zähnen, mit den Fingern oder mit einem Messer Fleisch (gründlich) von den Knochen lösen de 2025-09-29 18:26:27
8 A-Saite musik]]:'' Saite, die auf den Ton A oder a gestimmt ist de 2025-09-29 18:26:26
9 abgelegen weit weg und schlecht zugänglich de 2025-09-29 18:26:06
10 abhorreszieren abscheu gegen etwas oder jemanden empfinden de 2025-09-29 18:26:10
11 abackern etwas durch Bearbeitung der Erde absondern und somit aus dem Besitz einer anderen Person entziehen de 2025-09-29 18:26:07
12 abdikativ sich auf eine Abdikation beziehend oder sie bewirkend de 2025-09-29 18:27:03
13 ablandig geographie]], Hydrologie, Meteorologie, Klimatologie:'' hinsichtlich auf Luftströmungen und Meeresströmungen bezogen: vom Lande wegführend de 2025-09-29 18:26:43
14 abendfüllend den gesamten oder zumindest einen Großteil des Abends andauernd de 2025-09-29 18:26:20
15 abkarten eine heimliche Vereinbarung treffen, etwas heimlich zu jemandes Nachteil aushandeln de 2025-09-29 18:27:08
16 abdüsen sich schnell von einem Ort entfernen, meist mithilfe eines Fortbewegungsmittels de 2025-09-29 18:26:13
17 a-Schwa vokal, der zwischen einem [a] und einem [ə] (Schwa) liegt und im Deutschen nur unbetont vorkommt de 2025-09-29 18:26:46
18 abchecken überprüfen]], kontrollieren de 2025-09-29 18:27:09
19 abhörsicher von Räumen oder Geräten:'' vor Abhöraktionen geschützt; so beschaffen, dass es nicht abgehört werden kann de 2025-09-29 18:26:24
20 abhauen etwas abschlagen, abtrennen (starke und schwache Konjugation: hieb/haute ab und abgehauen/abgehaut) de 2025-09-29 18:27:07
21 abbestellen etwas abbestellen:'' eine Bestellung rückgängig machen, etwas kündigen de 2025-09-29 18:26:11
22 abkrüppeln sterben, abkratzen, verrecken de 2025-09-29 18:26:45
23 abhinfallen veraltet; bairisch, südalemannisch:'' vom Sprecher weg fallen de 2025-09-29 18:26:36
24 abbehalten (eine Kopfbedeckung) nach dem Abnehmen (vorerst) nicht erneut aufsetzen de 2025-09-29 18:26:44
25 abgemergelt in schlechter körperlicher Verfassung, durch Arbeit erschöpft de 2025-09-29 18:26:34
26 abflachen flach(er) machen de 2025-09-29 18:26:28
27 aboral vom Mund weg gelegen de 2025-09-29 18:26:32
28 abdreschen stroh ausdreschen, um das Getreide zu erhalten; das Getreide aus dem Stroh dreschen de 2025-09-29 18:26:50
29 abbussen süddeutsch, österreichisch, umgangssprachlich:'' über und über mit Küssen bedecken de 2025-09-29 18:26:49
30 abern einwände anbringen, erheben de 2025-09-29 18:27:06
31 aasfräßig sich von Aas ernährend de 2025-09-29 18:26:40
32 Aalstechen art des Aalfanges mittels Stechen nach den Aalen mit Aalspeeren de 2025-09-29 18:26:42
33 Aalmöwe im Ostatlantik und den nordeuropäischen Küsten lebender Barschartiger, de 2025-09-29 18:27:10
34 abenteuerhungrig übertragen:'' hungrig auf Abenteuer; ein Abenteuer vermissend, erwartend, sich nach einem Abenteuer sehnend de 2025-09-29 18:26:30
35 Aalbaum veraltet]], Botanik:'' Geißblattgewächs mit eierförmigen Blättern, gelben Blüten und roten, für den Menschen ungenießbaren Früchten de 2025-09-29 18:27:02
36 abendländisch das Abendland betreffend, zu ihm gehörend, charakteristisch für das Abendland de 2025-09-29 18:26:47
37 abfretten sich abmühen de 2025-09-29 18:27:04
38 abgelehnt zurückgewiesen, nicht genehmigt de 2025-09-29 18:26:26
39 abfohlen ein Jungtier (Fohlen) zur Welt bringen de 2025-09-29 18:26:09
40 abgrätschen mit gestreckten und gespreizten Beinen von einem Turngerät springen, Abgang von einem Gerät mit einer Grätsche de 2025-09-29 18:27:05
41 abfieren etwas am Tau herunterlassen de 2025-09-29 18:26:34
42 abkömmlich nicht zwingend benötigt; verzichtbar, unnotwendig, disponibel de 2025-09-29 18:27:06
43 abrufbar so, dass man Zugriff darauf hat; so, dass einem etwas zur Verfügung steht de 2025-09-29 18:26:32
44 abgekämpft erschöpft von sehr großer Anstrengung de 2025-09-29 18:26:30
45 abgerissen in einem schlechten Zustand befindlich, zerlumpt, mitgenommen de 2025-09-29 18:27:00
46 abbimsen zur Glättung/Reinigung mit einem Bimsstein abreiben de 2025-09-29 18:26:58
47 abberufen jemanden von seinem Amt oder seiner Funktion entheben de 2025-09-29 18:26:55
48 abbitten jemanden für etwas um Verzeihung bitten de 2025-09-29 18:26:29
49 Aakerbeere mehrjährige Pflanze mit leuchtend roten bis purpurnen Blüten und roten, essbaren Früchten, die im Norden Eurasiens und in Nordamerika vorkommt () de 2025-09-29 18:26:41
50 abfetzen etwas (in Fetzen) nach unten reißen de 2025-09-29 18:27:08
51 abalienieren entäußern, veräußern de 2025-09-29 18:26:38
52 abgegrenzt voneinander getrennt de 2025-09-29 18:26:57
53 Aalmolch familie]] der Schwanzlurche, die aalförmig gestreckt sind de 2025-09-29 18:26:52
54 abjustieren abgleichen]] de 2025-09-29 18:26:16
55 Aalsuppe suppe aus Aal de 2025-09-29 18:26:07
56 abbellen (zum Beispiel eine Rede) laut, aber unverständlich, mit heiserer Stimme vortragen de 2025-09-29 18:26:50
57 abjudizieren jemandem etwas (gerichtlich) aberkennen, absprechen de 2025-09-29 18:26:48
58 Aalenium geologie]]:'' geologisch unterste Stufe des Mittleren Jura Braunjura de 2025-09-29 18:26:59
59 Aalspeer gerät]], das einem Speer ähnelt, jedoch über mehrere Zinken verfügt und zum Aalstechen benutzt wird de 2025-09-29 18:26:54
60 aasfressend aas fressend, sich von toten Organismen ernährend de 2025-09-29 18:26:29
61 abblitzen von jemandem zurückgewiesen werden, jemandem gegenüber keine Chance zur Kommunikation haben de 2025-09-29 18:26:22
62 abenteuersüchtig mit einem übersteigerten Verlangen nach Abenteuern de 2025-09-29 18:26:25
63 aargauisch den Kanton Aargau oder dessen Bewohner betreffend, aus dem Aargau kommend de 2025-09-29 18:26:20
64 Aalfischer person, die Aale fängt de 2025-09-29 18:26:42
65 abschlägig ein Ansinnen verweigernd de 2025-09-29 18:27:01
66 abfechten jemandem etwas durch eine Kampfhandlung abgewinnen de 2025-09-29 18:26:18
67 abgasentgiftet so beschaffen, dass keine giftigen Abgase vorhanden sind de 2025-09-29 18:26:45
68 abdecken eine Decke entfernen de 2025-09-29 18:26:19
69 abfiedeln etwas mit einem stumpfen Schneidwerkzeug schneidend entfernen de 2025-09-29 18:26:12
70 abnorm von der Norm oder dem Üblichen (Normalen) abweichend de 2025-09-29 18:26:21
71 abblassen farblich]] weniger intensiv werden, blass werden de 2025-09-29 18:26:53
72 Aap kleines Stagsegel auf Segelschiffen de 2025-09-29 18:26:35
73 abgefuckt jargon]], derb, vulgär:'' in schlechtem, üblem Zustand befindlich de 2025-09-29 18:26:08
74 abdorren trocken werden (und dadurch absterben) de 2025-09-29 18:26:08
75 abgelenkt geistig]] abwesend, nicht auf eine Sache konzentriert de 2025-09-29 18:26:33
76 abduschen etwas oder sich durch Duschen reinigen de 2025-09-29 18:26:12
77 Aalrutte knochenfisch aus der Familie der Quappen de 2025-09-29 18:26:24
78 abfangen jemanden oder etwas auf seinem Weg aufhalten de 2025-09-29 18:27:03
79 abteilungsübergreifend mehr als eine Abteilung betreffend; mehrere Abteilungen umfassend de 2025-09-29 18:26:36
80 absichtlich die Auswirkung seiner Handlung wollend de 2025-09-29 18:26:23
81 abducken den Schlag eines Gegners durch Ducken ins Leere gehen lassen de 2025-09-29 18:26:39
82 aalähnlich wie ein Aal, an einen Aal erinnernd de 2025-09-29 18:26:48
83 abbaken den Fahrweg mit Baken oder eingeschlagenen Pfählen markieren de 2025-09-29 18:26:59
84 abfallarm so beschaffen, dass wenig Abfall entsteht de 2025-09-29 18:26:58
85 abhandeln mit jemandem über etwas verhandeln, sprechen, mit dem Ziel, dass man von ihm etwas bekommt de 2025-09-29 18:27:05
86 abbalzen die Balz beenden de 2025-09-29 18:26:33
87 abmarschbereit zum Abmarsch bereit de 2025-09-29 18:26:13
88 abkanzeln mit harschen Worten, von oben herab, kritisieren de 2025-09-29 18:26:31
89 abbreiten mit einem Breithammer (in bestimmtem Maße) breit schlagen de 2025-09-29 18:26:55
90 abbrüchig so beschaffen, dass es leicht abbricht de 2025-09-29 18:26:17
91 abdingbar durch andere Vereinbarungen abänderbar de 2025-09-29 18:26:57
92 abkommen veraltet]]:'' von (etwas, jemandem) stammen, auf (etwas, jemanden) zurückgehen de 2025-09-29 18:26:15
93 abborgen jemandem etwas abborgen:'' mit dem Versprechen der baldigen Rückgabe vorübergehend in seinen Besitz nehmen de 2025-09-29 18:27:02
94 abflusslos keinen Abfluss habend de 2025-09-29 18:26:59
95 abgasarm nur eine relativ kleine Menge an (schädlichen) Abgasen ausstoßend de 2025-09-29 18:26:18
96 abgaunern jemandem etwas durch List, Betrug, Täuschung widerrechtlich wegnehmen de 2025-09-29 18:27:05
97 abkindern einen Ehekredit durch das Zeugen von Kindern tilgen de 2025-09-29 18:26:39
98 abermalig nochmalig, wiederholt de 2025-09-29 18:26:17
99 Aalstreif zoologie]]:'' schwarzer Streifen längs auf dem Rücken eines Tieres, meist eines Pferdes de 2025-09-29 18:26:47
100 abachen sich bei etwas bis zur Erschöpfung anstrengen und ächzen de 2025-09-29 18:26:56
101 abasinisch auf das Volk der Abasiner/Abasinen bezogen, von ihm stammend, zu ihm gehörend de 2025-09-29 18:26:25
@@ -0,0 +1,489 @@
#!/usr/bin/env php
<?php
class WiktionaryClient {
private $lang;
private $api_url;
private $debug;
private $is_cli;
private $category_cache = [];
public function __construct($lang = 'en', $debug = false, $is_cli = true) {
$this->lang = $lang;
$this->api_url = "https://{$lang}.wiktionary.org/w/api.php";
$this->debug = $debug;
$this->is_cli = $is_cli;
}
private function debug_print($msg) {
if ($this->debug) {
echo "DEBUG: $msg\n";
}
}
private function progress_print($msg) {
if ($this->is_cli) {
echo "\r$msg";
} else {
echo "$msg<br>";
flush();
}
}
public function get_category_members($category) {
if (isset($this->category_cache[$category])) {
$this->debug_print("Cache-Hit für Kategorie '$category': " . count($this->category_cache[$category]) . " Member");
return $this->category_cache[$category];
}
$members = [];
$params = [
'action' => 'query',
'list' => 'categorymembers',
'cmtitle' => "Category:$category",
'cmtype' => 'page',
'cmlimit' => '500',
'format' => 'json'
];
$this->debug_print("Starte Abruf für Kategorie '$category' mit URL: {$this->api_url} und Params: " . http_build_query($params));
$cmcontinue = '';
$page = 1;
$total_members = 0;
while (true) {
$query = http_build_query($params) . ($cmcontinue ? "&cmcontinue=$cmcontinue" : '');
$ch = curl_init("{$this->api_url}?$query");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERAGENT, 'GrokScript/1.0');
$response = curl_exec($ch);
$curl_error = curl_error($ch);
$status_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($curl_error) {
echo "Fehler beim API-Aufruf für $category: $curl_error\n";
$this->debug_print("Curl Error Details: $curl_error");
break;
}
$this->debug_print("Response Status: $status_code, Content-Length: " . strlen($response));
if ($status_code != 200) {
echo "Fehler beim Abrufen von $category: $status_code\n";
$this->debug_print("Response Text: " . substr($response, 0, 500) . "...");
break;
}
$data = json_decode($response, true);
$this->debug_print("Data Keys: " . implode(', ', array_keys($data)));
$new_members = [];
foreach ($data['query']['categorymembers'] ?? [] as $member) {
$new_members[] = $member['title'];
}
$members = array_merge($members, $new_members);
$total_members += count($new_members);
$this->progress_print("Kategorie '$category' Paging: Seite $page abgerufen (" . count($new_members) . " neue, total $total_members)");
if (isset($data['continue']['cmcontinue'])) {
$cmcontinue = $data['continue']['cmcontinue'];
$this->debug_print("Paginierung fortsetzen mit cmcontinue: $cmcontinue");
$page++;
} else {
if ($this->is_cli) echo "\n"; else echo "<br>";
break;
}
sleep(0.5); // Pause
}
$this->category_cache[$category] = $members;
$this->debug_print("Abgerufene Member für '$category' (cached): " . count($members));
return $members;
}
public function extract_definition_and_variant($extract, $preferred_variant = null) {
$lines = explode("\n", $extract);
$definition = 'Keine Definition gefunden';
$variant = '';
$in_bedeutungen = false;
foreach ($lines as $line) {
if (strpos($line, 'Bedeutungen:') !== false) {
$in_bedeutungen = true;
continue;
}
if ($in_bedeutungen) {
if (strpos($line, '[1] ') === 0) {
$definition = trim(substr($line, 4));
break;
} elseif (strpos($line, '[1]') === 0) {
$definition = trim(substr($line, strpos($line, '[1]') + 3));
break;
} elseif (preg_match('/^\[\d+\]/', $line)) {
break;
}
}
}
// Alternative Schreibweise suchen
foreach ($lines as $line) {
if (strpos($line, 'Alternative Schreibweisen:') !== false || strpos($line, 'Nebenformen:') !== false) {
// Split nach Labels wie "Schweizerische Schreibweise:"
$parts = preg_split('/\w+ische Schreibweise:/i', $line);
foreach ($parts as $index => $part) {
if ($index > 0) { // Nach dem ersten Label
preg_match_all('/[\w\-]+/', $part, $matches);
if (!empty($matches[0])) {
if ($preferred_variant && stripos($line, $preferred_variant) !== false) {
$variant = $matches[0][0];
break 2; // Beende äußere Schleife
} elseif (!$preferred_variant) {
$variant = $matches[0][0];
break 2;
}
}
}
}
}
}
return [$definition, $variant];
}
public function get_definitions_batch($words) {
$definitions = [];
$variants = [];
foreach ($words as $idx => $word) {
$params = [
'action' => 'query',
'prop' => 'extracts',
'titles' => $word,
'explaintext' => '1',
'exsectionformat' => 'plain',
'format' => 'json'
];
$query = http_build_query($params);
$this->debug_print("Starte Definitionsabruf für Wort " . ($idx + 1) . "/" . count($words) . ": '$word' mit URL: {$this->api_url} und Params: $query");
$ch = curl_init("{$this->api_url}?$query");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERAGENT, 'GrokScript/1.0');
$response = curl_exec($ch);
$curl_error = curl_error($ch);
$status_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($curl_error) {
echo "Fehler beim Definitionsabruf für '$word': $curl_error\n";
$this->debug_print("Curl Error Details: $curl_error");
$definitions[$word] = 'Keine Definition gefunden';
$variants[$word] = '';
continue;
}
$this->debug_print("Response Status: $status_code, Content-Length: " . strlen($response));
if ($status_code != 200) {
echo "Fehler beim Abrufen von Definition für '$word': $status_code\n";
$this->debug_print("Response Text: " . substr($response, 0, 500) . "...");
$definitions[$word] = 'Keine Definition gefunden';
$variants[$word] = '';
continue;
}
$data = json_decode($response, true);
$this->debug_print("Data Keys: " . implode(', ', array_keys($data)));
$page = reset($data['query']['pages'] ?? []) ?? [];
$extract = $page['extract'] ?? '';
list($defn, $var) = $this->extract_definition_and_variant($extract, $this->preferred_variant);
$definitions[$word] = $defn;
$variants[$word] = $var;
$this->progress_print("Definitionen abrufen: " . ($idx + 1) . "/" . count($words) . " abgeschlossen ($word)");
sleep(0.5); // Pause pro Request zur Rate-Limiting-Vermeidung
}
if ($this->is_cli) echo "\n"; else echo "<br>";
$this->debug_print("Abgerufene Definitionen: " . count($definitions));
return [$definitions, $variants];
}
public function list_all_categories($filter_str = null) {
$categories = [];
$params = [
'action' => 'query',
'list' => 'allcategories',
'aclimit' => '500',
'format' => 'json'
];
$this->debug_print("Starte Abruf aller Kategorien mit URL: {$this->api_url} und Params: " . http_build_query($params));
$accontinue = '';
$page = 1;
$total_categories = 0;
while (true) {
$query = http_build_query($params) . ($accontinue ? "&accontinue=$accontinue" : '');
$ch = curl_init("{$this->api_url}?$query");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERAGENT, 'GrokScript/1.0');
$response = curl_exec($ch);
$curl_error = curl_error($ch);
$status_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($curl_error) {
echo "Fehler beim API-Aufruf für Kategorien: $curl_error\n";
$this->debug_print("Curl Error Details: $curl_error");
break;
}
$this->debug_print("Response Status: $status_code, Content-Length: " . strlen($response));
if ($status_code != 200) {
echo "Fehler beim Abrufen der Kategorien: $status_code\n";
$this->debug_print("Response Text: " . substr($response, 0, 500) . "...");
break;
}
$data = json_decode($response, true);
$this->debug_print("Data Keys: " . implode(', ', array_keys($data)));
$new_categories = [];
foreach ($data['query']['allcategories'] ?? [] as $cat) {
$new_categories[] = $cat['*'];
}
$categories = array_merge($categories, $new_categories);
$total_categories += count($new_categories);
$this->progress_print("Kategorien Paging: Seite $page abgerufen (" . count($new_categories) . " neue, total $total_categories)");
if (isset($data['continue']['accontinue'])) {
$accontinue = $data['continue']['accontinue'];
$this->debug_print("Paginierung fortsetzen mit accontinue: $accontinue");
$page++;
} else {
if ($this->is_cli) echo "\n"; else echo "<br>";
break;
}
sleep(0.5); // Pause
}
if ($filter_str) {
$filter_lower = strtolower($filter_str);
$categories = array_filter($categories, function($cat) use ($filter_lower) {
return strpos(strtolower($cat), $filter_lower) !== false;
});
$this->debug_print("Gefiltert nach '$filter_str': " . count($categories) . " Kategorien gefunden");
}
echo "Vorhandene Kategorien" . ($filter_str ? " (gefiltert nach '$filter_str')" : "") . ":\n";
sort($categories);
foreach ($categories as $cat) {
echo "$cat\n";
}
$this->debug_print("Gesamte Kategorien: " . count($categories));
return $categories;
}
public function parse_include_logic($include_str) {
if (empty($include_str)) {
return [];
}
$groups = explode(',', $include_str);
$parsed_groups = [];
foreach ($groups as $group) {
$cats = array_unique(explode('+', $group)); // Unique in Gruppe
$parsed_groups[] = array_filter(array_map('trim', $cats));
}
return array_filter($parsed_groups);
}
public function get_words_from_logic($include_groups) {
$all_words = [];
foreach ($include_groups as $group) {
if (empty($group)) continue;
$group_members = [];
foreach ($group as $cat) {
$group_members[] = $this->get_category_members($cat);
}
if (!empty($group_members)) {
$intersection = call_user_func_array('array_intersect', $group_members);
$all_words = array_merge($all_words, $intersection);
}
}
return array_unique($all_words);
}
public function generate_wordlist($include, $exclude, $num_words, $output_file, $preferred_variant = null, $headers_flag = false, $format = 'csv') {
$this->preferred_variant = $preferred_variant;
$include_groups = $this->parse_include_logic($include);
if (empty($include_groups)) {
echo "Fehler: --include muss angegeben werden, da 'alle' nicht unterstützt wird.\n";
return;
}
$all_words = $this->get_words_from_logic($include_groups);
$this->debug_print("Include-Wörter nach Logik: " . count($all_words));
$exclude_cats = array_unique(array_map('trim', explode(',', $exclude)));
$exclude_words = [];
foreach ($exclude_cats as $cat) {
if (empty($cat)) continue;
echo "Abrufen von Kategorie (exclude): $cat\n";
$members = $this->get_category_members($cat);
$exclude_words = array_merge($exclude_words, $members);
}
$this->debug_print("Exclude-Wörter: " . count($exclude_words));
$all_words = array_diff($all_words, $exclude_words);
$this->debug_print("Finale Wörter nach Exclude: " . count($all_words));
if (empty($all_words)) {
echo "Keine Wörter gefunden.\n";
return;
}
shuffle($all_words); // Hohe Zufälligkeit
$selected_words = array_slice($all_words, 0, $num_words);
$this->debug_print("Ausgewählte Wörter: " . count($selected_words));
echo "Abrufen von Definitionen für " . count($selected_words) . " Wörter...\n";
list($definitions, $variants) = $this->get_definitions_batch($selected_words);
if ($format === 'html') {
$output = "<!DOCTYPE html>\n<html lang='en'>\n<head>\n<meta charset='UTF-8'>\n";
$output .= "<title>Wiktionary Wortliste</title>\n";
$output .= "<link href='https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css' rel='stylesheet'>\n";
$output .= "</head>\n<body class='bg-gray-100 p-4'>\n";
$output .= "<div class='container mx-auto'>\n";
$output .= "<h1 class='text-2xl font-bold mb-4'>Wiktionary Wortliste ({$this->lang})</h1>\n";
$output .= "<table class='min-w-full bg-white border border-gray-300'>\n";
if ($headers_flag) {
$output .= "<thead><tr class='bg-gray-200'><th class='py-2 px-4 border'>Begriff</th><th class='py-2 px-4 border'>Erste Bedeutung</th></tr></thead>\n";
}
$output .= "<tbody>\n";
foreach ($selected_words as $word) {
$var = $variants[$word] ?? '';
$begriff = $var ? htmlspecialchars($var . ', ' . $word) : htmlspecialchars($word);
$defn = htmlspecialchars($definitions[$word] ?? 'Keine Definition gefunden');
$output .= "<tr><td class='py-2 px-4 border'>$begriff</td><td class='py-2 px-4 border'>$defn</td></tr>\n";
}
$output .= "</tbody>\n</table>\n</div>\n</body>\n</html>";
if ($this->is_cli) {
file_put_contents($output_file, $output);
echo "HTML-Tabelle gespeichert in $output_file\n";
} else {
header('Content-Type: text/html; charset=UTF-8');
echo $output;
}
} else {
$file = fopen($output_file, 'w');
if ($headers_flag) {
fputcsv($file, ['Begriff', 'Erste Bedeutung'], "\t");
}
foreach ($selected_words as $word) {
$var = $variants[$word] ?? '';
$begriff = $var ? ($var . ', ' . $word) : $word;
$defn = $definitions[$word] ?? 'Keine Definition gefunden';
fputcsv($file, [$begriff, $defn], "\t");
}
fclose($file);
echo "Tabelle gespeichert in $output_file\n";
}
}
}
function print_help() {
$help_text = <<<EOT
Wiktionary Wortliste Generator
Dieses Skript generiert eine Wortliste mit Begriffen und deren ersten Bedeutungen aus einer Wiktionary-Instanz
oder listet verfügbare Kategorien auf.
Verfügbare Argumente:
--lang, -l <Sprachcode> Sprachcode für die Wiktionary-Instanz (z.B. 'de' für de.wiktionary.org).
Default: 'en'. Erforderlich.
--include, -i <Logik> Logik für einzubeziehende Kategorien (z.B. 'A+B,C' für (A UND B) ODER C).
Erforderlich für Wortlisten (keine Includes führt zu Fehler), ignoriert bei --list-categories.
--exclude, -e <Kategorien> Komma-separierte Liste auszuschließender Kategorien (OR-Logik).
Optional, ignoriert bei --list-categories.
--preferred-variant <Name> Bevorzugte Schreibweise (z.B. 'Schweizerische' – extrahiert aus Excerpt und als erstes ausgegeben).
Optional.
--filter, -f <Text> Textfilter für Kategorien (Substring-Match, case-insensitive).
Optional, nur bei --list-categories relevant.
--format, -o <Format> Ausgabeformat: 'csv' (Standard) oder 'html' (Tabelle mit Tailwind CSS).
Optional.
--debug, -d Aktiviert ausführliche Debug-Ausgaben für API-Aufrufe und Prozesse.
Optional.
--headers, -r Aktiviert die Ausgabe der Spaltenüberschrift in der Ausgabe (default: deaktiviert).
Optional, nur für Wortlisten relevant.
--list-categories, -c Listet alle Kategorien der Wiktionary-Instanz (optional mit --filter).
Wenn gesetzt, werden andere Parameter (außer --lang, --filter, --debug) ignoriert.
--help, -h Zeigt diese Hilfe an und beendet das Skript.
num_words Anzahl der Wörter für die Wortliste (Integer).
Erforderlich für Wortlisten, ignoriert bei --list-categories.
output_file Ausgabedatei für die Wortliste (CSV oder HTML).
Erforderlich für Wortlisten, ignoriert bei --list-categories. Default: 'output.csv'.
Argumentkombinationen:
1. Wortliste generieren:
- Erforderlich: --lang, --include, num_words, output_file
- Optional: --exclude, --preferred-variant, --format, --debug, --headers
- Beispiel: php script.php --lang de --include "Substantiv (Deutsch)+Deutsch,Verb (Deutsch)" --exclude "Veraltete Substantive (Deutsch)" --preferred-variant "Schweizerische" --format html 10000 output.html --debug --headers
2. Kategorien auflisten:
- Erforderlich: --lang, --list-categories
- Optional: --filter, --debug
- Beispiel: php script.php --lang de --list-categories --filter "Deutsch" --debug
Hinweise:
- Include-Logik: + für UND, , für ODER. Keine Includes führt zu Fehler (da 'alle' nicht unterstützt).
- Duplikate in Kategorien werden automatisch entfernt und gecached, um redundante Abfragen zu vermeiden.
- Die API-Aufrufe enthalten Pausen (0.5s), um Rate-Limits zu respektieren.
- Große Kategorien oder Wortlisten (>10.000) können lange dauern.
- Stelle sicher, dass die Kategorien korrekt benannt sind (z.B. 'Substantiv (Deutsch)' für de.wiktionary.org).
EOT;
echo $help_text . "\n";
}
$is_cli = php_sapi_name() == 'cli';
if ($is_cli) {
$shortopts = "l:i:e:preferred-variant:f:o:d:c:r h";
$longopts = ["lang:", "include:", "exclude:", "preferred-variant:", "filter:", "format:", "debug", "list-categories", "headers", "help"];
$options = getopt($shortopts, $longopts);
// Positionale Argumente für CLI
$non_opt_args = array_filter($argv, function($arg) { return $arg[0] !== '-'; });
$non_opt_args = array_values(array_slice($non_opt_args, 1)); // Entferne Script-Namen
$options['num_words'] = $non_opt_args[0] ?? null;
$options['output_file'] = $non_opt_args[1] ?? null;
if (isset($options['help']) || isset($options['h'])) {
print_help();
exit;
}
$lang = $options['lang'] ?? $options['l'] ?? 'en';
$include = $options['include'] ?? $options['i'] ?? '';
$exclude = $options['exclude'] ?? $options['e'] ?? '';
$preferred_variant = $options['preferred-variant'] ?? null;
$filter = $options['filter'] ?? $options['f'] ?? null;
$format = $options['format'] ?? $options['o'] ?? 'csv';
$debug = isset($options['debug']) || isset($options['d']);
$headers_flag = isset($options['headers']) || isset($options['r']);
$list_categories = isset($options['list-categories']) || isset($options['c']);
$num_words = (int)($options['num_words'] ?? 0);
$output_file = $options['output_file'] ?? 'output.csv';
$client = new WiktionaryClient($lang, $debug, $is_cli);
if ($list_categories) {
$client->list_all_categories($filter);
} else {
$client->generate_wordlist($include, $exclude, $num_words, $output_file, $preferred_variant, $headers_flag, $format);
}
} else {
// Webserver-Kontext: Parameter aus $_GET
$options = $_GET;
$lang = $options['lang'] ?? 'en';
$include = $options['include'] ?? '';
$exclude = $options['exclude'] ?? '';
$preferred_variant = $options['preferred-variant'] ?? null;
$filter = $options['filter'] ?? null;
$format = $options['format'] ?? 'csv';
$debug = isset($options['debug']);
$headers_flag = isset($options['headers']);
$list_categories = isset($options['list-categories']);
$num_words = (int)($options['num_words'] ?? 0);
$output_file = $options['output_file'] ?? 'output.csv';
if (isset($options['help'])) {
header('Content-Type: text/plain; charset=UTF-8');
print_help();
exit;
}
$client = new WiktionaryClient($lang, $debug, $is_cli);
if ($list_categories) {
header('Content-Type: text/plain; charset=UTF-8');
$client->list_all_categories($filter);
} else {
$client->generate_wordlist($include, $exclude, $num_words, $output_file, $preferred_variant, $headers_flag, $format);
}
}
?>
@@ -0,0 +1,325 @@
#!/usr/bin/env python3
import requests
import random
import time
import re
import argparse
import sys
from functools import reduce
def debug_print(msg, debug=False):
if debug:
print(f"DEBUG: {msg}")
def print_help():
help_text = """
Wiktionary Wortliste Generator
Dieses Skript generiert eine Wortliste mit Begriffen und deren ersten Bedeutungen aus einer Wiktionary-Instanz
oder listet verfügbare Kategorien auf.
Verfügbare Argumente:
--lang, -l <Sprachcode> Sprachcode für die Wiktionary-Instanz (z.B. 'de' für de.wiktionary.org).
Default: 'en'. Erforderlich.
--include, -i <Logik> Logik für einzubeziehende Kategorien (z.B. 'A+B,C' für (A UND B) ODER C).
Erforderlich für Wortlisten (keine Includes führt zu Fehler), ignoriert bei --list-categories.
--exclude, -e <Kategorien> Komma-separierte Liste auszuschließender Kategorien (OR-Logik).
Optional, ignoriert bei --list-categories.
--preferred-variant <Name> Bevorzugte Schreibweise (z.B. 'Schweizerische' – extrahiert aus Excerpt und als erstes ausgegeben).
Optional.
--filter, -f <Text> Textfilter für Kategorien (Substring-Match, case-insensitive).
Optional, nur bei --list-categories relevant.
--debug, -d Aktiviert ausführliche Debug-Ausgaben für API-Aufrufe und Prozesse.
Optional.
--headers Aktiviert die Ausgabe der Spaltenüberschrift in der CSV-Datei (default: deaktiviert).
Optional, nur für Wortlisten relevant.
--list-categories, -c Listet alle Kategorien der Wiktionary-Instanz (optional mit --filter).
Wenn gesetzt, werden andere Parameter (außer --lang, --filter, --debug) ignoriert.
num_words Anzahl der Wörter für die Wortliste (Integer).
Erforderlich für Wortlisten, ignoriert bei --list-categories.
output_file Ausgabedatei für die Wortliste (CSV-Format).
Erforderlich für Wortlisten, ignoriert bei --list-categories. Default: 'output.csv'.
Argumentkombinationen:
1. Wortliste generieren:
- Erforderlich: --lang, --include, num_words, output_file
- Optional: --exclude, --preferred-variant, --debug, --headers
- Beispiel: python script.py --lang de --include "Substantiv (Deutsch)+Deutsch,Verb (Deutsch)" --exclude "Veraltete Substantive (Deutsch)" --preferred-variant "Schweizerische" 10000 output.csv --debug --headers
2. Kategorien auflisten:
- Erforderlich: --lang, --list-categories
- Optional: --filter, --debug
- Beispiel: python script.py --lang de --list-categories --filter "Deutsch" --debug
Hinweise:
- Include-Logik: + für UND, , für ODER. Keine Includes führt zu Fehler (da 'alle' nicht unterstützt).
- Duplikate in Kategorien werden automatisch entfernt und gecached, um redundante Abfragen zu vermeiden.
- Die API-Aufrufe enthalten Pausen (0.5s), um Rate-Limits zu respektieren.
- Große Kategorien oder Wortlisten (>10.000) können lange dauern.
- Stelle sicher, dass die Kategorien korrekt benannt sind (z.B. 'Substantiv (Deutsch)' für de.wiktionary.org).
"""
print(help_text)
def get_category_members(api_url, category, debug=False, category_cache={}):
if category in category_cache:
debug_print(f"Cache-Hit für Kategorie '{category}': {len(category_cache[category])} Member", debug)
return category_cache[category]
members = set()
params = {
'action': 'query',
'list': 'categorymembers',
'cmtitle': f'Category:{category}',
'cmtype': 'page',
'cmlimit': '500',
'format': 'json'
}
headers = {'User-Agent': 'GrokScript/1.0'}
debug_print(f"Starte Abruf für Kategorie '{category}' mit URL: {api_url} und Params: {params}", debug)
page = 1
total_members = 0
while True:
try:
response = requests.get(api_url, params=params, headers=headers)
debug_print(f"Response Status: {response.status_code}, Content-Length: {len(response.text)}", debug)
if response.status_code != 200:
print(f"Fehler beim Abrufen von {category}: {response.status_code}")
debug_print(f"Response Text: {response.text[:500]}...", debug)
break
data = response.json()
debug_print(f"Data Keys: {list(data.keys())}", debug)
new_members = [member['title'] for member in data.get('query', {}).get('categorymembers', [])]
members.update(new_members)
total_members += len(new_members)
print(f"\rKategorie '{category}' Paging: Seite {page} abgerufen ({len(new_members)} neue, total {total_members})", end='')
if 'continue' in data:
params['cmcontinue'] = data['continue']['cmcontinue']
debug_print(f"Paginierung fortsetzen mit cmcontinue: {params['cmcontinue']}", debug)
page += 1
else:
print() # Neue Zeile nach Abschluss
break
time.sleep(0.5) # Pause zur Rate-Limiting-Vermeidung
except Exception as e:
print(f"Fehler beim API-Aufruf für {category}: {str(e)}")
debug_print(f"Exception Details: {type(e).__name__} - {str(e)}", debug)
break
category_cache[category] = members
debug_print(f"Abgerufene Member für '{category}' (cached): {len(members)}", debug)
return members
def parse_include_logic(include_str):
if not include_str:
return []
groups = include_str.split(',')
parsed_groups = []
for group in groups:
cats = set(group.split('+')) # Set für unique in Gruppe
parsed_groups.append(list(cats))
return parsed_groups
def get_words_from_logic(api_url, include_groups, debug=False):
all_words = set()
for group in include_groups:
if not group:
continue
group_members = [get_category_members(api_url, cat, debug) for cat in group]
if group_members:
intersection = reduce(set.intersection, group_members)
all_words.update(intersection)
return all_words
def extract_definition_and_variant(extract, preferred_variant=None):
lines = extract.split('\n')
definition = 'Keine Definition gefunden'
variant = ''
in_bedeutungen = False
for line in lines:
if 'Bedeutungen:' in line:
in_bedeutungen = True
continue
if in_bedeutungen:
if line.startswith('[1] '):
definition = line[4:].strip()
break
if line.startswith('[1]'):
definition = line.split('[1]', 1)[1].strip()
break
# Alternative Schreibweise suchen
for line in lines:
if 'Alternative Schreibweisen:' in line or 'Nebenformen:' in line:
# Extrahiere Wörter nach Labels
parts = re.split(r'\w+ische Schreibweise:', line) # Split nach Labels wie "Schweizerische Schreibweise:"
for part in parts[1:]: # Ignoriere vor dem ersten Label
words = re.findall(r'[\w\-]+', part) # Extrahiere Wörter
if words:
if preferred_variant and preferred_variant.lower() in line.lower():
variant = words[0]
break
elif not preferred_variant:
variant = words[0]
break
if variant:
break
return definition, variant
def get_definitions_batch(api_url, words, debug=False):
definitions = {}
variants = {}
headers = {'User-Agent': 'GrokScript/1.0'}
for idx, word in enumerate(words):
params = {
'action': 'query',
'prop': 'extracts',
'titles': word,
'explaintext': '1',
'exsectionformat': 'plain',
'format': 'json'
}
debug_print(f"Starte Definitionsabruf für Wort {idx+1}/{len(words)}: '{word}' mit URL: {api_url} und Params: {params}", debug)
try:
response = requests.get(api_url, params=params, headers=headers)
debug_print(f"Response Status: {response.status_code}, Content-Length: {len(response.text)}", debug)
if response.status_code != 200:
print(f"Fehler beim Abrufen von Definition für '{word}': {response.status_code}")
debug_print(f"Response Text: {response.text[:500]}...", debug)
definitions[word] = 'Keine Definition gefunden'
variants[word] = ''
continue
data = response.json()
debug_print(f"Data Keys: {list(data.keys())}", debug)
page = next(iter(data.get('query', {}).get('pages', {}).values()), {})
extract = page.get('extract', '')
defn, var = extract_definition_and_variant(extract, args.preferred_variant)
definitions[word] = defn
variants[word] = var
print(f"\rDefinitionen abrufen: {idx+1}/{len(words)} abgeschlossen ({word})", end='')
time.sleep(0.5) # Pause pro Request zur Rate-Limiting-Vermeidung
except Exception as e:
print(f"Fehler beim Definitionsabruf für '{word}': {str(e)}")
debug_print(f"Exception Details: {type(e).__name__} - {str(e)}", debug)
definitions[word] = 'Keine Definition gefunden'
variants[word] = ''
print() # Neue Zeile nach Abschluss
debug_print(f"Abgerufene Definitionen: {len(definitions)}", debug)
return definitions, variants
def list_all_categories(api_url, filter_str=None, debug=False):
categories = []
params = {
'action': 'query',
'list': 'allcategories',
'aclimit': '500',
'format': 'json'
}
headers = {'User-Agent': 'GrokScript/1.0'}
debug_print(f"Starte Abruf aller Kategorien mit URL: {api_url} und Params: {params}", debug)
page = 1
total_categories = 0
while True:
try:
response = requests.get(api_url, params=params, headers=headers)
debug_print(f"Response Status: {response.status_code}, Content-Length: {len(response.text)}", debug)
if response.status_code != 200:
print("Fehler beim Abrufen der Kategorien")
debug_print(f"Response Text: {response.text[:500]}...", debug)
break
data = response.json()
debug_print(f"Data Keys: {list(data.keys())}", debug)
new_categories = [cat['*'] for cat in data.get('query', {}).get('allcategories', [])]
categories.extend(new_categories)
total_categories += len(new_categories)
print(f"\rKategorien Paging: Seite {page} abgerufen ({len(new_categories)} neue, total {total_categories})", end='')
if 'continue' in data:
params['accontinue'] = data['continue']['accontinue']
debug_print(f"Paginierung fortsetzen mit accontinue: {params['accontinue']}", debug)
page += 1
else:
print() # Neue Zeile nach Abschluss
break
time.sleep(0.5) # Pause
except Exception as e:
print(f"Fehler beim API-Aufruf für Kategorien: {str(e)}")
debug_print(f"Exception Details: {type(e).__name__} - {str(e)}", debug)
break
if filter_str:
filter_lower = filter_str.lower()
categories = [cat for cat in categories if filter_lower in cat.lower()]
debug_print(f"Gefiltert nach '{filter_str}': {len(categories)} Kategorien gefunden", debug)
print("Vorhandene Kategorien" + (f" (gefiltert nach '{filter_str}')" if filter_str else "") + ":")
for cat in sorted(categories):
print(cat)
debug_print(f"Gesamte Kategorien: {len(categories)}", debug)
return categories
def main(args):
category_cache = {} # Globaler Cache für Kategorien
if args.help:
print_help()
return
api_url = f'https://{args.lang}.wiktionary.org/w/api.php'
debug_print(f"API-URL: {api_url}", args.debug)
if args.list_categories:
list_all_categories(api_url, args.filter, args.debug)
return
include_groups = parse_include_logic(args.include)
if not include_groups:
print("Fehler: --include muss angegeben werden, da 'alle' nicht unterstützt wird.")
return
all_words = get_words_from_logic(api_url, include_groups, args.debug)
debug_print(f"Include-Wörter nach Logik: {len(all_words)}", args.debug)
exclude_cats = list(set([cat.strip() for cat in args.exclude.split(',') if cat.strip()])) if args.exclude else [] # Unique exclude
exclude_words = set()
for cat in exclude_cats:
print(f"Abrufen von Kategorie (exclude): {cat}")
members = get_category_members(api_url, cat, args.debug, category_cache)
exclude_words.update(members)
debug_print(f"Exclude-Wörter: {len(exclude_words)}", args.debug)
all_words -= exclude_words
debug_print(f"Finale Wörter nach Exclude: {len(all_words)}", args.debug)
if not all_words:
print("Keine Wörter gefunden. Überprüfe die Kategorien.")
return
words_list = list(all_words)
random.shuffle(words_list)
selected_words = words_list[:args.num_words]
debug_print(f"Ausgewählte Wörter: {len(selected_words)}", args.debug)
print(f"Abrufen von Definitionen für {len(selected_words)} Wörter...")
definitions, variants = get_definitions_batch(api_url, selected_words, args.debug)
# Erstelle Tabelle (als CSV-Datei)
with open(args.output_file, 'w', encoding='utf-8') as f:
if args.headers:
f.write("Begriff\tErste Bedeutung\n")
for word in selected_words:
var = variants.get(word, '')
begriff = f"{var}, {word}" if var else word
defn = definitions.get(word, 'Keine Definition gefunden')
f.write(f"{begriff}\t{defn}\n")
print(f"Tabelle gespeichert in {args.output_file}")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Wiktionary Wortliste Generator", add_help=False)
parser.add_argument('--lang', '-l', default='en', help='Sprachcode für Wiktionary (z.B. de für de.wiktionary.org)')
parser.add_argument('--include', '-i', default='', help='Logik für einzubeziehende Kategorien (z.B. A+B,C)')
parser.add_argument('--exclude', '-e', default='', help='Komma-separierte Liste auszuschließender Kategorien')
parser.add_argument('--preferred-variant', default=None, help='Bevorzugte Schreibweise (z.B. Schweizerische)')
parser.add_argument('num_words', type=int, nargs='?', default=0, help='Anzahl der Wörter')
parser.add_argument('output_file', nargs='?', default='output.csv', help='Ausgabedatei (CSV)')
parser.add_argument('--list-categories', '-c', action='store_true', help='Liste alle Kategorien der Wiktionary-Instanz')
parser.add_argument('--filter', '-f', default=None, help='Textfilter für Kategorien (Substring-Match, case-insensitive)')
parser.add_argument('--debug', '-d', action='store_true', help='Aktiviere Debug-Ausgaben')
parser.add_argument('--headers', action='store_true', help='Aktiviere Spaltenüberschrift in der Ausgabe')
parser.add_argument('--help', '-h', action='store_true', help='Zeige diese Hilfe an')
args = parser.parse_args()
main(args)
@@ -0,0 +1,410 @@
#!/usr/bin/env python3
"""
Wiktionary Random Word Generator 4.2 (Korrigierte Version)
Erstellt zufällige Wortlisten mit Definitionen aus Wiktionary.
- Echte Zufälligkeit durch MediaWiki Random API
- Integrierte Definition-Extraktion
- Kategorie-Filter und Zahlen-Ausschluss
- Debug-Modus für Fehlerdiagnose
"""
import argparse
import time
import csv
import re
import requests
import random
import sys
from datetime import datetime
from typing import List, Optional, Dict
class WiktionaryRandomGenerator:
def __init__(self, language: str, debug: bool = False):
self.language = language
self.debug = debug
self.session = requests.Session()
self.session.headers.update({'User-Agent': 'Wiktionary Generator 4.2'})
# API URLs
self.api_url = {
'de': 'https://de.wiktionary.org/w/api.php',
'en': 'https://en.wiktionary.org/w/api.php'
}[language]
# Wortarten-Kategorien (für Filterung)
self.pos_categories = {
'de': {
'substantiv': 'Kategorie:Substantiv (Deutsch)',
'verb': 'Kategorie:Verb (Deutsch)',
'adjektiv': 'Kategorie:Adjektiv (Deutsch)',
'adverb': 'Kategorie:Adverb (Deutsch)',
'artikel': 'Kategorie:Artikel (Deutsch)',
'pronomen': 'Kategorie:Pronomen (Deutsch)',
'präposition': 'Kategorie:Präposition (Deutsch)',
'konjunktion': 'Kategorie:Konjunktion (Deutsch)',
'interjektion': 'Kategorie:Interjektion (Deutsch)'
},
'en': {
'noun': 'Category:English nouns',
'verb': 'Category:English verbs',
'adjective': 'Category:English adjectives',
'adverb': 'Category:English adverbs',
'pronoun': 'Category:English pronouns',
'preposition': 'Category:English prepositions',
'conjunction': 'Category:English conjunctions',
'interjection': 'Category:English interjections'
}
}[language]
# Ausschluss-Kategorien (Namen, Geografie)
self.excluded_categories = {
'de': {
'Kategorie:Eigenname (Deutsch)',
'Kategorie:Vorname (Deutsch)',
'Kategorie:Nachname (Deutsch)',
'Kategorie:Toponym (Deutsch)',
'Kategorie:Stadt (Deutsch)',
'Kategorie:Land (Deutsch)'
},
'en': {
'Category:English proper nouns',
'Category:English given names',
'Category:English surnames'
}
}[language]
# Standard-Wortarten
self.default_pos = ['substantiv', 'verb', 'adjektiv'] if language == 'de' else ['noun', 'verb', 'adjective']
self.debug_print(f"Initialisiert für Sprache: {language}")
self.debug_print(f"API URL: {self.api_url}")
self.debug_print(f"Standard-Wortarten: {self.default_pos}")
def debug_print(self, msg: str):
if self.debug:
print(f"[DEBUG] {msg}")
def has_numbers(self, word: str) -> bool:
"""Prüft ob ein Wort Ziffern enthält"""
result = bool(re.search(r'\d', word))
if result and self.debug:
self.debug_print(f"Wort '{word}' enthält Zahlen -> ausgeschlossen")
return result
def clean_definition(self, text: str) -> str:
"""Bereinigt Definition von Wiki-Markup und Formatierung"""
if not text:
return ""
original = text
# Entferne führende Nummerierungen: [1], 2), etc.
text = re.sub(r'^\s*\[?\d+\]?\s*[\]\)]*\s*', '', text)
# Entferne führende Sonderzeichen: :, •, -, etc.
text = re.sub(r'^\s*[:\-\*\+>•]+\s*', '', text)
# Entferne führende Klammern/Anführungszeichen
text = re.sub(r'^\s*[\(\[\"\'`]+\s*', '', text)
# Normalisiere Leerzeichen
text = re.sub(r'\s+', ' ', text).strip()
# Kleinschreibung am Anfang (außer bei Eigennamen)
if text and len(text) > 1 and text[0].isupper() and text[1].islower():
text = text[0].lower() + text[1:]
if self.debug and original != text:
self.debug_print(f"Definition bereinigt: '{original[:30]}...' -> '{text[:30]}...'")
return text
def fetch_random_pages(self, count: int) -> List[Dict]:
"""
Holt zufällige Seiten mit Definitionen und Kategorien.
Verwendet zwei API-Calls: random + details
"""
self.debug_print(f"Hole {count} zufällige Seiten")
# Schritt 1: Hole zufällige Page-IDs
try:
params = {
'action': 'query',
'format': 'json',
'list': 'random',
'rnlimit': min(count, 500),
'rnnamespace': 0 # Nur Hauptnamensraum
}
self.debug_print(f"Random API Call: {params}")
response = self.session.get(self.api_url, params=params, timeout=15)
response.raise_for_status()
data = response.json()
if 'query' not in data or 'random' not in data['query']:
self.debug_print(f"Fehler in Random API Response: {data}")
return []
random_pages = data['query']['random']
page_ids = [str(page['id']) for page in random_pages]
titles = [page['title'] for page in random_pages]
self.debug_print(f"Gefunden: {len(page_ids)} zufällige Seiten")
if not page_ids:
return []
except Exception as e:
self.debug_print(f"Fehler beim Random API Call: {e}")
return []
# Schritt 2: Hole Details (Extract + Categories) für diese IDs
try:
params = {
'action': 'query',
'format': 'json',
'pageids': '|'.join(page_ids),
'prop': 'extracts|categories',
'exintro': True,
'explaintext': True,
'cllimit': 500
}
self.debug_print(f"Details API Call: {params}")
response = self.session.get(self.api_url, params=params, timeout=15)
response.raise_for_status()
data = response.json()
if 'query' not in data or 'pages' not in data['query']:
self.debug_print(f"Fehler in Details API Response: {data}")
return []
pages = data['query']['pages']
results = []
for page_id, page_data in pages.items():
if page_id == '-1': # Ungültige Seite
continue
title = page_data.get('title', '')
extract = page_data.get('extract', '')
categories = set()
# Kategorien sammeln
if 'categories' in page_data:
categories = {cat['title'] for cat in page_data['categories']}
results.append({
'title': title,
'extract': extract,
'categories': categories
})
self.debug_print(f"Seite '{title}': Extract={len(extract)} chars, Categories={len(categories)}")
self.debug_print(f"Details für {len(results)} Seiten geholt")
return results
except Exception as e:
self.debug_print(f"Fehler beim Details API Call: {e}")
return []
def check_word_categories(self, categories: set, include_pos: List[str]) -> bool:
"""
Prüft ob ein Wort in den gewünschten Wortart-Kategorien ist.
Wenn include_pos leer ist, werden alle Wörter akzeptiert.
"""
if not include_pos:
return True # Keine POS-Filter
# Prüfe ob eine der gewünschten Kategorien vorhanden ist
desired_categories = {self.pos_categories[pos] for pos in include_pos
if pos in self.pos_categories}
has_desired_category = bool(categories & desired_categories)
if self.debug:
if has_desired_category:
matching = categories & desired_categories
self.debug_print(f"Wort in gewünschter Kategorie: {matching}")
else:
self.debug_print(f"Wort nicht in gewünschten Kategorien: {desired_categories}")
return has_desired_category
def is_excluded_word(self, categories: set) -> bool:
"""Prüft ob ein Wort in Ausschluss-Kategorien ist"""
excluded = bool(categories & self.excluded_categories)
if excluded and self.debug:
matching = categories & self.excluded_categories
self.debug_print(f"Wort ausgeschlossen wegen: {matching}")
return excluded
def generate(self, count: int, include_pos: List[str], include_names: bool,
exclude_numbers: bool, outfile: str):
"""Generiert die Wortliste"""
pos = include_pos or []
self.debug_print(f"Parameter: count={count}, pos={pos}, names={include_names}, numbers={exclude_numbers}")
print(f"Generiere {count} zufällige Wörter aus {self.language.upper()}-Wiktionary")
if pos:
print(f"Wortarten-Filter: {', '.join(pos)}")
else:
print("Keine Wortarten-Filter (alle Wörter)")
print(f"Namen einschließen: {'Ja' if include_names else 'Nein'}")
print(f"Zahlen ausschließen: {'Ja' if exclude_numbers else 'Nein'}")
print()
results = []
attempts = 0
max_attempts = count * 5 # Maximal 5x so viele Versuche
print("Sammle zufällige Kandidaten...")
while len(results) < count and attempts < max_attempts:
batch_size = min(50, (count - len(results)) * 2) # Hole mehr als nötig
candidates = self.fetch_random_pages(batch_size)
if not candidates:
self.debug_print("Keine Kandidaten erhalten")
break
self.debug_print(f"Verarbeite {len(candidates)} Kandidaten...")
for candidate in candidates:
if len(results) >= count:
break
title = candidate['title']
extract = candidate['extract']
categories = candidate['categories']
self.debug_print(f"Prüfe Kandidat: '{title}'")
# Filter 1: Namen ausschließen (falls gewünscht)
if not include_names and self.is_excluded_word(categories):
self.debug_print(f"'{title}' wegen Namen-Filter übersprungen")
continue
# Filter 2: Zahlen ausschließen (falls gewünscht)
if exclude_numbers and self.has_numbers(title):
continue
# Filter 3: Wortarten-Filter (falls angegeben)
if pos and not self.check_word_categories(categories, pos):
self.debug_print(f"'{title}' wegen POS-Filter übersprungen")
continue
# Filter 4: Definition muss vorhanden sein
if not extract or len(extract) < 10:
self.debug_print(f"'{title}' hat keine ausreichende Definition")
continue
# Definition bereinigen
cleaned_definition = self.clean_definition(extract)
if len(cleaned_definition) < 10:
self.debug_print(f"'{title}' Definition zu kurz nach Bereinigung")
continue
# Wort akzeptieren
results.append((title, cleaned_definition))
print(f" {len(results):2d}/{count}: {title}")
self.debug_print(f"'{title}' erfolgreich hinzugefügt")
attempts += batch_size
# Kleine Pause zwischen Batches
if len(results) < count:
time.sleep(0.3)
if not results:
print("FEHLER: Keine Wörter generiert! Versuchen Sie:")
print("- --include-names für mehr Kandidaten")
print("- Andere/keine --include-pos Filter")
print("- --debug für detaillierte Ausgabe")
sys.exit(1)
# Finale Mischung und Begrenzung
random.shuffle(results)
results = results[:count]
print(f"\n✅ Erfolgreich {len(results)} Wörter generiert!")
print(f"💾 Speichere in '{outfile}'...")
# CSV-Datei schreiben
try:
with open(outfile, 'w', encoding='utf-8', newline='') as f:
writer = csv.writer(f, delimiter='\t')
writer.writerow(['Wort', 'Definition', 'Sprache', 'Zeitstempel'])
for word, definition in results:
writer.writerow([
word,
definition,
self.language,
datetime.now().strftime('%Y-%m-%d %H:%M:%S')
])
print(f"✅ Datei '{outfile}' erfolgreich erstellt!")
# Beispiele anzeigen
print("\n📖 Beispiele aus der generierten Liste:")
print("-" * 65)
for i, (word, definition) in enumerate(results[:5], 1):
short_def = definition[:50] + "..." if len(definition) > 50 else definition
print(f"{i:2d}. {word:15s} → {short_def}")
if len(results) > 5:
print(f" ... und {len(results) - 5} weitere Wörter")
except Exception as e:
print(f"❌ FEHLER beim Schreiben der Datei: {e}")
sys.exit(1)
if __name__ == '__main__':
parser = argparse.ArgumentParser(
description='Wiktionary Random Word Generator 4.2',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Beispiele:
python script.py -c 30 -l de
python script.py -c 50 --include-pos substantiv verb
python script.py -c 25 -l en --exclude-numbers
python script.py -c 40 --include-names --debug
Verfügbare Wortarten:
Deutsch: substantiv, verb, adjektiv, adverb, artikel, pronomen, präposition, konjunktion, interjektion
Englisch: noun, verb, adjective, adverb, pronoun, preposition, conjunction, interjection
"""
)
parser.add_argument('-c', '--count', type=int, default=50,
help='Anzahl der zu generierenden Wörter (Standard: 50)')
parser.add_argument('-l', '--language', choices=['de', 'en'], default='de',
help='Wiktionary-Sprache (Standard: de)')
parser.add_argument('-o', '--output', default='wiktionary_words.csv',
help='Output-Datei (Standard: wiktionary_words.csv)')
parser.add_argument('--include-pos', nargs='+',
help='Nur diese Wortarten einschließen')
parser.add_argument('--include-names', action='store_true',
help='Namen und geografische Bezeichnungen einschließen')
parser.add_argument('--exclude-numbers', action='store_true',
help='Wörter mit Ziffern ausschließen')
parser.add_argument('--debug', action='store_true',
help='Debug-Modus für detaillierte Ausgabe')
args = parser.parse_args()
# Validierung
if args.count <= 0:
print("❌ Anzahl muss größer als 0 sein!")
sys.exit(1)
# Generator erstellen und ausführen
gen = WiktionaryRandomGenerator(args.language, debug=args.debug)
gen.generate(
count=args.count,
include_pos=args.include_pos,
include_names=args.include_names,
exclude_numbers=args.exclude_numbers,
outfile=args.output
)