split some strings into common file; use lang file to setup page

This commit is contained in:
Harvey Tindall 2021-01-25 21:26:54 +00:00
parent 687edf2b0b
commit bf1e6230dc
Signed by: hrfee
GPG Key ID: BBC65952848FB1A2
15 changed files with 458 additions and 138 deletions

View File

@ -720,7 +720,7 @@
"required": false,
"requires_restart": false,
"type": "text",
"value": "Your account was deleted - Jellyfin",
"value": "",
"description": "Subject of account deletion emails."
},
"email_html": {

View File

@ -1,9 +1,8 @@
<!DOCTYPE html>
<html lang="en" class="{{ .cssClass }}">
<html lang="en" class="light-theme">
<head>
<link rel="stylesheet" type="text/css" href="../build/data/web/css/bundle.css">
{{ template "header.html" . }}
<title>{{ .strings.pageTitle }}</title>
<link rel="stylesheet" type="text/css" href="css/bundle.css">
<title>{{ .lang.Strings.pageTitle }}</title>
</head>
<body class="max-w-full overflow-x-hidden section">
<div id="notification-box"></div>
@ -20,328 +19,327 @@
<div class="page-container">
<div class="card ~neutral !low mb-1">
<div class="row">
<img class="banner header" src="../images/banner.svg" alt="jfa-go" />
<img class="banner header" src="banner.svg" alt="jfa-go" />
</div>
<div class="row col flex center">
<span class="heading">Welcome!</span>
<span class="heading">{{ .lang.StartPage.welcome }}</span>
</div>
<div class="row col flex center">
<p class="content">You'll need to do a few things to set up jfa-go. Press continue to get started.</p>
<p class="content">{{ .lang.StartPage.pressStart }}</p>
</div>
<section class="section ~neutral banner footer flex-expand middle">
<span class="support">Make sure you're accessing this page via HTTPS or a Private Network.</span>
<span class="button ~urge !normal">Continue</span>
<span class="support">{{ .lang.StartPage.httpsNotice }}</span>
<span class="button ~urge !normal">{{ .lang.StartPage.start }}</span>
</section>
</div>
<div class="card ~neutral !low mb-1">
<span class="heading">Language</span>
<p class="content">Community translations are available for most parts of jfa-go. You can choose the default languages below, but users can still change it for themselves. If you want to help out, sign up <a href="https://weblate.hrfee.pw">here</a> to start contributing!</p>
<span class="heading">{{ .lang.Language.title }}</span>
<p class="content" id="language-description"></p>
<label class="label">
<span class="mt-half">Default admin language</span>
<span class="mt-half">{{ .lang.Language.defaultAdminLang }}</span>
<div class="select ~neutral !normal mt-half mb-1">
<select id="ui-language-admin">
</select>
</div>
</label>
<label class="label">
<span class="mt-half">Default account creation language</span>
<span class="mt-half">{{ .lang.Language.defaultFormLang }}</span>
<div class="select ~neutral !normal mt-half mb-1">
<select id="ui-language-form">
</select>
</div>
</label>
<label class="label">
<span class="mt-half">Default email language</span>
<span class="mt-half">{{ .lang.Language.defaultEmailLang }}</span>
<div class="select ~neutral !normal mt-half mb-1">
<select id="email-language">
</select>
</div>
</label>
<section class="section ~neutral banner footer flex-expand middle">
<span class="button ~neutral !normal back">Back</span>
<span class="button ~urge !normal next">Continue</span>
<span class="button ~neutral !normal back">{{ .lang.Strings.back }}</span>
<span class="button ~urge !normal next">{{ .lang.Strings.next }}</span>
</section>
</div>
<div class="card ~neutral !low mb-1">
<span class="heading">Login</span>
<p class="content">To access the admin page, you need to login with a method below:</p>
<span class="heading">{{ .lang.Login.title }}</span>
<p class="content">{{ .lang.Login.description }}</p>
<div class="pl-1">
<label class="row switch pb-1">
<input type="radio" name="login-method" checked><span><b>Authorize through Jellyfin:</b> Login details are shared with Jellyfin, allowing for multiple users.</span>
<input type="radio" name="login-method" checked><span>{{ .lang.Login.authorizeWithJellyfin }}</span>
</label>
<label class="row switch pl-1 pb-1">
<input type="checkbox" id="login-jellyfin"><span>Admin users only (recommended)</span>
<input type="checkbox" id="login-jellyfin"><span>{{ .lang.Login.adminOnly }}</span>
</label>
<label class="row switch pb-1">
<input type="radio" name="login-method"><span><b>Username &amp; Password:</b> Manually set the username &amp; password.</span>
<input type="radio" name="login-method"><span>{{ .lang.Login.authorizeManual }}</span>
</label>
</div>
<div id="login-manual">
<label class="label">
<span class="mt-half">Username</span>
<input type="text" id="login-password" class="input ~neutral !normal mt-half mb-1" placeholder="Username">
<span class="mt-half">{{ .lang.Strings.username }}</span>
<input type="text" id="login-password" class="input ~neutral !normal mt-half mb-1" placeholder="{{ .lang.Strings.username }}">
</label>
<label class="label">
<span>Password</span>
<input type="password" id="login-username" class="input ~neutral !normal mt-half mb-1" placeholder="Password">
<span>{{ .lang.Strings.password }}</span>
<input type="password" id="login-username" class="input ~neutral !normal mt-half mb-1" placeholder="{{ .lang.Strings.password }}">
</label>
<label class="label">
<span>Email address (optional)</span>
<span>{{ .lang.Strings.emailAddress }} ({{ .lang.Strings.optional }})</span>
<input type="email" class="input ~neutral !normal mt-half" placeholder="email@address">
<span class="support mb-1">Your email address can be used to receive activity notifications.</span>
<span class="support mb-1">{{ .lang.Login.emailNotice }}</span>
</label>
</div>
<section class="section ~neutral banner footer flex-expand middle">
<span class="button ~neutral !normal back">Back</span>
<span class="button ~urge !normal next">Continue</span>
<span class="button ~neutral !normal back">{{ .lang.Strings.back }}</span>
<span class="button ~urge !normal next">{{ .lang.Strings.next }}</span>
</section>
</div>
<div class="card ~neutral !low mb-1">
<span class="heading">Jellyfin/Emby</span>
<p class="content">jfa-go needs admin access because API tokens don't allow user creation. You should create a separate account and check "Allow this user to manage the server". You can disable everything else. Once done, enter the credentials here.</p>
<span class="heading">{{ .lang.JellyfinEmby.title }}</span>
<p class="content">{{ .lang.JellyfinEmby.description }}</p>
<label class="label">
<span>Server Type</span>
<span>{{ .lang.Strings.serverType }}</span>
<div class="select ~neutral !normal mt-half">
<select id="jellyfin-type">
<option value="jellyfin">Jellyfin</option>
<option value="emby">Emby (experimental)</option>
<option value="emby">Emby</option>
</select>
</div>
<p class="support mb-1">Emby support is limited and does not support password resets.</p>
<p class="support mb-1">{{ .lang.JellyfinEmby.embyNotice }}</p>
</label>
<label class="label">
<span class="mt-half">Server Address (internal)</span>
<span class="mt-half">{{ .lang.Strings.serverAddress }} ({{ .lang.JellyfinEmby.internal }})</span>
<input type="url" class="input ~neutral !normal mt-half mb-1" id="jellyfin-server" placeholder="http://jellyf.in:80">
</label>
<label class="label">
<span class="mt-half">Server Address (public)</span>
<span class="mt-half">{{ .lang.Strings.serverAddress }} ({{ .lang.JellyfinEmby.external }})</span>
<input type="url" class="input ~neutral !normal mt-half" id="jellyfin-public-server" placeholder="https://jellyf.in">
<p class="support mb-1">Leave blank to use the same address.</p>
<p class="support mb-1">{{ .lang.JellyfinEmby.addressExternalNotice }}</p>
</label>
<label class="label">
<span class="mt-half">Username</span>
<input type="text" id="jellyfin-username" class="input ~neutral !normal mt-half mb-1" placeholder="Username">
<span class="mt-half">{{ .lang.Strings.username }}</span>
<input type="text" id="jellyfin-username" class="input ~neutral !normal mt-half mb-1" placeholder="{{ .lang.Strings.username }}">
</label>
<label class="label">
<span>Password</span>
<input type="password" id="jellyfin-password" class="input ~neutral !normal mt-half mb-1" placeholder="Password">
<span>{{ .lang.Strings.password }}</span>
<input type="password" id="jellyfin-password" class="input ~neutral !normal mt-half mb-1" placeholder="{{ .lang.Strings.password }}">
</label>
<section class="section ~neutral banner footer flex-expand middle">
<span class="button ~neutral !normal back">Back</span>
<span class="button ~neutral !normal back">{{ .lang.Strings.back }}</span>
<div>
<span class="button ~urge !normal">Test Connection</span>
<span class="button ~urge !normal next" disabled>Continue</span>
<span class="button ~urge !normal">{{ .lang.JellyfinEmby.testConnection }}</span>
<span class="button ~urge !normal next" disabled>{{ .lang.Strings.next }}</span>
</div>
</section>
</div>
<div class="card ~neutral !low mb-1">
<span class="heading">Email</span>
<p class="content">jfa-go can send password reset PINs and various notifications. You can connect to an SMTP server, or use the <a href="https://www.mailgun.com/">Mailgun</a> API.</p>
<span class="heading">{{ .lang.Email.title }}</span>
<p class="content" id="email-description"></p>
<div class="row">
<div class="col">
<label class="label">
<span>Server Type</span>
<span>{{ .lang.Strings.serverType }}</span>
<div class="select ~neutral !normal mt-half mb-1">
<select id="email-method">
<option value="">Disabled</option>
<option value="">{{ .lang.Strings.disabled }}</option>
<option value="smtp">SMTP</option>
<option value="mailgun">Mailgun</option>
</select>
</div>
</label>
<label class="label">
<span class="mt-half">From Address</span>
<span class="mt-half">{{ .lang.Email.fromAddress }}</span>
<input type="email" class="input ~neutral !normal mt-half mb-1" id="email-address" placeholder="mail@jellyf.in">
</label>
<label class="label">
<span class="mt-half">Sender Name</span>
<span class="mt-half">{{ .lang.Email.senderName }}</span>
<input type="text" class="input ~neutral !normal mt-half mb-1" id="email-from" value="Jellyfin">
</label>
<label class="label">
<span class="mt-half">Date Format</span>
<span class="mt-half">{{ .lang.Email.dateFormat }}</span>
<input type="text" class="input ~neutral !normal mt-half" id="email-date_format" value="%d/%m/%y">
<p class="support mb-1">Date follows the <a href="https://strftime.org/">strftime</a> format.</p>
<p class="support mb-1" id="email-dateformat-notice"></p>
</label>
<div>
<label class="row switch pb-1">
<input type="radio" name="email-24h" value="true" checked><span>24h Time</span>
<input type="radio" name="email-24h" value="true" checked><span>{{ .lang.Email.time24h }}</span>
</label>
<label class="row switch pb-1">
<input type="radio" name="email-24h" value="false"><span>12h Time</span>
<input type="radio" name="email-24h" value="false"><span>{{ .lang.Email.time12h }}</span>
</label>
</div>
<label class="label">
<span class="mt-half">Message</span>
<input type="text" class="input ~neutral !normal mt-half" id="email-message" value="Need help? Contact me.">
<p class="support mb-1">A short message displayed at the bottom of emails.</p>
</label>
</div>
<div class="col">
<div id="email-smtp">
<label class="label">
<span>Encryption</span>
<span>{{ .lang.Email.encryption }}</span>
<div class="select ~neutral !normal mt-half mb-1">
<select id="smtp-encryption">
<option value="starttls">STARTTLS (Usually port 587)</option>
<option value="ssl_tls">SSL/TLS (Usually port 465)</option>
<option value="starttls">STARTTLS ({{ .lang.Strings.port }} 587)</option>
<option value="ssl_tls">SSL/TLS ({{ .lang.Strings.port }} 465)</option>
</select>
</div>
</label>
<label class="label">
<span class="mt-half">Server Address</span>
<span class="mt-half">{{ .lang.Strings.serverAddress }}</span>
<input type="url" class="input ~neutral !normal mt-half mb-1" id="smtp-server" placeholder="smtp.jellyf.in">
</label>
<label class="label">
<span class="mt-half">Port</span>
<span class="mt-half">{{ .lang.Strings.port }}</span>
<input type="number" class="input ~neutral !normal mt-half mb-1" id="smtp-port" placeholder="587">
</label>
<label class="label">
<span class="mt-half">Username</span>
<span class="mt-half">{{ .lang.Strings.username }}</span>
<input type="text" class="input ~neutral !normal mt-half mb-1" id="smtp-username">
</label>
<label class="label">
<span class="mt-half">Password</span>
<span class="mt-half">{{ .lang.Strings.password }}</span>
<input type="password" class="input ~neutral !normal mt-half mb-1" id="smtp-password">
</label>
</div>
<div id="email-mailgun">
<label class="label">
<span class="mt-half">API URL</span>
<span class="mt-half">{{ .lang.Email.mailgunApiURL }}</span>
<input type="url" class="input ~neutral !normal mt-half mb-1" id="mailgun-api_url" placeholder="https://api.eu.mailgun.net/v3/mail.jellyf.in/messages">
</label>
<label class="label">
<span class="mt-half">API Key</span>
<span class="mt-half">{{ .lang.Email.mailgunApiKey }}</span>
<input type="text" class="input ~neutral !normal mt-half mb-1" id="mailgun-api_key">
</label>
</div>
</div>
</div>
<section class="section ~neutral banner footer flex-expand middle">
<span class="button ~neutral !normal back">Back</span>
<span class="button ~neutral !normal back">{{ .lang.Strings.back }}</span>
<div>
<span class="button ~urge !normal next">Continue</span>
<span class="button ~urge !normal next">{{ .lang.Strings.next }}</span>
</div>
</section>
</div>
<div class="card ~neutral !low mb-1">
<span class="heading">Notifications</span>
<p class="content">If enabled, you can choose (per invite) to receive an email when an invite expires, or a user is created. If you didn't choose Jellyfin Authentication, make sure you provided an email address.</p>
<span class="heading">{{ .lang.Notifications.title }}</span>
<p class="content">{{ .lang.Notifications.description }}</p>
<label class="row switch pb-1">
<input type="checkbox" id="notifications-enabled"><span>Enabled</span>
<input type="checkbox" id="notifications-enabled"><span>{{ .lang.Strings.enabled }}</span>
</label>
<span class="heading">Password Resets</span>
<p class="content">When a user tries to reset their password, Jellyfin creates a file named "passwordreset-*.json" which contains a PIN. jfa-go reads the file and sends the PIN to the user.</p>
<span class="heading">{{ .lang.PasswordResets.title }}</span>
<p class="content">{{ .lang.PasswordResets.description }}</p>
<label class="row switch pb-1">
<input type="checkbox" id="password_resets-enabled"><span>Enabled</span>
<input type="checkbox" id="password_resets-enabled"><span>{{ .lang.Strings.enabled }}</span>
</label>
<label class="label">
<span class="mt-half">Path to Jellyfin configuration directory</span>
<input type="text" class="input ~neutral !normal mt-half" id="password_resets-watch_directory" placeholder="/path/to/jellyfin">
<p class="support mb-1">If you don't know where this is, Try resetting your password in Jellyfin. A popup with the "&lt;path to jellyfin&gt;/passwordreset-*.json" will appear.</p>
<span class="mt-half">{{ .lang.PasswordResets.pathToJellyfin }}</span>
<input type="text" class="input ~neutral !normal mt-half" id="password_resets-watch_directory" placeholder="/config/jellyfin">
<p class="support mb-1">{{ .lang.PasswordResets.pathToJellyfinNotice }}</p>
</label>
<label class="label">
<span class="mt-half">Email subject</span>
<input type="text" class="input ~neutral !normal mt-half mb-1" id="password_resets-subject" value="Password Reset - Jellyfin">
<span class="mt-half">{{ .lang.Strings.emailSubject }}</span>
<input type="text" class="input ~neutral !normal mt-half mb-1" id="password_resets-subject" placeholder="{{ .emailLang.PasswordReset.title }}">
</label>
<section class="section ~neutral banner footer flex-expand middle">
<span class="button ~neutral !normal back">Back</span>
<span class="button ~neutral !normal back">{{ .lang.Strings.back }}</span>
<div>
<span class="button ~urge !normal next">Continue</span>
<span class="button ~urge !normal next">{{ .lang.Strings.next }}</span>
</div>
</section>
</div>
<div class="card ~neutral !low mb-1">
<span class="heading">Invite Emails</span>
<p class="content">If enabled, you can send invites directly to a user's email address. Because you might be using a reverse proxy, you need to provide the URL invites are accessed from. Write your URL Base and append "/invite".</p>
<span class="heading">{{ .lang.InviteEmails.title }}</span>
<p class="content">{{ .lang.InviteEmails.description }}</p>
<label class="row switch pb-1">
<input type="checkbox" id="invite_emails-enabled"><span>Enabled</span>
<input type="checkbox" id="invite_emails-enabled"><span>{{ .lang.Strings.enabled }}</span>
</label>
<label class="label">
<span class="mt-half">URL</span>
<span class="mt-half">{{ .lang.Strings.URL }}</span>
<input type="url" class="input ~neutral !normal mt-half mb-1" id="invite_emails-url_base" placeholder="https://accounts.jellyf.in/invite">
</label>
<label class="label">
<span class="mt-half">Email subject</span>
<input type="text" class="input ~neutral !normal mt-half mb-1" id="invite_emails-subject" value="Invite - Jellyfin">
<span class="mt-half">{{ .lang.Strings.emailSubject }}</span>
<input type="text" class="input ~neutral !normal mt-half mb-1" id="invite_emails-subject" placeholder="{{ .emailLang.InviteEmail.title }}">
</label>
<section class="section ~neutral banner footer flex-expand middle">
<span class="button ~neutral !normal back">Back</span>
<span class="button ~neutral !normal back">{{ .lang.Strings.back }}</span>
<div>
<span class="button ~urge !normal next">Continue</span>
<span class="button ~urge !normal next">{{ .lang.Strings.next }}</span>
</div>
</section>
</div>
<div class="card ~neutral !low mb-1">
<span class="heading">Password Validation</span>
<p class="content">If enabled, a set of password requirements will show on the create account page, such as minimum length, uppercase/lowercase characters, etc.</p>
<span class="heading">{{ .lang.PasswordValidation.title }}</span>
<p class="content">{{ .lang.PasswordValidation.description }}</p>
<label class="row switch pb-1">
<input type="checkbox" id="password_validation-enabled" checked><span>Enabled</span>
<input type="checkbox" id="password_validation-enabled" checked><span>{{ .lang.Strings.enabled }}</span>
</label>
<label class="label">
<span class="mt-half">Length</span>
<span class="mt-half">{{ .lang.PasswordValidation.length }}</span>
<input type="number" class="input ~neutral !normal mt-half mb-1" id="password_validation-min_length" value="8">
</label>
<label class="label">
<span class="mt-half">Uppercase characters</span>
<span class="mt-half">{{ .lang.PasswordValidation.uppercase }}</span>
<input type="number" class="input ~neutral !normal mt-half mb-1" id="password_validation-upper" value="1">
</label>
<label class="label">
<span class="mt-half">Lowercase characters</span>
<span class="mt-half">{{ .lang.PasswordValidation.lowercase }}</span>
<input type="number" class="input ~neutral !normal mt-half mb-1" id="password_validation-lower" value="0">
</label>
<label class="label">
<span class="mt-half">Numbers</span>
<span class="mt-half">{{ .lang.PasswordValidation.numbers }}</span>
<input type="number" class="input ~neutral !normal mt-half mb-1" id="password_validation-number" value="0">
</label>
<label class="label">
<span class="mt-half">Special (%, *, etc.)</span>
<span class="mt-half">{{ .lang.PasswordValidation.special }}</span>
<input type="number" class="input ~neutral !normal mt-half mb-1" id="password_validation-special" value="0">
</label>
<section class="section ~neutral banner footer flex-expand middle">
<span class="button ~neutral !normal back">Back</span>
<span class="button ~neutral !normal back">{{ .lang.Strings.back }}</span>
<div>
<span class="button ~urge !normal next">Continue</span>
<span class="button ~urge !normal next">{{ .lang.Strings.next }}</span>
</div>
</section>
</div>
<div class="card ~neutral !low mb-1">
<span class="heading">Help Messages</span>
<p class="content">These messages will display in the account creation page and in emails.</p>
<span class="heading">{{ .lang.HelpMessages.title }}</span>
<p class="content">{{ .lang.HelpMessages.description }}</p>
<label class="label">
<span class="mt-half">Contact Message</span>
<input type="text" class="input ~neutral !normal mt-half" id="ui-contact_message" value="Need help? Contact me.">
<p class="support mb-1">Displays at the bottom of all pages except admin.</p>
<span class="mt-half">{{ .lang.HelpMessages.contactMessage }}</span>
<input type="text" class="input ~neutral !normal mt-half" id="ui-contact_message">
<p class="support mb-1">{{ .lang.HelpMessages.contactMessageNotice }}</p>
</label>
<label class="label">
<span class="mt-half">Help Message</span>
<input type="text" class="input ~neutral !normal mt-half" id="ui-help_message" value="Enter your details to create an account.">
<p class="support mb-1">Displays on the account creation page.</p>
<span class="mt-half">{{ .lang.HelpMessages.helpMessage }}</span>
<input type="text" class="input ~neutral !normal mt-half" id="ui-help_message">
<p class="support mb-1">{{ .lang.HelpMessages.helpMessageNotice }}</p>
</label>
<label class="label">
<span class="mt-half">Success Message</span>
<input type="text" class="input ~neutral !normal mt-half" id="ui-success_message" value="Your account has been created. Click below to continue to Jellyfin.">
<p class="support mb-1">Displays when a user creates their account.</p>
<span class="mt-half">{{ .lang.HelpMessages.successMessage }}</span>
<input type="text" class="input ~neutral !normal mt-half" id="ui-success_message">
<p class="support mb-1">{{ .lang.HelpMessages.successMessageNotice }}</p>
</label>
<label class="label">
<span class="mt-half">{{ .lang.HelpMessages.emailMessage }}</span>
<input type="text" class="input ~neutral !normal mt-half" id="email-message">
<p class="support mb-1">{{ .lang.HelpMessages.emailMessageNotice }}</p>
</label>
<section class="section ~neutral banner footer flex-expand middle">
<span class="button ~neutral !normal back">Back</span>
<span class="button ~neutral !normal back">{{ .lang.Strings.back }}</span>
<div>
<span class="button ~urge !normal next">Continue</span>
<span class="button ~urge !normal next">{{ .lang.Strings.next }}</span>
</div>
</section>
</div>
<div class="card ~neutral !low mb-1">
<div class="row col flex center">
<span class="heading">Finished!</span>
<span class="heading">{{ .lang.EndPage.finished }}</span>
</div>
<div class="row col flex center">
<p class="content">There are more settings you can configure on the admin page. Click below to restart, then refresh the page.</p>
<p class="content">{{ .lang.EndPage.restartMessage }}</p>
</div>
<div class="row col flex center">
<span class="button ~urge !normal" id="restart">Submit</span>
<span class="button ~urge !normal" id="restart">{{ .lang.Strings.submit }}</span>
</div>
</div>
</div>
{{ template "form-base" . }}
</body>
</html>

36
lang.go
View File

@ -26,6 +26,13 @@ func (ls *adminLangs) getOptions(chosen string) (string, []string) {
return chosenLang, opts
}
type commonLangs map[string]commonLang
type commonLang struct {
Meta langMeta `json:"meta"`
Strings langSection `json:"strings"`
}
type adminLang struct {
Meta langMeta `json:"meta"`
Strings langSection `json:"strings"`
@ -77,6 +84,35 @@ type emailLang struct {
WelcomeEmail langSection `json:"welcomeEmail"`
}
type setupLangs map[string]setupLang
type setupLang struct {
Meta langMeta `json:"meta"`
Strings langSection `json:"strings"`
StartPage langSection `json:"startPage"`
EndPage langSection `json:"endPage"`
Language langSection `json:"language"`
Login langSection `json:"login"`
JellyfinEmby langSection `json:"jellyfinEmby"`
Email langSection `json:"email"`
Notifications langSection `json:"notifications"`
PasswordResets langSection `json:"passwordResets"`
InviteEmails langSection `json:"inviteEmails"`
PasswordValidation langSection `json:"passwordValidation"`
HelpMessages langSection `json:"helpMessages"`
}
func (ls *setupLangs) getOptions(chosen string) (string, []string) {
opts := make([]string, len(*ls))
chosenLang := (*ls)[chosen].Meta.Name
i := 0
for _, lang := range *ls {
opts[i] = lang.Meta.Name
i++
}
return chosenLang, opts
}
type langSection map[string]string
func (el langSection) format(field string, vals ...string) string {

View File

@ -19,12 +19,8 @@
"create": "Erstellen",
"apply": "Anwenden",
"delete": "Löschen",
"submit": "Absenden",
"name": "Name",
"date": "Datum",
"username": "Benutzername",
"password": "Passwort",
"emailAddress": "E-Mail-Adresse",
"lastActiveTime": "Zuletzt aktiv",
"from": "Von",
"user": "Benutzer",

View File

@ -19,12 +19,8 @@
"create": "Create",
"apply": "Apply",
"delete": "Delete",
"submit": "Submit",
"name": "Name",
"date": "Date",
"username": "Username",
"password": "Password",
"emailAddress": "Email Address",
"lastActiveTime": "Last Active",
"from": "From",
"user": "User",

View File

@ -20,12 +20,8 @@
"create": "Créer",
"apply": "Appliquer",
"delete": "Effacer",
"submit": "Soumettre",
"name": "Nom",
"date": "Date",
"username": "Nom d'utilisateur",
"password": "Mot de passe",
"emailAddress": "Addresse Email",
"lastActiveTime": "Dernière activité",
"from": "De",
"user": "Utilisateur",

View File

@ -19,12 +19,8 @@
"create": "Aanmaken",
"apply": "Toepassen",
"delete": "Verwijderen",
"submit": "Verstuur",
"name": "Naam",
"date": "Datum",
"username": "Gebruikersnaam",
"password": "Wachtwoord",
"emailAddress": "E-mailadres",
"lastActiveTime": "Laatst actief",
"from": "Van",
"user": "Gebruiker",

11
lang/common/de-de.json Normal file
View File

@ -0,0 +1,11 @@
{
"meta": {
"name": "Deutsch (DE)"
},
"strings": {
"username": "Benutzername",
"password": "Passwort",
"emailAddress": "E-Mail-Adresse",
"submit": "Absenden"
}
}

11
lang/common/en-us.json Normal file
View File

@ -0,0 +1,11 @@
{
"meta": {
"name": "English (US)"
},
"strings": {
"username": "Username",
"password": "Password",
"emailAddress": "Email Address",
"submit": "Submit"
}
}

12
lang/common/fr-fr.json Normal file
View File

@ -0,0 +1,12 @@
{
"meta": {
"name": "Francais (FR)",
"author": "https://github.com/Killianbe"
},
"strings": {
"username": "Nom d'utilisateur",
"password": "Mot de passe",
"emailAddress": "Addresse Email",
"submit": "Soumettre"
}
}

11
lang/common/nl-nl.json Normal file
View File

@ -0,0 +1,11 @@
{
"meta": {
"name": "Nederlands (NL)"
},
"strings": {
"username": "Gebruikersnaam",
"password": "Wachtwoord",
"emailAddress": "E-mailadres",
"submit": "Verstuur"
}
}

101
lang/setup/en-us.json Normal file
View File

@ -0,0 +1,101 @@
{
"meta": {
"name": "English (US)"
},
"strings": {
"pageTitle": "Setup - jfa-go",
"next": "Next",
"back": "Back",
"optional": "Optional",
"serverType": "Server Type",
"disabled": "Disabled",
"enabled": "Enabled",
"port": "Port",
"message": "Message",
"serverAddress": "Server Address",
"emailSubject": "Email Subject",
"URL": "URL"
},
"startPage": {
"welcome": "Welcome!",
"pressStart": "You'll need to do a few things to set up jfa-go. Press start to get continue.",
"httpsNotice": "Make sure you're accessing this page via HTTPS or on a private network.",
"start": "Start"
},
"endPage": {
"finished": "Finished!",
"restartMessage": "There are more settings you can configure on the admin page. Click below to restart, then refresh the page."
},
"language": {
"title": "Language",
"description": "Community translations are available for most parts of jfa-go. You can choose the default languages below, but users can still change it if they wish. If you want to help translate, sign up to {n} to start contributing!",
"defaultAdminLang": "Default admin language",
"defaultFormLang": "Default account creation language",
"defaultEmailLang": "Default email language"
},
"login": {
"title": "Login",
"description": "To access the admin page, you need to login with a method below:",
"authorizeWithJellyfin": "Authorize with Jellyfin/Emby: Login details are shared with Jellyfin, which allows for multiple users.",
"authorizeManual": "Username and Password: Manually set the username and password.",
"adminOnly": "Admin users only (recommended)",
"emailNotice": "Your email address can be used to receive notifications."
},
"jellyfinEmby": {
"title": "Jellyfin/Emby",
"description": "An admin account is needed because the API does not allow user creation using an API key. You should create a separate account and check 'Allow this user to manage the server'. You can disable everything else. Once done, enter the login details here.",
"embyNotice": "Emby support is limited and does not support password resets.",
"internal": "Internal",
"external": "External",
"addressExternalNotice": "Leave blank to use the same address.",
"testConnection": "Test Connection"
},
"email": {
"title": "Email",
"description": "jfa-go can send password reset PINs and various notifications through email. You can connect to an SMTP server, or use the {n} API.",
"fromAddress": "From Address",
"senderName": "Sender Name",
"dateFormat": "Date Format",
"dateFormatNotice": "Date follows the strftime format. For more info, visit {n}.",
"time24h": "24h Time",
"time12h": "12h Time",
"encryption": "Encryption",
"mailgunApiURL": "API URL",
"mailgunApiKey": "API Key"
},
"notifications": {
"title": "Notifications",
"description": "If enabled, you can choose (per invite) to receive an email when an invite expires, or a user is created. If you didn't choose the Jellyfin login method, make sure you provided your email address."
},
"passwordResets": {
"title": "Password Resets",
"description": "When a user tries to reset their password, Jellyfin creates a file named 'passwordreset-*.json' which contains a PIN. jfa-go reads the file and sends the PIN to the user.",
"pathToJellyfin": "Path to Jellyfin configuration directory",
"pathToJellyfinNotice": "If you don't know where this is, try resetting your password in Jellyfin. A popup with '<path to jellyfin>/passwordreset-*.json' will appear."
},
"inviteEmails": {
"title": "Invite Emails",
"description": "If enabled, you can send invites directly to a user's email address. Because you might be using a reverse proxy, you need to provide the URL invites are accessed from. Write your URL Base, and append '/invite'."
},
"passwordValidation": {
"title": "Password Validation",
"description": "If enabled, a set of password requirements will show on the account creation page, such as minimum length, uppercase/lowercase characters, etc.",
"length": "Length",
"uppercase": "Uppercase characters",
"lowercase": "Lowercase characters",
"numbers": "Numbers",
"special": "Special characters (%, *, etc.)"
},
"helpMessages": {
"title": "Help Messages",
"description": "These messages will display in the account creation page and in some emails.",
"contactMessage": "Contact Message",
"contactMessageNotice": "Displays at the bottom of all pages except admin.",
"helpMessage": "Help Message",
"helpMessageNotice": "Displays on the account creation page.",
"successMessage": "Success Message",
"successMessageNotice": "Displays when a user creates their account.",
"emailMessage": "Email Message",
"emailMessageNotice": "Displays at the bottom of emails."
}
}

21
main.go
View File

@ -516,6 +516,7 @@ func start(asDaemon, firstCall bool) {
}
}
}
app.storage.lang.CommonPath = filepath.Join(app.localPath, "lang", "common")
app.storage.lang.FormPath = filepath.Join(app.localPath, "lang", "form")
app.storage.lang.AdminPath = filepath.Join(app.localPath, "lang", "admin")
app.storage.lang.EmailPath = filepath.Join(app.localPath, "lang", "email")
@ -559,6 +560,22 @@ func start(asDaemon, firstCall bool) {
} else {
debugMode = false
address = "0.0.0.0:8056"
app.storage.lang.CommonPath = filepath.Join(app.localPath, "lang", "common")
app.storage.lang.EmailPath = filepath.Join(app.localPath, "lang", "email")
app.storage.lang.SetupPath = filepath.Join(app.localPath, "lang", "setup")
err := app.storage.loadLangCommon()
if err != nil {
app.info.Fatalf("Failed to load language files: %+v\n", err)
}
err = app.storage.loadLangEmail()
if err != nil {
app.info.Fatalf("Failed to load language files: %+v\n", err)
}
err = app.storage.loadLangSetup()
if err != nil {
app.info.Fatalf("Failed to load language files: %+v\n", err)
}
}
app.info.Println("Loading routes")
if debugMode {
@ -618,9 +635,7 @@ func start(asDaemon, firstCall bool) {
}
app.info.Printf("Starting router @ %s", address)
} else {
router.GET("/", func(gc *gin.Context) {
gc.HTML(200, "setup.html", gin.H{})
})
router.GET("/", app.ServeSetup)
router.POST("/jellyfin/test", app.TestJF)
router.POST("/config", app.ModifyConfig)
app.info.Printf("Loading setup @ %s", address)

View File

@ -1,11 +1,33 @@
package main
import (
"encoding/json"
"io/ioutil"
"path/filepath"
"strings"
"github.com/gin-gonic/gin"
"github.com/hrfee/jfa-go/common"
"github.com/hrfee/jfa-go/mediabrowser"
)
func (app *appContext) ServeSetup(gc *gin.Context) {
lang := gc.Query("lang")
if lang == "" {
lang = "en-us"
} else if _, ok := app.storage.lang.Admin[lang]; !ok {
lang = "en-us"
}
emailLang := lang
if _, ok := app.storage.lang.Email[lang]; !ok {
emailLang = "en-us"
}
gc.HTML(200, "setup2.html", gin.H{
"lang": app.storage.lang.Setup[lang],
"emailLang": app.storage.lang.Email[emailLang],
})
}
type testReq struct {
Host string `json:"jfHost"`
Username string `json:"jfUser"`
@ -24,3 +46,55 @@ func (app *appContext) TestJF(gc *gin.Context) {
}
gc.JSON(200, map[string]bool{"success": true})
}
func (st *Storage) loadLangSetup() error {
st.lang.Setup = map[string]setupLang{}
var english setupLang
load := func(fname string) error {
index := strings.TrimSuffix(fname, filepath.Ext(fname))
lang := setupLang{}
f, err := ioutil.ReadFile(filepath.Join(st.lang.SetupPath, fname))
if err != nil {
return err
}
err = json.Unmarshal(f, &lang)
if err != nil {
return err
}
st.lang.Common.patchCommon(index, &lang.Strings)
if fname != "en-us.json" {
patchLang(&english.Strings, &lang.Strings)
patchLang(&english.StartPage, &lang.StartPage)
patchLang(&english.EndPage, &lang.EndPage)
patchLang(&english.Language, &lang.Language)
patchLang(&english.Login, &lang.Login)
patchLang(&english.JellyfinEmby, &lang.JellyfinEmby)
patchLang(&english.Email, &lang.Email)
patchLang(&english.Notifications, &lang.Notifications)
patchLang(&english.PasswordResets, &lang.PasswordResets)
patchLang(&english.InviteEmails, &lang.InviteEmails)
patchLang(&english.PasswordValidation, &lang.PasswordValidation)
patchLang(&english.HelpMessages, &lang.HelpMessages)
}
st.lang.Setup[index] = lang
return nil
}
err := load("en-us.json")
if err != nil {
return err
}
english = st.lang.Setup["en-us"]
files, err := ioutil.ReadDir(st.lang.SetupPath)
if err != nil {
return err
}
for _, f := range files {
if f.Name() != "en-us.json" {
err = load(f.Name())
if err != nil {
return err
}
}
}
return nil
}

View File

@ -55,9 +55,17 @@ type Lang struct {
Form formLangs
EmailPath string
Email emailLangs
CommonPath string
Common commonLangs
SetupPath string
Setup setupLangs
}
func (st *Storage) loadLang() (err error) {
err = st.loadLangCommon()
if err != nil {
return
}
err = st.loadLangAdmin()
if err != nil {
return
@ -70,6 +78,20 @@ func (st *Storage) loadLang() (err error) {
return
}
func (common *commonLangs) patchCommon(lang string, other *langSection) {
if *other == nil {
*other = langSection{}
}
if _, ok := (*common)[lang]; !ok {
lang = "en-us"
}
for n, ev := range (*common)[lang].Strings {
if v, ok := (*other)[n]; !ok || v == "" {
(*other)[n] = ev
}
}
}
// If a given language has missing values, fill it in with the english value.
func patchLang(english, other *langSection) {
if *other == nil {
@ -97,6 +119,49 @@ func patchQuantityStrings(english, other *map[string]quantityString) {
}
}
func (st *Storage) loadLangCommon() error {
st.lang.Common = map[string]commonLang{}
var english commonLang
load := func(fname string) error {
index := strings.TrimSuffix(fname, filepath.Ext(fname))
lang := commonLang{}
f, err := ioutil.ReadFile(filepath.Join(st.lang.CommonPath, fname))
if err != nil {
return err
}
if substituteStrings != "" {
f = []byte(strings.ReplaceAll(string(f), "Jellyfin", substituteStrings))
}
err = json.Unmarshal(f, &lang)
if err != nil {
return err
}
if fname != "en-us.json" {
patchLang(&english.Strings, &lang.Strings)
}
st.lang.Common[index] = lang
return nil
}
err := load("en-us.json")
if err != nil {
return err
}
english = st.lang.Common["en-us"]
files, err := ioutil.ReadDir(st.lang.CommonPath)
if err != nil {
return err
}
for _, f := range files {
if f.Name() != "en-us.json" {
err = load(f.Name())
if err != nil {
return err
}
}
}
return nil
}
func (st *Storage) loadLangAdmin() error {
st.lang.Admin = map[string]adminLang{}
var english adminLang
@ -114,6 +179,7 @@ func (st *Storage) loadLangAdmin() error {
if err != nil {
return err
}
st.lang.Common.patchCommon(index, &lang.Strings)
if fname != "en-us.json" {
patchLang(&english.Strings, &lang.Strings)
patchLang(&english.Notifications, &lang.Notifications)
@ -164,6 +230,7 @@ func (st *Storage) loadLangForm() error {
if err != nil {
return err
}
st.lang.Common.patchCommon(index, &lang.Strings)
if fname != "en-us.json" {
patchLang(&english.Strings, &lang.Strings)
patchQuantityStrings(&english.ValidationStrings, &lang.ValidationStrings)