1
0
mirror of https://github.com/hrfee/jfa-go.git synced 2024-12-22 17:10:10 +00:00

userpage: show expiry

This commit is contained in:
Harvey Tindall 2023-06-18 12:27:18 +01:00
parent a22f032924
commit 5beeeb958b
Signed by: hrfee
GPG Key ID: BBC65952848FB1A2
42 changed files with 258 additions and 62 deletions

View File

@ -33,7 +33,7 @@ func (app *appContext) MyDetails(gc *gin.Context) {
if emailEnabled { if emailEnabled {
resp.Email = &MyDetailsContactMethodsDTO{} resp.Email = &MyDetailsContactMethodsDTO{}
if email, ok := app.storage.emails[user.ID]; ok { if email, ok := app.storage.emails[user.ID]; ok && email.Addr != "" {
resp.Email.Value = email.Addr resp.Email.Value = email.Addr
resp.Email.Enabled = email.Contact resp.Email.Enabled = email.Contact
} }

View File

@ -44,13 +44,16 @@
{{ template "login-modal.html" . }} {{ template "login-modal.html" . }}
<div class="page-container"> <div class="page-container">
<div class="card @low dark:~d_neutral mb-4" id="card-user"> <div class="card @low dark:~d_neutral mb-4" id="card-user">
<span class="heading"></span> <span class="heading mb-2"></span>
</div> </div>
<div class="grid grid-cols-2 gap-4"> <div class="grid grid-cols-2 gap-4">
<div class="card @low dark:~d_neutral" id="card-times"> <div class="card @low dark:~d_neutral unfocused" id="card-status">
<span class="heading mb-2">{{ .strings.expiry }}</span>
<aside class="aside ~warning user-expiry my-4"></aside>
<div class="user-expiry-countdown"></div>
</div> </div>
<div class="card @low dark:~d_neutral" id="card-contact"> <div class="card @low dark:~d_neutral" id="card-contact">
<span class="heading">{{ .strings.contactMethods }}</span> <span class="heading mb-2">{{ .strings.contactMethods }}</span>
<div class="content"></div> <div class="content"></div>
</div> </div>
</div> </div>

View File

@ -29,6 +29,7 @@ type commonLang struct {
Meta langMeta `json:"meta"` Meta langMeta `json:"meta"`
Strings langSection `json:"strings"` Strings langSection `json:"strings"`
Notifications langSection `json:"notifications"` Notifications langSection `json:"notifications"`
QuantityStrings map[string]quantityString `json:"quantityStrings"`
} }
type adminLang struct { type adminLang struct {
@ -58,6 +59,7 @@ type userLang struct {
notificationsJSON string notificationsJSON string
ValidationStrings map[string]quantityString `json:"validationStrings"` ValidationStrings map[string]quantityString `json:"validationStrings"`
validationStringsJSON string validationStringsJSON string
QuantityStrings map[string]quantityString `json:"quantityStrings"`
JSON string JSON string
} }

View File

@ -30,7 +30,6 @@
"lastActiveTime": "Sidst Aktiv", "lastActiveTime": "Sidst Aktiv",
"from": "Fra", "from": "Fra",
"user": "Bruger", "user": "Bruger",
"expiry": "Udløb",
"userExpiry": "Brugerens Udløb", "userExpiry": "Brugerens Udløb",
"userExpiryDescription": "En specificeret tid efter hver tilmelding, sletter/deaktiverer jfa-go kontoen. Du kan ændre denne adfærd i indstillingerne.", "userExpiryDescription": "En specificeret tid efter hver tilmelding, sletter/deaktiverer jfa-go kontoen. Du kan ændre denne adfærd i indstillingerne.",
"aboutProgram": "Om", "aboutProgram": "Om",

View File

@ -81,7 +81,6 @@
"download": "Herunterladen", "download": "Herunterladen",
"update": "Aktualisieren", "update": "Aktualisieren",
"updates": "Aktualisierungen", "updates": "Aktualisierungen",
"expiry": "Ablaufdatum",
"extendExpiry": "Ablaufdatum verlängern", "extendExpiry": "Ablaufdatum verlängern",
"donate": "Spenden", "donate": "Spenden",
"conditionals": "Bedingungen", "conditionals": "Bedingungen",

View File

@ -75,7 +75,6 @@
"download": "Λήψη", "download": "Λήψη",
"search": "Αναζήτηση", "search": "Αναζήτηση",
"inviteDuration": "Διάρκεια Πρόσκλησης", "inviteDuration": "Διάρκεια Πρόσκλησης",
"expiry": "Λήξη",
"userExpiry": "Λήξη Χρήστη", "userExpiry": "Λήξη Χρήστη",
"userExpiryDescription": "Μετά απο ένα καθορισμένο χρόνο μετά απο κάθε εγγραφή, το jfa-go θα διαγράφει/απενεργοποιεί τον λογαριασμό. Μπορείτε να αλλάξετε αυτή την συμπεριφορά στις ρυθμίσεις.", "userExpiryDescription": "Μετά απο ένα καθορισμένο χρόνο μετά απο κάθε εγγραφή, το jfa-go θα διαγράφει/απενεργοποιεί τον λογαριασμό. Μπορείτε να αλλάξετε αυτή την συμπεριφορά στις ρυθμίσεις.",
"announce": "Ανακοίνωση", "announce": "Ανακοίνωση",

View File

@ -71,7 +71,6 @@
"apply": "Apply", "apply": "Apply",
"delete": "Delete", "delete": "Delete",
"updates": "Updates", "updates": "Updates",
"expiry": "Expiry",
"variables": "Variables", "variables": "Variables",
"preview": "Preview", "preview": "Preview",
"markdownSupported": "Markdown is supported.", "markdownSupported": "Markdown is supported.",

View File

@ -33,7 +33,6 @@
"after": "After", "after": "After",
"before": "Before", "before": "Before",
"user": "User", "user": "User",
"expiry": "Expiry",
"userExpiry": "User Expiry", "userExpiry": "User Expiry",
"userExpiryDescription": "A specified amount of time after each signup, jfa-go will delete/disable the account. You can change this behaviour in settings.", "userExpiryDescription": "A specified amount of time after each signup, jfa-go will delete/disable the account. You can change this behaviour in settings.",
"aboutProgram": "About", "aboutProgram": "About",

View File

@ -28,7 +28,6 @@
"lastActiveTime": "Último activo", "lastActiveTime": "Último activo",
"from": "De", "from": "De",
"user": "Usuario", "user": "Usuario",
"expiry": "Expiración",
"userExpiry": "Caducidad del usuario", "userExpiry": "Caducidad del usuario",
"userExpiryDescription": "Una cantidad de tiempo específica después de cada registro, jfa-go eliminará / deshabilitará la cuenta. Puede cambiar este comportamiento en la configuración.", "userExpiryDescription": "Una cantidad de tiempo específica después de cada registro, jfa-go eliminará / deshabilitará la cuenta. Puede cambiar este comportamiento en la configuración.",
"aboutProgram": "Acerca de", "aboutProgram": "Acerca de",

View File

@ -76,7 +76,6 @@
"edit": "Éditer", "edit": "Éditer",
"customizeMessages": "Personnaliser les e-mails", "customizeMessages": "Personnaliser les e-mails",
"inviteDuration": "Durée de l'invitation", "inviteDuration": "Durée de l'invitation",
"expiry": "Expiration",
"advancedSettings": "Paramètres avancés", "advancedSettings": "Paramètres avancés",
"userExpiry": "Expiration de l'utilisateur", "userExpiry": "Expiration de l'utilisateur",
"updates": "Mises à jour", "updates": "Mises à jour",

View File

@ -31,7 +31,6 @@
"lastActiveTime": "Utoljára aktív", "lastActiveTime": "Utoljára aktív",
"from": "Feladó", "from": "Feladó",
"user": "Felhasználó", "user": "Felhasználó",
"expiry": "Lejárat",
"userExpiry": "Felhasználói lejárat", "userExpiry": "Felhasználói lejárat",
"userExpiryDescription": "Egy meghatározott idő után minden regisztrációt töröl, vagy felfüggeszt a jfa-go. Ezt a működést megváltoztathatod a beállításokban.", "userExpiryDescription": "Egy meghatározott idő után minden regisztrációt töröl, vagy felfüggeszt a jfa-go. Ezt a működést megváltoztathatod a beállításokban.",
"aboutProgram": "Névjegy", "aboutProgram": "Névjegy",

View File

@ -75,7 +75,6 @@
"customizeMessages": "E-mails aanpassen", "customizeMessages": "E-mails aanpassen",
"inviteDuration": "Geldigheidsduur uitnodiging", "inviteDuration": "Geldigheidsduur uitnodiging",
"userExpiryDescription": "Een bepaalde tijd na elke aanmelding, wordt de account verwijderd/uitgeschakeld door jfa-go. Dit kan aangepast worden in de instellingen.", "userExpiryDescription": "Een bepaalde tijd na elke aanmelding, wordt de account verwijderd/uitgeschakeld door jfa-go. Dit kan aangepast worden in de instellingen.",
"expiry": "Verloop",
"userExpiry": "Gebruikersverloop", "userExpiry": "Gebruikersverloop",
"extendExpiry": "Verleng verloop", "extendExpiry": "Verleng verloop",
"updates": "Updates", "updates": "Updates",

View File

@ -31,7 +31,6 @@
"lastActiveTime": "Ostatnia aktywność", "lastActiveTime": "Ostatnia aktywność",
"from": "Od", "from": "Od",
"user": "Użytkownik", "user": "Użytkownik",
"expiry": "Wygasa",
"userExpiry": "Użytkownik wygasa", "userExpiry": "Użytkownik wygasa",
"userExpiryDescription": "", "userExpiryDescription": "",
"aboutProgram": "O", "aboutProgram": "O",

View File

@ -75,7 +75,6 @@
"customizeMessages": "Customizar Emails", "customizeMessages": "Customizar Emails",
"userExpiryDescription": "Após um determinado período de tempo de cada inscrição, o jfa-go apagará/desabilitará a conta. Você pode alterar essa opção nas configurações.", "userExpiryDescription": "Após um determinado período de tempo de cada inscrição, o jfa-go apagará/desabilitará a conta. Você pode alterar essa opção nas configurações.",
"inviteDuration": "Duração do Convite", "inviteDuration": "Duração do Convite",
"expiry": "Expira",
"userExpiry": "Vencimento do Usuário", "userExpiry": "Vencimento do Usuário",
"extendExpiry": "Extender o vencimento", "extendExpiry": "Extender o vencimento",
"updates": "Atualizações", "updates": "Atualizações",

View File

@ -74,7 +74,6 @@
"notifyInviteExpiry": "Vid utgång", "notifyInviteExpiry": "Vid utgång",
"notifyUserCreation": "Vid användarskapande", "notifyUserCreation": "Vid användarskapande",
"inviteDuration": "Varaktighet för inbjudan", "inviteDuration": "Varaktighet för inbjudan",
"expiry": "Löper ut",
"userExpiry": "Användarutgång", "userExpiry": "Användarutgång",
"userExpiryDescription": "Efter en angiven tid efter varje registrering så tar jfa-go bort/inaktiverar kontot. Du kan ändra detta beteende i inställningarna.", "userExpiryDescription": "Efter en angiven tid efter varje registrering så tar jfa-go bort/inaktiverar kontot. Du kan ändra detta beteende i inställningarna.",
"extendExpiry": "Förläng utgång" "extendExpiry": "Förläng utgång"

View File

@ -31,7 +31,6 @@
"lastActiveTime": "Lần cuối Hoạt động", "lastActiveTime": "Lần cuối Hoạt động",
"from": "Từ", "from": "Từ",
"user": "Người dùng", "user": "Người dùng",
"expiry": "Hết hạn",
"userExpiry": "Hết hạn Người dùng", "userExpiry": "Hết hạn Người dùng",
"userExpiryDescription": "Sau một khoảng thời gian nhất định sau khi mỗi đăng ký, jfa-go sẽ xóa/vô hiệu hóa tài khoản. Bạn có thể chỉnh sửa chế độ này trong cài đặt.", "userExpiryDescription": "Sau một khoảng thời gian nhất định sau khi mỗi đăng ký, jfa-go sẽ xóa/vô hiệu hóa tài khoản. Bạn có thể chỉnh sửa chế độ này trong cài đặt.",
"aboutProgram": "Thông tin", "aboutProgram": "Thông tin",

View File

@ -30,7 +30,6 @@
"lastActiveTime": "上次活动", "lastActiveTime": "上次活动",
"from": "从", "from": "从",
"user": "用户", "user": "用户",
"expiry": "到期",
"userExpiry": "用户到期", "userExpiry": "用户到期",
"userExpiryDescription": "每次注册后的指定时间jfa-go 将删除/禁用该帐户。您可以在设置中更改此行为。", "userExpiryDescription": "每次注册后的指定时间jfa-go 将删除/禁用该帐户。您可以在设置中更改此行为。",
"aboutProgram": "关于", "aboutProgram": "关于",

View File

@ -31,7 +31,6 @@
"lastActiveTime": "上次啟用時間", "lastActiveTime": "上次啟用時間",
"from": "從", "from": "從",
"user": "帳戶", "user": "帳戶",
"expiry": "到期",
"userExpiry": "帳戶到期", "userExpiry": "帳戶到期",
"userExpiryDescription": "每次註冊后指定的時間jfa-go 將刪除/禁用該帳戶。您可以在設定中更改此行為。", "userExpiryDescription": "每次註冊后指定的時間jfa-go 將刪除/禁用該帳戶。您可以在設定中更改此行為。",
"aboutProgram": "關於", "aboutProgram": "關於",

View File

@ -3,5 +3,6 @@
"name": "العربية (AR)" "name": "العربية (AR)"
}, },
"strings": {}, "strings": {},
"notifications": {} "notifications": {},
"quantityStrings": {}
} }

