feat(ui): internationalize profile, subscription, integrations templates and user menu JS
- Add 155 new translation keys to en.json (common, nav, integrations, profile, subscription sections) - Add window.__i18n script block to base.html for user menu strings - Replace all hardcoded English strings in common.js with window.__i18n lookups using safe fallback pattern - Internationalize profile.html (page title, headings, labels, hints, placeholders, theme options) - Internationalize subscription.html (page title, plan cards, usage stats, plan actions, badges) - Internationalize integrations_dashboard.html (header, quota bars, modal, all form fields, action buttons, webhook section, delete modal) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -165,11 +165,11 @@ function _makeMenuLink(href, iconClass, label, extraClasses = '') {
|
||||
'flex items-center gap-2 px-2 py-1 rounded-md text-gray-700 hover:text-gray-900 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-blue-500';
|
||||
btn.setAttribute('aria-haspopup', 'true');
|
||||
btn.setAttribute('aria-expanded', 'false');
|
||||
btn.setAttribute('aria-label', `Account menu for ${displayName}`);
|
||||
btn.setAttribute('aria-label', `${(window.__i18n.accountMenuFor || 'Account menu for {name}').replace('{name}', displayName)}`);
|
||||
|
||||
const avatar = document.createElement('img');
|
||||
avatar.src = data.picture;
|
||||
avatar.alt = 'Avatar';
|
||||
avatar.alt = window.__i18n.avatar || 'Avatar';
|
||||
avatar.className = 'w-8 h-8 rounded-full';
|
||||
|
||||
const nameSpan = document.createElement('span');
|
||||
@@ -189,7 +189,7 @@ function _makeMenuLink(href, iconClass, label, extraClasses = '') {
|
||||
menu.className =
|
||||
'absolute right-0 mt-2 w-52 bg-white rounded-md shadow-lg ring-1 ring-black ring-opacity-5 z-50 hidden';
|
||||
menu.setAttribute('role', 'menu');
|
||||
menu.setAttribute('aria-label', 'Account menu');
|
||||
menu.setAttribute('aria-label', window.__i18n.accountMenu || 'Account menu');
|
||||
|
||||
// User info header
|
||||
const header = document.createElement('div');
|
||||
@@ -207,16 +207,16 @@ function _makeMenuLink(href, iconClass, label, extraClasses = '') {
|
||||
const linksDiv = document.createElement('div');
|
||||
linksDiv.className = 'py-1';
|
||||
linksDiv.appendChild(
|
||||
_makeMenuLink('/profile', 'fas fa-user-circle text-blue-400', 'Profile Settings', 'text-gray-700')
|
||||
_makeMenuLink('/profile', 'fas fa-user-circle text-blue-400', window.__i18n.profileSettings || 'Profile Settings', 'text-gray-700')
|
||||
);
|
||||
linksDiv.appendChild(
|
||||
_makeMenuLink('/subscription', 'fas fa-layer-group text-indigo-400', 'My Subscription', 'text-gray-700')
|
||||
_makeMenuLink('/subscription', 'fas fa-layer-group text-indigo-400', window.__i18n.mySubscription || 'My Subscription', 'text-gray-700')
|
||||
);
|
||||
linksDiv.appendChild(
|
||||
_makeMenuLink('/api-tokens', 'fas fa-key text-yellow-500', 'API Tokens', 'text-gray-700')
|
||||
_makeMenuLink('/api-tokens', 'fas fa-key text-yellow-500', window.__i18n.apiTokens || 'API Tokens', 'text-gray-700')
|
||||
);
|
||||
linksDiv.appendChild(
|
||||
_makeMenuLink('/shared-links', 'fas fa-share-alt text-blue-400', 'Shared Links', 'text-gray-700')
|
||||
_makeMenuLink('/shared-links', 'fas fa-share-alt text-blue-400', window.__i18n.sharedLinks || 'Shared Links', 'text-gray-700')
|
||||
);
|
||||
|
||||
// Divider + Sign Out
|
||||
@@ -225,7 +225,7 @@ function _makeMenuLink(href, iconClass, label, extraClasses = '') {
|
||||
const signOutDiv = document.createElement('div');
|
||||
signOutDiv.className = 'py-1';
|
||||
signOutDiv.appendChild(
|
||||
_makeMenuLink('/logout', 'fas fa-sign-out-alt text-red-400', 'Sign Out', 'text-red-600')
|
||||
_makeMenuLink('/logout', 'fas fa-sign-out-alt text-red-400', window.__i18n.signOut || 'Sign Out', 'text-red-600')
|
||||
);
|
||||
|
||||
menu.appendChild(header);
|
||||
@@ -259,7 +259,7 @@ function _makeMenuLink(href, iconClass, label, extraClasses = '') {
|
||||
userRow.className = 'flex items-center gap-3 px-3 py-3';
|
||||
const mAvatar = document.createElement('img');
|
||||
mAvatar.src = data.picture;
|
||||
mAvatar.alt = 'Avatar';
|
||||
mAvatar.alt = window.__i18n.avatar || 'Avatar';
|
||||
mAvatar.className = 'w-8 h-8 rounded-full flex-shrink-0';
|
||||
const mUserInfo = document.createElement('div');
|
||||
mUserInfo.className = 'min-w-0';
|
||||
@@ -284,7 +284,7 @@ function _makeMenuLink(href, iconClass, label, extraClasses = '') {
|
||||
profileIcon.className = 'fas fa-user-circle mr-2 text-blue-400';
|
||||
profileIcon.setAttribute('aria-hidden', 'true');
|
||||
profileLink.appendChild(profileIcon);
|
||||
profileLink.appendChild(document.createTextNode('Profile Settings'));
|
||||
profileLink.appendChild(document.createTextNode(window.__i18n.profileSettings || 'Profile Settings'));
|
||||
mobileAuthSection.appendChild(profileLink);
|
||||
|
||||
// Subscription link
|
||||
@@ -296,7 +296,7 @@ function _makeMenuLink(href, iconClass, label, extraClasses = '') {
|
||||
subIcon.className = 'fas fa-layer-group mr-2 text-indigo-400';
|
||||
subIcon.setAttribute('aria-hidden', 'true');
|
||||
subLink.appendChild(subIcon);
|
||||
subLink.appendChild(document.createTextNode('My Subscription'));
|
||||
subLink.appendChild(document.createTextNode(window.__i18n.mySubscription || 'My Subscription'));
|
||||
mobileAuthSection.appendChild(subLink);
|
||||
|
||||
// API Tokens link
|
||||
@@ -308,7 +308,7 @@ function _makeMenuLink(href, iconClass, label, extraClasses = '') {
|
||||
tokensIcon.className = 'fas fa-key mr-2 text-yellow-500';
|
||||
tokensIcon.setAttribute('aria-hidden', 'true');
|
||||
tokensLink.appendChild(tokensIcon);
|
||||
tokensLink.appendChild(document.createTextNode('API Tokens'));
|
||||
tokensLink.appendChild(document.createTextNode(window.__i18n.apiTokens || 'API Tokens'));
|
||||
mobileAuthSection.appendChild(tokensLink);
|
||||
|
||||
// Shared Links link
|
||||
@@ -320,7 +320,7 @@ function _makeMenuLink(href, iconClass, label, extraClasses = '') {
|
||||
sharedLinksIcon.className = 'fas fa-share-alt mr-2 text-blue-400';
|
||||
sharedLinksIcon.setAttribute('aria-hidden', 'true');
|
||||
sharedLinksLink.appendChild(sharedLinksIcon);
|
||||
sharedLinksLink.appendChild(document.createTextNode('Shared Links'));
|
||||
sharedLinksLink.appendChild(document.createTextNode(window.__i18n.sharedLinks || 'Shared Links'));
|
||||
mobileAuthSection.appendChild(sharedLinksLink);
|
||||
|
||||
// Logout link
|
||||
@@ -332,7 +332,7 @@ function _makeMenuLink(href, iconClass, label, extraClasses = '') {
|
||||
logoutIcon.className = 'fas fa-sign-out-alt mr-2';
|
||||
logoutIcon.setAttribute('aria-hidden', 'true');
|
||||
logoutLink.appendChild(logoutIcon);
|
||||
logoutLink.appendChild(document.createTextNode('Sign Out'));
|
||||
logoutLink.appendChild(document.createTextNode(window.__i18n.signOut || 'Sign Out'));
|
||||
mobileAuthSection.appendChild(logoutLink);
|
||||
}
|
||||
} else {
|
||||
@@ -367,7 +367,7 @@ function _renderLoggedOutAuth(authSection, mobileAuthSection) {
|
||||
loginLink.href = '/login';
|
||||
loginLink.className =
|
||||
'px-3 py-1.5 rounded-md text-sm font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-100 border border-gray-300';
|
||||
loginLink.textContent = 'Log In';
|
||||
loginLink.textContent = window.__i18n.logIn || 'Log In';
|
||||
row.appendChild(loginLink);
|
||||
|
||||
if (multiUser) {
|
||||
@@ -375,7 +375,7 @@ function _renderLoggedOutAuth(authSection, mobileAuthSection) {
|
||||
startLink.href = startHref;
|
||||
startLink.className =
|
||||
'px-3 py-1.5 rounded-md text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500';
|
||||
startLink.textContent = allowSignup ? 'Sign Up' : 'Get Started';
|
||||
startLink.textContent = allowSignup ? (window.__i18n.signUp || 'Sign Up') : (window.__i18n.getStarted || 'Get Started');
|
||||
row.appendChild(startLink);
|
||||
}
|
||||
|
||||
@@ -393,7 +393,7 @@ function _renderLoggedOutAuth(authSection, mobileAuthSection) {
|
||||
loginIcon.className = 'fas fa-sign-in-alt mr-2';
|
||||
loginIcon.setAttribute('aria-hidden', 'true');
|
||||
loginLink.appendChild(loginIcon);
|
||||
loginLink.appendChild(document.createTextNode('Log In'));
|
||||
loginLink.appendChild(document.createTextNode(window.__i18n.logIn || 'Log In'));
|
||||
mobileAuthSection.appendChild(loginLink);
|
||||
|
||||
if (multiUser) {
|
||||
@@ -405,7 +405,7 @@ function _renderLoggedOutAuth(authSection, mobileAuthSection) {
|
||||
startIcon.className = 'fas fa-arrow-right mr-2';
|
||||
startIcon.setAttribute('aria-hidden', 'true');
|
||||
startLink.appendChild(startIcon);
|
||||
startLink.appendChild(document.createTextNode(allowSignup ? 'Sign Up' : 'Get Started'));
|
||||
startLink.appendChild(document.createTextNode(allowSignup ? (window.__i18n.signUp || 'Sign Up') : (window.__i18n.getStarted || 'Get Started')));
|
||||
mobileAuthSection.appendChild(startLink);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -548,6 +548,23 @@
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!-- i18n strings for common.js (user menu) -->
|
||||
<script>
|
||||
window.__i18n = {
|
||||
accountMenu: {{ _("nav.account_menu") | tojson }},
|
||||
accountMenuFor: {{ _("nav.account_menu_for") | tojson }},
|
||||
avatar: {{ _("common.avatar") | tojson }},
|
||||
profileSettings: {{ _("nav.profile_settings") | tojson }},
|
||||
mySubscription: {{ _("nav.my_subscription") | tojson }},
|
||||
apiTokens: {{ _("nav.api_tokens") | tojson }},
|
||||
sharedLinks: {{ _("nav.shared_links") | tojson }},
|
||||
signOut: {{ _("nav.sign_out") | tojson }},
|
||||
logIn: {{ _("nav.login") | tojson }},
|
||||
signUp: {{ _("nav.signup") | tojson }},
|
||||
getStarted: {{ _("nav.get_started") | tojson }},
|
||||
};
|
||||
</script>
|
||||
|
||||
<!-- Common JS (shared) -->
|
||||
<script src="/static/js/common.js"></script>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Integrations – DocuElevate{% endblock %}
|
||||
{% block title %}{{ _("integrations.page_title") }} – DocuElevate{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div
|
||||
@@ -13,10 +13,10 @@
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold text-gray-900 dark:text-white flex items-center gap-2">
|
||||
<i class="fas fa-plug text-indigo-500" aria-hidden="true"></i>
|
||||
Integrations
|
||||
{{ _("integrations.page_title") }}
|
||||
</h1>
|
||||
<p class="text-gray-500 dark:text-gray-400 text-sm mt-1">
|
||||
Manage your ingestion sources and storage destinations in one place.
|
||||
{{ _("integrations.subtitle") }}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
@@ -27,7 +27,7 @@
|
||||
style="min-height:44px;"
|
||||
:aria-disabled="!canAddAny"
|
||||
>
|
||||
<i class="fas fa-plus mr-2" aria-hidden="true"></i> Add Integration
|
||||
<i class="fas fa-plus mr-2" aria-hidden="true"></i> {{ _("integrations.add_button") }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -41,24 +41,23 @@
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-sm font-medium text-gray-700 dark:text-gray-200">
|
||||
Mailbox Sources:
|
||||
{{ _("integrations.sources_quota_label") }}
|
||||
<strong x-text="quota.src_count"></strong>
|
||||
<template x-if="quota.max_sources !== null && quota.max_sources > 0">
|
||||
<span> / <strong x-text="quota.max_sources"></strong></span>
|
||||
</template>
|
||||
<template x-if="quota.max_sources === null">
|
||||
<span class="text-gray-400"> / unlimited</span>
|
||||
<span class="text-gray-400"> {{ _("integrations.quota_unlimited") }}</span>
|
||||
</template>
|
||||
<template x-if="quota.max_sources === 0">
|
||||
<span class="text-gray-400 ml-1">(not available)</span>
|
||||
<span class="text-gray-400 ml-1">{{ _("integrations.quota_not_available") }}</span>
|
||||
</template>
|
||||
</p>
|
||||
<p class="text-xs text-gray-400 dark:text-gray-500 mt-0.5">
|
||||
Plan: <span x-text="quota.tier_name"></span>
|
||||
{{ _("integrations.plan_label") }} <span x-text="quota.tier_name"></span>
|
||||
</p>
|
||||
</div>
|
||||
<!-- Mini usage bar -->
|
||||
<template x-if="quota.max_sources !== null && quota.max_sources > 0">
|
||||
<div class="w-24 flex-shrink-0">
|
||||
<div class="bg-gray-200 dark:bg-gray-600 rounded-full h-2 overflow-hidden">
|
||||
<div
|
||||
@@ -77,7 +76,7 @@
|
||||
</div>
|
||||
<template x-if="quota.max_sources !== null && quota.src_count >= quota.max_sources && quota.max_sources >= 0">
|
||||
<a href="/subscription" class="inline-flex items-center text-xs text-indigo-600 hover:text-indigo-800 mt-2">
|
||||
<i class="fas fa-arrow-up mr-1" aria-hidden="true"></i> Upgrade Plan
|
||||
<i class="fas fa-arrow-up mr-1" aria-hidden="true"></i> {{ _("integrations.upgrade_plan") }}
|
||||
</a>
|
||||
</template>
|
||||
</div>
|
||||
@@ -90,17 +89,17 @@
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-sm font-medium text-gray-700 dark:text-gray-200">
|
||||
Storage Destinations:
|
||||
{{ _("integrations.destinations_quota_label") }}
|
||||
<strong x-text="quota.dest_count"></strong>
|
||||
<template x-if="quota.max_destinations !== null">
|
||||
<span> / <strong x-text="quota.max_destinations"></strong></span>
|
||||
</template>
|
||||
<template x-if="quota.max_destinations === null">
|
||||
<span class="text-gray-400"> / unlimited</span>
|
||||
<span class="text-gray-400"> {{ _("integrations.quota_unlimited") }}</span>
|
||||
</template>
|
||||
</p>
|
||||
<p class="text-xs text-gray-400 dark:text-gray-500 mt-0.5">
|
||||
Plan: <span x-text="quota.tier_name"></span>
|
||||
{{ _("integrations.plan_label") }} <span x-text="quota.tier_name"></span>
|
||||
</p>
|
||||
</div>
|
||||
<!-- Mini usage bar -->
|
||||
@@ -123,7 +122,7 @@
|
||||
</div>
|
||||
<template x-if="quota.max_destinations !== null && quota.dest_count >= quota.max_destinations">
|
||||
<a href="/subscription" class="inline-flex items-center text-xs text-indigo-600 hover:text-indigo-800 mt-2">
|
||||
<i class="fas fa-arrow-up mr-1" aria-hidden="true"></i> Upgrade Plan
|
||||
<i class="fas fa-arrow-up mr-1" aria-hidden="true"></i> {{ _("integrations.upgrade_plan") }}
|
||||
</a>
|
||||
</template>
|
||||
</div>
|
||||
@@ -151,7 +150,7 @@
|
||||
<template x-if="loading">
|
||||
<div class="text-center py-12 text-gray-400">
|
||||
<i class="fas fa-spinner fa-spin text-3xl mb-3" aria-hidden="true"></i>
|
||||
<p>Loading integrations…</p>
|
||||
<p>{{ _("integrations.loading") }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -159,9 +158,9 @@
|
||||
<template x-if="!loading && integrations.length === 0">
|
||||
<div class="text-center py-16 bg-white dark:bg-gray-800 rounded-lg shadow">
|
||||
<i class="fas fa-plug text-5xl text-gray-300 mb-4" aria-hidden="true"></i>
|
||||
<h2 class="text-xl font-semibold text-gray-700 dark:text-gray-300 mb-2">No integrations configured</h2>
|
||||
<h2 class="text-xl font-semibold text-gray-700 dark:text-gray-300 mb-2">{{ _("integrations.empty_state") }}</h2>
|
||||
<p class="text-gray-500 dark:text-gray-400 mb-6 max-w-md mx-auto">
|
||||
Add your first integration to connect ingestion sources (like IMAP) and storage destinations (like S3, Dropbox, or Google Drive).
|
||||
{{ _("integrations.no_integrations_body") }}
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
@@ -170,7 +169,7 @@
|
||||
class="inline-flex items-center px-5 py-2.5 bg-indigo-600 hover:bg-indigo-700 disabled:bg-gray-300 disabled:cursor-not-allowed text-white text-sm font-medium rounded-md"
|
||||
style="min-height:44px;"
|
||||
>
|
||||
<i class="fas fa-plus mr-2" aria-hidden="true"></i> Add Your First Integration
|
||||
<i class="fas fa-plus mr-2" aria-hidden="true"></i> {{ _("integrations.add_first_button") }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
@@ -179,7 +178,7 @@
|
||||
<template x-if="!loading && sources.length > 0">
|
||||
<section class="mb-8">
|
||||
<h2 class="text-lg font-semibold text-gray-800 dark:text-gray-200 mb-3 flex items-center gap-2">
|
||||
<i class="fas fa-inbox text-blue-500" aria-hidden="true"></i> Sources
|
||||
<i class="fas fa-inbox text-blue-500" aria-hidden="true"></i> {{ _("integrations.sources_section") }}
|
||||
</h2>
|
||||
<div class="space-y-3">
|
||||
<template x-for="intg in sources" :key="intg.id">
|
||||
@@ -196,7 +195,7 @@
|
||||
<span
|
||||
class="ml-2 inline-flex items-center px-2 py-0.5 rounded text-xs font-medium"
|
||||
:class="intg.is_active ? 'bg-green-50 text-green-700 dark:bg-green-900 dark:text-green-300' : 'bg-gray-100 text-gray-500 dark:bg-gray-700 dark:text-gray-400'"
|
||||
x-text="intg.is_active ? 'Active' : 'Paused'"
|
||||
x-text="intg.is_active ? '{{ _('common.active') }}' : '{{ _('integrations.paused') }}'"
|
||||
></span>
|
||||
</p>
|
||||
<template x-if="intg.last_error">
|
||||
@@ -216,7 +215,7 @@
|
||||
:aria-label="`${intg.has_credentials ? 'Re-authorize' : 'Authorize'} ${intg.name}`"
|
||||
>
|
||||
<i class="fas fa-key mr-1" aria-hidden="true"></i>
|
||||
<span x-text="intg.has_credentials ? 'Re-Authorize' : 'Authorize'"></span>
|
||||
<span x-text="intg.has_credentials ? '{{ _('integrations.authorize_reauth') }}' : '{{ _('integrations.authorize_btn') }}'"></span>
|
||||
</a>
|
||||
</template>
|
||||
<button
|
||||
@@ -233,7 +232,7 @@
|
||||
<template x-if="testingId !== intg.id">
|
||||
<i class="fas fa-plug mr-1" aria-hidden="true"></i>
|
||||
</template>
|
||||
Test
|
||||
{{ _("common.test") }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -242,7 +241,7 @@
|
||||
style="min-height:36px;"
|
||||
:aria-label="`Edit ${intg.name}`"
|
||||
>
|
||||
<i class="fas fa-edit mr-1" aria-hidden="true"></i>Edit
|
||||
<i class="fas fa-edit mr-1" aria-hidden="true"></i>{{ _("common.edit") }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -251,7 +250,7 @@
|
||||
style="min-height:36px;"
|
||||
:aria-label="`Delete ${intg.name}`"
|
||||
>
|
||||
<i class="fas fa-trash-alt mr-1" aria-hidden="true"></i>Delete
|
||||
<i class="fas fa-trash-alt mr-1" aria-hidden="true"></i>{{ _("common.delete") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -265,7 +264,7 @@
|
||||
<template x-if="!loading && destinations.length > 0">
|
||||
<section class="mb-8">
|
||||
<h2 class="text-lg font-semibold text-gray-800 dark:text-gray-200 mb-3 flex items-center gap-2">
|
||||
<i class="fas fa-hdd text-green-500" aria-hidden="true"></i> Destinations
|
||||
<i class="fas fa-hdd text-green-500" aria-hidden="true"></i> {{ _("integrations.destinations_section") }}
|
||||
</h2>
|
||||
<div class="space-y-3">
|
||||
<template x-for="intg in destinations" :key="intg.id">
|
||||
@@ -282,7 +281,7 @@
|
||||
<span
|
||||
class="ml-2 inline-flex items-center px-2 py-0.5 rounded text-xs font-medium"
|
||||
:class="intg.is_active ? 'bg-green-50 text-green-700 dark:bg-green-900 dark:text-green-300' : 'bg-gray-100 text-gray-500 dark:bg-gray-700 dark:text-gray-400'"
|
||||
x-text="intg.is_active ? 'Active' : 'Paused'"
|
||||
x-text="intg.is_active ? '{{ _('common.active') }}' : '{{ _('integrations.paused') }}'"
|
||||
></span>
|
||||
</p>
|
||||
<template x-if="intg.last_error">
|
||||
@@ -302,7 +301,7 @@
|
||||
:aria-label="`${intg.has_credentials ? 'Re-authorize' : 'Authorize'} ${intg.name}`"
|
||||
>
|
||||
<i class="fas fa-key mr-1" aria-hidden="true"></i>
|
||||
<span x-text="intg.has_credentials ? 'Re-Authorize' : 'Authorize'"></span>
|
||||
<span x-text="intg.has_credentials ? '{{ _('integrations.authorize_reauth') }}' : '{{ _('integrations.authorize_btn') }}'"></span>
|
||||
</a>
|
||||
</template>
|
||||
<button
|
||||
@@ -319,7 +318,7 @@
|
||||
<template x-if="testingId !== intg.id">
|
||||
<i class="fas fa-plug mr-1" aria-hidden="true"></i>
|
||||
</template>
|
||||
Test
|
||||
{{ _("common.test") }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -328,7 +327,7 @@
|
||||
style="min-height:36px;"
|
||||
:aria-label="`Edit ${intg.name}`"
|
||||
>
|
||||
<i class="fas fa-edit mr-1" aria-hidden="true"></i>Edit
|
||||
<i class="fas fa-edit mr-1" aria-hidden="true"></i>{{ _("common.edit") }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -337,7 +336,7 @@
|
||||
style="min-height:36px;"
|
||||
:aria-label="`Delete ${intg.name}`"
|
||||
>
|
||||
<i class="fas fa-trash-alt mr-1" aria-hidden="true"></i>Delete
|
||||
<i class="fas fa-trash-alt mr-1" aria-hidden="true"></i>{{ _("common.delete") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -371,13 +370,13 @@
|
||||
<h2
|
||||
:id="editingIntegration ? 'modal-title-edit' : 'modal-title-create'"
|
||||
class="text-lg font-semibold text-gray-900 dark:text-white"
|
||||
x-text="editingIntegration ? 'Edit Integration' : 'Add Integration'"
|
||||
x-text="editingIntegration ? '{{ _('integrations.modal_title_edit') }}' : '{{ _('integrations.modal_title_add') }}'"
|
||||
></h2>
|
||||
<button
|
||||
type="button"
|
||||
@click="closeModal()"
|
||||
class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 focus:outline-none focus:ring-2 focus:ring-indigo-500 rounded"
|
||||
aria-label="Close modal"
|
||||
aria-label="{{ _('integrations.modal_close') }}"
|
||||
>
|
||||
<i class="fas fa-times text-xl" aria-hidden="true"></i>
|
||||
</button>
|
||||
@@ -388,7 +387,7 @@
|
||||
<!-- Direction -->
|
||||
<div>
|
||||
<label for="intg-direction" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">
|
||||
Direction <span class="text-red-500" aria-hidden="true">*</span>
|
||||
{{ _("integrations.direction_label") }} <span class="text-red-500" aria-hidden="true">*</span>
|
||||
</label>
|
||||
<select
|
||||
id="intg-direction"
|
||||
@@ -399,16 +398,16 @@
|
||||
aria-required="true"
|
||||
@change="onDirectionChange()"
|
||||
>
|
||||
<option value="">— Select —</option>
|
||||
<option value="SOURCE">Source (ingestion)</option>
|
||||
<option value="DESTINATION">Destination (storage)</option>
|
||||
<option value="">{{ _("integrations.direction_placeholder") }}</option>
|
||||
<option value="SOURCE">{{ _("integrations.direction_source") }}</option>
|
||||
<option value="DESTINATION">{{ _("integrations.direction_destination") }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Integration type -->
|
||||
<div>
|
||||
<label for="intg-type" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">
|
||||
Integration Type <span class="text-red-500" aria-hidden="true">*</span>
|
||||
{{ _("integrations.type_label") }} <span class="text-red-500" aria-hidden="true">*</span>
|
||||
</label>
|
||||
<select
|
||||
id="intg-type"
|
||||
@@ -419,7 +418,7 @@
|
||||
aria-required="true"
|
||||
@change="onTypeChange()"
|
||||
>
|
||||
<option value="">— Select direction first —</option>
|
||||
<option value="">{{ _("integrations.type_placeholder") }}</option>
|
||||
<template x-for="t in availableTypes" :key="t">
|
||||
<option :value="t" x-text="typeLabel(t)"></option>
|
||||
</template>
|
||||
@@ -429,13 +428,13 @@
|
||||
<!-- Name -->
|
||||
<div>
|
||||
<label for="intg-name" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">
|
||||
Label <span class="text-red-500" aria-hidden="true">*</span>
|
||||
{{ _("integrations.name_label") }} <span class="text-red-500" aria-hidden="true">*</span>
|
||||
</label>
|
||||
<input
|
||||
id="intg-name"
|
||||
type="text"
|
||||
x-model="form.name"
|
||||
placeholder="e.g. Work Gmail, S3 Archive"
|
||||
placeholder="{{ _('integrations.name_placeholder') }}"
|
||||
required
|
||||
maxlength="255"
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:bg-gray-700 dark:text-white text-sm"
|
||||
@@ -448,46 +447,46 @@
|
||||
<!-- IMAP fields -->
|
||||
<template x-if="form.integration_type === 'IMAP'">
|
||||
<div class="space-y-3 border-t border-gray-200 dark:border-gray-700 pt-3">
|
||||
<p class="text-xs font-semibold text-gray-400 uppercase tracking-wider">IMAP Settings</p>
|
||||
<p class="text-xs font-semibold text-gray-400 uppercase tracking-wider">{{ _("integrations.imap_settings") }}</p>
|
||||
<div class="grid grid-cols-3 gap-3">
|
||||
<div class="col-span-2">
|
||||
<label for="imap-host" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">Host <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<label for="imap-host" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">{{ _("integrations.host_label") }} <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<input id="imap-host" type="text" x-model="form.config.host" placeholder="imap.example.com" required class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:bg-gray-700 dark:text-white text-sm" aria-required="true" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="imap-port" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">Port</label>
|
||||
<label for="imap-port" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">{{ _("integrations.port_label") }}</label>
|
||||
<input id="imap-port" type="number" x-model.number="form.config.port" min="1" max="65535" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:bg-gray-700 dark:text-white text-sm" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label for="imap-username" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">Username <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<label for="imap-username" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">{{ _("integrations.username_label") }} <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<input id="imap-username" type="text" x-model="form.config.username" placeholder="user@example.com" required class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:bg-gray-700 dark:text-white text-sm" aria-required="true" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="imap-password" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">Password <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<label for="imap-password" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">{{ _("integrations.password_label") }} <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<input id="imap-password" :type="showPassword ? 'text' : 'password'" x-model="form.credentials.password" :placeholder="editingIntegration ? '(unchanged if blank)' : ''" :required="!editingIntegration" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:bg-gray-700 dark:text-white text-sm" />
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center gap-4">
|
||||
<label class="inline-flex items-center gap-2 text-sm text-gray-700 dark:text-gray-200 cursor-pointer">
|
||||
<input type="checkbox" x-model="form.config.use_ssl" class="rounded border-gray-300 text-indigo-600 focus:ring-indigo-500" />
|
||||
Use SSL
|
||||
{{ _("integrations.use_ssl") }}
|
||||
</label>
|
||||
<label class="inline-flex items-center gap-2 text-sm text-gray-700 dark:text-gray-200 cursor-pointer">
|
||||
<input type="checkbox" x-model="form.config.delete_after_process" class="rounded border-gray-300 text-indigo-600 focus:ring-indigo-500" />
|
||||
Delete emails after processing
|
||||
{{ _("integrations.delete_emails_label") }}
|
||||
</label>
|
||||
<label class="inline-flex items-center gap-2 text-sm text-gray-700 dark:text-gray-200 cursor-pointer">
|
||||
<input type="checkbox" x-model="form.config.gmail_apply_labels" class="rounded border-gray-300 text-indigo-600 focus:ring-indigo-500" />
|
||||
Apply Gmail labels & star
|
||||
{{ _("integrations.gmail_labels") }}
|
||||
</label>
|
||||
<label class="inline-flex items-center gap-2 text-sm text-gray-700 dark:text-gray-200 cursor-pointer">
|
||||
<input type="checkbox" x-model="showPassword" class="rounded border-gray-300 text-indigo-600 focus:ring-indigo-500" />
|
||||
Show password
|
||||
{{ _("integrations.show_password") }}
|
||||
</label>
|
||||
</div>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
||||
<i class="fas fa-info-circle mr-1" aria-hidden="true"></i>
|
||||
Gmail labels & star: when enabled, processed emails are starred and tagged with an "Ingested" label in Gmail. Only applies to Gmail servers.
|
||||
{{ _("integrations.gmail_hint") }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
@@ -495,36 +494,36 @@
|
||||
<!-- S3 fields -->
|
||||
<template x-if="form.integration_type === 'S3'">
|
||||
<div class="space-y-3 border-t border-gray-200 dark:border-gray-700 pt-3">
|
||||
<p class="text-xs font-semibold text-gray-400 uppercase tracking-wider">S3 Settings</p>
|
||||
<p class="text-xs font-semibold text-gray-400 uppercase tracking-wider">{{ _("integrations.s3_settings") }}</p>
|
||||
<div>
|
||||
<label for="s3-bucket" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">Bucket <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<label for="s3-bucket" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">{{ _("integrations.bucket_label") }} <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<input id="s3-bucket" type="text" x-model="form.config.bucket" placeholder="my-bucket" required class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:bg-gray-700 dark:text-white text-sm" aria-required="true" />
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label for="s3-region" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">Region</label>
|
||||
<label for="s3-region" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">{{ _("integrations.region_label") }}</label>
|
||||
<input id="s3-region" type="text" x-model="form.config.region" placeholder="us-east-1" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:bg-gray-700 dark:text-white text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="s3-prefix" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">Folder Prefix</label>
|
||||
<label for="s3-prefix" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">{{ _("integrations.folder_prefix_label") }}</label>
|
||||
<input id="s3-prefix" type="text" x-model="form.config.folder_prefix" placeholder="documents/" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:bg-gray-700 dark:text-white text-sm" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label for="s3-endpoint" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">Endpoint URL <span class="text-gray-400">(optional, for S3-compatible)</span></label>
|
||||
<label for="s3-endpoint" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">{{ _("integrations.endpoint_label") }} <span class="text-gray-400">{{ _("integrations.endpoint_optional") }}</span></label>
|
||||
<input id="s3-endpoint" type="url" x-model="form.config.endpoint_url" placeholder="https://s3.example.com" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:bg-gray-700 dark:text-white text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="s3-key" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">Access Key ID <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<label for="s3-key" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">{{ _("integrations.access_key_label") }} <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<input id="s3-key" type="text" x-model="form.credentials.access_key_id" :placeholder="editingIntegration ? '(unchanged if blank)' : ''" :required="!editingIntegration" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:bg-gray-700 dark:text-white text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="s3-secret" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">Secret Access Key <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<label for="s3-secret" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">{{ _("integrations.secret_key_label") }} <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<input id="s3-secret" :type="showPassword ? 'text' : 'password'" x-model="form.credentials.secret_access_key" :placeholder="editingIntegration ? '(unchanged if blank)' : ''" :required="!editingIntegration" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:bg-gray-700 dark:text-white text-sm" />
|
||||
</div>
|
||||
<label class="inline-flex items-center gap-2 text-sm text-gray-700 dark:text-gray-200 cursor-pointer">
|
||||
<input type="checkbox" x-model="showPassword" class="rounded border-gray-300 text-indigo-600 focus:ring-indigo-500" />
|
||||
Show secrets
|
||||
{{ _("integrations.show_secrets") }}
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
@@ -534,24 +533,24 @@
|
||||
<div class="space-y-3 border-t border-gray-200 dark:border-gray-700 pt-3">
|
||||
<p class="text-xs font-semibold text-gray-400 uppercase tracking-wider" x-text="form.integration_type === 'NEXTCLOUD' ? 'Nextcloud Settings' : 'WebDAV Settings'"></p>
|
||||
<div>
|
||||
<label for="webdav-url" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">URL <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<label for="webdav-url" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">{{ _("integrations.webdav_url_label") }} <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<input id="webdav-url" type="url" x-model="form.config.url" placeholder="https://cloud.example.com/remote.php/dav/files/user/" required class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:bg-gray-700 dark:text-white text-sm" aria-required="true" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="webdav-folder" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">Folder Path</label>
|
||||
<label for="webdav-folder" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">{{ _("integrations.folder_path_label") }}</label>
|
||||
<input id="webdav-folder" type="text" x-model="form.config.folder" placeholder="/Documents" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:bg-gray-700 dark:text-white text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="webdav-user" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">Username <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<label for="webdav-user" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">{{ _("integrations.username_label") }} <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<input id="webdav-user" type="text" x-model="form.credentials.username" :required="!editingIntegration" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:bg-gray-700 dark:text-white text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="webdav-pass" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">Password <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<label for="webdav-pass" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">{{ _("integrations.password_label") }} <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<input id="webdav-pass" :type="showPassword ? 'text' : 'password'" x-model="form.credentials.password" :placeholder="editingIntegration ? '(unchanged if blank)' : ''" :required="!editingIntegration" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:bg-gray-700 dark:text-white text-sm" />
|
||||
</div>
|
||||
<label class="inline-flex items-center gap-2 text-sm text-gray-700 dark:text-gray-200 cursor-pointer">
|
||||
<input type="checkbox" x-model="showPassword" class="rounded border-gray-300 text-indigo-600 focus:ring-indigo-500" />
|
||||
Show password
|
||||
{{ _("integrations.show_password") }}
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
@@ -562,29 +561,29 @@
|
||||
<p class="text-xs font-semibold text-gray-400 uppercase tracking-wider" x-text="form.integration_type + ' Settings'"></p>
|
||||
<div class="grid grid-cols-3 gap-3">
|
||||
<div class="col-span-2">
|
||||
<label for="ftp-host" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">Host <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<label for="ftp-host" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">{{ _("integrations.host_label") }} <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<input id="ftp-host" type="text" x-model="form.config.host" placeholder="ftp.example.com" required class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:bg-gray-700 dark:text-white text-sm" aria-required="true" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="ftp-port" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">Port</label>
|
||||
<label for="ftp-port" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">{{ _("integrations.port_label") }}</label>
|
||||
<input id="ftp-port" type="number" x-model.number="form.config.port" :placeholder="form.integration_type === 'SFTP' ? '22' : '21'" min="1" max="65535" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:bg-gray-700 dark:text-white text-sm" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label for="ftp-folder" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">Remote Path</label>
|
||||
<label for="ftp-folder" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">{{ _("integrations.remote_path_label") }}</label>
|
||||
<input id="ftp-folder" type="text" x-model="form.config.folder" placeholder="/upload" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:bg-gray-700 dark:text-white text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="ftp-user" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">Username <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<label for="ftp-user" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">{{ _("integrations.username_label") }} <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<input id="ftp-user" type="text" x-model="form.credentials.username" :required="!editingIntegration" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:bg-gray-700 dark:text-white text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="ftp-pass" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">Password <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<label for="ftp-pass" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">{{ _("integrations.password_label") }} <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<input id="ftp-pass" :type="showPassword ? 'text' : 'password'" x-model="form.credentials.password" :placeholder="editingIntegration ? '(unchanged if blank)' : ''" :required="!editingIntegration" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:bg-gray-700 dark:text-white text-sm" />
|
||||
</div>
|
||||
<label class="inline-flex items-center gap-2 text-sm text-gray-700 dark:text-gray-200 cursor-pointer">
|
||||
<input type="checkbox" x-model="showPassword" class="rounded border-gray-300 text-indigo-600 focus:ring-indigo-500" />
|
||||
Show password
|
||||
{{ _("integrations.show_password") }}
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
@@ -594,13 +593,12 @@
|
||||
<div class="space-y-3 border-t border-gray-200 dark:border-gray-700 pt-3">
|
||||
<p class="text-xs font-semibold text-gray-400 uppercase tracking-wider" x-text="typeLabel(form.integration_type) + ' Settings'"></p>
|
||||
<div>
|
||||
<label for="oauth-folder" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">Folder <span class="text-gray-400">(optional)</span></label>
|
||||
<label for="oauth-folder" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">{{ _("integrations.oauth_folder_label") }} <span class="text-gray-400">{{ _("integrations.folder_optional") }}</span></label>
|
||||
<input id="oauth-folder" type="text" x-model="form.config.folder" placeholder="/DocuElevate" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:bg-gray-700 dark:text-white text-sm" />
|
||||
</div>
|
||||
<div class="bg-blue-50 dark:bg-blue-900/30 rounded-md p-3 text-sm text-blue-700 dark:text-blue-300">
|
||||
<i class="fas fa-info-circle mr-1" aria-hidden="true"></i>
|
||||
Save this integration first, then use the <strong>Authorize</strong> button to complete the OAuth flow.
|
||||
Your credentials will be stored securely in your personal integration record.
|
||||
{{ _("integrations.oauth_hint") }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -608,9 +606,9 @@
|
||||
<!-- Email (destination) fields -->
|
||||
<template x-if="form.integration_type === 'EMAIL'">
|
||||
<div class="space-y-3 border-t border-gray-200 dark:border-gray-700 pt-3">
|
||||
<p class="text-xs font-semibold text-gray-400 uppercase tracking-wider">Email Forward Settings</p>
|
||||
<p class="text-xs font-semibold text-gray-400 uppercase tracking-wider">{{ _("integrations.email_settings") }}</p>
|
||||
<div>
|
||||
<label for="email-to" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">Recipient Email <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<label for="email-to" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">{{ _("integrations.recipient_label") }} <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<input id="email-to" type="email" x-model="form.config.recipient" placeholder="archive@example.com" required class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:bg-gray-700 dark:text-white text-sm" aria-required="true" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -619,11 +617,11 @@
|
||||
<!-- Watch Folder fields -->
|
||||
<template x-if="form.integration_type === 'WATCH_FOLDER'">
|
||||
<div class="space-y-3 border-t border-gray-200 dark:border-gray-700 pt-3">
|
||||
<p class="text-xs font-semibold text-gray-400 uppercase tracking-wider">Watch Folder Settings</p>
|
||||
<p class="text-xs font-semibold text-gray-400 uppercase tracking-wider">{{ _("integrations.watch_folder_settings") }}</p>
|
||||
<div>
|
||||
<label for="wf-source-type" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">Source Type <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<label for="wf-source-type" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">{{ _("integrations.source_type_label") }} <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<select id="wf-source-type" x-model="form.config.source_type" @change="onWatchFolderSourceTypeChange()" required class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:bg-gray-700 dark:text-white text-sm" aria-required="true">
|
||||
<option value="local">Local Filesystem</option>
|
||||
<option value="local">{{ _("integrations.source_local") }}</option>
|
||||
<option value="s3">Amazon S3</option>
|
||||
<option value="dropbox">Dropbox</option>
|
||||
<option value="google_drive">Google Drive</option>
|
||||
@@ -636,7 +634,7 @@
|
||||
<!-- Local filesystem fields -->
|
||||
<template x-if="form.config.source_type === 'local'">
|
||||
<div>
|
||||
<label for="wf-path" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">Folder Path <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<label for="wf-path" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">{{ _("integrations.folder_path_label") }} <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<input id="wf-path" type="text" x-model="form.config.folder_path" placeholder="/data/inbox" required class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:bg-gray-700 dark:text-white text-sm" aria-required="true" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -646,29 +644,29 @@
|
||||
<div class="space-y-3">
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label for="wf-s3-bucket" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">Bucket <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<label for="wf-s3-bucket" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">{{ _("integrations.bucket_label") }} <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<input id="wf-s3-bucket" type="text" x-model="form.config.bucket" placeholder="my-bucket" required class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:bg-gray-700 dark:text-white text-sm" aria-required="true" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="wf-s3-region" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">Region</label>
|
||||
<label for="wf-s3-region" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">{{ _("integrations.region_label") }}</label>
|
||||
<input id="wf-s3-region" type="text" x-model="form.config.region" placeholder="us-east-1" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:bg-gray-700 dark:text-white text-sm" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label for="wf-s3-prefix" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">Prefix (folder path)</label>
|
||||
<label for="wf-s3-prefix" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">{{ _("integrations.s3_prefix_label") }}</label>
|
||||
<input id="wf-s3-prefix" type="text" x-model="form.config.prefix" placeholder="inbox/" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:bg-gray-700 dark:text-white text-sm" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="wf-s3-endpoint" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">Endpoint URL <span class="text-xs text-gray-400">(optional, for S3-compatible)</span></label>
|
||||
<label for="wf-s3-endpoint" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">{{ _("integrations.endpoint_label") }} <span class="text-xs text-gray-400">{{ _("integrations.endpoint_optional") }}</span></label>
|
||||
<input id="wf-s3-endpoint" type="text" x-model="form.config.endpoint_url" placeholder="https://s3.example.com" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:bg-gray-700 dark:text-white text-sm" />
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label for="wf-s3-ak" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">Access Key ID <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<label for="wf-s3-ak" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">{{ _("integrations.access_key_label") }} <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<input id="wf-s3-ak" type="text" x-model="form.credentials.access_key_id" :required="!editingIntegration" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:bg-gray-700 dark:text-white text-sm" aria-required="true" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="wf-s3-sk" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">Secret Access Key <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<label for="wf-s3-sk" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">{{ _("integrations.secret_key_label") }} <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<input id="wf-s3-sk" :type="showPassword ? 'text' : 'password'" x-model="form.credentials.secret_access_key" :placeholder="editingIntegration ? '(unchanged if blank)' : ''" :required="!editingIntegration" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:bg-gray-700 dark:text-white text-sm" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -679,7 +677,7 @@
|
||||
<template x-if="form.config.source_type === 'dropbox'">
|
||||
<div class="space-y-3">
|
||||
<div>
|
||||
<label for="wf-dbx-folder" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">Folder Path <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<label for="wf-dbx-folder" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">{{ _("integrations.folder_path_label") }} <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<input id="wf-dbx-folder" type="text" x-model="form.config.folder_path" placeholder="/Inbox/Scanner" required class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:bg-gray-700 dark:text-white text-sm" aria-required="true" />
|
||||
</div>
|
||||
<div>
|
||||
@@ -717,7 +715,7 @@
|
||||
<template x-if="form.config.source_type === 'onedrive'">
|
||||
<div class="space-y-3">
|
||||
<div>
|
||||
<label for="wf-od-folder" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">Folder Path <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<label for="wf-od-folder" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">{{ _("integrations.folder_path_label") }} <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<input id="wf-od-folder" type="text" x-model="form.config.folder_path" placeholder="/Documents/Inbox" required class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:bg-gray-700 dark:text-white text-sm" aria-required="true" />
|
||||
</div>
|
||||
<div>
|
||||
@@ -741,20 +739,20 @@
|
||||
<template x-if="form.config.source_type === 'nextcloud'">
|
||||
<div class="space-y-3">
|
||||
<div>
|
||||
<label for="wf-nc-url" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">Nextcloud URL <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<label for="wf-nc-url" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">{{ _("integrations.nextcloud_settings") }} URL <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<input id="wf-nc-url" type="url" x-model="form.config.url" placeholder="https://cloud.example.com" required class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:bg-gray-700 dark:text-white text-sm" aria-required="true" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="wf-nc-folder" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">Folder Path <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<label for="wf-nc-folder" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">{{ _("integrations.folder_path_label") }} <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<input id="wf-nc-folder" type="text" x-model="form.config.folder_path" placeholder="/Documents/Inbox" required class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:bg-gray-700 dark:text-white text-sm" aria-required="true" />
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label for="wf-nc-user" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">Username <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<label for="wf-nc-user" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">{{ _("integrations.username_label") }} <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<input id="wf-nc-user" type="text" x-model="form.credentials.username" :required="!editingIntegration" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:bg-gray-700 dark:text-white text-sm" aria-required="true" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="wf-nc-pass" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">Password <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<label for="wf-nc-pass" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">{{ _("integrations.password_label") }} <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<input id="wf-nc-pass" :type="showPassword ? 'text' : 'password'" x-model="form.credentials.password" :placeholder="editingIntegration ? '(unchanged if blank)' : ''" :required="!editingIntegration" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:bg-gray-700 dark:text-white text-sm" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -765,20 +763,20 @@
|
||||
<template x-if="form.config.source_type === 'webdav'">
|
||||
<div class="space-y-3">
|
||||
<div>
|
||||
<label for="wf-dav-url" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">WebDAV URL <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<label for="wf-dav-url" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">{{ _("integrations.webdav_url_label") }} <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<input id="wf-dav-url" type="url" x-model="form.config.url" placeholder="https://webdav.example.com/dav/" required class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:bg-gray-700 dark:text-white text-sm" aria-required="true" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="wf-dav-folder" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">Folder Path <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<label for="wf-dav-folder" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">{{ _("integrations.folder_path_label") }} <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<input id="wf-dav-folder" type="text" x-model="form.config.folder_path" placeholder="/remote.php/webdav/Inbox" required class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:bg-gray-700 dark:text-white text-sm" aria-required="true" />
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label for="wf-dav-user" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">Username <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<label for="wf-dav-user" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">{{ _("integrations.username_label") }} <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<input id="wf-dav-user" type="text" x-model="form.credentials.username" :required="!editingIntegration" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:bg-gray-700 dark:text-white text-sm" aria-required="true" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="wf-dav-pass" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">Password <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<label for="wf-dav-pass" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">{{ _("integrations.password_label") }} <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<input id="wf-dav-pass" :type="showPassword ? 'text' : 'password'" x-model="form.credentials.password" :placeholder="editingIntegration ? '(unchanged if blank)' : ''" :required="!editingIntegration" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:bg-gray-700 dark:text-white text-sm" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -789,12 +787,12 @@
|
||||
<div class="flex flex-wrap items-center gap-4 pt-2">
|
||||
<label class="inline-flex items-center gap-2 text-sm text-gray-700 dark:text-gray-200 cursor-pointer">
|
||||
<input type="checkbox" x-model="form.config.delete_after_process" class="rounded border-gray-300 text-indigo-600 focus:ring-indigo-500" />
|
||||
Delete files after processing
|
||||
{{ _("integrations.delete_files_label") }}
|
||||
</label>
|
||||
<template x-if="form.config.source_type !== 'local'">
|
||||
<label class="inline-flex items-center gap-2 text-sm text-gray-700 dark:text-gray-200 cursor-pointer">
|
||||
<input type="checkbox" x-model="showPassword" class="rounded border-gray-300 text-indigo-600 focus:ring-indigo-500" />
|
||||
Show secrets
|
||||
{{ _("integrations.show_secrets") }}
|
||||
</label>
|
||||
</template>
|
||||
</div>
|
||||
@@ -804,18 +802,18 @@
|
||||
<!-- Paperless fields -->
|
||||
<template x-if="form.integration_type === 'PAPERLESS'">
|
||||
<div class="space-y-3 border-t border-gray-200 dark:border-gray-700 pt-3">
|
||||
<p class="text-xs font-semibold text-gray-400 uppercase tracking-wider">Paperless NGX Settings</p>
|
||||
<p class="text-xs font-semibold text-gray-400 uppercase tracking-wider">{{ _("integrations.paperless_settings") }}</p>
|
||||
<div>
|
||||
<label for="paperless-url" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">URL <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<input id="paperless-url" type="url" x-model="form.config.url" placeholder="https://paperless.example.com" required class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:bg-gray-700 dark:text-white text-sm" aria-required="true" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="paperless-token" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">API Token <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<label for="paperless-token" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">{{ _("integrations.api_token_label") }} <span class="text-red-500" aria-hidden="true">*</span></label>
|
||||
<input id="paperless-token" :type="showPassword ? 'text' : 'password'" x-model="form.credentials.api_token" :placeholder="editingIntegration ? '(unchanged if blank)' : ''" :required="!editingIntegration" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:bg-gray-700 dark:text-white text-sm" />
|
||||
</div>
|
||||
<label class="inline-flex items-center gap-2 text-sm text-gray-700 dark:text-gray-200 cursor-pointer">
|
||||
<input type="checkbox" x-model="showPassword" class="rounded border-gray-300 text-indigo-600 focus:ring-indigo-500" />
|
||||
Show token
|
||||
{{ _("integrations.show_token") }}
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
@@ -824,38 +822,33 @@
|
||||
<template x-if="form.integration_type === 'WEBHOOK'">
|
||||
<div class="space-y-4 border-t border-gray-200 dark:border-gray-700 pt-3">
|
||||
<p class="text-xs font-semibold text-purple-500 uppercase tracking-wider">
|
||||
<i class="fas fa-bolt mr-1" aria-hidden="true"></i> Webhook Ingestion
|
||||
<i class="fas fa-bolt mr-1" aria-hidden="true"></i> {{ _("integrations.webhook_heading") }}
|
||||
</p>
|
||||
|
||||
<div class="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg p-4">
|
||||
<h4 class="text-sm font-semibold text-blue-800 dark:text-blue-200 mb-2">
|
||||
<i class="fas fa-info-circle mr-1" aria-hidden="true"></i>
|
||||
How Webhook Ingestion Works
|
||||
{{ _("integrations.webhook_how_heading") }}
|
||||
</h4>
|
||||
<p class="text-sm text-blue-700 dark:text-blue-300 leading-relaxed">
|
||||
A webhook integration allows external systems to push documents directly into DocuElevate
|
||||
via the REST API. Instead of DocuElevate polling for new files (like IMAP), <strong>your
|
||||
application sends files to DocuElevate</strong> using an HTTP request with an API token for
|
||||
authentication.
|
||||
{{ _("integrations.webhook_how_text") }}
|
||||
</p>
|
||||
<p class="text-sm text-blue-700 dark:text-blue-300 leading-relaxed mt-2">
|
||||
This is ideal for <strong>CI/CD pipelines</strong>, <strong>automation scripts</strong>,
|
||||
<strong>scanner integrations</strong>, or any system that generates documents and needs to
|
||||
send them for processing.
|
||||
{{ _("integrations.webhook_ideal_text") }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-gray-50 dark:bg-gray-750 rounded-lg p-4">
|
||||
<h4 class="text-sm font-semibold text-gray-800 dark:text-gray-200 mb-2">
|
||||
<i class="fas fa-terminal mr-1" aria-hidden="true"></i>
|
||||
Quick Start
|
||||
{{ _("integrations.webhook_quick_start") }}
|
||||
</h4>
|
||||
<ol class="text-sm text-gray-600 dark:text-gray-400 space-y-2 list-decimal list-inside">
|
||||
<li>
|
||||
Go to <a href="/api-tokens" class="text-indigo-600 hover:underline font-medium">API Tokens</a>
|
||||
and create a personal token.
|
||||
{{ _("integrations.webhook_create_token") }}
|
||||
</li>
|
||||
<li>Use the token to upload documents via the API:</li>
|
||||
<li>{{ _("integrations.webhook_upload_with_token") }}</li>
|
||||
</ol>
|
||||
|
||||
<div class="mt-3 relative">
|
||||
@@ -901,9 +894,7 @@ print(response.json())</code></pre>
|
||||
<div class="bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg p-3">
|
||||
<p class="text-xs text-yellow-800 dark:text-yellow-300">
|
||||
<i class="fas fa-shield-alt mr-1" aria-hidden="true"></i>
|
||||
<strong>Security tip:</strong> Create a dedicated API token for each integration and
|
||||
revoke it immediately if compromised. Tokens can be managed on the
|
||||
<a href="/api-tokens" class="underline font-medium">API Tokens</a> page.
|
||||
{{ _("integrations.webhook_security_tip") }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -914,7 +905,7 @@ print(response.json())</code></pre>
|
||||
<div class="space-y-3 border-t border-gray-200 dark:border-gray-700 pt-3">
|
||||
<p class="text-xs font-semibold text-gray-400 uppercase tracking-wider" x-text="form.integration_type + ' Settings'"></p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
Configuration for this integration type can be provided as JSON after creation via the API.
|
||||
{{ _("integrations.generic_settings_hint") }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
@@ -923,7 +914,7 @@ print(response.json())</code></pre>
|
||||
<div class="flex items-center gap-2 pt-2">
|
||||
<label class="inline-flex items-center gap-2 text-sm text-gray-700 dark:text-gray-200 cursor-pointer">
|
||||
<input type="checkbox" x-model="form.is_active" class="rounded border-gray-300 text-indigo-600 focus:ring-indigo-500" />
|
||||
Active
|
||||
{{ _("integrations.active_label") }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@@ -934,7 +925,7 @@ print(response.json())</code></pre>
|
||||
:class="testResult.success ? 'bg-green-50 border-green-400 text-green-800 dark:bg-opacity-10' : 'bg-red-50 border-red-400 text-red-800 dark:bg-opacity-10'"
|
||||
role="status"
|
||||
>
|
||||
<p class="font-medium" x-text="testResult.success ? 'Connection successful' : 'Connection failed'"></p>
|
||||
<p class="font-medium" x-text="testResult.success ? '{{ _('integrations.connection_success') }}' : '{{ _('integrations.connection_failed') }}'"></p>
|
||||
<p class="text-xs mt-0.5" x-text="testResult.message"></p>
|
||||
</div>
|
||||
</template>
|
||||
@@ -962,7 +953,7 @@ print(response.json())</code></pre>
|
||||
<template x-if="!testing">
|
||||
<i class="fas fa-plug mr-2" aria-hidden="true"></i>
|
||||
</template>
|
||||
Test Connection
|
||||
{{ _("common.test_connection") }}
|
||||
</button>
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
@@ -971,7 +962,7 @@ print(response.json())</code></pre>
|
||||
class="px-4 py-2 text-sm font-medium rounded border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-gray-500"
|
||||
style="min-height:40px;"
|
||||
>
|
||||
Cancel
|
||||
{{ _("common.cancel") }}
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
@@ -982,7 +973,7 @@ print(response.json())</code></pre>
|
||||
<template x-if="saving">
|
||||
<i class="fas fa-spinner fa-spin mr-2" aria-hidden="true"></i>
|
||||
</template>
|
||||
<span x-text="editingIntegration ? 'Update' : 'Save'"></span>
|
||||
<span x-text="editingIntegration ? '{{ _('common.update') }}' : '{{ _('common.save') }}'"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1003,12 +994,12 @@ print(response.json())</code></pre>
|
||||
>
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-xl w-full max-w-sm p-6" @click.stop>
|
||||
<h2 id="delete-modal-title" class="text-lg font-semibold text-gray-900 dark:text-white mb-2">
|
||||
Delete Integration?
|
||||
{{ _("integrations.delete_confirm_title") }}
|
||||
</h2>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-300 mb-4">
|
||||
Are you sure you want to delete
|
||||
{{ _("integrations.delete_confirm_are_you_sure") }}
|
||||
<strong x-text="integrationToDelete ? integrationToDelete.name : ''"></strong>?
|
||||
This action cannot be undone.
|
||||
{{ _("integrations.delete_confirm_undone") }}
|
||||
</p>
|
||||
<div class="flex justify-end gap-2">
|
||||
<button
|
||||
@@ -1017,7 +1008,7 @@ print(response.json())</code></pre>
|
||||
class="px-4 py-2 text-sm font-medium rounded border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-gray-500"
|
||||
style="min-height:40px;"
|
||||
>
|
||||
Cancel
|
||||
{{ _("common.cancel") }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -1029,7 +1020,7 @@ print(response.json())</code></pre>
|
||||
<template x-if="deleting">
|
||||
<i class="fas fa-spinner fa-spin mr-2" aria-hidden="true"></i>
|
||||
</template>
|
||||
Delete
|
||||
{{ _("common.delete") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Profile Settings – DocuElevate{% endblock %}
|
||||
{% block title %}{{ _("profile.page_title") }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div
|
||||
@@ -12,10 +12,10 @@
|
||||
<header class="mb-8">
|
||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white flex items-center gap-2">
|
||||
<i class="fas fa-user-circle text-blue-500" aria-hidden="true"></i>
|
||||
Profile Settings
|
||||
{{ _("profile.heading") }}
|
||||
</h1>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
Manage your display name, avatar, language, and theme preferences.
|
||||
{{ _("profile.subtitle") }}
|
||||
</p>
|
||||
</header>
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
type="button"
|
||||
@click="banner.visible = false"
|
||||
class="ml-auto"
|
||||
aria-label="Dismiss"
|
||||
aria-label="{{ _('profile.dismiss') }}"
|
||||
style="min-height:44px; min-width:44px;"
|
||||
>
|
||||
<i class="fas fa-times" aria-hidden="true"></i>
|
||||
@@ -49,7 +49,7 @@
|
||||
aria-labelledby="avatar-heading"
|
||||
>
|
||||
<h2 id="avatar-heading" class="text-base font-semibold text-gray-900 dark:text-white mb-4">
|
||||
<i class="fas fa-camera text-gray-400 mr-2" aria-hidden="true"></i>Profile Picture
|
||||
<i class="fas fa-camera text-gray-400 mr-2" aria-hidden="true"></i>{{ _("profile.avatar_heading") }}
|
||||
</h2>
|
||||
|
||||
<div class="flex flex-col sm:flex-row items-center gap-6">
|
||||
@@ -57,7 +57,7 @@
|
||||
<div class="relative flex-shrink-0">
|
||||
<img
|
||||
:src="avatarUrl"
|
||||
alt="Your profile picture"
|
||||
alt="{{ _('profile.avatar_alt') }}"
|
||||
class="w-24 h-24 rounded-full object-cover border-2 border-gray-200 dark:border-gray-600"
|
||||
/>
|
||||
<template x-if="hasCustomAvatar">
|
||||
@@ -66,8 +66,8 @@
|
||||
@click="removeAvatar()"
|
||||
:disabled="saving"
|
||||
class="absolute -top-1 -right-1 bg-red-500 hover:bg-red-600 text-white rounded-full w-6 h-6 flex items-center justify-center shadow focus:outline-none focus:ring-2 focus:ring-red-400"
|
||||
aria-label="Remove custom avatar"
|
||||
title="Remove custom avatar"
|
||||
aria-label="{{ _('profile.avatar_remove') }}"
|
||||
title="{{ _('profile.avatar_remove') }}"
|
||||
>
|
||||
<i class="fas fa-times text-xs" aria-hidden="true"></i>
|
||||
</button>
|
||||
@@ -77,8 +77,7 @@
|
||||
<!-- Upload controls -->
|
||||
<div class="flex-1 space-y-3">
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||
Upload a JPEG, PNG, GIF, or WebP image up to 2 MB.
|
||||
If no custom picture is set, your <a href="https://gravatar.com" class="underline" target="_blank" rel="noopener noreferrer" aria-label="Gravatar (opens in new tab)">Gravatar</a> is shown.
|
||||
{{ _("profile.avatar_upload_hint") }} <a href="https://gravatar.com" class="underline" target="_blank" rel="noopener noreferrer" aria-label="{{ _('profile.gravatar_link') }} (opens in new tab)">{{ _("profile.gravatar_link") }}</a> {{ _("profile.avatar_gravatar_suffix") }}
|
||||
</p>
|
||||
<label
|
||||
for="avatar-input"
|
||||
@@ -86,7 +85,7 @@
|
||||
style="min-height:44px;"
|
||||
>
|
||||
<i class="fas fa-upload" aria-hidden="true"></i>
|
||||
<span>Choose image…</span>
|
||||
<span>{{ _("profile.choose_image") }}</span>
|
||||
<input
|
||||
id="avatar-input"
|
||||
type="file"
|
||||
@@ -107,49 +106,49 @@
|
||||
aria-labelledby="general-heading"
|
||||
>
|
||||
<h2 id="general-heading" class="text-base font-semibold text-gray-900 dark:text-white mb-4">
|
||||
<i class="fas fa-id-card text-gray-400 mr-2" aria-hidden="true"></i>General Information
|
||||
<i class="fas fa-id-card text-gray-400 mr-2" aria-hidden="true"></i>{{ _("profile.general_heading") }}
|
||||
</h2>
|
||||
|
||||
<div class="space-y-4">
|
||||
<!-- Display name -->
|
||||
<div>
|
||||
<label for="display-name" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
Display Name
|
||||
{{ _("profile.display_name_label") }}
|
||||
</label>
|
||||
<input
|
||||
id="display-name"
|
||||
type="text"
|
||||
x-model="form.display_name"
|
||||
maxlength="255"
|
||||
placeholder="Your name as shown in the UI"
|
||||
placeholder="{{ _('profile.display_name_placeholder') }}"
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm text-sm
|
||||
focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-white"
|
||||
style="min-height:44px;"
|
||||
aria-describedby="display-name-hint"
|
||||
/>
|
||||
<p id="display-name-hint" class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||
Leave blank to use your account username or email.
|
||||
{{ _("profile.display_name_hint") }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Contact email -->
|
||||
<div>
|
||||
<label for="contact-email" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
Contact / Notification E-mail
|
||||
{{ _("profile.contact_email_label") }}
|
||||
</label>
|
||||
<input
|
||||
id="contact-email"
|
||||
type="email"
|
||||
x-model="form.contact_email"
|
||||
maxlength="255"
|
||||
placeholder="you@example.com"
|
||||
placeholder="{{ _('profile.contact_email_placeholder') }}"
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm text-sm
|
||||
focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-white"
|
||||
style="min-height:44px;"
|
||||
aria-describedby="contact-email-hint"
|
||||
/>
|
||||
<p id="contact-email-hint" class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||
Used for system notifications. This does not change your login e-mail.
|
||||
{{ _("profile.contact_email_hint") }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -161,14 +160,14 @@
|
||||
aria-labelledby="prefs-heading"
|
||||
>
|
||||
<h2 id="prefs-heading" class="text-base font-semibold text-gray-900 dark:text-white mb-4">
|
||||
<i class="fas fa-sliders-h text-gray-400 mr-2" aria-hidden="true"></i>Preferences
|
||||
<i class="fas fa-sliders-h text-gray-400 mr-2" aria-hidden="true"></i>{{ _("profile.preferences_heading") }}
|
||||
</h2>
|
||||
|
||||
<div class="space-y-5">
|
||||
<!-- Language -->
|
||||
<div>
|
||||
<label for="lang-select" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
<i class="fas fa-globe text-gray-400 mr-1" aria-hidden="true"></i>UI Language
|
||||
<i class="fas fa-globe text-gray-400 mr-1" aria-hidden="true"></i>{{ _("profile.language_label") }}
|
||||
</label>
|
||||
<select
|
||||
id="lang-select"
|
||||
@@ -177,20 +176,20 @@
|
||||
focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-white"
|
||||
style="min-height:44px;"
|
||||
>
|
||||
<option value="">Auto-detect (from browser)</option>
|
||||
<option value="">{{ _("profile.language_auto_detect") }}</option>
|
||||
{% for lang in supported_languages %}
|
||||
<option value="{{ lang.code }}">{{ lang.flag }} {{ lang.native }} ({{ lang.name }})</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||
Choose your preferred interface language. "Auto-detect" follows your browser settings.
|
||||
{{ _("profile.language_hint") }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Theme -->
|
||||
<fieldset>
|
||||
<legend class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
<i class="fas fa-palette text-gray-400 mr-1" aria-hidden="true"></i>Colour Scheme
|
||||
<i class="fas fa-palette text-gray-400 mr-1" aria-hidden="true"></i>{{ _("profile.theme_label") }}
|
||||
</legend>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<label
|
||||
@@ -202,7 +201,7 @@
|
||||
style="min-height:44px; min-width:44px;"
|
||||
>
|
||||
<input type="radio" name="theme" value="light" x-model="form.preferred_theme" class="sr-only" />
|
||||
<i class="fas fa-sun" aria-hidden="true"></i> Light
|
||||
<i class="fas fa-sun" aria-hidden="true"></i> {{ _("profile.theme_light") }}
|
||||
</label>
|
||||
<label
|
||||
class="relative flex items-center gap-2 cursor-pointer rounded-lg border px-4 py-3 text-sm transition-colors
|
||||
@@ -213,7 +212,7 @@
|
||||
style="min-height:44px; min-width:44px;"
|
||||
>
|
||||
<input type="radio" name="theme" value="dark" x-model="form.preferred_theme" class="sr-only" />
|
||||
<i class="fas fa-moon" aria-hidden="true"></i> Dark
|
||||
<i class="fas fa-moon" aria-hidden="true"></i> {{ _("profile.theme_dark") }}
|
||||
</label>
|
||||
<label
|
||||
class="relative flex items-center gap-2 cursor-pointer rounded-lg border px-4 py-3 text-sm transition-colors
|
||||
@@ -224,11 +223,11 @@
|
||||
style="min-height:44px; min-width:44px;"
|
||||
>
|
||||
<input type="radio" name="theme" value="system" x-model="form.preferred_theme" class="sr-only" />
|
||||
<i class="fas fa-desktop" aria-hidden="true"></i> System Default
|
||||
<i class="fas fa-desktop" aria-hidden="true"></i> {{ _("profile.theme_system") }}
|
||||
</label>
|
||||
</div>
|
||||
<p class="mt-2 text-xs text-gray-500 dark:text-gray-400">
|
||||
"System Default" follows your device's dark-mode setting.
|
||||
{{ _("profile.theme_hint") }}
|
||||
</p>
|
||||
</fieldset>
|
||||
</div>
|
||||
@@ -245,7 +244,7 @@
|
||||
style="min-height:44px; min-width:44px;"
|
||||
>
|
||||
<i class="fas fa-save" aria-hidden="true"></i>
|
||||
<span x-text="saving ? 'Saving…' : 'Save Changes'"></span>
|
||||
<span x-text="saving ? '{{ _('profile.saving') }}' : '{{ _('profile.save_button') }}'"></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -256,13 +255,13 @@
|
||||
aria-labelledby="password-heading"
|
||||
>
|
||||
<h2 id="password-heading" class="text-base font-semibold text-gray-900 dark:text-white mb-4">
|
||||
<i class="fas fa-lock text-gray-400 mr-2" aria-hidden="true"></i>Change Password
|
||||
<i class="fas fa-lock text-gray-400 mr-2" aria-hidden="true"></i>{{ _("profile.password_heading") }}
|
||||
</h2>
|
||||
|
||||
<div class="space-y-4 max-w-md">
|
||||
<div>
|
||||
<label for="current-password" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
Current Password
|
||||
{{ _("profile.current_password") }}
|
||||
</label>
|
||||
<input
|
||||
id="current-password"
|
||||
@@ -276,7 +275,7 @@
|
||||
</div>
|
||||
<div>
|
||||
<label for="new-password" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
New Password
|
||||
{{ _("profile.new_password") }}
|
||||
</label>
|
||||
<input
|
||||
id="new-password"
|
||||
@@ -288,11 +287,11 @@
|
||||
style="min-height:44px;"
|
||||
aria-describedby="new-pw-hint"
|
||||
/>
|
||||
<p id="new-pw-hint" class="mt-1 text-xs text-gray-500 dark:text-gray-400">Minimum 8 characters.</p>
|
||||
<p id="new-pw-hint" class="mt-1 text-xs text-gray-500 dark:text-gray-400">{{ _("profile.new_password_hint") }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<label for="confirm-password" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
Confirm New Password
|
||||
{{ _("profile.confirm_password") }}
|
||||
</label>
|
||||
<input
|
||||
id="confirm-password"
|
||||
@@ -314,7 +313,7 @@
|
||||
style="min-height:44px;"
|
||||
>
|
||||
<i class="fas fa-key" aria-hidden="true"></i>
|
||||
<span x-text="pwSaving ? 'Updating…' : 'Update Password'"></span>
|
||||
<span x-text="pwSaving ? '{{ _('profile.updating') }}' : '{{ _('profile.update_password') }}'"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}My Subscription – DocuElevate{% endblock %}
|
||||
{% block title %}{{ _("subscription.page_title") }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container mx-auto px-4 py-8 max-w-4xl">
|
||||
@@ -8,9 +8,9 @@
|
||||
<div class="mb-8">
|
||||
<h1 class="text-3xl font-bold text-gray-900 flex items-center gap-3">
|
||||
<i class="fas fa-layer-group text-indigo-500" aria-hidden="true"></i>
|
||||
My Subscription
|
||||
{{ _("subscription.heading") }}
|
||||
</h1>
|
||||
<p class="text-gray-500 text-sm mt-1">Your current plan, usage and available upgrades.</p>
|
||||
<p class="text-gray-500 text-sm mt-1">{{ _("subscription.subtitle") }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Flash messages -->
|
||||
@@ -21,11 +21,10 @@
|
||||
<div class="bg-blue-50 border border-blue-200 rounded-xl p-6 flex items-start gap-4">
|
||||
<i class="fas fa-info-circle text-blue-500 text-2xl flex-shrink-0 mt-0.5" aria-hidden="true"></i>
|
||||
<div>
|
||||
<p class="font-semibold text-blue-800">Multi-user mode is not enabled.</p>
|
||||
<p class="font-semibold text-blue-800">{{ _("subscription.single_user_title") }}</p>
|
||||
<p class="text-blue-700 text-sm mt-1">
|
||||
Subscription tiers apply when multi-user mode is active. Your instance currently runs in
|
||||
single-user mode with no processing limits.
|
||||
An admin can enable multi-user mode via <a href="/settings" class="underline hover:text-blue-900">Settings</a>.
|
||||
{{ _("subscription.single_user_body_before_link") }}
|
||||
<a href="/settings" class="underline hover:text-blue-900">{{ _("nav.settings") }}</a>{{ _("subscription.single_user_body_after_link") }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -37,17 +36,17 @@
|
||||
<div class="bg-amber-50 border border-amber-300 rounded-xl p-4 mb-6 flex items-start gap-3">
|
||||
<i class="fas fa-clock text-amber-500 text-xl flex-shrink-0 mt-0.5" aria-hidden="true"></i>
|
||||
<div class="flex-1">
|
||||
<p class="font-semibold text-amber-800">Pending plan change scheduled</p>
|
||||
<p class="font-semibold text-amber-800">{{ _("subscription.pending_title") }}</p>
|
||||
<p class="text-amber-700 text-sm mt-1">
|
||||
Your plan will change to <strong>{{ pending_tier.name }}</strong>
|
||||
on <strong>{% if pending_date %}{{ pending_date.strftime('%B') }} {{ pending_date.day }}, {{ pending_date.year }}{% endif %}</strong>.
|
||||
You keep all current plan benefits until then.
|
||||
{{ _("subscription.pending_will_change") }} <strong>{{ pending_tier.name }}</strong>
|
||||
{{ _("subscription.pending_on") }} <strong>{% if pending_date %}{{ pending_date.strftime('%B') }} {{ pending_date.day }}, {{ pending_date.year }}{% endif %}</strong>.
|
||||
{{ _("subscription.pending_benefits") }}
|
||||
</p>
|
||||
</div>
|
||||
<button type="button" onclick="cancelPendingChange()"
|
||||
class="ml-auto flex-shrink-0 text-sm text-amber-700 underline hover:text-amber-900 font-medium"
|
||||
aria-label="Cancel pending plan change">
|
||||
Cancel change
|
||||
aria-label="{{ _('subscription.cancel_pending') }}">
|
||||
{{ _("subscription.cancel_pending") }}
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -68,18 +67,18 @@
|
||||
{% else %}fa-bolt text-purple-600{% endif %}
|
||||
text-2xl" aria-hidden="true"></i>
|
||||
</div>
|
||||
<p class="text-xs font-semibold text-gray-400 uppercase tracking-widest">Current Plan</p>
|
||||
<p class="text-xs font-semibold text-gray-400 uppercase tracking-widest">{{ _("subscription.current_plan") }}</p>
|
||||
<p class="text-2xl font-extrabold text-gray-900 mt-1">{{ tier.name }}</p>
|
||||
<p class="text-gray-500 text-sm mt-1">{{ tier.tagline }}</p>
|
||||
{% if tier.price_monthly > 0 %}
|
||||
<p class="mt-3 text-lg font-bold text-indigo-600">${{ tier.price_monthly }}<span class="text-sm font-normal text-gray-400">/month</span></p>
|
||||
<p class="mt-3 text-lg font-bold text-indigo-600">${{ tier.price_monthly }}<span class="text-sm font-normal text-gray-400">{{ _("subscription.per_month") }}</span></p>
|
||||
{% else %}
|
||||
<p class="mt-3 text-lg font-bold text-gray-500">Free</p>
|
||||
<p class="mt-3 text-lg font-bold text-gray-500">{{ _("subscription.free") }}</p>
|
||||
{% endif %}
|
||||
{% if period_start %}
|
||||
<p class="text-xs text-gray-400 mt-2">
|
||||
<i class="fas fa-calendar-alt mr-1" aria-hidden="true"></i>
|
||||
Since {{ period_start.strftime('%b') }} {{ period_start.day }}, {{ period_start.year }}
|
||||
{{ _("subscription.since") }} {{ period_start.strftime('%b') }} {{ period_start.day }}, {{ period_start.year }}
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -87,13 +86,13 @@
|
||||
<!-- Usage this period -->
|
||||
{% if usage %}
|
||||
<div class="md:col-span-2 bg-white rounded-xl shadow p-6">
|
||||
<h2 class="text-sm font-semibold text-gray-500 uppercase tracking-widest mb-4">Usage</h2>
|
||||
<h2 class="text-sm font-semibold text-gray-500 uppercase tracking-widest mb-4">{{ _("subscription.usage_heading") }}</h2>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4">
|
||||
|
||||
<!-- Lifetime -->
|
||||
<div class="text-center">
|
||||
<p class="text-3xl font-extrabold text-gray-900">{{ usage.lifetime }}</p>
|
||||
<p class="text-xs text-gray-500 mt-1">Total files (lifetime)</p>
|
||||
<p class="text-xs text-gray-500 mt-1">{{ _("subscription.lifetime_files") }}</p>
|
||||
{% if tier.lifetime_file_limit > 0 %}
|
||||
<div class="mt-2">
|
||||
{% set lifetime_pct = ((usage.lifetime / tier.lifetime_file_limit) * 100) | int %}
|
||||
@@ -106,14 +105,14 @@
|
||||
<p class="text-xs text-gray-400 mt-1">{{ usage.lifetime }} / {{ tier.lifetime_file_limit }}</p>
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="text-xs text-green-600 mt-1">Unlimited</p>
|
||||
<p class="text-xs text-green-600 mt-1">{{ _("subscription.unlimited") }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Today -->
|
||||
<div class="text-center">
|
||||
<p class="text-3xl font-extrabold text-gray-900">{{ usage.today }}</p>
|
||||
<p class="text-xs text-gray-500 mt-1">Files today</p>
|
||||
<p class="text-xs text-gray-500 mt-1">{{ _("subscription.today_files") }}</p>
|
||||
{% if tier.daily_upload_limit > 0 %}
|
||||
<div class="mt-2">
|
||||
{% set today_pct = ((usage.today / tier.daily_upload_limit) * 100) | int %}
|
||||
@@ -123,17 +122,17 @@
|
||||
{% if today_pct >= 90 %}bg-red-500{% elif today_pct >= 70 %}bg-yellow-500{% else %}bg-blue-500{% endif %}"
|
||||
style="width: {{ [today_pct, 100] | min }}%"></div>
|
||||
</div>
|
||||
<p class="text-xs text-gray-400 mt-1">{{ usage.today }} / {{ tier.daily_upload_limit }} today</p>
|
||||
<p class="text-xs text-gray-400 mt-1">{{ usage.today }} / {{ tier.daily_upload_limit }} {{ _("subscription.today_label") }}</p>
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="text-xs text-green-600 mt-1">Unlimited</p>
|
||||
<p class="text-xs text-green-600 mt-1">{{ _("subscription.unlimited") }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- This month -->
|
||||
<div class="text-center">
|
||||
<p class="text-3xl font-extrabold text-gray-900">{{ usage.month }}</p>
|
||||
<p class="text-xs text-gray-500 mt-1">Files this month</p>
|
||||
<p class="text-xs text-gray-500 mt-1">{{ _("subscription.month_files") }}</p>
|
||||
{% if tier.monthly_upload_limit > 0 %}
|
||||
<div class="mt-2">
|
||||
{% set month_pct = ((usage.month / tier.monthly_upload_limit) * 100) | int %}
|
||||
@@ -143,10 +142,10 @@
|
||||
{% if month_pct >= 90 %}bg-red-500{% elif month_pct >= 70 %}bg-yellow-500{% else %}bg-indigo-500{% endif %}"
|
||||
style="width: {{ [month_pct, 100] | min }}%"></div>
|
||||
</div>
|
||||
<p class="text-xs text-gray-400 mt-1">{{ usage.month }} / {{ tier.monthly_upload_limit }} this month</p>
|
||||
<p class="text-xs text-gray-400 mt-1">{{ usage.month }} / {{ tier.monthly_upload_limit }} {{ _("subscription.this_month_label") }}</p>
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="text-xs text-green-600 mt-1">Unlimited</p>
|
||||
<p class="text-xs text-green-600 mt-1">{{ _("subscription.unlimited") }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
@@ -160,7 +159,7 @@
|
||||
<div class="bg-white rounded-xl shadow p-6 mb-8">
|
||||
<h2 class="text-lg font-semibold text-gray-900 mb-4 flex items-center gap-2">
|
||||
<i class="fas fa-list-check text-green-500" aria-hidden="true"></i>
|
||||
What's included in your plan
|
||||
{{ _("subscription.features_heading") }}
|
||||
</h2>
|
||||
<ul class="grid grid-cols-1 sm:grid-cols-2 gap-2 text-sm text-gray-600">
|
||||
{% for feature in tier.features %}
|
||||
@@ -176,7 +175,7 @@
|
||||
<div class="mb-8">
|
||||
<h2 class="text-lg font-semibold text-gray-900 mb-4 flex items-center gap-2">
|
||||
<i class="fas fa-layer-group text-indigo-500" aria-hidden="true"></i>
|
||||
Available Plans
|
||||
{{ _("subscription.available_plans_heading") }}
|
||||
</h2>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{% for t in all_tiers %}
|
||||
@@ -192,11 +191,11 @@
|
||||
|
||||
{% if is_current %}
|
||||
<span class="absolute top-3 right-3 text-xs bg-indigo-100 text-indigo-700 font-semibold rounded-full px-2 py-0.5">
|
||||
Current
|
||||
{{ _("subscription.current_badge") }}
|
||||
</span>
|
||||
{% elif is_pending %}
|
||||
<span class="absolute top-3 right-3 text-xs bg-amber-100 text-amber-700 font-semibold rounded-full px-2 py-0.5">
|
||||
Pending
|
||||
{{ _("subscription.pending_badge") }}
|
||||
</span>
|
||||
{% elif t.badge %}
|
||||
<span class="absolute top-3 right-3 text-xs bg-indigo-100 text-indigo-700 font-semibold rounded-full px-2 py-0.5">
|
||||
@@ -208,9 +207,9 @@
|
||||
<p class="font-bold text-gray-900 pr-16">{{ t.name }}</p>
|
||||
<p class="text-gray-500 text-xs mt-1 mb-3">{{ t.tagline }}</p>
|
||||
{% if t.price_monthly > 0 %}
|
||||
<p class="text-xl font-extrabold text-gray-900">${{ t.price_monthly }}<span class="text-sm font-normal text-gray-400">/mo</span></p>
|
||||
<p class="text-xl font-extrabold text-gray-900">${{ t.price_monthly }}<span class="text-sm font-normal text-gray-400">{{ _("subscription.per_mo") }}</span></p>
|
||||
{% else %}
|
||||
<p class="text-xl font-extrabold text-gray-500">Free</p>
|
||||
<p class="text-xl font-extrabold text-gray-500">{{ _("subscription.free") }}</p>
|
||||
{% endif %}
|
||||
<ul class="mt-3 space-y-1 text-xs text-gray-600">
|
||||
{% for feature in t.features[:3] %}
|
||||
@@ -220,7 +219,7 @@
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% if t.features | length > 3 %}
|
||||
<li class="text-indigo-500 font-medium">+ {{ (t.features | length) - 3 }} more</li>
|
||||
<li class="text-indigo-500 font-medium">+ {{ (t.features | length) - 3 }} {{ _("subscription.more_features_label") }}</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
@@ -228,14 +227,14 @@
|
||||
<div class="mt-4">
|
||||
{% if is_current and not pending_tier_id %}
|
||||
<span class="block text-center py-2 px-4 rounded-lg bg-gray-100 text-gray-400 text-sm font-semibold cursor-default">
|
||||
Your current plan
|
||||
{{ _("subscription.current_plan_cta") }}
|
||||
</span>
|
||||
{% elif is_current and pending_tier_id %}
|
||||
<!-- Keep current plan (cancel pending downgrade) -->
|
||||
<button type="button"
|
||||
onclick="changePlan('{{ t.id }}')"
|
||||
class="block w-full text-center py-2 px-4 rounded-lg bg-indigo-600 hover:bg-indigo-700 text-white text-sm font-semibold transition">
|
||||
Keep {{ t.name }}
|
||||
{{ _("subscription.keep_plan_prefix") }} {{ t.name }}
|
||||
</button>
|
||||
{% elif is_upgrade %}
|
||||
{% if t.id == 'business' %}
|
||||
@@ -247,21 +246,21 @@
|
||||
<button type="button"
|
||||
onclick="changePlan('{{ t.id }}')"
|
||||
class="block w-full text-center py-2 px-4 rounded-lg bg-green-600 hover:bg-green-700 text-white text-sm font-semibold transition">
|
||||
Upgrade to {{ t.name }}
|
||||
{{ _("subscription.upgrade_to_prefix") }} {{ t.name }}
|
||||
</button>
|
||||
{% endif %}
|
||||
{% elif is_pending %}
|
||||
<button type="button"
|
||||
onclick="cancelPendingChange()"
|
||||
class="block w-full text-center py-2 px-4 rounded-lg bg-amber-100 hover:bg-amber-200 text-amber-800 text-sm font-semibold transition">
|
||||
Cancel scheduled change
|
||||
{{ _("subscription.cancel_scheduled") }}
|
||||
</button>
|
||||
{% else %}
|
||||
<!-- Downgrade -->
|
||||
<button type="button"
|
||||
onclick="changePlan('{{ t.id }}')"
|
||||
class="block w-full text-center py-2 px-4 rounded-lg bg-gray-100 hover:bg-gray-200 text-gray-700 text-sm font-semibold transition">
|
||||
Downgrade to {{ t.name }}
|
||||
{{ _("subscription.downgrade_to_prefix") }} {{ t.name }}
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -270,7 +269,7 @@
|
||||
</div>
|
||||
<p class="text-xs text-gray-400 mt-3">
|
||||
<i class="fas fa-info-circle mr-1" aria-hidden="true"></i>
|
||||
Upgrades take effect immediately. Downgrades are scheduled for the end of your current billing period.
|
||||
{{ _("subscription.upgrade_info") }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -279,7 +278,7 @@
|
||||
<!-- Back link -->
|
||||
<div class="mt-8">
|
||||
<a href="/" class="text-sm text-indigo-600 hover:text-indigo-800 font-medium flex items-center gap-1">
|
||||
<i class="fas fa-arrow-left text-xs" aria-hidden="true"></i> Back to Dashboard
|
||||
<i class="fas fa-arrow-left text-xs" aria-hidden="true"></i> {{ _("subscription.back_to_dashboard") }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -187,6 +187,7 @@
|
||||
"common.actions": "Actions",
|
||||
"common.active": "Active",
|
||||
"common.all": "All",
|
||||
"common.avatar": "Avatar",
|
||||
"common.back": "Back",
|
||||
"common.cancel": "Cancel",
|
||||
"common.close": "Close",
|
||||
@@ -218,6 +219,7 @@
|
||||
"common.no": "No",
|
||||
"common.none": "None",
|
||||
"common.page": "Page",
|
||||
"common.paused": "Paused",
|
||||
"common.pending": "Pending",
|
||||
"common.prev_page": "Previous page",
|
||||
"common.previous": "Previous",
|
||||
@@ -234,7 +236,10 @@
|
||||
"common.submit": "Submit",
|
||||
"common.success": "Success",
|
||||
"common.tags": "Tags",
|
||||
"common.test": "Test",
|
||||
"common.test_connection": "Test Connection",
|
||||
"common.type": "Type",
|
||||
"common.update": "Update",
|
||||
"common.updated": "Updated",
|
||||
"common.upload": "Upload",
|
||||
"common.view": "View",
|
||||
@@ -597,20 +602,94 @@
|
||||
"index.usage_my_usage": "My usage",
|
||||
"index.usage_today": "Files today",
|
||||
"index.usage_unlimited": "Unlimited",
|
||||
"integrations.access_key_label": "Access Key ID",
|
||||
"integrations.active_label": "Active",
|
||||
"integrations.add_button": "Add Integration",
|
||||
"integrations.add_first_button": "Add Your First Integration",
|
||||
"integrations.api_token_label": "API Token",
|
||||
"integrations.authorize_btn": "Authorize",
|
||||
"integrations.authorize_reauth": "Re-Authorize",
|
||||
"integrations.bucket_label": "Bucket",
|
||||
"integrations.configure": "Configure",
|
||||
"integrations.connect": "Connect",
|
||||
"integrations.connected": "Connected",
|
||||
"integrations.connection_failed": "Connection failed",
|
||||
"integrations.connection_success": "Connection successful",
|
||||
"integrations.delete_confirm_are_you_sure": "Are you sure you want to delete",
|
||||
"integrations.delete_confirm_title": "Delete Integration?",
|
||||
"integrations.delete_confirm_undone": "This action cannot be undone.",
|
||||
"integrations.delete_emails_label": "Delete emails after processing",
|
||||
"integrations.delete_files_label": "Delete files after processing",
|
||||
"integrations.destinations_quota_label": "Storage Destinations:",
|
||||
"integrations.destinations_section": "Destinations",
|
||||
"integrations.direction_destination": "Destination (storage)",
|
||||
"integrations.direction_label": "Direction",
|
||||
"integrations.direction_placeholder": "— Select —",
|
||||
"integrations.direction_source": "Source (ingestion)",
|
||||
"integrations.disconnect": "Disconnect",
|
||||
"integrations.email_settings": "Email Forward Settings",
|
||||
"integrations.empty_state": "No integrations configured",
|
||||
"integrations.endpoint_label": "Endpoint URL",
|
||||
"integrations.endpoint_optional": "(optional, for S3-compatible)",
|
||||
"integrations.folder_label": "Folder",
|
||||
"integrations.folder_optional": "(optional)",
|
||||
"integrations.folder_path_label": "Folder Path",
|
||||
"integrations.folder_prefix_label": "Folder Prefix",
|
||||
"integrations.generic_settings_hint": "Configuration for this integration type can be provided as JSON after creation via the API.",
|
||||
"integrations.gmail_hint": "Gmail labels & star: when enabled, processed emails are starred and tagged with an \"Ingested\" label in Gmail. Only applies to Gmail servers.",
|
||||
"integrations.gmail_labels": "Apply Gmail labels & star",
|
||||
"integrations.host_label": "Host",
|
||||
"integrations.imap_settings": "IMAP Settings",
|
||||
"integrations.loading": "Loading integrations…",
|
||||
"integrations.modal_close": "Close modal",
|
||||
"integrations.modal_title_add": "Add Integration",
|
||||
"integrations.modal_title_edit": "Edit Integration",
|
||||
"integrations.name_label": "Label",
|
||||
"integrations.name_placeholder": "e.g. Work Gmail, S3 Archive",
|
||||
"integrations.nextcloud_settings": "Nextcloud Settings",
|
||||
"integrations.no_integrations_body": "Add your first integration to connect ingestion sources (like IMAP) and storage destinations (like S3, Dropbox, or Google Drive).",
|
||||
"integrations.not_connected": "Not Connected",
|
||||
"integrations.oauth_folder_label": "Folder",
|
||||
"integrations.oauth_hint": "Save this integration first, then use the Authorize button to complete the OAuth flow. Your credentials will be stored securely in your personal integration record.",
|
||||
"integrations.page_title": "Integrations",
|
||||
"integrations.paperless_settings": "Paperless NGX Settings",
|
||||
"integrations.password_label": "Password",
|
||||
"integrations.paused": "Paused",
|
||||
"integrations.plan_label": "Plan:",
|
||||
"integrations.port_label": "Port",
|
||||
"integrations.quota_not_available": "(not available)",
|
||||
"integrations.quota_unlimited": "/ unlimited",
|
||||
"integrations.recipient_label": "Recipient Email",
|
||||
"integrations.region_label": "Region",
|
||||
"integrations.remote_path_label": "Remote Path",
|
||||
"integrations.s3_prefix_label": "Prefix (folder path)",
|
||||
"integrations.s3_settings": "S3 Settings",
|
||||
"integrations.secret_key_label": "Secret Access Key",
|
||||
"integrations.show_password": "Show password",
|
||||
"integrations.show_secrets": "Show secrets",
|
||||
"integrations.show_token": "Show token",
|
||||
"integrations.source_local": "Local Filesystem",
|
||||
"integrations.source_type_label": "Source Type",
|
||||
"integrations.sources_quota_label": "Mailbox Sources:",
|
||||
"integrations.sources_section": "Sources",
|
||||
"integrations.subtitle": "Manage your ingestion sources and storage destinations in one place.",
|
||||
"integrations.title": "Integrations",
|
||||
"integrations.type_label": "Integration Type",
|
||||
"integrations.type_placeholder": "— Select direction first —",
|
||||
"integrations.upgrade_plan": "Upgrade Plan",
|
||||
"integrations.use_ssl": "Use SSL",
|
||||
"integrations.username_label": "Username",
|
||||
"integrations.watch_folder_settings": "Watch Folder Settings",
|
||||
"integrations.webdav_settings": "WebDAV Settings",
|
||||
"integrations.webdav_url_label": "WebDAV URL",
|
||||
"integrations.webhook_create_token": "and create a personal token.",
|
||||
"integrations.webhook_heading": "Webhook Ingestion",
|
||||
"integrations.webhook_how_heading": "How Webhook Ingestion Works",
|
||||
"integrations.webhook_how_text": "A webhook integration allows external systems to push documents directly into DocuElevate via the REST API. Instead of DocuElevate polling for new files (like IMAP), your application sends files to DocuElevate using an HTTP request with an API token for authentication.",
|
||||
"integrations.webhook_ideal_text": "This is ideal for CI/CD pipelines, automation scripts, scanner integrations, or any system that generates documents and needs to send them for processing.",
|
||||
"integrations.webhook_quick_start": "Quick Start",
|
||||
"integrations.webhook_security_tip": "Create a dedicated API token for each integration and revoke it immediately if compromised. Tokens can be managed on the API Tokens page.",
|
||||
"integrations.webhook_upload_with_token": "Use the token to upload documents via the API:",
|
||||
"language.bg": "Български",
|
||||
"language.ca": "Català",
|
||||
"language.change_success": "Language changed to {language}",
|
||||
@@ -649,6 +728,8 @@
|
||||
"language.uk": "Українська",
|
||||
"language.zh": "中文",
|
||||
"nav.about": "About",
|
||||
"nav.account_menu": "Account menu",
|
||||
"nav.account_menu_for": "Account menu for {name}",
|
||||
"nav.admin": "Admin",
|
||||
"nav.admin.audit_logs": "Audit Logs",
|
||||
"nav.admin.backups": "Backups",
|
||||
@@ -667,6 +748,7 @@
|
||||
"nav.duplicates": "Duplicates",
|
||||
"nav.file_manager": "File Manager",
|
||||
"nav.files": "Files",
|
||||
"nav.get_started": "Get Started",
|
||||
"nav.help": "Help",
|
||||
"nav.help_center": "Help Center",
|
||||
"nav.imap": "Email Import",
|
||||
@@ -675,18 +757,21 @@
|
||||
"nav.login": "Log In",
|
||||
"nav.logout": "Log Out",
|
||||
"nav.main_navigation": "Main navigation",
|
||||
"nav.my_subscription": "My Subscription",
|
||||
"nav.notifications": "Notifications",
|
||||
"nav.open_main_menu": "Open main menu",
|
||||
"nav.pipelines": "Pipelines",
|
||||
"nav.plan_designer": "Plan Designer",
|
||||
"nav.pricing": "Pricing",
|
||||
"nav.profile": "Profile",
|
||||
"nav.profile_settings": "Profile Settings",
|
||||
"nav.queue": "Queue",
|
||||
"nav.queue_monitor": "Queue Monitor",
|
||||
"nav.scheduled_jobs": "Scheduled Jobs",
|
||||
"nav.search": "Search",
|
||||
"nav.settings": "Settings",
|
||||
"nav.shared_links": "Shared Links",
|
||||
"nav.sign_out": "Sign Out",
|
||||
"nav.signup": "Sign Up",
|
||||
"nav.similarity": "Similarity",
|
||||
"nav.skip_to_content": "Skip to main content",
|
||||
@@ -742,6 +827,42 @@
|
||||
"pipelines.system_label": "System",
|
||||
"pipelines.system_pipeline_label": "System pipeline (visible to all users)",
|
||||
"pipelines.title": "Processing Pipelines",
|
||||
"profile.avatar_alt": "Your profile picture",
|
||||
"profile.avatar_gravatar_suffix": "is shown.",
|
||||
"profile.avatar_heading": "Profile Picture",
|
||||
"profile.avatar_remove": "Remove custom avatar",
|
||||
"profile.avatar_upload_hint": "Upload a JPEG, PNG, GIF, or WebP image up to 2 MB. If no custom picture is set, your",
|
||||
"profile.choose_image": "Choose image…",
|
||||
"profile.confirm_password": "Confirm New Password",
|
||||
"profile.contact_email_hint": "Used for system notifications. This does not change your login e-mail.",
|
||||
"profile.contact_email_label": "Contact / Notification E-mail",
|
||||
"profile.contact_email_placeholder": "you@example.com",
|
||||
"profile.current_password": "Current Password",
|
||||
"profile.dismiss": "Dismiss",
|
||||
"profile.display_name_hint": "Leave blank to use your account username or email.",
|
||||
"profile.display_name_label": "Display Name",
|
||||
"profile.display_name_placeholder": "Your name as shown in the UI",
|
||||
"profile.general_heading": "General Information",
|
||||
"profile.gravatar_link": "Gravatar",
|
||||
"profile.heading": "Profile Settings",
|
||||
"profile.language_auto_detect": "Auto-detect (from browser)",
|
||||
"profile.language_hint": "Choose your preferred interface language. “Auto-detect” follows your browser settings.",
|
||||
"profile.language_label": "UI Language",
|
||||
"profile.new_password": "New Password",
|
||||
"profile.new_password_hint": "Minimum 8 characters.",
|
||||
"profile.page_title": "Profile Settings – DocuElevate",
|
||||
"profile.password_heading": "Change Password",
|
||||
"profile.preferences_heading": "Preferences",
|
||||
"profile.save_button": "Save Changes",
|
||||
"profile.saving": "Saving…",
|
||||
"profile.subtitle": "Manage your display name, avatar, language, and theme preferences.",
|
||||
"profile.theme_dark": "Dark",
|
||||
"profile.theme_hint": "“System Default” follows your device’s dark-mode setting.",
|
||||
"profile.theme_label": "Colour Scheme",
|
||||
"profile.theme_light": "Light",
|
||||
"profile.theme_system": "System Default",
|
||||
"profile.update_password": "Update Password",
|
||||
"profile.updating": "Updating…",
|
||||
"queue.active_tasks": "Active Tasks",
|
||||
"queue.auto_refresh_1": "Auto-refreshes every",
|
||||
"queue.auto_refresh_2": "seconds",
|
||||
@@ -927,6 +1048,42 @@
|
||||
"status.page_title": "System Status",
|
||||
"status.setting_label": "Setting",
|
||||
"status.value_label": "Value",
|
||||
"subscription.available_plans_heading": "Available Plans",
|
||||
"subscription.back_to_dashboard": "Back to Dashboard",
|
||||
"subscription.cancel_pending": "Cancel change",
|
||||
"subscription.cancel_scheduled": "Cancel scheduled change",
|
||||
"subscription.contact_sales": "Contact Sales",
|
||||
"subscription.current_badge": "Current",
|
||||
"subscription.current_plan": "Current Plan",
|
||||
"subscription.current_plan_cta": "Your current plan",
|
||||
"subscription.downgrade_to_prefix": "Downgrade to",
|
||||
"subscription.features_heading": "What’s included in your plan",
|
||||
"subscription.free": "Free",
|
||||
"subscription.heading": "My Subscription",
|
||||
"subscription.keep_plan_prefix": "Keep",
|
||||
"subscription.lifetime_files": "Total files (lifetime)",
|
||||
"subscription.month_files": "Files this month",
|
||||
"subscription.more_features_label": "more",
|
||||
"subscription.page_title": "My Subscription – DocuElevate",
|
||||
"subscription.pending_badge": "Pending",
|
||||
"subscription.pending_benefits": "You keep all current plan benefits until then.",
|
||||
"subscription.pending_on": "on",
|
||||
"subscription.pending_title": "Pending plan change scheduled",
|
||||
"subscription.pending_will_change": "Your plan will change to",
|
||||
"subscription.per_mo": "/mo",
|
||||
"subscription.per_month": "/month",
|
||||
"subscription.since": "Since",
|
||||
"subscription.single_user_body_after_link": ".",
|
||||
"subscription.single_user_body_before_link": "Subscription tiers apply when multi-user mode is active. Your instance currently runs in single-user mode with no processing limits. An admin can enable multi-user mode via",
|
||||
"subscription.single_user_title": "Multi-user mode is not enabled.",
|
||||
"subscription.subtitle": "Your current plan, usage and available upgrades.",
|
||||
"subscription.this_month_label": "this month",
|
||||
"subscription.today_files": "Files today",
|
||||
"subscription.today_label": "today",
|
||||
"subscription.unlimited": "Unlimited",
|
||||
"subscription.upgrade_info": "Upgrades take effect immediately. Downgrades are scheduled for the end of your current billing period.",
|
||||
"subscription.upgrade_to_prefix": "Upgrade to",
|
||||
"subscription.usage_heading": "Usage",
|
||||
"upload.browse_button": "Browse Files",
|
||||
"upload.button_processing": "Processing...",
|
||||
"upload.camera_button": "Take Photo / Scan Document",
|
||||
|
||||
Reference in New Issue
Block a user