@@ -43,11 +43,11 @@
{{ task.priority_display }}
{% if current_user.active_timer and current_user.active_timer.task_id == task.id %}
- Active
+ {{ _('Active') }}
{% endif %}
{% if task.is_overdue %}
- Overdue
+ {{ _('Overdue') }}
{% endif %}
@@ -143,7 +143,13 @@
const board = document.getElementById('kanbanBoard');
if (!board) return;
- const statusLabels = { todo: 'To Do', in_progress: 'In Progress', review: 'Review', done: 'Done', cancelled: 'Cancelled' };
+ const statusLabels = {
+ todo: '{{ _('To Do') }}',
+ in_progress: '{{ _('In Progress') }}',
+ review: '{{ _('Review') }}',
+ done: '{{ _('Done') }}',
+ cancelled: '{{ _('Cancelled') }}'
+ };
const updateUrlTemplate = "{{ url_for('tasks.api_update_status', task_id=0) }}"; // will replace 0 with actual id
function showAlert(kind, msg) {
@@ -220,13 +226,13 @@
body: JSON.stringify({ status: targetStatus })
});
if (!resp.ok) {
- throw new Error('Failed to update status');
+ throw new Error('{{ _('Failed to update status') }}');
}
const data = await resp.json();
if (!data.success) {
throw new Error(data.error || 'Update rejected');
}
- showAlert('success', 'Task moved to ' + (statusLabels[targetStatus] || targetStatus));
+ showAlert('success', '{{ _('Task moved to') }} ' + (statusLabels[targetStatus] || targetStatus));
} catch (err) {
// revert
if (originalParent) originalParent.appendChild(card);
@@ -236,7 +242,7 @@
statusBadge.textContent = statusLabels[originalStatus] || originalStatus;
}
updateCounts();
- showAlert('error', err.message || 'Could not update task');
+ showAlert('error', err.message || '{{ _('Could not update task') }}');
}
});
});
diff --git a/app/templates/tasks/create.html b/app/templates/tasks/create.html
index 6b0c051..deb9c61 100644
--- a/app/templates/tasks/create.html
+++ b/app/templates/tasks/create.html
@@ -1,6 +1,6 @@
{% extends "base.html" %}
-{% block title %}Create Task - Time Tracker{% endblock %}
+{% block title %}{{ _('Create Task') }} - Time Tracker{% endblock %}
{% block extra_css %}
@@ -19,8 +19,8 @@
-
Create New Task
-
Add a new task to your project to break down work into manageable components
+
{{ _('Create New Task') }}
+
{{ _('Add a new task to your project to break down work into manageable components') }}
@@ -34,7 +34,7 @@
- Task Information
+ {{ _('Task Information') }}
@@ -42,65 +42,65 @@
- Choose a clear, descriptive name that explains what needs to be done
+ value="{{ request.form.get('name', '') }}" placeholder="{{ _('Enter a descriptive task name') }}" required>
+ {{ _('Choose a clear, descriptive name that explains what needs to be done') }}
-
+
- Optional: Add context, requirements, or specific instructions for the task
+ {{ _('Optional: Add context, requirements, or specific instructions for the task') }}
- Select the project this task belongs to
+ {{ _('Select the project this task belongs to') }}
@@ -109,45 +109,45 @@
- Optional: Set a deadline for this task
+ {{ _('Optional: Set a deadline for this task') }}
- Optional: Estimate how long this task will take
+ {{ _('Optional: Estimate how long this task will take') }}
- Optional: Assign this task to a team member
+ {{ _('Optional: Assign this task to a team member') }}
- Clear Naming
- Use action verbs and be specific about what needs to be done
+ {{ _('Clear Naming') }}
+ {{ _('Use action verbs and be specific about what needs to be done') }}
@@ -183,8 +183,8 @@
- Realistic Estimates
- Consider complexity and dependencies when estimating time
+ {{ _('Realistic Estimates') }}
+ {{ _('Consider complexity and dependencies when estimating time') }}
@@ -195,8 +195,8 @@
- Set Deadlines
- Due dates help prioritize work and track progress
+ {{ _('Set Deadlines') }}
+ {{ _('Due dates help prioritize work and track progress') }}
@@ -207,8 +207,8 @@
- Priority Matters
- Use priority levels to help team members focus on what's most important
+ {{ _('Priority Matters') }}
+ {{ _('Use priority levels to help team members focus on what\'s most important') }}
@@ -219,35 +219,35 @@
- Priority Guide
+ {{ _('Priority Guide') }}
- Low
- Non-urgent, can be done later
+ {{ _('Low') }}
+ {{ _('Non-urgent, can be done later') }}
- Medium
- Normal priority, standard timeline
+ {{ _('Medium') }}
+ {{ _('Normal priority, standard timeline') }}
Update task details and settings for "{{ task.name }}"
+
{{ _('Edit Task') }}
+
{{ _('Update task details and settings for "%(task)s"', task=task.name) }}
@@ -34,7 +34,7 @@
- Task Information
+ {{ _('Task Information') }}
@@ -42,65 +42,65 @@
- Choose a clear, descriptive name that explains what needs to be done
+ value="{{ task.name }}" placeholder="{{ _('Enter a descriptive task name') }}" required>
+ {{ _('Choose a clear, descriptive name that explains what needs to be done') }}
-
+
- Optional: Add context, requirements, or specific instructions for the task
+ {{ _('Optional: Add context, requirements, or specific instructions for the task') }}
- Select the project this task belongs to
+ {{ _('Select the project this task belongs to') }}
@@ -109,45 +109,45 @@
- Optional: Set a deadline for this task
+ {{ _('Optional: Set a deadline for this task') }}
- Optional: Estimate how long this task will take
+ {{ _('Optional: Estimate how long this task will take') }}
- Optional: Assign this task to a team member
+ {{ _('Optional: Assign this task to a team member') }}
- Status Changes
- Changing status may affect time tracking and progress calculations
+ {{ _('Status Changes') }}
+ {{ _('Changing status may affect time tracking and progress calculations') }}
@@ -293,8 +293,8 @@
- Due Date Updates
- Consider team workload when adjusting deadlines
+ {{ _('Due Date Updates') }}
+ {{ _('Consider team workload when adjusting deadlines') }}
@@ -305,8 +305,8 @@
- Assignment Changes
- Notify team members when reassigning tasks
+ {{ _('Assignment Changes') }}
+ {{ _('Notify team members when reassigning tasks') }}
@@ -484,7 +484,7 @@ document.addEventListener('DOMContentLoaded', function() {
previewStyle: 'vertical',
usageStatistics: false,
hideModeSwitch: false,
- placeholder: 'Provide detailed information about the task, requirements, and any specific instructions...',
+ placeholder: '{{ _('Provide detailed information about the task, requirements, and any specific instructions...') }}',
theme: theme,
toolbarItems: [
['heading', 'bold', 'italic', 'strike'],
@@ -560,7 +560,7 @@ document.addEventListener('DOMContentLoaded', function() {
// Show loading state
const submitBtn = form.querySelector('button[type="submit"]');
const originalText = submitBtn.innerHTML;
- submitBtn.innerHTML = 'Updating...';
+ submitBtn.innerHTML = '{{ _('Updating...') }}';
submitBtn.disabled = true;
// Re-enable after a delay (in case of validation errors)
diff --git a/app/templates/tasks/my_tasks.html b/app/templates/tasks/my_tasks.html
index 7cac2c6..27f32ce 100644
--- a/app/templates/tasks/my_tasks.html
+++ b/app/templates/tasks/my_tasks.html
@@ -1,6 +1,6 @@
{% extends "base.html" %}
-{% block title %}My Tasks - Time Tracker{% endblock %}
+{% block title %}{{ _('My Tasks') }} - Time Tracker{% endblock %}
{% block content %}
@@ -10,10 +10,10 @@
{% set actions %}
- New Task
+ {{ _('New Task') }}
{% endset %}
- {{ page_header('fas fa-user', 'My Tasks', 'Tasks assigned to or created by you • ' ~ (tasks|length) ~ ' total', actions) }}
+ {{ page_header('fas fa-user', _('My Tasks'), _('Tasks assigned to or created by you') ~ ' • ' ~ (tasks|length) ~ ' ' ~ _('total'), actions) }}
- There are {{ tasks|length }} overdue tasks that require immediate attention.
- Please review and update these tasks to prevent further delays.
+ {{ _('There are') }} {{ tasks|length }} {{ _('overdue tasks that require immediate attention.') }}
+ {{ _('Please review and update these tasks to prevent further delays.') }}
- Delete Time Entry
+ {{ _('Delete Time Entry') }}
- Warning: This action cannot be undone.
+ {{ _('Warning:') }} {{ _('This action cannot be undone.') }}
-
Are you sure you want to delete the time entry for ?
-
Duration:
+
{{ _('Are you sure you want to delete the time entry for') }} ?
+
{{ _('Duration:') }}
diff --git a/app/utils/license_server.py b/app/utils/license_server.py
index 7aceab6..38f7627 100644
--- a/app/utils/license_server.py
+++ b/app/utils/license_server.py
@@ -13,15 +13,15 @@ import socket
logger = logging.getLogger(__name__)
class LicenseServerClient:
- """Client for communicating with the DryLicenseServer"""
+ """Client for communicating with the Metrics Server"""
def __init__(self, app_identifier: str = "timetracker", app_version: str = "1.0.0"):
# Server configuration (env-overridable)
- # Defaults target the dev API port per docs (HTTP 8082). In prod, set HTTPS 8443 via env.
+ # Default targets the public metrics endpoint; override via environment if needed.
default_server_url = "http://metrics.drytrix.com:58082"
- configured_server_url = default_server_url
+ configured_server_url = os.getenv("METRICS_SERVER_URL", os.getenv("LICENSE_SERVER_BASE_URL", default_server_url))
self.server_url = self._normalize_base_url(configured_server_url)
- self.api_key = "no-license-required"
+ self.api_key = os.getenv("METRICS_SERVER_API_KEY", os.getenv("LICENSE_SERVER_API_KEY", "no-license-required"))
self.app_identifier = app_identifier
self.app_version = app_version
@@ -31,14 +31,14 @@ class LicenseServerClient:
self.is_registered = False
self.heartbeat_thread = None
# Timing configuration
- self.heartbeat_interval = int(os.getenv("LICENSE_HEARTBEAT_SECONDS", "3600")) # default: 1 hour
- self.request_timeout = int(os.getenv("LICENSE_SERVER_TIMEOUT_SECONDS", "30")) # default: 30s per docs
+ self.heartbeat_interval = int(os.getenv("METRICS_HEARTBEAT_SECONDS", os.getenv("LICENSE_HEARTBEAT_SECONDS", "3600"))) # default: 1 hour
+ self.request_timeout = int(os.getenv("METRICS_SERVER_TIMEOUT_SECONDS", os.getenv("LICENSE_SERVER_TIMEOUT_SECONDS", "30"))) # default: 30s per docs
self.running = False
# System information
self.system_info = self._collect_system_info()
- logger.info(f"License server configured: base='{self.server_url}', app='{self.app_identifier}', version='{self.app_version}'")
+ logger.info(f"Metrics server configured: base='{self.server_url}', app='{self.app_identifier}', version='{self.app_version}'")
if not self.api_key:
logger.warning("X-API-Key is empty; server may reject requests. Set LICENSE_SERVER_API_KEY.")
@@ -340,7 +340,7 @@ class LicenseServerClient:
logger.info(f"License validation headers: {validation_headers}")
logger.info(f"License validation body: {validation_data}")
- logger.info("Validating phone home token (no license required)")
+ logger.info("Validating metrics token (no license required)")
response = self._make_request("/api/v1/validate", "POST", validation_data)
if response and response.get("valid", False):
@@ -488,7 +488,7 @@ class LicenseServerClient:
return response is not None
def get_status(self) -> Dict[str, Any]:
- """Get current status of the phone home client"""
+ """Get current status of the metrics client"""
return {
"is_registered": self.is_registered,
"instance_id": self.instance_id,
@@ -496,7 +496,11 @@ class LicenseServerClient:
"server_healthy": self.check_server_health(),
"offline_data_count": len(self.offline_data),
"app_identifier": self.app_identifier,
- "app_version": self.app_version
+ "app_version": self.app_version,
+ "server_url": self.server_url,
+ "heartbeat_interval": self.heartbeat_interval,
+ "analytics_enabled": not bool(self.system_info.get("analytics_disabled", False)),
+ "system_info": self.system_info
}
# Global instance
@@ -544,7 +548,7 @@ def stop_license_client():
license_client.stop()
def send_usage_event(event_type: str, event_data: Dict[str, Any] = None):
- """Send a usage event to the license server"""
+ """Send a usage event to the metrics server"""
if not license_client:
return False
diff --git a/docs/LICENSE_SERVER_INTEGRATION.md b/docs/LICENSE_SERVER_INTEGRATION.md
index 39ebf40..3a810b0 100644
--- a/docs/LICENSE_SERVER_INTEGRATION.md
+++ b/docs/LICENSE_SERVER_INTEGRATION.md
@@ -1,32 +1,31 @@
-# License Server Integration
+# Metrics Server Integration
-This document describes the implementation of the DryLicenseServer communication in the TimeTracker application.
+This document describes the implementation of the metrics server communication in the TimeTracker application.
## Overview
-The TimeTracker application includes a license server client that communicates with a DryLicenseServer for monitoring and analytics purposes. **No license is required** to use the application - this integration is purely for usage tracking and system monitoring.
+The TimeTracker application includes a metrics client that communicates with a metrics server for monitoring and analytics purposes. **No license is required** to use the application — this integration is purely for usage tracking and system monitoring.
## Implementation Details
### Core Components
1. **LicenseServerClient** (`app/utils/license_server.py`)
- - Main client class for communicating with the license server
+ - Main client class for communicating with the metrics server
- Handles instance registration, heartbeats, and usage data transmission
- Implements offline data storage for failed requests
- Runs background heartbeat thread
-2. **Configuration** (`app/config.py`)
- - License server settings in the main configuration
- - Environment variable support for customization
- - Default values for development
+2. **Configuration** (`app/utils/license_server.py` and environment)
+ - Metrics server settings are environment-configurable with sane defaults
+ - Default values are provided for development
3. **Integration** (`app/__init__.py`)
- Automatic initialization during application startup
- - Graceful error handling if license server is unavailable
+ - Graceful error handling if metrics server is unavailable
4. **Admin Interface** (`app/routes/admin.py`)
- - License server status monitoring
+ - Metrics server status monitoring
- Testing and restart capabilities
- Integration with admin dashboard
@@ -36,7 +35,7 @@ The TimeTracker application includes a license server client that communicates w
- ✅ **Periodic Heartbeats**: Sends status updates every hour (configurable)
- ✅ **Usage Data Collection**: Tracks application usage and features
- ✅ **Offline Storage**: Stores data locally when server is unavailable
-- ✅ **Graceful Error Handling**: Continues operation even if license server fails
+- ✅ **Graceful Error Handling**: Continues operation even if metrics server fails
- ✅ **System Information Collection**: Gathers OS, hardware, and environment details
- ✅ **Admin Monitoring**: Web interface for status and management
@@ -44,31 +43,19 @@ The TimeTracker application includes a license server client that communicates w
### Environment Variables
-**No environment variables are needed for license server configuration.**
+Metrics server configuration supports environment overrides (legacy variable names are also accepted for compatibility):
-All license server settings are hardcoded in the application code and cannot be changed by clients:
-
-- **Server URL**: `http://192.168.1.100:8081` (hardcoded in `app/utils/license_server.py`)
-- **App ID**: `timetracker` (hardcoded in `app/config.py`)
-- **App Version**: `1.0.0` (hardcoded in `app/config.py`)
-- **Heartbeat Interval**: 3600 seconds (1 hour) (hardcoded in `app/config.py`)
-- **Enabled by default**: Yes (hardcoded in `app/config.py`)
-
-### Hardcoded Values
-
-The license server configuration is intentionally hardcoded to ensure:
-
-- **Consistent monitoring** across all deployments
-- **No client configuration** required
-- **Predictable behavior** regardless of environment
-- **Security** - clients cannot change monitoring endpoints
+- `METRICS_SERVER_URL` (legacy: `LICENSE_SERVER_BASE_URL`)
+- `METRICS_SERVER_API_KEY` (legacy: `LICENSE_SERVER_API_KEY`)
+- `METRICS_HEARTBEAT_SECONDS` (legacy: `LICENSE_HEARTBEAT_SECONDS`) — default 3600
+- `METRICS_SERVER_TIMEOUT_SECONDS` (legacy: `LICENSE_SERVER_TIMEOUT_SECONDS`) — default 30
## API Endpoints Used
| Endpoint | Purpose | Status |
|----------|---------|---------|
| `/api/v1/register` | Instance registration | ✅ Implemented |
-| `/api/v1/validate` | License validation | ✅ Implemented (always succeeds) |
+| `/api/v1/validate` | Token validation | ✅ Implemented (no license required) |
| `/api/v1/heartbeat` | Status updates | ✅ Implemented |
| `/api/v1/data` | Usage data transmission | ✅ Implemented |
| `/api/v1/status` | Server health check | ✅ Implemented |
@@ -77,7 +64,7 @@ The license server configuration is intentionally hardcoded to ensure:
### Automatic Operation
-The license server client starts automatically when the application starts:
+The metrics client starts automatically when the application starts:
1. **Application Startup**: Client initializes and registers instance
2. **Background Heartbeats**: Sends status updates every hour
@@ -89,22 +76,22 @@ The license server client starts automatically when the application starts:
#### CLI Commands
```bash
-# Check license server status
+# Check metrics status
flask license-status
-# Test license server communication
+# Test metrics communication
flask license-test
-# Restart license server client
+# Restart metrics client
flask license-restart
```
#### Admin Interface
-- **Dashboard**: Quick access to license status
-- **License Status Page**: Detailed status information
+- **Dashboard**: Quick access to metrics status
+- **Metrics Status Page**: Detailed status information
- **Test Connection**: Verify server communication
-- **Restart Client**: Restart the license server client
+- **Restart Client**: Restart the metrics client
### Programmatic Usage
@@ -123,7 +110,7 @@ if client:
## Data Collection
-### System Information
+### System Information (minimal)
- Operating system and version
- Hardware architecture
@@ -131,7 +118,7 @@ if client:
- Hostname and local IP
- Processor information
-### Usage Events
+### Usage Events (aggregate)
- Feature usage tracking
- User actions
@@ -172,14 +159,14 @@ if client:
### Startup Failures
- Application continues to function
-- License server client marked as unavailable
+- Metrics client marked as unavailable
- Admin interface shows error status
## Monitoring and Debugging
### Logs
-License server operations are logged with appropriate levels:
+Metrics client operations are logged with appropriate levels:
- **INFO**: Successful operations and status changes
- **WARNING**: Failed requests and connection issues
@@ -221,13 +208,13 @@ python test_license_server.py
### Manual Testing
1. Start the application
-2. Check admin dashboard for license status
+2. Check admin dashboard for metrics status
3. Use CLI commands to test functionality
4. Monitor logs for operation details
### Integration Testing
-1. Start a license server on localhost:8081
+1. Start a metrics server on localhost:8081
2. Verify registration and heartbeats
3. Test usage data transmission
4. Check offline data handling
@@ -237,7 +224,7 @@ python test_license_server.py
### Common Issues
1. **Server Not Responding**
- - Check if license server is running on port 8081
+ - Check if metrics server is running on port 8081
- Verify network connectivity
- Check firewall settings
@@ -274,12 +261,12 @@ tail -f logs/timetracker.log
## Support
-For issues with the license server integration:
+For issues with the metrics server integration:
1. Check the admin interface for status
2. Review application logs
3. Use CLI commands for testing
-4. Verify license server availability
+4. Verify metrics server availability
5. Check configuration values
The integration is designed to be non-intrusive and will not prevent the application from functioning normally.
diff --git a/templates/admin/create_user.html b/templates/admin/create_user.html
index 5a51723..353e7be 100644
--- a/templates/admin/create_user.html
+++ b/templates/admin/create_user.html
@@ -1,6 +1,6 @@
{% extends "base.html" %}
-{% block title %}New User - {{ app_name }}{% endblock %}
+{% block title %}{{ _('New User') }} - {{ app_name }}{% endblock %}
{% block content %}
- This TimeTracker application communicates with a license server for monitoring and analytics purposes only.
- No license is required to use the application. The license server is used to:
-
-
-
Track application usage and features
-
Monitor system health and performance
-
Collect analytics data for improvement
-
Provide support and troubleshooting information
-
-
-
-
-
-
Server Configuration
-
-
Server URL:http://host.docker.internal:8081
-
API Key:no-license-required
-
Heartbeat Interval: 3600 seconds (1 hour)
-
-
-
-
Features
-
-
Automatic instance registration
-
Periodic heartbeats
-
Usage data collection
-
Offline data storage
-
Graceful error handling
-
-
-
-
-
-
-
Privacy & Analytics Settings
-
-
- Analytics Setting:
- {% if settings and settings.allow_analytics %}
- Enabled
- System information is being shared with the license server
- {% else %}
- Disabled
- System information sharing is disabled
- {% endif %}
-
-
-
- This setting can be changed in System Settings.
- License validation continues to work regardless of this setting.
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+ {{ _('Configuration') }}
+
+
+
+
+
{{ _('About This Implementation') }}
+
+ {{ _('This TimeTracker application communicates with a metrics server for monitoring and analytics purposes only.') }}
+ {{ _('No license is required') }} {{ _('to use the application. The metrics server is used to:') }}
+
+
+
{{ _('Track application usage and features') }}
+
{{ _('Monitor system health and performance') }}
+
{{ _('Collect analytics data for improvement') }}
+
{{ _('Provide support and troubleshooting information') }}
- Restoring a backup will overwrite your current database and files. Ensure you have a recent backup before proceeding.
+ {{ _('Restoring a backup will overwrite your current database and files. Ensure you have a recent backup before proceeding.') }}
- When enabled, basic system information (OS, version, etc.) may be shared with the license server for monitoring purposes.
- License validation will continue to work regardless of this setting.
- This helps improve the application and monitor system health.
+ {{ _('When enabled, basic system information (OS, version, etc.) may be shared with the metrics server for monitoring purposes.') }}
+ {{ _('Core functionality will continue to work regardless of this setting.') }}
+ {{ _('This helps improve the application and monitor system health.') }}
- in {{ settings.timezone if settings else 'Europe/Rome' }} timezone
+ {{ _('in') }} {{ settings.timezone if settings else 'Europe/Rome' }} {{ _('timezone') }}
@@ -369,13 +370,13 @@
- This time updates every second and shows the current time in your selected timezone
+ {{ _('This time updates every second and shows the current time in your selected timezone') }}
- Current offset: --
+ {{ _('Current offset:') }} --
@@ -391,18 +392,18 @@
-
Help
+
{{ _('Help') }}
-
Configure application-wide settings such as timezone, currency, timer behavior, data export options, and company branding for invoices.
+
{{ _('Configure application-wide settings such as timezone, currency, timer behavior, data export options, and company branding for invoices.') }}
-
Rounding affects how durations are rounded when displayed.
-
Single Active Timer stops any running timer when a new one is started.
-
Self Register allows new usernames to be created on login.
-
Company branding settings are used for PDF invoice generation.
-
Company logos can be uploaded directly through the interface (PNG, JPG, JPEG, GIF, SVG, WEBP formats supported).
-
Analytics setting controls whether system information is shared with the license server for monitoring purposes.
-
License validation will continue to work regardless of the analytics setting.
+
{{ _('Rounding affects how durations are rounded when displayed.') }}
+
{{ _('Single Active Timer stops any running timer when a new one is started.') }}
+
{{ _('Self Register allows new usernames to be created on login.') }}
+
{{ _('Company branding settings are used for PDF invoice generation.') }}
+
{{ _('Company logos can be uploaded directly through the interface (PNG, JPG, JPEG, GIF, SVG, WEBP formats supported).') }}
+
{{ _('Analytics setting controls whether system information is shared with the metrics server for monitoring purposes.') }}
+
{{ _('Core functionality will continue to work regardless of the analytics setting.') }}
- The request you made is invalid or contains errors. This could be due to:
+ {{ _('The request you made is invalid or contains errors. This could be due to:') }}
- You don't have permission to access this resource. This could be due to:
+ {{ _("You don't have permission to access this resource. This could be due to:") }}
- Selected entries will be converted to invoice line items with the project's hourly rate.
+ {{ _("Selected entries will be converted to invoice line items with the project's hourly rate.") }}
- There are no time entries available for this project to generate invoice items from.
+ {{ _('There are no time entries available for this project to generate invoice items from.') }}
- Last 7 Days
+ {{ _('Last 7 Days') }}
- This Month
+ {{ _('This Month') }}
- High Value Tasks
+ {{ _('High Value Tasks') }}
- Clear Selection
+ {{ _('Clear Selection') }}
@@ -243,7 +243,7 @@
- Tips
+ {{ _('Tips') }}
@@ -252,9 +252,9 @@
- Smart Selection
+ {{ _('Smart Selection') }}
- Use quick actions to select time entries by date ranges or task types
+ {{ _('Use quick actions to select time entries by date ranges or task types') }}
- Line items are automatically calculated using the project's hourly rate
+ {{ _("Line items are automatically calculated using the project's hourly rate") }}
@@ -276,9 +276,9 @@
- Review & Edit
+ {{ _('Review & Edit') }}
- You can edit generated items before finalizing the invoice
+ {{ _('You can edit generated items before finalizing the invoice') }}
@@ -460,7 +460,7 @@ document.addEventListener('DOMContentLoaded', function() {
const selectedCount = document.querySelectorAll('.time-entry-checkbox:checked').length;
if (selectedCount === 0) {
e.preventDefault();
- showAlert('Please select at least one time entry to generate invoice items.', 'warning');
+ showAlert('{{ _('Please select at least one time entry to generate invoice items.') }}', 'warning');
}
});
}
diff --git a/templates/invoices/list.html b/templates/invoices/list.html
index e413e51..c26b28f 100644
--- a/templates/invoices/list.html
+++ b/templates/invoices/list.html
@@ -231,7 +231,7 @@
+ data-confirm="{{ _('Are you sure you want to delete this invoice? This action cannot be undone.') }}">
{{ _('Delete') }}
@@ -406,6 +406,21 @@
{% endblock %}
{% block scripts %}
+
+
{% endblock %}
diff --git a/templates/invoices/view.html b/templates/invoices/view.html
index 7c391f8..4a774b1 100644
--- a/templates/invoices/view.html
+++ b/templates/invoices/view.html
@@ -1,6 +1,6 @@
{% extends "base.html" %}
-{% block title %}Invoice {{ invoice.invoice_number }} - TimeTracker{% endblock %}
+{% block title %}{{ _('Invoice') }} {{ invoice.invoice_number }} - TimeTracker{% endblock %}
{% block content %}
@@ -588,12 +588,12 @@ $(document).ready(function() {
if (newStatus === currentStatus) {
e.preventDefault();
- alert('The selected status is the same as the current status.');
+ alert('{{ _('The selected status is the same as the current status.') }}');
return false;
}
if (newStatus === 'cancelled') {
- if (!confirm('Are you sure you want to cancel this invoice? This action cannot be undone.')) {
+ if (!confirm('{{ _('Are you sure you want to cancel this invoice? This action cannot be undone.') }}')) {
e.preventDefault();
return false;
}
diff --git a/templates/main/about.html b/templates/main/about.html
index 1ca8566..57d4a07 100644
--- a/templates/main/about.html
+++ b/templates/main/about.html
@@ -14,9 +14,9 @@
{% if settings and settings.has_logo() %}
-
+
{% else %}
-
+
{% endif %}
- {{ app_name }} is designed for internal use and prioritizes data security and privacy:
+ {{ _('%(app)s is designed for internal use and prioritizes data security and privacy:', app=app_name) }}
-
Username-only authentication for simplicity
-
Role-based access control
-
Secure session management
-
Data stored locally on your infrastructure
-
No external data sharing or tracking
+
{{ _('Username-only authentication for simplicity') }}
+
{{ _('Role-based access control') }}
+
{{ _('Secure session management') }}
+
{{ _('Data stored locally on your infrastructure') }}
+
{{ _('No external data sharing or tracking') }}
@@ -177,34 +177,34 @@
- Getting Help
+ {{ _('Getting Help') }}
- Need help using {{ app_name }}? Here are some resources:
+ {{ _('Need help using %(app)s? Here are some resources:', app=app_name) }}
-
Documentation
+
{{ _('Documentation') }}
- Check the help section for detailed instructions on using all features.
+ {{ _('Check the help section for detailed instructions on using all features.') }}
{{ _('Summary Report') }}: {{ _('Overview of key metrics and trends') }}
+
{{ _('CSV Export') }}: {{ _('Export time entries for external analysis') }}
-
Filtering Options
+
{{ _('Filtering Options') }}
-
Date Range: Filter by start and end dates
-
Project: Filter by specific project
-
User: Filter by specific user
-
Status: Filter by project status
+
{{ _('Date Range') }}: {{ _('Filter by start and end dates') }}
+
{{ _('Project') }}: {{ _('Filter by specific project') }}
+
{{ _('User') }}: {{ _('Filter by specific user') }}
+
{{ _('Status') }}: {{ _('Filter by project status') }}
-
Exporting Data
-
Use the CSV export feature to download time entries for external tools. Find it on the Reports page.
+
{{ _('Exporting Data') }}
+
{{ _('Use the CSV export feature to download time entries for external tools. Find it on the Reports page.') }}
@@ -180,31 +183,32 @@
{% if current_user.is_admin %}
-
Admin Features
+
{{ _('Admin Features') }}
-
User Management
+
{{ _('User Management') }}
-
Create new users with username-only authentication
-
Assign user roles (user or admin)
-
Activate/deactivate user accounts
-
View user statistics and activity
+
{{ _('Create new users with username-only authentication') }}
+
{{ _('Assign user roles (user or admin)') }}
+
{{ _('Activate/deactivate user accounts') }}
+
{{ _('View user statistics and activity') }}
-
System Settings
+
{{ _('System Settings') }}
-
Configure timezone and currency
-
Set time rounding rules
-
Enable/disable self-registration
-
Configure backup settings
+
{{ _('Configure timezone and currency') }}
+
{{ _('Set time rounding rules') }}
+
{{ _('Enable/disable self-registration') }}
+
{{ _('Configure backup settings') }}
-
System Maintenance
+
{{ _('System Maintenance') }}
-
Create manual database backups
-
View system information and health
-
Monitor system statistics
-
Clean up old data
+
{{ _('Create manual database backups') }}
+
{{ _('View system information and health') }}
+
{{ _('Monitor system statistics and metrics') }}
+
{{ _('Review metrics server status and connectivity') }}
+
{{ _('Clean up old data') }}
@@ -213,19 +217,19 @@
-
Frequently Asked Questions
+
{{ _('Frequently Asked Questions') }}
- What happens if I forget to stop my timer?
+ {{ _('What happens if I forget to stop my timer?') }}
- The timer will continue running until you stop it. You can see your active timer on the dashboard and timer page. The timer persists even if you close your browser.
+ {{ _('The timer will continue running until you stop it. You can see your active timer on the dashboard and timer page. The timer persists even if you close your browser.') }}
@@ -233,12 +237,12 @@
- Can I edit time entries after they're created?
+ {{ _("Can I edit time entries after they're created?") }}
- Yes, you can edit any time entry by clicking the edit button. You can modify the project, start/end times, notes, tags, and billable status.
+ {{ _('Yes, you can edit any time entry by clicking the edit button. You can modify the project, start/end times, notes, tags, and billable status.') }}
@@ -246,12 +250,12 @@
- How do I track time for multiple projects?
+ {{ _('How do I track time for multiple projects?') }}
- You can only have one active timer at a time. To switch projects, stop your current timer and start a new one for the different project. You can also create manual time entries for past work.
+ {{ _('You can only have one active timer at a time. To switch projects, stop your current timer and start a new one for the different project. You can also create manual time entries for past work.') }}
@@ -259,12 +263,12 @@
- How do I export my time data?
+ {{ _('How do I export my time data?') }}
- Go to the Reports page and use the "Export CSV" feature. You can apply filters to export specific data, or export all time entries. The CSV file can be opened in Excel or other spreadsheet applications.
+ {{ _('Go to the Reports page and use the "Export CSV" feature. You can apply filters to export specific data, or export all time entries. The CSV file can be opened in Excel or other spreadsheet applications.') }}
diff --git a/templates/projects/create.html b/templates/projects/create.html
index 65b26dd..af35eec 100644
--- a/templates/projects/create.html
+++ b/templates/projects/create.html
@@ -418,7 +418,7 @@ document.addEventListener('DOMContentLoaded', function() {
// Show loading state
const submitBtn = form.querySelector('button[type="submit"]');
const originalText = submitBtn.innerHTML;
- submitBtn.innerHTML = 'Creating...';
+ submitBtn.innerHTML = '{{ _('Creating...') }}';
submitBtn.disabled = true;
// Re-enable after a delay (in case of validation errors)
diff --git a/templates/projects/list.html b/templates/projects/list.html
index 984884e..10bf23b 100644
--- a/templates/projects/list.html
+++ b/templates/projects/list.html
@@ -334,12 +334,30 @@
}
+
+