View File

@ -31,7 +31,8 @@
"enabled": "Aktiveret", "enabled": "Aktiveret",
"disabled": "Deaktiveret", "disabled": "Deaktiveret",
"reEnable": "Genaktiver", "reEnable": "Genaktiver",
"disable": "Deaktiver" "disable": "Deaktiver",
"expiry": "Udløb"
}, },
"notifications": { "notifications": {
"errorLoginBlank": "Brugernavnet og/eller adgangskoden blev efterladt tomme.", "errorLoginBlank": "Brugernavnet og/eller adgangskoden blev efterladt tomme.",
@ -39,5 +40,6 @@
"errorUnknown": "Ukendt fejl.", "errorUnknown": "Ukendt fejl.",
"error401Unauthorized": "Adgang nægtet. Prøv at genindlæse siden.", "error401Unauthorized": "Adgang nægtet. Prøv at genindlæse siden.",
"errorSaveSettings": "Kunne ikke gemme indstillingerne." "errorSaveSettings": "Kunne ikke gemme indstillingerne."
} },
"quantityStrings": {}
} }

View File

@ -31,7 +31,8 @@
"enabled": "Aktiviert", "enabled": "Aktiviert",
"disabled": "Deaktiviert", "disabled": "Deaktiviert",
"reEnable": "Wieder aktivieren", "reEnable": "Wieder aktivieren",
"disable": "Deaktivieren" "disable": "Deaktivieren",
"expiry": "Ablaufdatum"
}, },
"notifications": { "notifications": {
"errorLoginBlank": "Der Benutzername und/oder das Passwort wurden nicht ausgefüllt.", "errorLoginBlank": "Der Benutzername und/oder das Passwort wurden nicht ausgefüllt.",
@ -39,5 +40,6 @@
"errorUnknown": "Unbekannter Fehler.", "errorUnknown": "Unbekannter Fehler.",
"error401Unauthorized": "Unberechtigt. Versuch, die Seite zu aktualisieren.", "error401Unauthorized": "Unberechtigt. Versuch, die Seite zu aktualisieren.",
"errorSaveSettings": "Einstellungen konnten nicht gespeichert werden." "errorSaveSettings": "Einstellungen konnten nicht gespeichert werden."
} },
"quantityStrings": {}
} }

