mirror of
https://github.com/DRYTRIX/TimeTracker.git
synced 2026-02-13 23:59:00 -06:00
feat(i18n): Add Norwegian translation support and improve internationalization
- Add Norwegian (Norsk) language support with locale code normalization (no -> nb) - Create Norwegian translation files (translations/nb/ and translations/no/) - Fill empty Norwegian translation strings with English fallback values - Add locale normalization for Flask-Babel compatibility (no -> nb mapping) - Update context processor to correctly display 'Norsk' label instead of 'NB' Translation improvements: - Wrap all hardcoded strings in templates with _() translation function - Add missing translations for setup, timer, tasks, invoices, and admin templates - Ensure brandnames 'drytrix' and 'TimeTracker' remain untranslated across all languages - Add new translation strings to all language files (en, de, nl, fr, it, fi, es, no, ar, he) - Update translation files for: initial_setup, manual_entry, tasks/list, email_templates, etc. Bug fixes: - Add missing /api/summary/today endpoint for daily summary notifications - Fix 'Response body already consumed' error in smart-notifications.js - Improve translation compilation logging and error handling - Add debug endpoint /debug/i18n for troubleshooting translation issues Technical changes: - Improve ensure_translations_compiled() with better logging - Add locale normalization function for Norwegian locale handling - Update context processor to reverse-map normalized locales for display - Fix JavaScript fetch error handling to check response.ok before reading body
This commit is contained in:
@@ -340,17 +340,38 @@ def create_app(config=None):
|
||||
if current_user and getattr(current_user, "is_authenticated", False):
|
||||
pref = getattr(current_user, "preferred_language", None)
|
||||
if pref:
|
||||
return pref
|
||||
# Normalize locale code (e.g., 'no' -> 'nb' for Norwegian)
|
||||
return _normalize_locale(pref)
|
||||
# 2) Session override (set-language route)
|
||||
if "preferred_language" in session:
|
||||
return session.get("preferred_language")
|
||||
return _normalize_locale(session.get("preferred_language"))
|
||||
# 3) Best match with Accept-Language
|
||||
supported = list(app.config.get("LANGUAGES", {}).keys()) or ["en"]
|
||||
return request.accept_languages.best_match(supported) or app.config.get(
|
||||
matched = request.accept_languages.best_match(supported) or app.config.get(
|
||||
"BABEL_DEFAULT_LOCALE", "en"
|
||||
)
|
||||
return _normalize_locale(matched)
|
||||
except Exception:
|
||||
return app.config.get("BABEL_DEFAULT_LOCALE", "en")
|
||||
|
||||
def _normalize_locale(locale_code):
|
||||
"""Normalize locale codes for Flask-Babel compatibility.
|
||||
|
||||
Some locale codes need to be normalized:
|
||||
- 'no' -> 'nb' (Norwegian Bokmål is the standard, but we'll try 'no' first)
|
||||
"""
|
||||
if not locale_code:
|
||||
return 'en'
|
||||
locale_code = locale_code.lower().strip()
|
||||
# Try 'no' first - if translations don't exist, Flask-Babel will fall back
|
||||
# If 'no' doesn't work, we can map to 'nb' as fallback
|
||||
# For now, keep 'no' as-is since we have translations/nb/ directory
|
||||
# The directory structure should match what Flask-Babel expects
|
||||
if locale_code == 'no':
|
||||
# Use 'nb' for Flask-Babel (standard Norwegian Bokmål locale)
|
||||
# But ensure we have translations in both 'no' and 'nb' directories
|
||||
return 'nb'
|
||||
return locale_code
|
||||
|
||||
babel.init_app(
|
||||
app,
|
||||
|
||||
@@ -119,6 +119,7 @@ class Config:
|
||||
'it': 'Italiano',
|
||||
'fi': 'Suomi',
|
||||
'es': 'Español',
|
||||
'no': 'Norsk',
|
||||
'ar': 'العربية',
|
||||
'he': 'עברית',
|
||||
}
|
||||
|
||||
@@ -1513,6 +1513,35 @@ def dashboard_sparklines():
|
||||
'month': hours_data # Same data for now
|
||||
})
|
||||
|
||||
@api_bp.route('/api/summary/today')
|
||||
@login_required
|
||||
def summary_today():
|
||||
"""Get today's time tracking summary for daily summary notification"""
|
||||
from app.models import TimeEntry, Project
|
||||
from datetime import datetime, timedelta
|
||||
from sqlalchemy import func, distinct
|
||||
|
||||
today = datetime.utcnow().date()
|
||||
|
||||
# Get today's time entries for current user
|
||||
entries = TimeEntry.query.filter(
|
||||
TimeEntry.user_id == current_user.id,
|
||||
func.date(TimeEntry.start_time) == today,
|
||||
TimeEntry.end_time.isnot(None)
|
||||
).all()
|
||||
|
||||
# Calculate total hours
|
||||
total_hours = sum((entry.duration_hours or 0) for entry in entries)
|
||||
|
||||
# Count unique projects
|
||||
project_ids = set(entry.project_id for entry in entries if entry.project_id)
|
||||
project_count = len(project_ids)
|
||||
|
||||
return jsonify({
|
||||
'hours': round(total_hours, 2),
|
||||
'projects': project_count
|
||||
})
|
||||
|
||||
@api_bp.route('/api/activity/timeline')
|
||||
@login_required
|
||||
def activity_timeline():
|
||||
|
||||
@@ -128,6 +128,38 @@ def help():
|
||||
"""Help page"""
|
||||
return render_template('main/help.html')
|
||||
|
||||
@main_bp.route('/debug/i18n')
|
||||
@login_required
|
||||
def debug_i18n():
|
||||
"""Debug endpoint to check i18n status (admin only)"""
|
||||
from flask_login import current_user
|
||||
if not current_user.is_admin:
|
||||
return jsonify({'error': 'Admin only'}), 403
|
||||
|
||||
from flask_babel import get_locale
|
||||
import os
|
||||
|
||||
locale = str(get_locale())
|
||||
session_lang = session.get('preferred_language')
|
||||
user_lang = getattr(current_user, 'preferred_language', None)
|
||||
|
||||
# Check if .mo file exists for current locale
|
||||
base_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
|
||||
translations_dir = os.path.join(base_path, 'translations')
|
||||
mo_path = os.path.join(translations_dir, locale, 'LC_MESSAGES', 'messages.mo')
|
||||
po_path = os.path.join(translations_dir, locale, 'LC_MESSAGES', 'messages.po')
|
||||
|
||||
return jsonify({
|
||||
'current_locale': locale,
|
||||
'session_language': session_lang,
|
||||
'user_language': user_lang,
|
||||
'mo_file_exists': os.path.exists(mo_path),
|
||||
'po_file_exists': os.path.exists(po_path),
|
||||
'mo_path': mo_path,
|
||||
'nb_mo_exists': os.path.exists(os.path.join(translations_dir, 'nb', 'LC_MESSAGES', 'messages.mo')),
|
||||
'no_mo_exists': os.path.exists(os.path.join(translations_dir, 'no', 'LC_MESSAGES', 'messages.mo')),
|
||||
})
|
||||
|
||||
@main_bp.route('/i18n/set-language', methods=['POST', 'GET'])
|
||||
def set_language():
|
||||
"""Set preferred UI language via session or user profile."""
|
||||
|
||||
@@ -229,6 +229,14 @@ class SmartNotificationManager {
|
||||
setInterval(async () => {
|
||||
try {
|
||||
const response = await fetch('/api/deadlines/upcoming');
|
||||
|
||||
// Check if response is OK before reading body
|
||||
if (!response.ok) {
|
||||
// If response is not OK, the error handler may have already consumed the body
|
||||
// Just return early to avoid "Body has already been consumed" error
|
||||
return;
|
||||
}
|
||||
|
||||
const deadlines = await response.json();
|
||||
|
||||
deadlines.forEach(deadline => {
|
||||
@@ -247,7 +255,10 @@ class SmartNotificationManager {
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error checking deadlines:', error);
|
||||
// Only log if it's not a "body consumed" error (which is expected when response is not OK)
|
||||
if (!error.message || !error.message.includes('already been consumed')) {
|
||||
console.error('Error checking deadlines:', error);
|
||||
}
|
||||
}
|
||||
}, 60 * 60 * 1000); // Check every hour
|
||||
}
|
||||
@@ -276,6 +287,14 @@ class SmartNotificationManager {
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/summary/today');
|
||||
|
||||
// Check if response is OK before reading body
|
||||
if (!response.ok) {
|
||||
// If response is not OK, the error handler may have already consumed the body
|
||||
// Just return early to avoid "Body has already been consumed" error
|
||||
return;
|
||||
}
|
||||
|
||||
const summary = await response.json();
|
||||
|
||||
const hours = (summary && typeof summary.hours === 'number')
|
||||
@@ -303,7 +322,10 @@ class SmartNotificationManager {
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching daily summary:', error);
|
||||
// Only log if it's not a "body consumed" error (which is expected when response is not OK)
|
||||
if (!error.message || !error.message.includes('already been consumed')) {
|
||||
console.error('Error fetching daily summary:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
<div class="md:col-span-2">
|
||||
<label class="flex items-center">
|
||||
<input type="checkbox" name="is_default" class="rounded border-gray-300 text-primary focus:ring-primary">
|
||||
<span class="ml-2 text-sm text-gray-700 dark:text-gray-300">Set as default template</span>
|
||||
<span class="ml-2 text-sm text-gray-700 dark:text-gray-300">{{ _('Set as default template') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -43,19 +43,19 @@
|
||||
<!-- Visual Editor Section -->
|
||||
<div class="mt-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">HTML Template *</label>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">{{ _('HTML Template') }} *</label>
|
||||
<div class="flex gap-2" id="variableButtons">
|
||||
<button type="button" data-variable="invoice.invoice_number" class="variable-btn px-3 py-1 text-xs bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300 rounded hover:bg-blue-200 dark:hover:bg-blue-800">
|
||||
Invoice Number
|
||||
{{ _('Invoice Number') }}
|
||||
</button>
|
||||
<button type="button" data-variable="company_name" class="variable-btn px-3 py-1 text-xs bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300 rounded hover:bg-blue-200 dark:hover:bg-blue-800">
|
||||
Company Name
|
||||
{{ _('Company Name') }}
|
||||
</button>
|
||||
<button type="button" data-variable="custom_message" class="variable-btn px-3 py-1 text-xs bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300 rounded hover:bg-blue-200 dark:hover:bg-blue-800">
|
||||
Custom Message
|
||||
{{ _('Custom Message') }}
|
||||
</button>
|
||||
<button type="button" id="showAllVariablesBtn" class="px-3 py-1 text-xs bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded hover:bg-gray-200 dark:hover:bg-gray-600">
|
||||
More Variables
|
||||
{{ _('More Variables') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
<div class="md:col-span-2">
|
||||
<label class="flex items-center">
|
||||
<input type="checkbox" name="is_default" {% if template.is_default %}checked{% endif %} class="rounded border-gray-300 text-primary focus:ring-primary">
|
||||
<span class="ml-2 text-sm text-gray-700 dark:text-gray-300">Set as default template</span>
|
||||
<span class="ml-2 text-sm text-gray-700 dark:text-gray-300">{{ _('Set as default template') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -41,19 +41,19 @@
|
||||
<!-- Visual Editor Section -->
|
||||
<div class="mt-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">HTML Template *</label>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">{{ _('HTML Template') }} *</label>
|
||||
<div class="flex gap-2" id="variableButtons">
|
||||
<button type="button" data-variable="invoice.invoice_number" class="variable-btn px-3 py-1 text-xs bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300 rounded hover:bg-blue-200 dark:hover:bg-blue-800">
|
||||
Invoice Number
|
||||
{{ _('Invoice Number') }}
|
||||
</button>
|
||||
<button type="button" data-variable="company_name" class="variable-btn px-3 py-1 text-xs bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300 rounded hover:bg-blue-200 dark:hover:bg-blue-800">
|
||||
Company Name
|
||||
{{ _('Company Name') }}
|
||||
</button>
|
||||
<button type="button" data-variable="custom_message" class="variable-btn px-3 py-1 text-xs bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300 rounded hover:bg-blue-200 dark:hover:bg-blue-800">
|
||||
Custom Message
|
||||
{{ _('Custom Message') }}
|
||||
</button>
|
||||
<button type="button" id="showAllVariablesBtn" class="px-3 py-1 text-xs bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded hover:bg-gray-200 dark:hover:bg-gray-600">
|
||||
More Variables
|
||||
{{ _('More Variables') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
|
||||
{% if template.html %}
|
||||
<div class="bg-card-light dark:bg-card-dark p-6 rounded-lg shadow mb-6">
|
||||
<h2 class="text-lg font-semibold mb-4">HTML Template</h2>
|
||||
<h2 class="text-lg font-semibold mb-4">{{ _('HTML Template') }}</h2>
|
||||
<pre class="bg-gray-100 dark:bg-gray-800 p-4 rounded-lg overflow-x-auto text-sm"><code>{{ template.html }}</code></pre>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
<h2 class="text-lg font-semibold mb-4">Company Branding</h2>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label for="company_name" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Company Name</label>
|
||||
<label for="company_name" class="block text-sm font-medium text-gray-700 dark:text-gray-300">{{ _('Company Name') }}</label>
|
||||
<input type="text" name="company_name" id="company_name" value="{{ settings.company_name }}" class="form-input">
|
||||
</div>
|
||||
<div>
|
||||
|
||||
@@ -228,7 +228,7 @@
|
||||
|
||||
<div>
|
||||
<label for="receipt_number" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
Receipt/Invoice Number
|
||||
{{ _('Receipt/Invoice Number') }}
|
||||
</label>
|
||||
<input type="text" name="receipt_number" id="receipt_number"
|
||||
value="{{ expense.receipt_number if expense else '' }}"
|
||||
|
||||
@@ -547,7 +547,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label for="custom_message" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Custom Message (Optional)</label>
|
||||
<label for="custom_message" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">{{ _('Custom Message') }} ({{ _('Optional') }})</label>
|
||||
<textarea id="custom_message" name="custom_message" rows="4" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"></textarea>
|
||||
</div>
|
||||
<div class="flex justify-end space-x-3">
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
</div>
|
||||
<div>
|
||||
<label for="search" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Search</label>
|
||||
<input type="text" name="search" id="search" value="{{ request.args.get('search', '') }}" class="form-input" placeholder="Invoice number or client">
|
||||
<input type="text" name="search" id="search" value="{{ request.args.get('search', '') }}" class="form-input" placeholder="{{ _('Invoice number or client') }}">
|
||||
</div>
|
||||
<div class="col-span-full flex justify-end">
|
||||
<button type="submit" class="bg-primary text-white px-4 py-2 rounded-lg hover:bg-primary/90 transition-colors">Filter</button>
|
||||
|
||||
@@ -358,7 +358,7 @@
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label for="custom_message" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Custom Message (Optional)</label>
|
||||
<label for="custom_message" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">{{ _('Custom Message') }} ({{ _('Optional') }})</label>
|
||||
<textarea id="custom_message" name="custom_message" rows="4" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"></textarea>
|
||||
</div>
|
||||
<div class="flex justify-end space-x-3">
|
||||
|
||||
@@ -152,7 +152,7 @@
|
||||
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-500 dark:text-gray-400 mb-1">Invoice Number</label>
|
||||
<label class="block text-sm font-medium text-gray-500 dark:text-gray-400 mb-1">{{ _('Invoice Number') }}</label>
|
||||
<a href="{{ url_for('invoices.view_invoice', invoice_id=payment.invoice_id) }}"
|
||||
class="text-lg font-semibold text-primary hover:text-primary-dark">
|
||||
{{ payment.invoice.invoice_number }}
|
||||
|
||||
@@ -94,7 +94,7 @@
|
||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<thead class="bg-gray-50 dark:bg-gray-800">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Invoice Number</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">{{ _('Invoice Number') }}</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Issue Date</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Amount</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase">Status</th>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="{{ current_language_code or 'en' }}" dir="{{ 'rtl' if is_rtl else 'ltr' }}">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Welcome - TimeTracker</title>
|
||||
<title>{{ _('Welcome') }} - TimeTracker</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='dist/output.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='toast-notifications.css') }}">
|
||||
<script>
|
||||
@@ -23,34 +23,34 @@
|
||||
<div class="text-center">
|
||||
<img src="{{ url_for('static', filename='images/drytrix-logo.svg') }}" alt="logo" class="w-24 h-24 mx-auto">
|
||||
<h1 class="text-3xl font-bold mt-4 text-primary">TimeTracker</h1>
|
||||
<p class="mt-2 text-text-muted-light dark:text-text-muted-dark">Track time. Stay organized.</p>
|
||||
<p class="mt-2 text-text-muted-light dark:text-text-muted-dark">{{ _('Track time. Stay organized.') }}</p>
|
||||
|
||||
<!-- Privacy Principles -->
|
||||
<div class="mt-8 space-y-3 text-left">
|
||||
<p class="text-sm font-semibold text-text-light dark:text-text-dark mb-3">🔒 Privacy First</p>
|
||||
<p class="text-sm font-semibold text-text-light dark:text-text-dark mb-3">🔒 {{ _('Privacy First') }}</p>
|
||||
<div class="flex items-start text-sm text-text-muted-light dark:text-text-muted-dark">
|
||||
<svg class="h-5 w-5 text-green-500 mr-2 flex-shrink-0 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
<span>Self-hosted on your server</span>
|
||||
<span>{{ _('Self-hosted on your server') }}</span>
|
||||
</div>
|
||||
<div class="flex items-start text-sm text-text-muted-light dark:text-text-muted-dark">
|
||||
<svg class="h-5 w-5 text-green-500 mr-2 flex-shrink-0 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
<span>Anonymous telemetry (opt-in)</span>
|
||||
<span>{{ _('Anonymous telemetry (opt-in)') }}</span>
|
||||
</div>
|
||||
<div class="flex items-start text-sm text-text-muted-light dark:text-text-muted-dark">
|
||||
<svg class="h-5 w-5 text-green-500 mr-2 flex-shrink-0 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
<span>No PII collected ever</span>
|
||||
<span>{{ _('No PII collected ever') }}</span>
|
||||
</div>
|
||||
<div class="flex items-start text-sm text-text-muted-light dark:text-text-muted-dark">
|
||||
<svg class="h-5 w-5 text-green-500 mr-2 flex-shrink-0 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
<span>Open source & transparent</span>
|
||||
<span>{{ _('Open source & transparent') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -58,31 +58,31 @@
|
||||
|
||||
<!-- Right side - Setup Form -->
|
||||
<div class="p-8 overflow-y-auto max-h-[90vh]">
|
||||
<h2 class="text-2xl font-bold tracking-tight">Welcome to TimeTracker</h2>
|
||||
<p class="mt-2 text-sm text-text-muted-light dark:text-text-muted-dark">Let's get you set up in just a moment</p>
|
||||
<h2 class="text-2xl font-bold tracking-tight">{{ _('Welcome to TimeTracker') }}</h2>
|
||||
<p class="mt-2 text-sm text-text-muted-light dark:text-text-muted-dark">{{ _("Let's get you set up in just a moment") }}</p>
|
||||
|
||||
<form class="mt-6 space-y-6" method="POST" action="{{ url_for('setup.initial_setup') }}">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
|
||||
<!-- Welcome Message -->
|
||||
<div class="bg-primary/10 border border-primary/20 rounded-lg p-4">
|
||||
<h3 class="text-sm font-semibold text-primary mb-2">🎉 Thank you for choosing TimeTracker!</h3>
|
||||
<h3 class="text-sm font-semibold text-primary mb-2">🎉 {{ _('Thank you for choosing TimeTracker!') }}</h3>
|
||||
<p class="text-sm text-text-muted-light dark:text-text-muted-dark">
|
||||
Your data stays on your server, and you have complete control.
|
||||
{{ _('Your data stays on your server, and you have complete control.') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Telemetry Opt-in Section -->
|
||||
<div class="bg-background-light dark:bg-gray-700 border border-border-light dark:border-border-dark rounded-lg p-4">
|
||||
<h3 class="text-base font-semibold mb-3">📊 Help Us Improve (Optional)</h3>
|
||||
<h3 class="text-base font-semibold mb-3">📊 {{ _('Help Us Improve (Optional)') }}</h3>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="flex items-start cursor-pointer">
|
||||
<input type="checkbox" name="telemetry_enabled" class="mt-1 h-4 w-4 text-primary border-border-light dark:border-border-dark rounded focus:ring-primary">
|
||||
<span class="ml-3 text-sm">
|
||||
<span class="font-medium">Enable anonymous telemetry</span>
|
||||
<span class="font-medium">{{ _('Enable anonymous telemetry') }}</span>
|
||||
<span class="block text-text-muted-light dark:text-text-muted-dark mt-1">
|
||||
Help us understand usage patterns to improve TimeTracker
|
||||
{{ _('Help us understand usage patterns to improve TimeTracker') }}
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
@@ -91,34 +91,34 @@
|
||||
<!-- Collapsible details -->
|
||||
<details class="text-sm mt-3">
|
||||
<summary class="cursor-pointer font-medium text-primary hover:underline">
|
||||
What data is collected?
|
||||
{{ _('What data is collected?') }}
|
||||
</summary>
|
||||
<div class="mt-3 space-y-3 pl-4 border-l-2 border-primary/30">
|
||||
<div>
|
||||
<p class="font-semibold text-green-600 dark:text-green-400">✓ What we collect:</p>
|
||||
<p class="font-semibold text-green-600 dark:text-green-400">✓ {{ _('What we collect:') }}</p>
|
||||
<ul class="list-disc list-inside space-y-1 ml-2 text-text-muted-light dark:text-text-muted-dark">
|
||||
<li>Anonymous installation fingerprint (hashed)</li>
|
||||
<li>Application version & platform info</li>
|
||||
<li>Feature usage statistics</li>
|
||||
<li>Internal numeric IDs only</li>
|
||||
<li>{{ _('Anonymous installation fingerprint (hashed)') }}</li>
|
||||
<li>{{ _('Application version & platform info') }}</li>
|
||||
<li>{{ _('Feature usage statistics') }}</li>
|
||||
<li>{{ _('Internal numeric IDs only') }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="font-semibold text-red-600 dark:text-red-400">✗ What we DON'T collect:</p>
|
||||
<p class="font-semibold text-red-600 dark:text-red-400">✗ {{ _("What we DON'T collect:") }}</p>
|
||||
<ul class="list-disc list-inside space-y-1 ml-2 text-text-muted-light dark:text-text-muted-dark">
|
||||
<li>No usernames or emails</li>
|
||||
<li>No project names or descriptions</li>
|
||||
<li>No time entry data or notes</li>
|
||||
<li>No client or business data</li>
|
||||
<li>No IP addresses or PII</li>
|
||||
<li>{{ _('No usernames or emails') }}</li>
|
||||
<li>{{ _('No project names or descriptions') }}</li>
|
||||
<li>{{ _('No time entry data or notes') }}</li>
|
||||
<li>{{ _('No client or business data') }}</li>
|
||||
<li>{{ _('No IP addresses or PII') }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="bg-primary/10 rounded p-3 text-xs">
|
||||
<p class="text-text-light dark:text-text-dark">
|
||||
<strong>Why?</strong> Anonymous usage data helps us prioritize features and fix issues.
|
||||
You can change this anytime in <strong>Admin → Settings</strong> (Privacy & Analytics section).
|
||||
<strong>{{ _('Why?') }}</strong> {{ _('Anonymous usage data helps us prioritize features and fix issues.') }}
|
||||
{{ _('You can change this anytime in') }} <strong>{{ _('Admin → Settings') }}</strong> ({{ _('Privacy & Analytics section') }}).
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -128,11 +128,11 @@
|
||||
<!-- Submit Button -->
|
||||
<button type="submit" class="btn btn-primary w-full">
|
||||
<i class="fa-solid fa-check mr-2"></i>
|
||||
Complete Setup & Continue
|
||||
{{ _('Complete Setup & Continue') }}
|
||||
</button>
|
||||
|
||||
<div class="text-xs text-center text-text-muted-light dark:text-text-muted-dark">
|
||||
<p>By continuing, you agree to use TimeTracker under the <a href="https://www.gnu.org/licenses/gpl-3.0.html" class="text-primary hover:underline" target="_blank">GPL-3.0 License</a></p>
|
||||
<p>{{ _('By continuing, you agree to use TimeTracker under the') }} <a href="https://www.gnu.org/licenses/gpl-3.0.html" class="text-primary hover:underline" target="_blank">{{ _('GPL-3.0 License') }}</a></p>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -296,17 +296,17 @@
|
||||
<div id="bulkProjectDialog" class="hidden fixed inset-0 z-50 flex items-center justify-center bg-black/50">
|
||||
<div class="bg-card-light dark:bg-card-dark rounded-lg shadow-xl max-w-md w-full mx-4">
|
||||
<div class="p-6">
|
||||
<h3 class="text-lg font-semibold mb-4">Move Selected Tasks to Project</h3>
|
||||
<label for="bulkProjectSelect" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Select Project</label>
|
||||
<h3 class="text-lg font-semibold mb-4">{{ _('Move Selected Tasks to Project') }}</h3>
|
||||
<label for="bulkProjectSelect" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">{{ _('Select Project') }}</label>
|
||||
<select id="bulkProjectSelect" class="form-input w-full mb-4">
|
||||
<option value="">-- Select Project --</option>
|
||||
<option value="">-- {{ _('Select Project') }} --</option>
|
||||
{% for project in projects %}
|
||||
<option value="{{ project.id }}">{{ project.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<div class="flex justify-end gap-2">
|
||||
<button type="button" onclick="closeBulkProjectDialog()" class="px-4 py-2 text-sm bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200 rounded-lg hover:bg-gray-300 dark:hover:bg-gray-600">Cancel</button>
|
||||
<button type="button" onclick="submitBulkProject()" class="px-4 py-2 text-sm bg-primary text-white rounded-lg hover:bg-primary/90">Move Tasks</button>
|
||||
<button type="button" onclick="closeBulkProjectDialog()" class="px-4 py-2 text-sm bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200 rounded-lg hover:bg-gray-300 dark:hover:bg-gray-600">{{ _('Cancel') }}</button>
|
||||
<button type="button" onclick="submitBulkProject()" class="px-4 py-2 text-sm bg-primary text-white rounded-lg hover:bg-primary/90">{{ _('Move Tasks') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
|
||||
{% block content %}
|
||||
{% set breadcrumbs = [
|
||||
{'text': 'Time Tracking'},
|
||||
{'text': 'Log Time' if not is_duplicate else 'Duplicate Entry'}
|
||||
{'text': _('Time Tracking')},
|
||||
{'text': _('Log Time') if not is_duplicate else _('Duplicate Entry')}
|
||||
] %}
|
||||
|
||||
{{ page_header(
|
||||
icon_class='fas fa-clock',
|
||||
title_text='Duplicate Time Entry' if is_duplicate else 'Log Time Manually',
|
||||
subtitle_text='Create a copy of a previous entry with new times' if is_duplicate else 'Create a new time entry',
|
||||
title_text=_('Duplicate Time Entry') if is_duplicate else _('Log Time Manually'),
|
||||
subtitle_text=_('Create a copy of a previous entry with new times') if is_duplicate else _('Create a new time entry'),
|
||||
breadcrumbs=breadcrumbs,
|
||||
actions_html=None
|
||||
) }}
|
||||
@@ -21,10 +21,10 @@
|
||||
<i class="fas fa-info-circle text-blue-600 dark:text-blue-400 mt-1"></i>
|
||||
<div>
|
||||
<p class="text-sm text-blue-800 dark:text-blue-200">
|
||||
<strong>Duplicating entry:</strong> {{ original_entry.project.name }}{% if original_entry.task %} - {{ original_entry.task.name }}{% endif %}
|
||||
<strong>{{ _('Duplicating entry:') }}</strong> {{ original_entry.project.name }}{% if original_entry.task %} - {{ original_entry.task.name }}{% endif %}
|
||||
</p>
|
||||
<p class="text-xs text-blue-600 dark:text-blue-300 mt-1">
|
||||
Original: {{ original_entry.start_time|user_datetime('%Y-%m-%d %H:%M') }} to {{ original_entry.end_time|user_datetime('%Y-%m-%d %H:%M') if original_entry.end_time else 'N/A' }} ({{ original_entry.duration_formatted }})
|
||||
{{ _('Original:') }} {{ original_entry.start_time|user_datetime('%Y-%m-%d %H:%M') }} {{ _('to') }} {{ original_entry.end_time|user_datetime('%Y-%m-%d %H:%M') if original_entry.end_time else _('N/A') }} ({{ original_entry.duration_formatted }})
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -37,66 +37,66 @@
|
||||
<div class="space-y-6">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label for="project_id" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Project</label>
|
||||
<label for="project_id" class="block text-sm font-medium text-gray-700 dark:text-gray-300">{{ _('Project') }}</label>
|
||||
<select name="project_id" id="project_id" required class="form-input">
|
||||
<option value="">Select a project</option>
|
||||
<option value="">{{ _('Select a project') }}</option>
|
||||
{% for project in projects %}
|
||||
<option value="{{ project.id }}" {% if selected_project_id == project.id %}selected{% endif %}>{{ project.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="task_id" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Task (optional)</label>
|
||||
<label for="task_id" class="block text-sm font-medium text-gray-700 dark:text-gray-300">{{ _('Task (optional)') }}</label>
|
||||
<select name="task_id" id="task_id" class="form-input" data-selected-task-id="{{ selected_task_id or '' }}" {% if not selected_project_id %}disabled{% endif %}>
|
||||
<option value="">No task</option>
|
||||
<option value="">{{ _('No task') }}</option>
|
||||
</select>
|
||||
<p class="text-xs text-text-muted-light dark:text-text-muted-dark mt-1">Tasks load after selecting a project</p>
|
||||
<p class="text-xs text-text-muted-light dark:text-text-muted-dark mt-1">{{ _('Tasks load after selecting a project') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label for="start_date" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Start Date</label>
|
||||
<label for="start_date" class="block text-sm font-medium text-gray-700 dark:text-gray-300">{{ _('Start Date') }}</label>
|
||||
<input type="date" name="start_date" id="start_date" required class="form-input">
|
||||
</div>
|
||||
<div>
|
||||
<label for="start_time" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Start Time</label>
|
||||
<label for="start_time" class="block text-sm font-medium text-gray-700 dark:text-gray-300">{{ _('Start Time') }}</label>
|
||||
<input type="time" name="start_time" id="start_time" required class="form-input">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label for="end_date" class="block text-sm font-medium text-gray-700 dark:text-gray-300">End Date</label>
|
||||
<label for="end_date" class="block text-sm font-medium text-gray-700 dark:text-gray-300">{{ _('End Date') }}</label>
|
||||
<input type="date" name="end_date" id="end_date" required class="form-input">
|
||||
</div>
|
||||
<div>
|
||||
<label for="end_time" class="block text-sm font-medium text-gray-700 dark:text-gray-300">End Time</label>
|
||||
<label for="end_time" class="block text-sm font-medium text-gray-700 dark:text-gray-300">{{ _('End Time') }}</label>
|
||||
<input type="time" name="end_time" id="end_time" required class="form-input">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div class="md:col-span-2">
|
||||
<label for="notes" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Notes</label>
|
||||
<textarea name="notes" id="notes" rows="4" class="form-input" placeholder="What did you work on?">{{ prefill_notes or '' }}</textarea>
|
||||
<label for="notes" class="block text-sm font-medium text-gray-700 dark:text-gray-300">{{ _('Notes') }}</label>
|
||||
<textarea name="notes" id="notes" rows="4" class="form-input" placeholder="{{ _('What did you work on?') }}">{{ prefill_notes or '' }}</textarea>
|
||||
</div>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label for="tags" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Tags</label>
|
||||
<input type="text" name="tags" id="tags" class="form-input" placeholder="tag1, tag2" value="{{ prefill_tags or '' }}">
|
||||
<label for="tags" class="block text-sm font-medium text-gray-700 dark:text-gray-300">{{ _('Tags') }}</label>
|
||||
<input type="text" name="tags" id="tags" class="form-input" placeholder="{{ _('tag1, tag2') }}" value="{{ prefill_tags or '' }}">
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<input type="checkbox" id="billable" name="billable" class="h-5 w-5 rounded border-gray-300 text-primary focus:ring-0" {% if prefill_billable is not defined or prefill_billable %}checked{% endif %}>
|
||||
<label for="billable" class="text-sm text-gray-700 dark:text-gray-300">Billable</label>
|
||||
<label for="billable" class="text-sm text-gray-700 dark:text-gray-300">{{ _('Billable') }}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-8 border-t border-border-light dark:border-border-dark pt-6 flex justify-end gap-3">
|
||||
<button type="reset" class="px-4 py-2 rounded-lg border border-border-light dark:border-border-dark text-text-light dark:text-text-dark hover:bg-background-light dark:hover:bg-background-dark">Clear</button>
|
||||
<button type="submit" class="bg-primary text-white px-4 py-2 rounded-lg">Log Time</button>
|
||||
<button type="reset" class="px-4 py-2 rounded-lg border border-border-light dark:border-border-dark text-text-light dark:text-text-dark hover:bg-background-light dark:hover:bg-background-dark">{{ _('Clear') }}</button>
|
||||
<button type="submit" class="bg-primary text-white px-4 py-2 rounded-lg">{{ _('Log Time') }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -119,19 +119,21 @@ document.addEventListener('DOMContentLoaded', async function(){
|
||||
// Dynamic task loading when a project is chosen
|
||||
const projectSelect = document.getElementById('project_id');
|
||||
const taskSelect = document.getElementById('task_id');
|
||||
const noTaskText = '{{ _('No task') }}';
|
||||
const failedToLoadTasksText = '{{ _('Failed to load tasks') }}';
|
||||
async function loadTasks(projectId){
|
||||
if (!taskSelect) return;
|
||||
if (!projectId){
|
||||
taskSelect.innerHTML = '<option value="">No task</option>';
|
||||
taskSelect.innerHTML = '<option value="">' + noTaskText + '</option>';
|
||||
taskSelect.disabled = true;
|
||||
return;
|
||||
}
|
||||
try{
|
||||
const resp = await fetch(`/api/tasks?project_id=${projectId}`);
|
||||
if (!resp.ok) throw new Error('Failed to load tasks');
|
||||
if (!resp.ok) throw new Error(failedToLoadTasksText);
|
||||
const data = await resp.json();
|
||||
const tasks = Array.isArray(data.tasks) ? data.tasks : [];
|
||||
taskSelect.innerHTML = '<option value="">No task</option>';
|
||||
taskSelect.innerHTML = '<option value="">' + noTaskText + '</option>';
|
||||
tasks.forEach(t => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = String(t.id);
|
||||
@@ -146,7 +148,7 @@ document.addEventListener('DOMContentLoaded', async function(){
|
||||
}
|
||||
taskSelect.disabled = false;
|
||||
}catch(e){
|
||||
taskSelect.innerHTML = '<option value="">No task</option>';
|
||||
taskSelect.innerHTML = '<option value="">' + noTaskText + '</option>';
|
||||
taskSelect.disabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,24 +121,24 @@
|
||||
|
||||
<div>
|
||||
<label for="task_id" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Task <span class="text-text-muted-light dark:text-text-muted-dark">(Optional)</span>
|
||||
{{ _('Task') }} <span class="text-text-muted-light dark:text-text-muted-dark">({{ _('Optional') }})</span>
|
||||
</label>
|
||||
<select name="task_id" id="task_id" class="form-input w-full">
|
||||
<option value="">No task</option>
|
||||
<option value="">{{ _('No task') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="notes" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Notes <span class="text-text-muted-light dark:text-text-muted-dark">(Optional)</span>
|
||||
{{ _('Notes') }} <span class="text-text-muted-light dark:text-text-muted-dark">({{ _('Optional') }})</span>
|
||||
</label>
|
||||
<textarea name="notes" id="notes" rows="3" class="form-input w-full" placeholder="Add notes about what you're working on..."></textarea>
|
||||
<textarea name="notes" id="notes" rows="3" class="form-input w-full" placeholder="{{ _('Add notes about what you\'re working on...') }}"></textarea>
|
||||
</div>
|
||||
|
||||
{% if templates %}
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Or use a template
|
||||
{{ _('Or use a template') }}
|
||||
</label>
|
||||
<div class="space-y-2">
|
||||
{% for template in templates %}
|
||||
|
||||
@@ -80,8 +80,15 @@ def register_context_processors(app):
|
||||
current_locale = 'en'
|
||||
# Normalize to short code for comparisons (e.g., 'en' from 'en_US')
|
||||
short_locale = (current_locale.split('_', 1)[0] if current_locale else 'en')
|
||||
|
||||
# Reverse-map normalized locale codes back to config keys for label lookup
|
||||
# 'nb' (used by Flask-Babel) should map back to 'no' (used in LANGUAGES config)
|
||||
display_locale = short_locale
|
||||
if short_locale == 'nb':
|
||||
display_locale = 'no'
|
||||
|
||||
available_languages = current_app.config.get('LANGUAGES', {}) or {}
|
||||
current_language_label = available_languages.get(short_locale, short_locale.upper())
|
||||
current_language_label = available_languages.get(display_locale, short_locale.upper())
|
||||
|
||||
# Check if current language is RTL
|
||||
rtl_languages = current_app.config.get('RTL_LANGUAGES', set())
|
||||
@@ -94,7 +101,7 @@ def register_context_processors(app):
|
||||
'timezone_offset': get_timezone_offset_for_timezone(timezone_name),
|
||||
'user_timezone': user_timezone,
|
||||
'current_locale': current_locale,
|
||||
'current_language_code': short_locale,
|
||||
'current_language_code': display_locale, # Use display locale (e.g., 'no' not 'nb')
|
||||
'current_language_label': current_language_label,
|
||||
'is_rtl': is_rtl,
|
||||
'available_languages': available_languages,
|
||||
|
||||
@@ -28,7 +28,15 @@ def compile_po_to_mo(po_path: str, mo_path: str) -> bool:
|
||||
with open(mo_path, 'wb') as mo_file:
|
||||
write_mo(mo_file, catalog)
|
||||
return True
|
||||
except Exception:
|
||||
except ImportError:
|
||||
# Babel not installed - this is expected in some environments
|
||||
# The app will fall back to English if .mo files don't exist
|
||||
return False
|
||||
except Exception as e:
|
||||
# Log the actual error for debugging
|
||||
import logging
|
||||
logger = logging.getLogger('timetracker')
|
||||
logger.warning(f"Error compiling {po_path}: {e}")
|
||||
return False
|
||||
|
||||
|
||||
@@ -52,9 +60,23 @@ def ensure_translations_compiled(translations_dir: str) -> None:
|
||||
po_path = os.path.join(lang_dir, 'messages.po')
|
||||
mo_path = os.path.join(lang_dir, 'messages.mo')
|
||||
if os.path.exists(po_path) and _needs_compile(po_path, mo_path):
|
||||
compile_po_to_mo(po_path, mo_path)
|
||||
except Exception:
|
||||
import logging
|
||||
logger = logging.getLogger('timetracker')
|
||||
logger.info(f"Compiling translations for {lang}...")
|
||||
success = compile_po_to_mo(po_path, mo_path)
|
||||
if success:
|
||||
if os.path.exists(mo_path):
|
||||
logger.info(f"Successfully compiled translations for {lang}")
|
||||
else:
|
||||
logger.warning(f"Compilation reported success but {mo_path} not found")
|
||||
else:
|
||||
# Log failure - this is important for debugging
|
||||
logger.warning(f"Failed to compile translations for {lang} - translations may not work. Check if Babel is installed.")
|
||||
except Exception as e:
|
||||
# Non-fatal; i18n will fall back to msgid if mo missing
|
||||
import logging
|
||||
logger = logging.getLogger('timetracker')
|
||||
logger.warning(f"Error compiling translations: {e}")
|
||||
pass
|
||||
|
||||
|
||||
|
||||
2
setup.py
2
setup.py
@@ -7,7 +7,7 @@ from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name='timetracker',
|
||||
version='3.10.1',
|
||||
version='3.10.2',
|
||||
packages=find_packages(),
|
||||
include_package_data=True,
|
||||
install_requires=[
|
||||
|
||||
@@ -294,7 +294,7 @@ msgid "Company Logo"
|
||||
msgstr "شعار الشركة"
|
||||
|
||||
msgid "DryTrix Logo"
|
||||
msgstr "شعار DryTrix"
|
||||
msgstr "DryTrix Logo"
|
||||
|
||||
msgid "TimeTracker"
|
||||
msgstr "TimeTracker"
|
||||
@@ -479,3 +479,226 @@ msgstr "%(app)s هو تطبيق ويب لتتبع الوقت مصمم للاست
|
||||
msgid "Learn more about "
|
||||
msgstr "تعرف على المزيد حول "
|
||||
|
||||
#: app/templates/setup/initial_setup.html:30
|
||||
msgid "Privacy First"
|
||||
msgstr "الخصوصية أولاً"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:35
|
||||
msgid "Self-hosted on your server"
|
||||
msgstr "مستضاف ذاتياً على خادمك"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:41
|
||||
msgid "Anonymous telemetry (opt-in)"
|
||||
msgstr "قياس مجهول (اختياري)"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:47
|
||||
msgid "No PII collected ever"
|
||||
msgstr "لا يتم جمع بيانات شخصية أبداً"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:53
|
||||
msgid "Open source & transparent"
|
||||
msgstr "مفتوح المصدر وشفاف"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:61
|
||||
msgid "Welcome to TimeTracker"
|
||||
msgstr "مرحباً بك في TimeTracker"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:62
|
||||
msgid "Let's get you set up in just a moment"
|
||||
msgstr "دعنا نعدك في لحظة"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:69
|
||||
msgid "Thank you for choosing TimeTracker!"
|
||||
msgstr "شكراً لاختيارك TimeTracker!"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:71
|
||||
msgid "Your data stays on your server, and you have complete control."
|
||||
msgstr "تبقى بياناتك على خادمك ولديك سيطرة كاملة."
|
||||
|
||||
#: app/templates/setup/initial_setup.html:77
|
||||
msgid "Help Us Improve (Optional)"
|
||||
msgstr "ساعدنا على التحسين (اختياري)"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:83
|
||||
msgid "Enable anonymous telemetry"
|
||||
msgstr "تفعيل القياس المجهول"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:85
|
||||
msgid "Help us understand usage patterns to improve TimeTracker"
|
||||
msgstr "ساعدنا على فهم أنماط الاستخدام لتحسين TimeTracker"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:94
|
||||
msgid "What data is collected?"
|
||||
msgstr "ما هي البيانات التي يتم جمعها؟"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:98
|
||||
msgid "What we collect:"
|
||||
msgstr "ما نجمع:"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:100
|
||||
msgid "Anonymous installation fingerprint (hashed)"
|
||||
msgstr "بصمة تثبيت مجهولة (مشفرة)"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:101
|
||||
msgid "Application version & platform info"
|
||||
msgstr "إصدار التطبيق ومعلومات المنصة"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:102
|
||||
msgid "Feature usage statistics"
|
||||
msgstr "إحصائيات استخدام الميزات"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:103
|
||||
msgid "Internal numeric IDs only"
|
||||
msgstr "معرفات رقمية داخلية فقط"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:108
|
||||
msgid "What we DON'T collect:"
|
||||
msgstr "ما لا نجمع:"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:110
|
||||
msgid "No usernames or emails"
|
||||
msgstr "لا أسماء مستخدمين أو بريد إلكتروني"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:111
|
||||
msgid "No project names or descriptions"
|
||||
msgstr "لا أسماء مشاريع أو أوصاف"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:112
|
||||
msgid "No time entry data or notes"
|
||||
msgstr "لا بيانات إدخال الوقت أو ملاحظات"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:113
|
||||
msgid "No client or business data"
|
||||
msgstr "لا بيانات عميل أو أعمال"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:114
|
||||
msgid "No IP addresses or PII"
|
||||
msgstr "لا عناوين IP أو بيانات شخصية"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:120
|
||||
msgid "Why?"
|
||||
msgstr "لماذا؟"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:120
|
||||
msgid "Anonymous usage data helps us prioritize features and fix issues."
|
||||
msgstr "بيانات الاستخدام المجهولة تساعدنا على تحديد أولويات الميزات وإصلاح المشكلات."
|
||||
|
||||
#: app/templates/setup/initial_setup.html:121
|
||||
msgid "You can change this anytime in"
|
||||
msgstr "يمكنك تغيير هذا في أي وقت في"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:121
|
||||
msgid "Admin → Settings"
|
||||
msgstr "المسؤول → الإعدادات"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:121
|
||||
msgid "Privacy & Analytics section"
|
||||
msgstr "قسم الخصوصية والتحليلات"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:131
|
||||
msgid "Complete Setup & Continue"
|
||||
msgstr "إكمال الإعداد والمتابعة"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:135
|
||||
msgid "By continuing, you agree to use TimeTracker under the"
|
||||
msgstr "بالمتابعة، أنت توافق على استخدام TimeTracker بموجب"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:135
|
||||
msgid "GPL-3.0 License"
|
||||
msgstr "رخصة GPL-3.0"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:12
|
||||
msgid "Duplicate Time Entry"
|
||||
msgstr "تكرار إدخال الوقت"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:12
|
||||
msgid "Log Time Manually"
|
||||
msgstr "تسجيل الوقت يدوياً"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:13
|
||||
msgid "Create a copy of a previous entry with new times"
|
||||
msgstr "إنشاء نسخة من إدخال سابق بأوقات جديدة"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:13
|
||||
msgid "Create a new time entry"
|
||||
msgstr "إنشاء إدخال وقت جديد"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:24
|
||||
msgid "Duplicating entry:"
|
||||
msgstr "تكرار الإدخال:"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:27
|
||||
msgid "Original:"
|
||||
msgstr "الأصلي:"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:27
|
||||
msgid "to"
|
||||
msgstr "إلى"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:27
|
||||
msgid "N/A"
|
||||
msgstr "غير متاح"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:82
|
||||
msgid "What did you work on?"
|
||||
msgstr "على ماذا عملت؟"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:87
|
||||
msgid "tag1, tag2"
|
||||
msgstr "tag1, tag2"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:131
|
||||
msgid "Failed to load tasks"
|
||||
msgstr "فشل تحميل المهام"
|
||||
|
||||
#: app/templates/tasks/list.html:299
|
||||
msgid "Move Selected Tasks to Project"
|
||||
msgstr "نقل المهام المحددة إلى المشروع"
|
||||
|
||||
#: app/templates/tasks/list.html:309
|
||||
msgid "Move Tasks"
|
||||
msgstr "نقل المهام"
|
||||
|
||||
#: app/templates/timer/timer_page.html:135
|
||||
msgid "Add notes about what you're working on..."
|
||||
msgstr "أضف ملاحظات حول ما تعمل عليه..."
|
||||
|
||||
#: app/templates/admin/email_templates/edit.html:36
|
||||
#: app/templates/admin/email_templates/create.html:38
|
||||
msgid "Set as default template"
|
||||
msgstr "تعيين كقالب افتراضي"
|
||||
|
||||
#: app/templates/admin/email_templates/edit.html:44
|
||||
#: app/templates/admin/email_templates/create.html:46
|
||||
#: app/templates/admin/email_templates/view.html:56
|
||||
msgid "HTML Template"
|
||||
msgstr "قالب HTML"
|
||||
|
||||
#: app/templates/admin/email_templates/edit.html:47
|
||||
#: app/templates/admin/email_templates/create.html:49
|
||||
msgid "Invoice Number"
|
||||
msgstr "رقم الفاتورة"
|
||||
|
||||
#: app/templates/admin/email_templates/edit.html:50
|
||||
#: app/templates/admin/email_templates/create.html:52
|
||||
msgid "Company Name"
|
||||
msgstr "اسم الشركة"
|
||||
|
||||
#: app/templates/admin/email_templates/edit.html:53
|
||||
#: app/templates/admin/email_templates/create.html:55
|
||||
msgid "Custom Message"
|
||||
msgstr "رسالة مخصصة"
|
||||
|
||||
#: app/templates/admin/email_templates/edit.html:56
|
||||
#: app/templates/admin/email_templates/create.html:58
|
||||
msgid "More Variables"
|
||||
msgstr "المزيد من المتغيرات"
|
||||
|
||||
#: app/templates/invoices/list.html:72
|
||||
msgid "Invoice number or client"
|
||||
msgstr "رقم الفاتورة أو العميل"
|
||||
|
||||
#: app/templates/expenses/form.html:231
|
||||
msgid "Receipt/Invoice Number"
|
||||
msgstr "رقم الإيصال/الفاتورة"
|
||||
|
||||
|
||||
@@ -478,3 +478,226 @@ msgstr "%(app)s ist eine webbasierte Zeiterfassungsanwendung für die interne Nu
|
||||
|
||||
msgid "Learn more about "
|
||||
msgstr "Erfahren Sie mehr über "
|
||||
|
||||
#: app/templates/setup/initial_setup.html:30
|
||||
msgid "Privacy First"
|
||||
msgstr "Datenschutz zuerst"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:35
|
||||
msgid "Self-hosted on your server"
|
||||
msgstr "Selbst gehostet auf Ihrem Server"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:41
|
||||
msgid "Anonymous telemetry (opt-in)"
|
||||
msgstr "Anonyme Telemetrie (Opt-in)"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:47
|
||||
msgid "No PII collected ever"
|
||||
msgstr "Keine personenbezogenen Daten werden jemals gesammelt"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:53
|
||||
msgid "Open source & transparent"
|
||||
msgstr "Open Source & transparent"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:61
|
||||
msgid "Welcome to TimeTracker"
|
||||
msgstr "Willkommen bei TimeTracker"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:62
|
||||
msgid "Let's get you set up in just a moment"
|
||||
msgstr "Lassen Sie uns Sie in einem Moment einrichten"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:69
|
||||
msgid "Thank you for choosing TimeTracker!"
|
||||
msgstr "Vielen Dank, dass Sie TimeTracker gewählt haben!"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:71
|
||||
msgid "Your data stays on your server, and you have complete control."
|
||||
msgstr "Ihre Daten bleiben auf Ihrem Server und Sie haben die vollständige Kontrolle."
|
||||
|
||||
#: app/templates/setup/initial_setup.html:77
|
||||
msgid "Help Us Improve (Optional)"
|
||||
msgstr "Helfen Sie uns zu verbessern (Optional)"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:83
|
||||
msgid "Enable anonymous telemetry"
|
||||
msgstr "Anonyme Telemetrie aktivieren"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:85
|
||||
msgid "Help us understand usage patterns to improve TimeTracker"
|
||||
msgstr "Helfen Sie uns, Nutzungsmuster zu verstehen, um TimeTracker zu verbessern"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:94
|
||||
msgid "What data is collected?"
|
||||
msgstr "Welche Daten werden gesammelt?"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:98
|
||||
msgid "What we collect:"
|
||||
msgstr "Was wir sammeln:"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:100
|
||||
msgid "Anonymous installation fingerprint (hashed)"
|
||||
msgstr "Anonymer Installations-Fingerabdruck (gehasht)"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:101
|
||||
msgid "Application version & platform info"
|
||||
msgstr "Anwendungsversion & Plattforminformationen"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:102
|
||||
msgid "Feature usage statistics"
|
||||
msgstr "Funktionsnutzungsstatistiken"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:103
|
||||
msgid "Internal numeric IDs only"
|
||||
msgstr "Nur interne numerische IDs"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:108
|
||||
msgid "What we DON'T collect:"
|
||||
msgstr "Was wir NICHT sammeln:"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:110
|
||||
msgid "No usernames or emails"
|
||||
msgstr "Keine Benutzernamen oder E-Mails"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:111
|
||||
msgid "No project names or descriptions"
|
||||
msgstr "Keine Projektnamen oder Beschreibungen"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:112
|
||||
msgid "No time entry data or notes"
|
||||
msgstr "Keine Zeiteintragsdaten oder Notizen"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:113
|
||||
msgid "No client or business data"
|
||||
msgstr "Keine Kunden- oder Geschäftsdaten"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:114
|
||||
msgid "No IP addresses or PII"
|
||||
msgstr "Keine IP-Adressen oder personenbezogene Daten"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:120
|
||||
msgid "Why?"
|
||||
msgstr "Warum?"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:120
|
||||
msgid "Anonymous usage data helps us prioritize features and fix issues."
|
||||
msgstr "Anonyme Nutzungsdaten helfen uns, Funktionen zu priorisieren und Probleme zu beheben."
|
||||
|
||||
#: app/templates/setup/initial_setup.html:121
|
||||
msgid "You can change this anytime in"
|
||||
msgstr "Sie können dies jederzeit ändern in"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:121
|
||||
msgid "Admin → Settings"
|
||||
msgstr "Admin → Einstellungen"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:121
|
||||
msgid "Privacy & Analytics section"
|
||||
msgstr "Bereich Datenschutz & Analyse"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:131
|
||||
msgid "Complete Setup & Continue"
|
||||
msgstr "Einrichtung abschließen & fortfahren"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:135
|
||||
msgid "By continuing, you agree to use TimeTracker under the"
|
||||
msgstr "Durch Fortfahren stimmen Sie zu, TimeTracker unter der"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:135
|
||||
msgid "GPL-3.0 License"
|
||||
msgstr "GPL-3.0-Lizenz"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:12
|
||||
msgid "Duplicate Time Entry"
|
||||
msgstr "Zeiteintrag duplizieren"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:12
|
||||
msgid "Log Time Manually"
|
||||
msgstr "Zeit manuell erfassen"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:13
|
||||
msgid "Create a copy of a previous entry with new times"
|
||||
msgstr "Eine Kopie eines vorherigen Eintrags mit neuen Zeiten erstellen"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:13
|
||||
msgid "Create a new time entry"
|
||||
msgstr "Neuen Zeiteintrag erstellen"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:24
|
||||
msgid "Duplicating entry:"
|
||||
msgstr "Eintrag duplizieren:"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:27
|
||||
msgid "Original:"
|
||||
msgstr "Original:"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:27
|
||||
msgid "to"
|
||||
msgstr "bis"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:27
|
||||
msgid "N/A"
|
||||
msgstr "N/V"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:82
|
||||
msgid "What did you work on?"
|
||||
msgstr "Woran haben Sie gearbeitet?"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:87
|
||||
msgid "tag1, tag2"
|
||||
msgstr "tag1, tag2"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:131
|
||||
msgid "Failed to load tasks"
|
||||
msgstr "Aufgaben konnten nicht geladen werden"
|
||||
|
||||
#: app/templates/tasks/list.html:299
|
||||
msgid "Move Selected Tasks to Project"
|
||||
msgstr "Ausgewählte Aufgaben zum Projekt verschieben"
|
||||
|
||||
#: app/templates/tasks/list.html:309
|
||||
msgid "Move Tasks"
|
||||
msgstr "Aufgaben verschieben"
|
||||
|
||||
#: app/templates/timer/timer_page.html:135
|
||||
msgid "Add notes about what you're working on..."
|
||||
msgstr "Notizen hinzufügen, woran Sie arbeiten..."
|
||||
|
||||
#: app/templates/admin/email_templates/edit.html:36
|
||||
#: app/templates/admin/email_templates/create.html:38
|
||||
msgid "Set as default template"
|
||||
msgstr "Als Standardvorlage festlegen"
|
||||
|
||||
#: app/templates/admin/email_templates/edit.html:44
|
||||
#: app/templates/admin/email_templates/create.html:46
|
||||
#: app/templates/admin/email_templates/view.html:56
|
||||
msgid "HTML Template"
|
||||
msgstr "HTML-Vorlage"
|
||||
|
||||
#: app/templates/admin/email_templates/edit.html:47
|
||||
#: app/templates/admin/email_templates/create.html:49
|
||||
msgid "Invoice Number"
|
||||
msgstr "Rechnungsnummer"
|
||||
|
||||
#: app/templates/admin/email_templates/edit.html:50
|
||||
#: app/templates/admin/email_templates/create.html:52
|
||||
msgid "Company Name"
|
||||
msgstr "Firmenname"
|
||||
|
||||
#: app/templates/admin/email_templates/edit.html:53
|
||||
#: app/templates/admin/email_templates/create.html:55
|
||||
msgid "Custom Message"
|
||||
msgstr "Benutzerdefinierte Nachricht"
|
||||
|
||||
#: app/templates/admin/email_templates/edit.html:56
|
||||
#: app/templates/admin/email_templates/create.html:58
|
||||
msgid "More Variables"
|
||||
msgstr "Weitere Variablen"
|
||||
|
||||
#: app/templates/invoices/list.html:72
|
||||
msgid "Invoice number or client"
|
||||
msgstr "Rechnungsnummer oder Kunde"
|
||||
|
||||
#: app/templates/expenses/form.html:231
|
||||
msgid "Receipt/Invoice Number"
|
||||
msgstr "Beleg-/Rechnungsnummer"
|
||||
@@ -10555,6 +10555,229 @@ msgstr ""
|
||||
#~ "tracking application designed for internal "
|
||||
#~ "use within organizations."
|
||||
|
||||
#: app/templates/setup/initial_setup.html:30
|
||||
msgid "Privacy First"
|
||||
msgstr "Privacy First"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:35
|
||||
msgid "Self-hosted on your server"
|
||||
msgstr "Self-hosted on your server"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:41
|
||||
msgid "Anonymous telemetry (opt-in)"
|
||||
msgstr "Anonymous telemetry (opt-in)"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:47
|
||||
msgid "No PII collected ever"
|
||||
msgstr "No PII collected ever"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:53
|
||||
msgid "Open source & transparent"
|
||||
msgstr "Open source & transparent"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:61
|
||||
msgid "Welcome to TimeTracker"
|
||||
msgstr "Welcome to TimeTracker"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:62
|
||||
msgid "Let's get you set up in just a moment"
|
||||
msgstr "Let's get you set up in just a moment"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:69
|
||||
msgid "Thank you for choosing TimeTracker!"
|
||||
msgstr "Thank you for choosing TimeTracker!"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:71
|
||||
msgid "Your data stays on your server, and you have complete control."
|
||||
msgstr "Your data stays on your server, and you have complete control."
|
||||
|
||||
#: app/templates/setup/initial_setup.html:77
|
||||
msgid "Help Us Improve (Optional)"
|
||||
msgstr "Help Us Improve (Optional)"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:83
|
||||
msgid "Enable anonymous telemetry"
|
||||
msgstr "Enable anonymous telemetry"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:85
|
||||
msgid "Help us understand usage patterns to improve TimeTracker"
|
||||
msgstr "Help us understand usage patterns to improve TimeTracker"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:94
|
||||
msgid "What data is collected?"
|
||||
msgstr "What data is collected?"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:98
|
||||
msgid "What we collect:"
|
||||
msgstr "What we collect:"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:100
|
||||
msgid "Anonymous installation fingerprint (hashed)"
|
||||
msgstr "Anonymous installation fingerprint (hashed)"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:101
|
||||
msgid "Application version & platform info"
|
||||
msgstr "Application version & platform info"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:102
|
||||
msgid "Feature usage statistics"
|
||||
msgstr "Feature usage statistics"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:103
|
||||
msgid "Internal numeric IDs only"
|
||||
msgstr "Internal numeric IDs only"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:108
|
||||
msgid "What we DON'T collect:"
|
||||
msgstr "What we DON'T collect:"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:110
|
||||
msgid "No usernames or emails"
|
||||
msgstr "No usernames or emails"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:111
|
||||
msgid "No project names or descriptions"
|
||||
msgstr "No project names or descriptions"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:112
|
||||
msgid "No time entry data or notes"
|
||||
msgstr "No time entry data or notes"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:113
|
||||
msgid "No client or business data"
|
||||
msgstr "No client or business data"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:114
|
||||
msgid "No IP addresses or PII"
|
||||
msgstr "No IP addresses or PII"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:120
|
||||
msgid "Why?"
|
||||
msgstr "Why?"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:120
|
||||
msgid "Anonymous usage data helps us prioritize features and fix issues."
|
||||
msgstr "Anonymous usage data helps us prioritize features and fix issues."
|
||||
|
||||
#: app/templates/setup/initial_setup.html:121
|
||||
msgid "You can change this anytime in"
|
||||
msgstr "You can change this anytime in"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:121
|
||||
msgid "Admin → Settings"
|
||||
msgstr "Admin → Settings"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:121
|
||||
msgid "Privacy & Analytics section"
|
||||
msgstr "Privacy & Analytics section"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:131
|
||||
msgid "Complete Setup & Continue"
|
||||
msgstr "Complete Setup & Continue"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:135
|
||||
msgid "By continuing, you agree to use TimeTracker under the"
|
||||
msgstr "By continuing, you agree to use TimeTracker under the"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:135
|
||||
msgid "GPL-3.0 License"
|
||||
msgstr "GPL-3.0 License"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:12
|
||||
msgid "Duplicate Time Entry"
|
||||
msgstr "Duplicate Time Entry"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:12
|
||||
msgid "Log Time Manually"
|
||||
msgstr "Log Time Manually"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:13
|
||||
msgid "Create a copy of a previous entry with new times"
|
||||
msgstr "Create a copy of a previous entry with new times"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:13
|
||||
msgid "Create a new time entry"
|
||||
msgstr "Create a new time entry"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:24
|
||||
msgid "Duplicating entry:"
|
||||
msgstr "Duplicating entry:"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:27
|
||||
msgid "Original:"
|
||||
msgstr "Original:"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:27
|
||||
msgid "to"
|
||||
msgstr "to"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:27
|
||||
msgid "N/A"
|
||||
msgstr "N/A"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:82
|
||||
msgid "What did you work on?"
|
||||
msgstr "What did you work on?"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:87
|
||||
msgid "tag1, tag2"
|
||||
msgstr "tag1, tag2"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:131
|
||||
msgid "Failed to load tasks"
|
||||
msgstr "Failed to load tasks"
|
||||
|
||||
#: app/templates/tasks/list.html:299
|
||||
msgid "Move Selected Tasks to Project"
|
||||
msgstr "Move Selected Tasks to Project"
|
||||
|
||||
#: app/templates/tasks/list.html:309
|
||||
msgid "Move Tasks"
|
||||
msgstr "Move Tasks"
|
||||
|
||||
#: app/templates/timer/timer_page.html:135
|
||||
msgid "Add notes about what you're working on..."
|
||||
msgstr "Add notes about what you're working on..."
|
||||
|
||||
#: app/templates/admin/email_templates/edit.html:36
|
||||
#: app/templates/admin/email_templates/create.html:38
|
||||
msgid "Set as default template"
|
||||
msgstr "Set as default template"
|
||||
|
||||
#: app/templates/admin/email_templates/edit.html:44
|
||||
#: app/templates/admin/email_templates/create.html:46
|
||||
#: app/templates/admin/email_templates/view.html:56
|
||||
msgid "HTML Template"
|
||||
msgstr "HTML Template"
|
||||
|
||||
#: app/templates/admin/email_templates/edit.html:47
|
||||
#: app/templates/admin/email_templates/create.html:49
|
||||
msgid "Invoice Number"
|
||||
msgstr "Invoice Number"
|
||||
|
||||
#: app/templates/admin/email_templates/edit.html:50
|
||||
#: app/templates/admin/email_templates/create.html:52
|
||||
msgid "Company Name"
|
||||
msgstr "Company Name"
|
||||
|
||||
#: app/templates/admin/email_templates/edit.html:53
|
||||
#: app/templates/admin/email_templates/create.html:55
|
||||
msgid "Custom Message"
|
||||
msgstr "Custom Message"
|
||||
|
||||
#: app/templates/admin/email_templates/edit.html:56
|
||||
#: app/templates/admin/email_templates/create.html:58
|
||||
msgid "More Variables"
|
||||
msgstr "More Variables"
|
||||
|
||||
#: app/templates/invoices/list.html:72
|
||||
msgid "Invoice number or client"
|
||||
msgstr "Invoice number or client"
|
||||
|
||||
#: app/templates/expenses/form.html:231
|
||||
msgid "Receipt/Invoice Number"
|
||||
msgstr "Receipt/Invoice Number"
|
||||
|
||||
#~ msgid "Learn more about "
|
||||
#~ msgstr "Learn more about "
|
||||
|
||||
|
||||
@@ -294,7 +294,7 @@ msgid "Company Logo"
|
||||
msgstr "Logo de la Empresa"
|
||||
|
||||
msgid "DryTrix Logo"
|
||||
msgstr "Logo de DryTrix"
|
||||
msgstr "DryTrix Logo"
|
||||
|
||||
msgid "TimeTracker"
|
||||
msgstr "TimeTracker"
|
||||
@@ -479,3 +479,226 @@ msgstr "%(app)s es una aplicación web de seguimiento del tiempo diseñada para
|
||||
msgid "Learn more about "
|
||||
msgstr "Más información sobre "
|
||||
|
||||
#: app/templates/setup/initial_setup.html:30
|
||||
msgid "Privacy First"
|
||||
msgstr "Privacidad primero"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:35
|
||||
msgid "Self-hosted on your server"
|
||||
msgstr "Alojado en su servidor"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:41
|
||||
msgid "Anonymous telemetry (opt-in)"
|
||||
msgstr "Telemetría anónima (opt-in)"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:47
|
||||
msgid "No PII collected ever"
|
||||
msgstr "Nunca se recopilan datos personales"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:53
|
||||
msgid "Open source & transparent"
|
||||
msgstr "Código abierto y transparente"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:61
|
||||
msgid "Welcome to TimeTracker"
|
||||
msgstr "Bienvenido a TimeTracker"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:62
|
||||
msgid "Let's get you set up in just a moment"
|
||||
msgstr "Configuremos en un momento"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:69
|
||||
msgid "Thank you for choosing TimeTracker!"
|
||||
msgstr "¡Gracias por elegir TimeTracker!"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:71
|
||||
msgid "Your data stays on your server, and you have complete control."
|
||||
msgstr "Sus datos permanecen en su servidor y tiene control total."
|
||||
|
||||
#: app/templates/setup/initial_setup.html:77
|
||||
msgid "Help Us Improve (Optional)"
|
||||
msgstr "Ayúdenos a mejorar (Opcional)"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:83
|
||||
msgid "Enable anonymous telemetry"
|
||||
msgstr "Habilitar telemetría anónima"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:85
|
||||
msgid "Help us understand usage patterns to improve TimeTracker"
|
||||
msgstr "Ayúdenos a entender los patrones de uso para mejorar TimeTracker"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:94
|
||||
msgid "What data is collected?"
|
||||
msgstr "¿Qué datos se recopilan?"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:98
|
||||
msgid "What we collect:"
|
||||
msgstr "Lo que recopilamos:"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:100
|
||||
msgid "Anonymous installation fingerprint (hashed)"
|
||||
msgstr "Huella digital de instalación anónima (hash)"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:101
|
||||
msgid "Application version & platform info"
|
||||
msgstr "Versión de la aplicación e información de la plataforma"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:102
|
||||
msgid "Feature usage statistics"
|
||||
msgstr "Estadísticas de uso de funciones"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:103
|
||||
msgid "Internal numeric IDs only"
|
||||
msgstr "Solo ID numéricos internos"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:108
|
||||
msgid "What we DON'T collect:"
|
||||
msgstr "Lo que NO recopilamos:"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:110
|
||||
msgid "No usernames or emails"
|
||||
msgstr "Sin nombres de usuario ni correos electrónicos"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:111
|
||||
msgid "No project names or descriptions"
|
||||
msgstr "Sin nombres o descripciones de proyectos"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:112
|
||||
msgid "No time entry data or notes"
|
||||
msgstr "Sin datos de entrada de tiempo ni notas"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:113
|
||||
msgid "No client or business data"
|
||||
msgstr "Sin datos de clientes o comerciales"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:114
|
||||
msgid "No IP addresses or PII"
|
||||
msgstr "Sin direcciones IP ni datos personales"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:120
|
||||
msgid "Why?"
|
||||
msgstr "¿Por qué?"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:120
|
||||
msgid "Anonymous usage data helps us prioritize features and fix issues."
|
||||
msgstr "Los datos de uso anónimos nos ayudan a priorizar funciones y corregir problemas."
|
||||
|
||||
#: app/templates/setup/initial_setup.html:121
|
||||
msgid "You can change this anytime in"
|
||||
msgstr "Puede cambiar esto en cualquier momento en"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:121
|
||||
msgid "Admin → Settings"
|
||||
msgstr "Admin → Configuración"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:121
|
||||
msgid "Privacy & Analytics section"
|
||||
msgstr "Sección de Privacidad y Análisis"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:131
|
||||
msgid "Complete Setup & Continue"
|
||||
msgstr "Completar configuración y continuar"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:135
|
||||
msgid "By continuing, you agree to use TimeTracker under the"
|
||||
msgstr "Al continuar, acepta usar TimeTracker bajo la"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:135
|
||||
msgid "GPL-3.0 License"
|
||||
msgstr "Licencia GPL-3.0"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:12
|
||||
msgid "Duplicate Time Entry"
|
||||
msgstr "Duplicar entrada de tiempo"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:12
|
||||
msgid "Log Time Manually"
|
||||
msgstr "Registrar tiempo manualmente"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:13
|
||||
msgid "Create a copy of a previous entry with new times"
|
||||
msgstr "Crear una copia de una entrada anterior con nuevos horarios"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:13
|
||||
msgid "Create a new time entry"
|
||||
msgstr "Crear una nueva entrada de tiempo"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:24
|
||||
msgid "Duplicating entry:"
|
||||
msgstr "Duplicando entrada:"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:27
|
||||
msgid "Original:"
|
||||
msgstr "Original:"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:27
|
||||
msgid "to"
|
||||
msgstr "a"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:27
|
||||
msgid "N/A"
|
||||
msgstr "N/D"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:82
|
||||
msgid "What did you work on?"
|
||||
msgstr "¿En qué trabajaste?"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:87
|
||||
msgid "tag1, tag2"
|
||||
msgstr "tag1, tag2"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:131
|
||||
msgid "Failed to load tasks"
|
||||
msgstr "Error al cargar tareas"
|
||||
|
||||
#: app/templates/tasks/list.html:299
|
||||
msgid "Move Selected Tasks to Project"
|
||||
msgstr "Mover tareas seleccionadas al proyecto"
|
||||
|
||||
#: app/templates/tasks/list.html:309
|
||||
msgid "Move Tasks"
|
||||
msgstr "Mover tareas"
|
||||
|
||||
#: app/templates/timer/timer_page.html:135
|
||||
msgid "Add notes about what you're working on..."
|
||||
msgstr "Agregar notas sobre en qué estás trabajando..."
|
||||
|
||||
#: app/templates/admin/email_templates/edit.html:36
|
||||
#: app/templates/admin/email_templates/create.html:38
|
||||
msgid "Set as default template"
|
||||
msgstr "Establecer como plantilla predeterminada"
|
||||
|
||||
#: app/templates/admin/email_templates/edit.html:44
|
||||
#: app/templates/admin/email_templates/create.html:46
|
||||
#: app/templates/admin/email_templates/view.html:56
|
||||
msgid "HTML Template"
|
||||
msgstr "Plantilla HTML"
|
||||
|
||||
#: app/templates/admin/email_templates/edit.html:47
|
||||
#: app/templates/admin/email_templates/create.html:49
|
||||
msgid "Invoice Number"
|
||||
msgstr "Número de factura"
|
||||
|
||||
#: app/templates/admin/email_templates/edit.html:50
|
||||
#: app/templates/admin/email_templates/create.html:52
|
||||
msgid "Company Name"
|
||||
msgstr "Nombre de la empresa"
|
||||
|
||||
#: app/templates/admin/email_templates/edit.html:53
|
||||
#: app/templates/admin/email_templates/create.html:55
|
||||
msgid "Custom Message"
|
||||
msgstr "Mensaje personalizado"
|
||||
|
||||
#: app/templates/admin/email_templates/edit.html:56
|
||||
#: app/templates/admin/email_templates/create.html:58
|
||||
msgid "More Variables"
|
||||
msgstr "Más variables"
|
||||
|
||||
#: app/templates/invoices/list.html:72
|
||||
msgid "Invoice number or client"
|
||||
msgstr "Número de factura o cliente"
|
||||
|
||||
#: app/templates/expenses/form.html:231
|
||||
msgid "Receipt/Invoice Number"
|
||||
msgstr "Número de recibo/factura"
|
||||
|
||||
|
||||
@@ -478,3 +478,226 @@ msgstr "%(app)s on verkkopohjainen ajanseurantasovellus, joka on suunniteltu org
|
||||
|
||||
msgid "Learn more about "
|
||||
msgstr "Lue lisää "
|
||||
|
||||
#: app/templates/setup/initial_setup.html:30
|
||||
msgid "Privacy First"
|
||||
msgstr "Yksityisyys ensin"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:35
|
||||
msgid "Self-hosted on your server"
|
||||
msgstr "Oma palvelin"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:41
|
||||
msgid "Anonymous telemetry (opt-in)"
|
||||
msgstr "Anonyymi telemetria (valinnainen)"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:47
|
||||
msgid "No PII collected ever"
|
||||
msgstr "Henkilötietoja ei koskaan kerätä"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:53
|
||||
msgid "Open source & transparent"
|
||||
msgstr "Avoin lähdekoodi ja läpinäkyvä"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:61
|
||||
msgid "Welcome to TimeTracker"
|
||||
msgstr "Tervetuloa TimeTrackeriin"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:62
|
||||
msgid "Let's get you set up in just a moment"
|
||||
msgstr "Asetetaan sinut hetkessä käyttöön"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:69
|
||||
msgid "Thank you for choosing TimeTracker!"
|
||||
msgstr "Kiitos, että valitsit TimeTrackerin!"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:71
|
||||
msgid "Your data stays on your server, and you have complete control."
|
||||
msgstr "Tietosi pysyvät palvelimellasi ja sinulla on täysi kontrolli."
|
||||
|
||||
#: app/templates/setup/initial_setup.html:77
|
||||
msgid "Help Us Improve (Optional)"
|
||||
msgstr "Auta meitä parantamaan (Valinnainen)"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:83
|
||||
msgid "Enable anonymous telemetry"
|
||||
msgstr "Ota anonyymi telemetria käyttöön"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:85
|
||||
msgid "Help us understand usage patterns to improve TimeTracker"
|
||||
msgstr "Auta meitä ymmärtämään käyttömalleja parantaaksemme TimeTrackeria"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:94
|
||||
msgid "What data is collected?"
|
||||
msgstr "Mitä tietoja kerätään?"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:98
|
||||
msgid "What we collect:"
|
||||
msgstr "Mitä keräämme:"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:100
|
||||
msgid "Anonymous installation fingerprint (hashed)"
|
||||
msgstr "Anonyymi asennuksen sormenjälki (hash)"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:101
|
||||
msgid "Application version & platform info"
|
||||
msgstr "Sovelluksen versio ja alustatiedot"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:102
|
||||
msgid "Feature usage statistics"
|
||||
msgstr "Ominaisuuksien käyttötilastot"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:103
|
||||
msgid "Internal numeric IDs only"
|
||||
msgstr "Vain sisäiset numeeriset ID:t"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:108
|
||||
msgid "What we DON'T collect:"
|
||||
msgstr "Mitä emme kerää:"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:110
|
||||
msgid "No usernames or emails"
|
||||
msgstr "Ei käyttäjänimiä tai sähköposteja"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:111
|
||||
msgid "No project names or descriptions"
|
||||
msgstr "Ei projektinimiä tai kuvauksia"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:112
|
||||
msgid "No time entry data or notes"
|
||||
msgstr "Ei ajan kirjaustietoja tai muistiinpanoja"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:113
|
||||
msgid "No client or business data"
|
||||
msgstr "Ei asiakas- tai liiketoimintatietoja"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:114
|
||||
msgid "No IP addresses or PII"
|
||||
msgstr "Ei IP-osoitteita tai henkilötietoja"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:120
|
||||
msgid "Why?"
|
||||
msgstr "Miksi?"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:120
|
||||
msgid "Anonymous usage data helps us prioritize features and fix issues."
|
||||
msgstr "Anonyymi käyttötieto auttaa meitä priorisoimaan ominaisuuksia ja korjaamaan ongelmia."
|
||||
|
||||
#: app/templates/setup/initial_setup.html:121
|
||||
msgid "You can change this anytime in"
|
||||
msgstr "Voit muuttaa tämän milloin tahansa"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:121
|
||||
msgid "Admin → Settings"
|
||||
msgstr "Admin → Asetukset"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:121
|
||||
msgid "Privacy & Analytics section"
|
||||
msgstr "Yksityisyys ja analytiikka -osio"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:131
|
||||
msgid "Complete Setup & Continue"
|
||||
msgstr "Viimeistele asennus ja jatka"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:135
|
||||
msgid "By continuing, you agree to use TimeTracker under the"
|
||||
msgstr "Jatkamalla hyväksyt TimeTrackerin käytön"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:135
|
||||
msgid "GPL-3.0 License"
|
||||
msgstr "GPL-3.0-lisenssi"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:12
|
||||
msgid "Duplicate Time Entry"
|
||||
msgstr "Monista ajan kirjaus"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:12
|
||||
msgid "Log Time Manually"
|
||||
msgstr "Kirjaa aika manuaalisesti"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:13
|
||||
msgid "Create a copy of a previous entry with new times"
|
||||
msgstr "Luo kopio aiemmasta merkinnästä uusilla ajoilla"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:13
|
||||
msgid "Create a new time entry"
|
||||
msgstr "Luo uusi ajan kirjaus"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:24
|
||||
msgid "Duplicating entry:"
|
||||
msgstr "Monistetaan merkintää:"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:27
|
||||
msgid "Original:"
|
||||
msgstr "Alkuperäinen:"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:27
|
||||
msgid "to"
|
||||
msgstr "–"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:27
|
||||
msgid "N/A"
|
||||
msgstr "E/T"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:82
|
||||
msgid "What did you work on?"
|
||||
msgstr "Mitä teit?"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:87
|
||||
msgid "tag1, tag2"
|
||||
msgstr "tag1, tag2"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:131
|
||||
msgid "Failed to load tasks"
|
||||
msgstr "Tehtävien lataaminen epäonnistui"
|
||||
|
||||
#: app/templates/tasks/list.html:299
|
||||
msgid "Move Selected Tasks to Project"
|
||||
msgstr "Siirrä valitut tehtävät projektiin"
|
||||
|
||||
#: app/templates/tasks/list.html:309
|
||||
msgid "Move Tasks"
|
||||
msgstr "Siirrä tehtävät"
|
||||
|
||||
#: app/templates/timer/timer_page.html:135
|
||||
msgid "Add notes about what you're working on..."
|
||||
msgstr "Lisää muistiinpanoja siitä, mitä teet..."
|
||||
|
||||
#: app/templates/admin/email_templates/edit.html:36
|
||||
#: app/templates/admin/email_templates/create.html:38
|
||||
msgid "Set as default template"
|
||||
msgstr "Aseta oletusmalliksi"
|
||||
|
||||
#: app/templates/admin/email_templates/edit.html:44
|
||||
#: app/templates/admin/email_templates/create.html:46
|
||||
#: app/templates/admin/email_templates/view.html:56
|
||||
msgid "HTML Template"
|
||||
msgstr "HTML-malli"
|
||||
|
||||
#: app/templates/admin/email_templates/edit.html:47
|
||||
#: app/templates/admin/email_templates/create.html:49
|
||||
msgid "Invoice Number"
|
||||
msgstr "Laskunumero"
|
||||
|
||||
#: app/templates/admin/email_templates/edit.html:50
|
||||
#: app/templates/admin/email_templates/create.html:52
|
||||
msgid "Company Name"
|
||||
msgstr "Yrityksen nimi"
|
||||
|
||||
#: app/templates/admin/email_templates/edit.html:53
|
||||
#: app/templates/admin/email_templates/create.html:55
|
||||
msgid "Custom Message"
|
||||
msgstr "Mukautettu viesti"
|
||||
|
||||
#: app/templates/admin/email_templates/edit.html:56
|
||||
#: app/templates/admin/email_templates/create.html:58
|
||||
msgid "More Variables"
|
||||
msgstr "Lisää muuttujia"
|
||||
|
||||
#: app/templates/invoices/list.html:72
|
||||
msgid "Invoice number or client"
|
||||
msgstr "Laskunumero tai asiakas"
|
||||
|
||||
#: app/templates/expenses/form.html:231
|
||||
msgid "Receipt/Invoice Number"
|
||||
msgstr "Kuitti-/Laskunumero"
|
||||
@@ -478,3 +478,226 @@ msgstr "%(app)s est une application web de suivi du temps conçue pour un usage
|
||||
|
||||
msgid "Learn more about "
|
||||
msgstr "En savoir plus sur "
|
||||
|
||||
#: app/templates/setup/initial_setup.html:30
|
||||
msgid "Privacy First"
|
||||
msgstr "Confidentialité d'abord"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:35
|
||||
msgid "Self-hosted on your server"
|
||||
msgstr "Auto-hébergé sur votre serveur"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:41
|
||||
msgid "Anonymous telemetry (opt-in)"
|
||||
msgstr "Télémétrie anonyme (opt-in)"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:47
|
||||
msgid "No PII collected ever"
|
||||
msgstr "Aucune donnée personnelle collectée"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:53
|
||||
msgid "Open source & transparent"
|
||||
msgstr "Open source et transparent"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:61
|
||||
msgid "Welcome to TimeTracker"
|
||||
msgstr "Bienvenue sur TimeTracker"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:62
|
||||
msgid "Let's get you set up in just a moment"
|
||||
msgstr "Configurons-vous en un instant"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:69
|
||||
msgid "Thank you for choosing TimeTracker!"
|
||||
msgstr "Merci d'avoir choisi TimeTracker !"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:71
|
||||
msgid "Your data stays on your server, and you have complete control."
|
||||
msgstr "Vos données restent sur votre serveur et vous avez un contrôle total."
|
||||
|
||||
#: app/templates/setup/initial_setup.html:77
|
||||
msgid "Help Us Improve (Optional)"
|
||||
msgstr "Aidez-nous à nous améliorer (Optionnel)"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:83
|
||||
msgid "Enable anonymous telemetry"
|
||||
msgstr "Activer la télémétrie anonyme"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:85
|
||||
msgid "Help us understand usage patterns to improve TimeTracker"
|
||||
msgstr "Aidez-nous à comprendre les modèles d'utilisation pour améliorer TimeTracker"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:94
|
||||
msgid "What data is collected?"
|
||||
msgstr "Quelles données sont collectées ?"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:98
|
||||
msgid "What we collect:"
|
||||
msgstr "Ce que nous collectons :"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:100
|
||||
msgid "Anonymous installation fingerprint (hashed)"
|
||||
msgstr "Empreinte d'installation anonyme (hachée)"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:101
|
||||
msgid "Application version & platform info"
|
||||
msgstr "Version de l'application et informations sur la plateforme"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:102
|
||||
msgid "Feature usage statistics"
|
||||
msgstr "Statistiques d'utilisation des fonctionnalités"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:103
|
||||
msgid "Internal numeric IDs only"
|
||||
msgstr "ID numériques internes uniquement"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:108
|
||||
msgid "What we DON'T collect:"
|
||||
msgstr "Ce que nous ne collectons PAS :"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:110
|
||||
msgid "No usernames or emails"
|
||||
msgstr "Aucun nom d'utilisateur ou e-mail"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:111
|
||||
msgid "No project names or descriptions"
|
||||
msgstr "Aucun nom ou description de projet"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:112
|
||||
msgid "No time entry data or notes"
|
||||
msgstr "Aucune donnée d'entrée de temps ou note"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:113
|
||||
msgid "No client or business data"
|
||||
msgstr "Aucune donnée client ou commerciale"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:114
|
||||
msgid "No IP addresses or PII"
|
||||
msgstr "Aucune adresse IP ou donnée personnelle"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:120
|
||||
msgid "Why?"
|
||||
msgstr "Pourquoi ?"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:120
|
||||
msgid "Anonymous usage data helps us prioritize features and fix issues."
|
||||
msgstr "Les données d'utilisation anonymes nous aident à prioriser les fonctionnalités et à corriger les problèmes."
|
||||
|
||||
#: app/templates/setup/initial_setup.html:121
|
||||
msgid "You can change this anytime in"
|
||||
msgstr "Vous pouvez modifier cela à tout moment dans"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:121
|
||||
msgid "Admin → Settings"
|
||||
msgstr "Admin → Paramètres"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:121
|
||||
msgid "Privacy & Analytics section"
|
||||
msgstr "Section Confidentialité et Analyses"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:131
|
||||
msgid "Complete Setup & Continue"
|
||||
msgstr "Terminer la configuration et continuer"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:135
|
||||
msgid "By continuing, you agree to use TimeTracker under the"
|
||||
msgstr "En continuant, vous acceptez d'utiliser TimeTracker sous la"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:135
|
||||
msgid "GPL-3.0 License"
|
||||
msgstr "Licence GPL-3.0"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:12
|
||||
msgid "Duplicate Time Entry"
|
||||
msgstr "Dupliquer l'entrée de temps"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:12
|
||||
msgid "Log Time Manually"
|
||||
msgstr "Enregistrer le temps manuellement"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:13
|
||||
msgid "Create a copy of a previous entry with new times"
|
||||
msgstr "Créer une copie d'une entrée précédente avec de nouveaux horaires"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:13
|
||||
msgid "Create a new time entry"
|
||||
msgstr "Créer une nouvelle entrée de temps"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:24
|
||||
msgid "Duplicating entry:"
|
||||
msgstr "Duplication de l'entrée :"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:27
|
||||
msgid "Original:"
|
||||
msgstr "Original :"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:27
|
||||
msgid "to"
|
||||
msgstr "à"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:27
|
||||
msgid "N/A"
|
||||
msgstr "N/D"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:82
|
||||
msgid "What did you work on?"
|
||||
msgstr "Sur quoi avez-vous travaillé ?"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:87
|
||||
msgid "tag1, tag2"
|
||||
msgstr "tag1, tag2"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:131
|
||||
msgid "Failed to load tasks"
|
||||
msgstr "Échec du chargement des tâches"
|
||||
|
||||
#: app/templates/tasks/list.html:299
|
||||
msgid "Move Selected Tasks to Project"
|
||||
msgstr "Déplacer les tâches sélectionnées vers le projet"
|
||||
|
||||
#: app/templates/tasks/list.html:309
|
||||
msgid "Move Tasks"
|
||||
msgstr "Déplacer les tâches"
|
||||
|
||||
#: app/templates/timer/timer_page.html:135
|
||||
msgid "Add notes about what you're working on..."
|
||||
msgstr "Ajouter des notes sur ce sur quoi vous travaillez..."
|
||||
|
||||
#: app/templates/admin/email_templates/edit.html:36
|
||||
#: app/templates/admin/email_templates/create.html:38
|
||||
msgid "Set as default template"
|
||||
msgstr "Définir comme modèle par défaut"
|
||||
|
||||
#: app/templates/admin/email_templates/edit.html:44
|
||||
#: app/templates/admin/email_templates/create.html:46
|
||||
#: app/templates/admin/email_templates/view.html:56
|
||||
msgid "HTML Template"
|
||||
msgstr "Modèle HTML"
|
||||
|
||||
#: app/templates/admin/email_templates/edit.html:47
|
||||
#: app/templates/admin/email_templates/create.html:49
|
||||
msgid "Invoice Number"
|
||||
msgstr "Numéro de facture"
|
||||
|
||||
#: app/templates/admin/email_templates/edit.html:50
|
||||
#: app/templates/admin/email_templates/create.html:52
|
||||
msgid "Company Name"
|
||||
msgstr "Nom de l'entreprise"
|
||||
|
||||
#: app/templates/admin/email_templates/edit.html:53
|
||||
#: app/templates/admin/email_templates/create.html:55
|
||||
msgid "Custom Message"
|
||||
msgstr "Message personnalisé"
|
||||
|
||||
#: app/templates/admin/email_templates/edit.html:56
|
||||
#: app/templates/admin/email_templates/create.html:58
|
||||
msgid "More Variables"
|
||||
msgstr "Plus de variables"
|
||||
|
||||
#: app/templates/invoices/list.html:72
|
||||
msgid "Invoice number or client"
|
||||
msgstr "Numéro de facture ou client"
|
||||
|
||||
#: app/templates/expenses/form.html:231
|
||||
msgid "Receipt/Invoice Number"
|
||||
msgstr "Numéro de reçu/facture"
|
||||
@@ -294,7 +294,7 @@ msgid "Company Logo"
|
||||
msgstr "לוגו החברה"
|
||||
|
||||
msgid "DryTrix Logo"
|
||||
msgstr "לוגו DryTrix"
|
||||
msgstr "DryTrix Logo"
|
||||
|
||||
msgid "TimeTracker"
|
||||
msgstr "TimeTracker"
|
||||
@@ -479,3 +479,226 @@ msgstr "%(app)s הוא אפליקציית מעקב זמן מבוססת אינט
|
||||
msgid "Learn more about "
|
||||
msgstr "למד עוד על "
|
||||
|
||||
#: app/templates/setup/initial_setup.html:30
|
||||
msgid "Privacy First"
|
||||
msgstr "פרטיות קודם כל"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:35
|
||||
msgid "Self-hosted on your server"
|
||||
msgstr "מארח עצמי בשרת שלך"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:41
|
||||
msgid "Anonymous telemetry (opt-in)"
|
||||
msgstr "טלמטריה אנונימית (אופציונלי)"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:47
|
||||
msgid "No PII collected ever"
|
||||
msgstr "אין איסוף מידע אישי לעולם"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:53
|
||||
msgid "Open source & transparent"
|
||||
msgstr "קוד פתוח ושקוף"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:61
|
||||
msgid "Welcome to TimeTracker"
|
||||
msgstr "ברוך הבא ל-TimeTracker"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:62
|
||||
msgid "Let's get you set up in just a moment"
|
||||
msgstr "בואו נגדיר אותך בעוד רגע"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:69
|
||||
msgid "Thank you for choosing TimeTracker!"
|
||||
msgstr "תודה שבחרת ב-TimeTracker!"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:71
|
||||
msgid "Your data stays on your server, and you have complete control."
|
||||
msgstr "הנתונים שלך נשארים בשרת שלך ויש לך שליטה מלאה."
|
||||
|
||||
#: app/templates/setup/initial_setup.html:77
|
||||
msgid "Help Us Improve (Optional)"
|
||||
msgstr "עזור לנו להשתפר (אופציונלי)"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:83
|
||||
msgid "Enable anonymous telemetry"
|
||||
msgstr "הפעל טלמטריה אנונימית"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:85
|
||||
msgid "Help us understand usage patterns to improve TimeTracker"
|
||||
msgstr "עזור לנו להבין דפוסי שימוש כדי לשפר את TimeTracker"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:94
|
||||
msgid "What data is collected?"
|
||||
msgstr "אילו נתונים נאספים?"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:98
|
||||
msgid "What we collect:"
|
||||
msgstr "מה שאנחנו אוספים:"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:100
|
||||
msgid "Anonymous installation fingerprint (hashed)"
|
||||
msgstr "טביעת אצבע של התקנה אנונימית (מוצפנת)"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:101
|
||||
msgid "Application version & platform info"
|
||||
msgstr "גרסת האפליקציה ומידע על הפלטפורמה"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:102
|
||||
msgid "Feature usage statistics"
|
||||
msgstr "סטטיסטיקות שימוש בתכונות"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:103
|
||||
msgid "Internal numeric IDs only"
|
||||
msgstr "רק מזהה מספרי פנימי"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:108
|
||||
msgid "What we DON'T collect:"
|
||||
msgstr "מה שאנחנו לא אוספים:"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:110
|
||||
msgid "No usernames or emails"
|
||||
msgstr "אין שמות משתמש או אימיילים"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:111
|
||||
msgid "No project names or descriptions"
|
||||
msgstr "אין שמות פרויקטים או תיאורים"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:112
|
||||
msgid "No time entry data or notes"
|
||||
msgstr "אין נתוני רישום זמן או הערות"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:113
|
||||
msgid "No client or business data"
|
||||
msgstr "אין נתוני לקוח או עסק"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:114
|
||||
msgid "No IP addresses or PII"
|
||||
msgstr "אין כתובות IP או מידע אישי"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:120
|
||||
msgid "Why?"
|
||||
msgstr "למה?"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:120
|
||||
msgid "Anonymous usage data helps us prioritize features and fix issues."
|
||||
msgstr "נתוני שימוש אנונימיים עוזרים לנו לתעדף תכונות ולתקן בעיות."
|
||||
|
||||
#: app/templates/setup/initial_setup.html:121
|
||||
msgid "You can change this anytime in"
|
||||
msgstr "אתה יכול לשנות זאת בכל עת ב"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:121
|
||||
msgid "Admin → Settings"
|
||||
msgstr "מנהל → הגדרות"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:121
|
||||
msgid "Privacy & Analytics section"
|
||||
msgstr "סעיף פרטיות ואנליטיקה"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:131
|
||||
msgid "Complete Setup & Continue"
|
||||
msgstr "השלם הגדרה והמשך"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:135
|
||||
msgid "By continuing, you agree to use TimeTracker under the"
|
||||
msgstr "בהמשך, אתה מסכים להשתמש ב-TimeTracker תחת"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:135
|
||||
msgid "GPL-3.0 License"
|
||||
msgstr "רישיון GPL-3.0"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:12
|
||||
msgid "Duplicate Time Entry"
|
||||
msgstr "שכפל רישום זמן"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:12
|
||||
msgid "Log Time Manually"
|
||||
msgstr "רשום זמן ידנית"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:13
|
||||
msgid "Create a copy of a previous entry with new times"
|
||||
msgstr "צור עותק של רישום קודם עם זמנים חדשים"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:13
|
||||
msgid "Create a new time entry"
|
||||
msgstr "צור רישום זמן חדש"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:24
|
||||
msgid "Duplicating entry:"
|
||||
msgstr "משכפל רישום:"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:27
|
||||
msgid "Original:"
|
||||
msgstr "מקורי:"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:27
|
||||
msgid "to"
|
||||
msgstr "עד"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:27
|
||||
msgid "N/A"
|
||||
msgstr "לא זמין"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:82
|
||||
msgid "What did you work on?"
|
||||
msgstr "על מה עבדת?"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:87
|
||||
msgid "tag1, tag2"
|
||||
msgstr "tag1, tag2"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:131
|
||||
msgid "Failed to load tasks"
|
||||
msgstr "נכשל בטעינת משימות"
|
||||
|
||||
#: app/templates/tasks/list.html:299
|
||||
msgid "Move Selected Tasks to Project"
|
||||
msgstr "העבר משימות נבחרות לפרויקט"
|
||||
|
||||
#: app/templates/tasks/list.html:309
|
||||
msgid "Move Tasks"
|
||||
msgstr "העבר משימות"
|
||||
|
||||
#: app/templates/timer/timer_page.html:135
|
||||
msgid "Add notes about what you're working on..."
|
||||
msgstr "הוסף הערות על מה שאתה עובד..."
|
||||
|
||||
#: app/templates/admin/email_templates/edit.html:36
|
||||
#: app/templates/admin/email_templates/create.html:38
|
||||
msgid "Set as default template"
|
||||
msgstr "הגדר כתבנית ברירת מחדל"
|
||||
|
||||
#: app/templates/admin/email_templates/edit.html:44
|
||||
#: app/templates/admin/email_templates/create.html:46
|
||||
#: app/templates/admin/email_templates/view.html:56
|
||||
msgid "HTML Template"
|
||||
msgstr "תבנית HTML"
|
||||
|
||||
#: app/templates/admin/email_templates/edit.html:47
|
||||
#: app/templates/admin/email_templates/create.html:49
|
||||
msgid "Invoice Number"
|
||||
msgstr "מספר חשבונית"
|
||||
|
||||
#: app/templates/admin/email_templates/edit.html:50
|
||||
#: app/templates/admin/email_templates/create.html:52
|
||||
msgid "Company Name"
|
||||
msgstr "שם החברה"
|
||||
|
||||
#: app/templates/admin/email_templates/edit.html:53
|
||||
#: app/templates/admin/email_templates/create.html:55
|
||||
msgid "Custom Message"
|
||||
msgstr "הודעה מותאמת אישית"
|
||||
|
||||
#: app/templates/admin/email_templates/edit.html:56
|
||||
#: app/templates/admin/email_templates/create.html:58
|
||||
msgid "More Variables"
|
||||
msgstr "משתנים נוספים"
|
||||
|
||||
#: app/templates/invoices/list.html:72
|
||||
msgid "Invoice number or client"
|
||||
msgstr "מספר חשבונית או לקוח"
|
||||
|
||||
#: app/templates/expenses/form.html:231
|
||||
msgid "Receipt/Invoice Number"
|
||||
msgstr "מספר קבלה/חשבונית"
|
||||
|
||||
|
||||
@@ -478,3 +478,226 @@ msgstr "%(app)s è un'applicazione web per la registrazione del tempo progettata
|
||||
|
||||
msgid "Learn more about "
|
||||
msgstr "Scopri di più su "
|
||||
|
||||
#: app/templates/setup/initial_setup.html:30
|
||||
msgid "Privacy First"
|
||||
msgstr "Privacy prima di tutto"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:35
|
||||
msgid "Self-hosted on your server"
|
||||
msgstr "Self-hosted sul tuo server"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:41
|
||||
msgid "Anonymous telemetry (opt-in)"
|
||||
msgstr "Telemetria anonima (opt-in)"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:47
|
||||
msgid "No PII collected ever"
|
||||
msgstr "Nessun dato personale raccolto mai"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:53
|
||||
msgid "Open source & transparent"
|
||||
msgstr "Open source e trasparente"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:61
|
||||
msgid "Welcome to TimeTracker"
|
||||
msgstr "Benvenuto su TimeTracker"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:62
|
||||
msgid "Let's get you set up in just a moment"
|
||||
msgstr "Configuriamoti in un attimo"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:69
|
||||
msgid "Thank you for choosing TimeTracker!"
|
||||
msgstr "Grazie per aver scelto TimeTracker!"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:71
|
||||
msgid "Your data stays on your server, and you have complete control."
|
||||
msgstr "I tuoi dati rimangono sul tuo server e hai il controllo completo."
|
||||
|
||||
#: app/templates/setup/initial_setup.html:77
|
||||
msgid "Help Us Improve (Optional)"
|
||||
msgstr "Aiutaci a migliorare (Opzionale)"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:83
|
||||
msgid "Enable anonymous telemetry"
|
||||
msgstr "Abilita telemetria anonima"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:85
|
||||
msgid "Help us understand usage patterns to improve TimeTracker"
|
||||
msgstr "Aiutaci a capire i modelli di utilizzo per migliorare TimeTracker"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:94
|
||||
msgid "What data is collected?"
|
||||
msgstr "Quali dati vengono raccolti?"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:98
|
||||
msgid "What we collect:"
|
||||
msgstr "Cosa raccogliamo:"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:100
|
||||
msgid "Anonymous installation fingerprint (hashed)"
|
||||
msgstr "Impronta digitale di installazione anonima (hash)"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:101
|
||||
msgid "Application version & platform info"
|
||||
msgstr "Versione applicazione e informazioni sulla piattaforma"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:102
|
||||
msgid "Feature usage statistics"
|
||||
msgstr "Statistiche di utilizzo delle funzionalità"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:103
|
||||
msgid "Internal numeric IDs only"
|
||||
msgstr "Solo ID numerici interni"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:108
|
||||
msgid "What we DON'T collect:"
|
||||
msgstr "Cosa NON raccogliamo:"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:110
|
||||
msgid "No usernames or emails"
|
||||
msgstr "Nessun nome utente o email"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:111
|
||||
msgid "No project names or descriptions"
|
||||
msgstr "Nessun nome o descrizione del progetto"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:112
|
||||
msgid "No time entry data or notes"
|
||||
msgstr "Nessun dato di registrazione del tempo o note"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:113
|
||||
msgid "No client or business data"
|
||||
msgstr "Nessun dato cliente o aziendale"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:114
|
||||
msgid "No IP addresses or PII"
|
||||
msgstr "Nessun indirizzo IP o dato personale"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:120
|
||||
msgid "Why?"
|
||||
msgstr "Perché?"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:120
|
||||
msgid "Anonymous usage data helps us prioritize features and fix issues."
|
||||
msgstr "I dati di utilizzo anonimi ci aiutano a dare priorità alle funzionalità e a risolvere i problemi."
|
||||
|
||||
#: app/templates/setup/initial_setup.html:121
|
||||
msgid "You can change this anytime in"
|
||||
msgstr "Puoi modificarlo in qualsiasi momento in"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:121
|
||||
msgid "Admin → Settings"
|
||||
msgstr "Admin → Impostazioni"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:121
|
||||
msgid "Privacy & Analytics section"
|
||||
msgstr "Sezione Privacy e Analisi"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:131
|
||||
msgid "Complete Setup & Continue"
|
||||
msgstr "Completa configurazione e continua"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:135
|
||||
msgid "By continuing, you agree to use TimeTracker under the"
|
||||
msgstr "Continuando, accetti di utilizzare TimeTracker sotto la"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:135
|
||||
msgid "GPL-3.0 License"
|
||||
msgstr "Licenza GPL-3.0"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:12
|
||||
msgid "Duplicate Time Entry"
|
||||
msgstr "Duplica registrazione tempo"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:12
|
||||
msgid "Log Time Manually"
|
||||
msgstr "Registra tempo manualmente"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:13
|
||||
msgid "Create a copy of a previous entry with new times"
|
||||
msgstr "Crea una copia di una registrazione precedente con nuovi orari"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:13
|
||||
msgid "Create a new time entry"
|
||||
msgstr "Crea una nuova registrazione tempo"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:24
|
||||
msgid "Duplicating entry:"
|
||||
msgstr "Duplicazione registrazione:"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:27
|
||||
msgid "Original:"
|
||||
msgstr "Originale:"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:27
|
||||
msgid "to"
|
||||
msgstr "a"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:27
|
||||
msgid "N/A"
|
||||
msgstr "N/D"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:82
|
||||
msgid "What did you work on?"
|
||||
msgstr "Su cosa hai lavorato?"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:87
|
||||
msgid "tag1, tag2"
|
||||
msgstr "tag1, tag2"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:131
|
||||
msgid "Failed to load tasks"
|
||||
msgstr "Impossibile caricare le attività"
|
||||
|
||||
#: app/templates/tasks/list.html:299
|
||||
msgid "Move Selected Tasks to Project"
|
||||
msgstr "Sposta attività selezionate al progetto"
|
||||
|
||||
#: app/templates/tasks/list.html:309
|
||||
msgid "Move Tasks"
|
||||
msgstr "Sposta attività"
|
||||
|
||||
#: app/templates/timer/timer_page.html:135
|
||||
msgid "Add notes about what you're working on..."
|
||||
msgstr "Aggiungi note su ciò su cui stai lavorando..."
|
||||
|
||||
#: app/templates/admin/email_templates/edit.html:36
|
||||
#: app/templates/admin/email_templates/create.html:38
|
||||
msgid "Set as default template"
|
||||
msgstr "Imposta come modello predefinito"
|
||||
|
||||
#: app/templates/admin/email_templates/edit.html:44
|
||||
#: app/templates/admin/email_templates/create.html:46
|
||||
#: app/templates/admin/email_templates/view.html:56
|
||||
msgid "HTML Template"
|
||||
msgstr "Modello HTML"
|
||||
|
||||
#: app/templates/admin/email_templates/edit.html:47
|
||||
#: app/templates/admin/email_templates/create.html:49
|
||||
msgid "Invoice Number"
|
||||
msgstr "Numero fattura"
|
||||
|
||||
#: app/templates/admin/email_templates/edit.html:50
|
||||
#: app/templates/admin/email_templates/create.html:52
|
||||
msgid "Company Name"
|
||||
msgstr "Nome azienda"
|
||||
|
||||
#: app/templates/admin/email_templates/edit.html:53
|
||||
#: app/templates/admin/email_templates/create.html:55
|
||||
msgid "Custom Message"
|
||||
msgstr "Messaggio personalizzato"
|
||||
|
||||
#: app/templates/admin/email_templates/edit.html:56
|
||||
#: app/templates/admin/email_templates/create.html:58
|
||||
msgid "More Variables"
|
||||
msgstr "Più variabili"
|
||||
|
||||
#: app/templates/invoices/list.html:72
|
||||
msgid "Invoice number or client"
|
||||
msgstr "Numero fattura o cliente"
|
||||
|
||||
#: app/templates/expenses/form.html:231
|
||||
msgid "Receipt/Invoice Number"
|
||||
msgstr "Numero ricevuta/fattura"
|
||||
10779
translations/nb/LC_MESSAGES/messages.po
Normal file
10779
translations/nb/LC_MESSAGES/messages.po
Normal file
File diff suppressed because it is too large
Load Diff
@@ -478,3 +478,226 @@ msgstr "%(app)s is een webgebaseerde tijdregistratie-applicatie ontworpen voor i
|
||||
|
||||
msgid "Learn more about "
|
||||
msgstr "Meer informatie over "
|
||||
|
||||
#: app/templates/setup/initial_setup.html:30
|
||||
msgid "Privacy First"
|
||||
msgstr "Privacy eerst"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:35
|
||||
msgid "Self-hosted on your server"
|
||||
msgstr "Zelf gehost op uw server"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:41
|
||||
msgid "Anonymous telemetry (opt-in)"
|
||||
msgstr "Anonieme telemetrie (opt-in)"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:47
|
||||
msgid "No PII collected ever"
|
||||
msgstr "Geen persoonsgegevens worden ooit verzameld"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:53
|
||||
msgid "Open source & transparent"
|
||||
msgstr "Open source & transparant"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:61
|
||||
msgid "Welcome to TimeTracker"
|
||||
msgstr "Welkom bij TimeTracker"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:62
|
||||
msgid "Let's get you set up in just a moment"
|
||||
msgstr "Laten we u in een ogenblik instellen"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:69
|
||||
msgid "Thank you for choosing TimeTracker!"
|
||||
msgstr "Bedankt voor het kiezen van TimeTracker!"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:71
|
||||
msgid "Your data stays on your server, and you have complete control."
|
||||
msgstr "Uw gegevens blijven op uw server en u heeft volledige controle."
|
||||
|
||||
#: app/templates/setup/initial_setup.html:77
|
||||
msgid "Help Us Improve (Optional)"
|
||||
msgstr "Help ons te verbeteren (Optioneel)"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:83
|
||||
msgid "Enable anonymous telemetry"
|
||||
msgstr "Anonieme telemetrie inschakelen"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:85
|
||||
msgid "Help us understand usage patterns to improve TimeTracker"
|
||||
msgstr "Help ons gebruikspatronen te begrijpen om TimeTracker te verbeteren"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:94
|
||||
msgid "What data is collected?"
|
||||
msgstr "Welke gegevens worden verzameld?"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:98
|
||||
msgid "What we collect:"
|
||||
msgstr "Wat we verzamelen:"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:100
|
||||
msgid "Anonymous installation fingerprint (hashed)"
|
||||
msgstr "Anonieme installatiefingerafdruk (gehasht)"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:101
|
||||
msgid "Application version & platform info"
|
||||
msgstr "Applicatieversie & platforminfo"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:102
|
||||
msgid "Feature usage statistics"
|
||||
msgstr "Functiegebruikstatistieken"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:103
|
||||
msgid "Internal numeric IDs only"
|
||||
msgstr "Alleen interne numerieke ID's"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:108
|
||||
msgid "What we DON'T collect:"
|
||||
msgstr "Wat we NIET verzamelen:"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:110
|
||||
msgid "No usernames or emails"
|
||||
msgstr "Geen gebruikersnamen of e-mails"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:111
|
||||
msgid "No project names or descriptions"
|
||||
msgstr "Geen projectnamen of beschrijvingen"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:112
|
||||
msgid "No time entry data or notes"
|
||||
msgstr "Geen tijdregistratiegegevens of notities"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:113
|
||||
msgid "No client or business data"
|
||||
msgstr "Geen klant- of bedrijfsgegevens"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:114
|
||||
msgid "No IP addresses or PII"
|
||||
msgstr "Geen IP-adressen of persoonsgegevens"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:120
|
||||
msgid "Why?"
|
||||
msgstr "Waarom?"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:120
|
||||
msgid "Anonymous usage data helps us prioritize features and fix issues."
|
||||
msgstr "Anonieme gebruiksgegevens helpen ons functies te prioriteren en problemen op te lossen."
|
||||
|
||||
#: app/templates/setup/initial_setup.html:121
|
||||
msgid "You can change this anytime in"
|
||||
msgstr "U kunt dit altijd wijzigen in"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:121
|
||||
msgid "Admin → Settings"
|
||||
msgstr "Admin → Instellingen"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:121
|
||||
msgid "Privacy & Analytics section"
|
||||
msgstr "Sectie Privacy & Analytics"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:131
|
||||
msgid "Complete Setup & Continue"
|
||||
msgstr "Setup voltooien & doorgaan"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:135
|
||||
msgid "By continuing, you agree to use TimeTracker under the"
|
||||
msgstr "Door door te gaan, stemt u ermee in TimeTracker te gebruiken onder de"
|
||||
|
||||
#: app/templates/setup/initial_setup.html:135
|
||||
msgid "GPL-3.0 License"
|
||||
msgstr "GPL-3.0-licentie"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:12
|
||||
msgid "Duplicate Time Entry"
|
||||
msgstr "Tijdregistratie dupliceren"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:12
|
||||
msgid "Log Time Manually"
|
||||
msgstr "Tijd handmatig registreren"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:13
|
||||
msgid "Create a copy of a previous entry with new times"
|
||||
msgstr "Maak een kopie van een eerdere registratie met nieuwe tijden"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:13
|
||||
msgid "Create a new time entry"
|
||||
msgstr "Nieuwe tijdregistratie maken"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:24
|
||||
msgid "Duplicating entry:"
|
||||
msgstr "Registratie dupliceren:"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:27
|
||||
msgid "Original:"
|
||||
msgstr "Origineel:"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:27
|
||||
msgid "to"
|
||||
msgstr "tot"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:27
|
||||
msgid "N/A"
|
||||
msgstr "N.v.t."
|
||||
|
||||
#: app/templates/timer/manual_entry.html:82
|
||||
msgid "What did you work on?"
|
||||
msgstr "Waar heb je aan gewerkt?"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:87
|
||||
msgid "tag1, tag2"
|
||||
msgstr "tag1, tag2"
|
||||
|
||||
#: app/templates/timer/manual_entry.html:131
|
||||
msgid "Failed to load tasks"
|
||||
msgstr "Taken konden niet worden geladen"
|
||||
|
||||
#: app/templates/tasks/list.html:299
|
||||
msgid "Move Selected Tasks to Project"
|
||||
msgstr "Geselecteerde taken naar project verplaatsen"
|
||||
|
||||
#: app/templates/tasks/list.html:309
|
||||
msgid "Move Tasks"
|
||||
msgstr "Taken verplaatsen"
|
||||
|
||||
#: app/templates/timer/timer_page.html:135
|
||||
msgid "Add notes about what you're working on..."
|
||||
msgstr "Notities toevoegen over waar je aan werkt..."
|
||||
|
||||
#: app/templates/admin/email_templates/edit.html:36
|
||||
#: app/templates/admin/email_templates/create.html:38
|
||||
msgid "Set as default template"
|
||||
msgstr "Instellen als standaardsjabloon"
|
||||
|
||||
#: app/templates/admin/email_templates/edit.html:44
|
||||
#: app/templates/admin/email_templates/create.html:46
|
||||
#: app/templates/admin/email_templates/view.html:56
|
||||
msgid "HTML Template"
|
||||
msgstr "HTML-sjabloon"
|
||||
|
||||
#: app/templates/admin/email_templates/edit.html:47
|
||||
#: app/templates/admin/email_templates/create.html:49
|
||||
msgid "Invoice Number"
|
||||
msgstr "Factuurnummer"
|
||||
|
||||
#: app/templates/admin/email_templates/edit.html:50
|
||||
#: app/templates/admin/email_templates/create.html:52
|
||||
msgid "Company Name"
|
||||
msgstr "Bedrijfsnaam"
|
||||
|
||||
#: app/templates/admin/email_templates/edit.html:53
|
||||
#: app/templates/admin/email_templates/create.html:55
|
||||
msgid "Custom Message"
|
||||
msgstr "Aangepast bericht"
|
||||
|
||||
#: app/templates/admin/email_templates/edit.html:56
|
||||
#: app/templates/admin/email_templates/create.html:58
|
||||
msgid "More Variables"
|
||||
msgstr "Meer variabelen"
|
||||
|
||||
#: app/templates/invoices/list.html:72
|
||||
msgid "Invoice number or client"
|
||||
msgstr "Factuurnummer of klant"
|
||||
|
||||
#: app/templates/expenses/form.html:231
|
||||
msgid "Receipt/Invoice Number"
|
||||
msgstr "Bon-/Factuurnummer"
|
||||
10779
translations/no/LC_MESSAGES/messages.po
Normal file
10779
translations/no/LC_MESSAGES/messages.po
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user