-
Notifications
You must be signed in to change notification settings - Fork 13
Internationalization
AutoApply supports full internationalization with a JSON-based locale system:
┌─────────────────────────────────┐
│ static/locales/en.json │ ◀── String catalog (383 keys, 23 sections)
└───────────┬─────────────────────┘
│
┌───────┴───────┐
│ │
▼ ▼
┌────────┐ ┌──────────┐
│Backend │ │ Frontend │
│core/ │ │ static/ │
│i18n.py │ │ js/ │
│ │ │ i18n.js │
│ t() │ │ t() │
└────────┘ └──────────┘
-
Locale files: JSON files in
static/locales/(e.g.,en.json). -
Backend translation:
core/i18n.pyprovides at()function for Python code. -
Frontend translation:
static/js/i18n.jsprovides at()function for JavaScript code. -
HTML auto-translation:
data-i18nattributes on HTML elements are auto-translated when the locale changes. - No build step: Locale files are served as static assets. Adding a new language requires no code changes.
The English locale file (static/locales/en.json) contains 383 keys organized into 23 sections:
| Section | Key Prefix | Example Keys | Description |
|---|---|---|---|
common |
common.* |
common.save, common.cancel, common.delete
|
Shared UI labels |
nav |
nav.* |
nav.dashboard, nav.applications, nav.settings
|
Navigation items |
dashboard |
dashboard.* |
dashboard.title, dashboard.bot_status
|
Dashboard tab |
applications |
applications.* |
applications.title, applications.no_results
|
Applications tab |
settings |
settings.* |
settings.title, settings.profile
|
Settings tab |
wizard |
wizard.* |
wizard.welcome, wizard.step_profile
|
Setup wizard |
bot |
bot.* |
bot.start, bot.stop, bot.status_idle
|
Bot controls |
review |
review.* |
review.approve, review.reject, review.skip
|
Review mode |
analytics |
analytics.* |
analytics.total, analytics.today
|
Analytics panel |
feed |
feed.* |
feed.job_found, feed.job_applied
|
Live feed messages |
errors |
errors.* |
errors.not_found, errors.unauthorized
|
Error messages |
validation |
validation.* |
validation.required, validation.email
|
Form validation |
status |
status.* |
status.applied, status.interview
|
Application statuses |
platforms |
platforms.* |
platforms.linkedin, platforms.indeed
|
Platform names |
schedule |
schedule.* |
schedule.enabled, schedule.start_time
|
Scheduler UI |
profile |
profile.* |
profile.full_name, profile.email
|
Profile fields |
search |
search.* |
search.job_titles, search.locations
|
Search criteria |
llm |
llm.* |
llm.provider, llm.api_key
|
AI provider config |
export |
export.* |
export.csv, export.downloading
|
Export features |
login |
login.* |
login.open_browser, login.close_browser
|
Platform login |
a11y |
a11y.* |
a11y.skip_to_main, a11y.loading
|
Accessibility labels |
confirm |
confirm.* |
confirm.delete, confirm.stop_bot
|
Confirmation dialogs |
toast |
toast.* |
toast.saved, toast.error
|
Toast notifications |
from core.i18n import tmessage = t("errors.not_found")
# Returns: "Not found"String placeholders use {placeholder} syntax:
message = t("feed.job_found", title="Software Engineer", company="Acme Corp")
# Template: "Found: {title} at {company}"
# Returns: "Found: Software Engineer at Acme Corp"from core.i18n import t
@bp.route('/api/example/<id>')
def get_example(id):
item = db.find(id)
if not item:
return jsonify({"error": t("errors.not_found")}), 404
return jsonify(item)If a key is not found in the locale file, t() returns the key itself as a fallback:
t("nonexistent.key")
# Returns: "nonexistent.key"import { t } from './i18n.js';const title = t('dashboard.title');
// Returns: "Dashboard"const message = t('feed.job_applied', { title: 'Software Engineer', company: 'Acme Corp' });
// Template: "Applied to {title} at {company}"
// Returns: "Applied to Software Engineer at Acme Corp"document.getElementById('status').textContent = t('bot.status_idle');HTML elements can be tagged for automatic translation using data-i18n attributes:
<h1 data-i18n="dashboard.title">Dashboard</h1><input data-i18n-placeholder="search.placeholder" placeholder="Search jobs..."><button data-i18n-aria-label="a11y.close_dialog" aria-label="Close dialog">X</button><span data-i18n-title="common.tooltip_info" title="More information">i</span><input
data-i18n-placeholder="search.placeholder"
data-i18n-aria-label="search.aria_label"
placeholder="Search..."
aria-label="Search applications"
>When the locale changes, the _applyDataI18n() function automatically translates all tagged elements in the DOM:
// Called internally when locale changes
function _applyDataI18n() {
// Translate text content
document.querySelectorAll('[data-i18n]').forEach(el => {
el.textContent = t(el.dataset.i18n);
});
// Translate placeholders
document.querySelectorAll('[data-i18n-placeholder]').forEach(el => {
el.placeholder = t(el.dataset.i18nPlaceholder);
});
// Translate aria-labels
document.querySelectorAll('[data-i18n-aria-label]').forEach(el => {
el.setAttribute('aria-label', t(el.dataset.i18nAriaLabel));
});
// Translate title attributes
document.querySelectorAll('[data-i18n-title]').forEach(el => {
el.title = t(el.dataset.i18nTitle);
});
}This function is called automatically when:
- The application loads for the first time.
- The user switches to a different locale.
- A new section of the UI is rendered dynamically.
-
Copy the English locale file:
cp static/locales/en.json static/locales/es.json
-
Translate all values in the new file. Keep the keys identical:
{ "common": { "save": "Guardar", "cancel": "Cancelar", "delete": "Eliminar" } } -
Done. The new locale automatically appears in the
GET /api/localesresponse and can be selected in the UI.
No code changes, no configuration updates, no build step required.
-
Add the key to
static/locales/en.jsonin the appropriate section:{ "dashboard": { "new_feature_label": "My New Feature" } } -
Use in backend code:
from core.i18n import t label = t("dashboard.new_feature_label")
-
Use in frontend code:
import { t } from './i18n.js'; const label = t('dashboard.new_feature_label');
-
Use in HTML:
<span data-i18n="dashboard.new_feature_label">My New Feature</span>
-
Add translations to other locale files (e.g.,
es.json). If a key is missing from a locale file, the English fallback is used.
i18n functionality is tested in tests/test_i18n.py with 21 tests covering:
| Test Area | Description |
|---|---|
| Key resolution |
t() returns correct string for valid keys |
| Missing keys |
t() returns key name as fallback |
| Placeholders |
{placeholder} interpolation works correctly |
| Nested keys | Dot-notation key resolution (section.subsection.key) |
| Locale loading | JSON files parsed correctly |
| Locale listing |
GET /api/locales returns available locales |
| Section completeness | All sections present in en.json |
| Key count | Validates expected number of keys |
Run i18n tests:
pytest tests/test_i18n.py -vAutoApply Wiki
User Guide
Technical
Build & Deploy
Project