View File

@ -22,7 +22,8 @@
"enabled": "Ενεργοποιημένο", "enabled": "Ενεργοποιημένο",
"disabled": "Απενεργοποιημένο", "disabled": "Απενεργοποιημένο",
"reEnable": "Επανα-ενεργοποίηση", "reEnable": "Επανα-ενεργοποίηση",
"disable": "Απενεργοποίηση" "disable": "Απενεργοποίηση",
"expiry": "Λήξη"
}, },
"notifications": { "notifications": {
"errorLoginBlank": "Το όνομα χρήστη και/ή ο κωδικός ήταν κενά.", "errorLoginBlank": "Το όνομα χρήστη και/ή ο κωδικός ήταν κενά.",
@ -30,5 +31,6 @@
"errorUnknown": "Άγνωστο σφάλμα.", "errorUnknown": "Άγνωστο σφάλμα.",
"error401Unauthorized": "Ανεξουσιοδότητος. Προσπαθήστε να κάνετε επαναφόρτωση την σελίδα.", "error401Unauthorized": "Ανεξουσιοδότητος. Προσπαθήστε να κάνετε επαναφόρτωση την σελίδα.",
"errorSaveSettings": "Αποτυχία αποθήκευσης ρυθμίσεων." "errorSaveSettings": "Αποτυχία αποθήκευσης ρυθμίσεων."
} },
"quantityStrings": {}
} }

View File

@ -31,7 +31,8 @@
"enabled": "Enabled", "enabled": "Enabled",
"disabled": "Disabled", "disabled": "Disabled",
"reEnable": "Re-enable", "reEnable": "Re-enable",
"disable": "Disable" "disable": "Disable",
"expiry": "Expiry"
}, },
"notifications": { "notifications": {
"errorLoginBlank": "The username and/or password was left blank.", "errorLoginBlank": "The username and/or password was left blank.",
@ -39,5 +40,6 @@
"errorUnknown": "Unknown error.", "errorUnknown": "Unknown error.",
"error401Unauthorized": "Unauthorised. Try refreshing the page.", "error401Unauthorized": "Unauthorised. Try refreshing the page.",
"errorSaveSettings": "Couldn't save settings." "errorSaveSettings": "Couldn't save settings."
} },
"quantityStrings": {}
} }

View File

@ -32,7 +32,10 @@
"disabled": "Disabled", "disabled": "Disabled",
"reEnable": "Re-enable", "reEnable": "Re-enable",
"disable": "Disable", "disable": "Disable",
"contactMethods": "Contact Methods" "contactMethods": "Contact Methods",
"accountStatus": "Account Status",
"notSet": "Not set",
"expiry": "Expiry"
}, },
"notifications": { "notifications": {
"errorLoginBlank": "The username and/or password were left blank.", "errorLoginBlank": "The username and/or password were left blank.",
@ -40,5 +43,19 @@
"errorUnknown": "Unknown error.", "errorUnknown": "Unknown error.",
"error401Unauthorized": "Unauthorized. Try refreshing the page.", "error401Unauthorized": "Unauthorized. Try refreshing the page.",
"errorSaveSettings": "Couldn't save settings." "errorSaveSettings": "Couldn't save settings."
},
"quantityStrings": {
"year": {
"singular": "{n} Year",
"plural": "{n} Years"
},
"month": {
"singular": "{n} Month",
"plural": "{n} Months"
},
"day": {
"singular": "{n} Day",
"plural": "{n} Days"
}
} }
} }

View File

@ -31,7 +31,8 @@
"enabled": "Activado", "enabled": "Activado",
"disabled": "Desactivado", "disabled": "Desactivado",
"reEnable": "Reactivar", "reEnable": "Reactivar",
"disable": "Desactivar" "disable": "Desactivar",
"expiry": "Expiración"
}, },
"notifications": { "notifications": {
"errorLoginBlank": "El nombre de usuario y/o la contraseña se dejaron en blanco.", "errorLoginBlank": "El nombre de usuario y/o la contraseña se dejaron en blanco.",
@ -39,5 +40,6 @@
"errorUnknown": "Error desconocido.", "errorUnknown": "Error desconocido.",
"error401Unauthorized": "No autorizado. Intente actualizar la página.", "error401Unauthorized": "No autorizado. Intente actualizar la página.",
"errorSaveSettings": "No se pudo guardar la configuración." "errorSaveSettings": "No se pudo guardar la configuración."
} },
"quantityStrings": {}
} }

View File

@ -24,5 +24,6 @@
"contactDiscord": "از طریق دیسکورد تماس بگیرید", "contactDiscord": "از طریق دیسکورد تماس بگیرید",
"theme": "موضوع" "theme": "موضوع"
}, },
"notifications": {} "notifications": {},
"quantityStrings": {}
} }

View File

@ -31,7 +31,8 @@
"enabled": "Activé", "enabled": "Activé",
"disabled": "Désactivé", "disabled": "Désactivé",
"reEnable": "Ré-activé", "reEnable": "Ré-activé",
"disable": "Désactivé" "disable": "Désactivé",
"expiry": "Expiration"
}, },
"notifications": { "notifications": {
"errorLoginBlank": "Le nom d'utilisateur et/ou le mot de passe sont vides.", "errorLoginBlank": "Le nom d'utilisateur et/ou le mot de passe sont vides.",
@ -39,5 +40,6 @@
"errorUnknown": "Erreur inconnue.", "errorUnknown": "Erreur inconnue.",
"error401Unauthorized": "Non autorisé. Essayez d'actualiser la page.", "error401Unauthorized": "Non autorisé. Essayez d'actualiser la page.",
"errorSaveSettings": "Impossible d'enregistrer les paramètres." "errorSaveSettings": "Impossible d'enregistrer les paramètres."
} },
"quantityStrings": {}
} }

View File

@ -9,7 +9,9 @@
"enabled": "Engedélyezve", "enabled": "Engedélyezve",
"disabled": "Tiltva", "disabled": "Tiltva",
"reEnable": "Újra engedélyezés", "reEnable": "Újra engedélyezés",
"disable": "Letiltás" "disable": "Letiltás",
"expiry": "Lejárat"
}, },
"notifications": {} "notifications": {},
"quantityStrings": {}
} }

View File

@ -25,5 +25,6 @@
"errorUnknown": "Kesalahan yang tidak diketahui.", "errorUnknown": "Kesalahan yang tidak diketahui.",
"error401Unauthorized": "Tidak ter-otorisasi. Coba segarkan halaman.", "error401Unauthorized": "Tidak ter-otorisasi. Coba segarkan halaman.",
"errorSaveSettings": "Tidak dapat menyimpan pengaturan." "errorSaveSettings": "Tidak dapat menyimpan pengaturan."
} },
"quantityStrings": {}
} }

View File

@ -26,5 +26,6 @@
"refresh": "Aggiorna", "refresh": "Aggiorna",
"required": "Richiesto" "required": "Richiesto"
}, },
"notifications": {} "notifications": {},
"quantityStrings": {}
} }

View File

@ -31,7 +31,8 @@
"enabled": "Ingeschakeld", "enabled": "Ingeschakeld",
"disabled": "Uitgeschakeld", "disabled": "Uitgeschakeld",
"reEnable": "Opnieuw inschakelen", "reEnable": "Opnieuw inschakelen",
"disable": "Uitschakelen" "disable": "Uitschakelen",
"expiry": "Verloop"
}, },
"notifications": { "notifications": {
"errorLoginBlank": "De gebruikersnaam en/of wachtwoord is leeg.", "errorLoginBlank": "De gebruikersnaam en/of wachtwoord is leeg.",
@ -39,5 +40,6 @@
"errorUnknown": "Onbekende fout.", "errorUnknown": "Onbekende fout.",
"error401Unauthorized": "Geen toegang. Probeer de pagina te vernieuwen.", "error401Unauthorized": "Geen toegang. Probeer de pagina te vernieuwen.",
"errorSaveSettings": "Opslaan van instellingen mislukt." "errorSaveSettings": "Opslaan van instellingen mislukt."
} },
"quantityStrings": {}
} }

View File

@ -28,11 +28,13 @@
"admin": "Admin", "admin": "Admin",
"enabled": "Włączone", "enabled": "Włączone",
"disabled": "Wyłączone", "disabled": "Wyłączone",
"disable": "Wyłączone" "disable": "Wyłączone",
"expiry": "Wygasa"
}, },
"notifications": { "notifications": {
"errorConnection": "Nie udało się połączyć z jfa-go.", "errorConnection": "Nie udało się połączyć z jfa-go.",
"errorUnknown": "Nieznany błąd.", "errorUnknown": "Nieznany błąd.",
"error401Unauthorized": "Nieautoryzowany. Spróbuj odświeżyć stronę." "error401Unauthorized": "Nieautoryzowany. Spróbuj odświeżyć stronę."
} },
"quantityStrings": {}
} }

View File

@ -31,7 +31,8 @@
"enabled": "Habilitado", "enabled": "Habilitado",
"disabled": "Desativado", "disabled": "Desativado",
"reEnable": "Reativar", "reEnable": "Reativar",
"disable": "Desativar" "disable": "Desativar",
"expiry": "Expira"
}, },
"notifications": { "notifications": {
"errorLoginBlank": "O nome de usuário e/ou senha foram deixados em branco.", "errorLoginBlank": "O nome de usuário e/ou senha foram deixados em branco.",
@ -39,5 +40,6 @@
"errorUnknown": "Erro desconhecido.", "errorUnknown": "Erro desconhecido.",
"error401Unauthorized": "Não autorizado. Tente atualizar a página.", "error401Unauthorized": "Não autorizado. Tente atualizar a página.",
"errorSaveSettings": "Não foi possível salvar as configurações." "errorSaveSettings": "Não foi possível salvar as configurações."
} },
"quantityStrings": {}
} }

View File

@ -3,5 +3,6 @@
"name": "Română (ROU)" "name": "Română (ROU)"
}, },
"strings": {}, "strings": {},
"notifications": {} "notifications": {},
"quantityStrings": {}
} }

View File

@ -26,5 +26,6 @@
"refresh": "Osveži", "refresh": "Osveži",
"required": "Obvezno" "required": "Obvezno"
}, },
"notifications": {} "notifications": {},
"quantityStrings": {}
} }

View File

@ -19,7 +19,8 @@
"logout": "Logga ut", "logout": "Logga ut",
"admin": "Admin", "admin": "Admin",
"enabled": "Aktiverad", "enabled": "Aktiverad",
"disabled": "Inaktiverad" "disabled": "Inaktiverad",
"expiry": "Löper ut"
}, },
"notifications": { "notifications": {
"errorLoginBlank": "Användarnamnet och/eller lösenordet lämnades tomt.", "errorLoginBlank": "Användarnamnet och/eller lösenordet lämnades tomt.",
@ -27,5 +28,6 @@
"errorUnknown": "Okänt fel.", "errorUnknown": "Okänt fel.",
"error401Unauthorized": "Obehörig. Prova att uppdatera sidan.", "error401Unauthorized": "Obehörig. Prova att uppdatera sidan.",
"errorSaveSettings": "Det gick inte att spara inställningarna." "errorSaveSettings": "Det gick inte att spara inställningarna."
} },
"quantityStrings": {}
} }

View File

@ -9,10 +9,12 @@
"enabled": "Mở", "enabled": "Mở",
"disabled": "Tắt", "disabled": "Tắt",
"reEnable": "Mở lại", "reEnable": "Mở lại",
"disable": "Tắt" "disable": "Tắt",
"expiry": "Hết hạn"
}, },
"notifications": { "notifications": {
"errorConnection": "Không thể kết nối với jfa-go.", "errorConnection": "Không thể kết nối với jfa-go.",
"error401Unauthorized": "Không được phép. Hãy thử làm mới trang." "error401Unauthorized": "Không được phép. Hãy thử làm mới trang."
} },
"quantityStrings": {}
} }

View File

@ -31,7 +31,8 @@
"enabled": "已启用", "enabled": "已启用",
"disabled": "已禁用", "disabled": "已禁用",
"reEnable": "重新启用", "reEnable": "重新启用",
"disable": "禁用" "disable": "禁用",
"expiry": "到期"
}, },
"notifications": { "notifications": {
"errorLoginBlank": "用户名/密码留空。", "errorLoginBlank": "用户名/密码留空。",
@ -39,5 +40,6 @@
"errorUnknown": "未知错误。", "errorUnknown": "未知错误。",
"error401Unauthorized": "无授权。尝试刷新页面。", "error401Unauthorized": "无授权。尝试刷新页面。",
"errorSaveSettings": "无法保存设置。" "errorSaveSettings": "无法保存设置。"
} },
"quantityStrings": {}
} }

View File

@ -31,7 +31,8 @@
"enabled": "已啟用", "enabled": "已啟用",
"disabled": "已禁用", "disabled": "已禁用",
"reEnable": "重新啟用", "reEnable": "重新啟用",
"disable": "禁用" "disable": "禁用",
"expiry": "到期"
}, },
"notifications": { "notifications": {
"errorLoginBlank": "帳戶名稱和/或密碼留空。", "errorLoginBlank": "帳戶名稱和/或密碼留空。",
@ -39,5 +40,6 @@
"errorUnknown": "未知的錯誤。", "errorUnknown": "未知的錯誤。",
"error401Unauthorized": "未經授權。嘗試重新整理頁面。", "error401Unauthorized": "未經授權。嘗試重新整理頁面。",
"errorSaveSettings": "無法儲存設置。" "errorSaveSettings": "無法儲存設置。"
} },
"quantityStrings": {}
} }

View File

@ -0,0 +1,52 @@
{
"meta": {
"name": "English (US)"
},
"strings": {
"username": "common",
"password": "common",
"emailAddress": "common",
"name": "common",
"submit": "common",
"send": "common",
"success": "common",
"continue": "common",
"error": "common",
"copy": "common",
"copied": "common",
"time24h": "common",
"time12h": "common",
"linkTelegram": "common",
"contactEmail": "common",
"contactTelegram": "common",
"linkDiscord": "common",
"linkMatrix": "common",
"contactDiscord": "common",
"theme": "common",
"refresh": "common",
"required": "common",
"login": "common",
"logout": "common",
"admin": "common",
"enabled": "common",
"disabled": "common",
"reEnable": "common",
"disable": "common",
"contactMethods": "common",
"accountStatus": "common",
"notSet": "common",
"expiry": "admin"
},
"notifications": {
"errorLoginBlank": "common",
"errorConnection": "common",
"errorUnknown": "common",
"error401Unauthorized": "common",
"errorSaveSettings": "common"
},
"quantityStrings": {
"year": "common",
"month": "common",
"day": "common"
}
}

View File

@ -202,6 +202,25 @@ func (common *commonLangs) patchCommonNotifications(to *langSection, from ...str
} }
} }
func (common *commonLangs) patchCommonQuantityStrings(to *map[string]quantityString, from ...string) {
if *to == nil {
*to = map[string]quantityString{}
}
for n, ev := range (*common)[from[len(from)-1]].QuantityStrings {
if v, ok := (*to)[n]; !ok || (v.Singular == "" && v.Plural == "") {
i := 0
for i < len(from)-1 {
ev, ok = (*common)[from[i]].QuantityStrings[n]
if ok && ev.Singular != "" && ev.Plural != "" {
break
}
i++
}
(*to)[n] = ev
}
}
}
func patchLang(to *langSection, from ...*langSection) { func patchLang(to *langSection, from ...*langSection) {
if *to == nil { if *to == nil {
*to = langSection{} *to = langSection{}
@ -283,10 +302,14 @@ func (st *Storage) loadLangCommon(filesystems ...fs.FS) error {
if err == nil { if err == nil {
loadedLangs[fsIndex][lang.Meta.Fallback+".json"] = true loadedLangs[fsIndex][lang.Meta.Fallback+".json"] = true
patchLang(&lang.Strings, &fallback.Strings, &english.Strings) patchLang(&lang.Strings, &fallback.Strings, &english.Strings)
patchLang(&lang.Notifications, &fallback.Notifications, &english.Notifications)
patchQuantityStrings(&lang.QuantityStrings, &fallback.QuantityStrings, &english.QuantityStrings)
} }
} }
if (lang.Meta.Fallback != "" && err != nil) || lang.Meta.Fallback == "" { if (lang.Meta.Fallback != "" && err != nil) || lang.Meta.Fallback == "" {
patchLang(&lang.Strings, &english.Strings) patchLang(&lang.Strings, &english.Strings)
patchLang(&lang.Notifications, &english.Notifications)
patchQuantityStrings(&lang.QuantityStrings, &english.QuantityStrings)
} }
} }
st.lang.Common[index] = lang st.lang.Common[index] = lang
@ -437,6 +460,7 @@ func (st *Storage) loadLangUser(filesystems ...fs.FS) error {
} }
st.lang.Common.patchCommonStrings(&lang.Strings, index) st.lang.Common.patchCommonStrings(&lang.Strings, index)
st.lang.Common.patchCommonNotifications(&lang.Notifications, index) st.lang.Common.patchCommonNotifications(&lang.Notifications, index)
st.lang.Common.patchCommonQuantityStrings(&lang.QuantityStrings, index)
// turns out, a lot of email strings are useful on the user page. // turns out, a lot of email strings are useful on the user page.
emailLang := []langSection{st.lang.Email[index].WelcomeEmail, st.lang.Email[index].UserDisabled, st.lang.Email[index].UserExpired} emailLang := []langSection{st.lang.Email[index].WelcomeEmail, st.lang.Email[index].UserDisabled, st.lang.Email[index].UserExpired}
for _, v := range emailLang { for _, v := range emailLang {

View File

@ -1,7 +1,7 @@
import { ThemeManager } from "./modules/theme.js"; import { ThemeManager } from "./modules/theme.js";
import { lang, LangFile, loadLangSelector } from "./modules/lang.js"; import { lang, LangFile, loadLangSelector } from "./modules/lang.js";
import { Modal } from "./modules/modal.js"; import { Modal } from "./modules/modal.js";
import { _get, _post, notificationBox, whichAnimationEvent } from "./modules/common.js"; import { _get, _post, notificationBox, whichAnimationEvent, toDateString } from "./modules/common.js";
import { Login } from "./modules/login.js"; import { Login } from "./modules/login.js";
interface userWindow extends Window { interface userWindow extends Window {
@ -31,6 +31,7 @@ window.notifications = new notificationBox(document.getElementById('notification
var rootCard = document.getElementById("card-user"); var rootCard = document.getElementById("card-user");
var contactCard = document.getElementById("card-contact"); var contactCard = document.getElementById("card-contact");
var statusCard = document.getElementById("card-status");
interface MyDetailsContactMethod { interface MyDetailsContactMethod {
value: string; value: string;
@ -58,15 +59,17 @@ interface ContactDTO {
class ContactMethods { class ContactMethods {
private _card: HTMLElement; private _card: HTMLElement;
private _content: HTMLElement;
private _buttons: { [name: string]: { element: HTMLElement, details: MyDetailsContactMethod } }; private _buttons: { [name: string]: { element: HTMLElement, details: MyDetailsContactMethod } };
constructor (card: HTMLElement) { constructor (card: HTMLElement) {
this._card = card; this._card = card;
this._content = this._card.querySelector(".content");
this._buttons = {}; this._buttons = {};
} }
clear = () => { clear = () => {
this._card.textContent = ""; this._content.textContent = "";
this._buttons = {}; this._buttons = {};
} }
@ -80,7 +83,7 @@ class ContactMethods {
${icon} ${icon}
</span> </span>
</span> </span>
<span class="ml-2 font-bold">${details.value}</span> <span class="ml-2 font-bold">${(details.value == "") ? window.lang.strings("notSet") : details.value}</span>
</div> </div>
<div class="flex items-center"> <div class="flex items-center">
<button class="user-contact-enabled-disabled button ~neutral"> <button class="user-contact-enabled-disabled button ~neutral">
@ -121,7 +124,7 @@ class ContactMethods {
checkbox.checked = details.enabled; checkbox.checked = details.enabled;
setButtonAppearance(); setButtonAppearance();
this._card.appendChild(row); this._content.appendChild(row);
}; };
private _save = () => { private _save = () => {
@ -141,6 +144,80 @@ class ContactMethods {
}; };
} }
class ExpiryCard {
private _card: HTMLElement;
private _expiry: Date;
private _aside: HTMLElement;
private _countdown: HTMLElement;
private _interval: number = null;
constructor(card: HTMLElement) {
this._card = card;
this._aside = this._card.querySelector(".user-expiry") as HTMLElement;
this._countdown = this._card.querySelector(".user-expiry-countdown") as HTMLElement;
}
private _drawCountdown = () => {
let now = new Date();
// Years, Months, Days
let ymd = [0, 0, 0];
while (now.getFullYear() != this._expiry.getFullYear()) {
ymd[0] += 1;
now.setFullYear(now.getFullYear()+1);
}
if (now.getMonth() > this._expiry.getMonth()) {
ymd[0] -=1;
now.setFullYear(now.getFullYear()-1);
}
while (now.getMonth() != this._expiry.getMonth()) {
ymd[1] += 1;
now.setMonth(now.getMonth() + 1);
}
if (now.getDate() > this._expiry.getDate()) {
ymd[1] -=1;
now.setMonth(now.getMonth()-1);
}
while (now.getDate() != this._expiry.getDate()) {
ymd[2] += 1;
now.setDate(now.getDate() + 1);
}
const langKeys = ["year", "month", "day"];
let innerHTML = ``;
for (let i = 0; i < langKeys.length; i++) {
if (ymd[i] == 0) continue;
const words = window.lang.quantity(langKeys[i], ymd[i]).split(" ");
innerHTML += `
<div class="row my-3">
<div class="inline baseline">
<span class="text-2xl">${words[0]}</span> <span class="text-gray-400 text-lg">${words[1]}</span>
</div>
</div>
`;
}
this._countdown.innerHTML = innerHTML;
};
get expiry(): Date { return this._expiry; };
set expiry(expiryUnix: number) {
if (this._interval !== null) {
window.clearInterval(this._interval);
this._interval = null;
}
if (expiryUnix == 0) return;
this._expiry = new Date(expiryUnix * 1000);
this._aside.textContent = window.lang.strings("yourAccountIsValidUntil").replace("{date}", toDateString(this._expiry));
this._card.classList.remove("unfocused");
this._interval = window.setInterval(this._drawCountdown, 60*1000);
this._drawCountdown();
}
}
var expiryCard = new ExpiryCard(statusCard);
var contactMethodList = new ContactMethods(contactCard); var contactMethodList = new ContactMethods(contactCard);
document.addEventListener("details-reload", () => { document.addEventListener("details-reload", () => {
@ -179,6 +256,8 @@ document.addEventListener("details-reload", () => {
contactMethodList.append(method[0], details[method[0]], method[1]); contactMethodList.append(method[0], details[method[0]], method[1]);
} }
} }
expiryCard.expiry = details.expiry;
} }
}); });
}); });