i18n: Add more translatable fields (#1358)

This commit is contained in:
leguan 2024-05-25 10:28:04 +02:00 committed by GitHub
parent 5addf88f9b
commit d282755ba9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 140 additions and 60 deletions

View File

@ -41,18 +41,18 @@ if (authStore.user) {
type NavBarLinks = { link: string; label: string; icon?: any }[];
const navBarLinks: NavBarLinks = [
{ link: "index", label: "Home" },
{ link: "authors", label: "Authors" },
{ link: "staff", label: "Team" },
{ link: "index", label: t("nav.indexTitle") },
{ link: "authors", label: t("nav.authorsTitle") },
{ link: "staff", label: t("nav.staffTitle") },
];
const navBarMenuLinksHangar: NavBarLinks = [
{ link: "index", label: "Home", icon: IconMdiHome },
{ link: "guidelines", label: "Resource Guidelines", icon: IconMdiFileDocumentAlert },
{ link: "new", label: "Create Project", icon: IconMdiFolderPlusOutline },
{ link: "neworganization", label: "Create Organization", icon: IconMdiFolderPlusOutline },
{ link: "authors", label: "Authors", icon: IconMdiAccountGroup },
{ link: "staff", label: "Team", icon: IconMdiAccountGroup },
{ link: "index", label: t("general.home"), icon: IconMdiHome },
{ link: "guidelines", label: t("guidelines.title"), icon: IconMdiFileDocumentAlert },
{ link: "new", label: t("nav.links.createProject"), icon: IconMdiFolderPlusOutline },
{ link: "neworganization", label: t("nav.links.createOrganization"), icon: IconMdiFolderPlusOutline },
{ link: "authors", label: t("nav.authorsTitle"), icon: IconMdiAccountGroup },
{ link: "staff", label: t("nav.staffTitle"), icon: IconMdiAccountGroup },
];
if (!authStore.user) {
navBarMenuLinksHangar.splice(2, 2);

View File

@ -25,9 +25,9 @@ defineProps<{
<div class="overflow-clip overflow-hidden min-w-0">
<div class="inline-flex items-center gap-x-1">
<h2>
<span class="text-xl font-bold">{{ project.name }}</span>
<span class="text-xl font-bold">{{ project.name }}&nbsp;</span>
<span class="text-sm"> {{ i18n.t("general.by") }}&nbsp;</span>
<span class="text-sm">
by
<object type="html/sucks">
<Link v-slot="{ classes }" custom>
<RouterLink :to="'/' + project.namespace.owner" :class="classes"> {{ project.namespace.owner }} </RouterLink>

View File

@ -6,6 +6,7 @@
"icon": "US"
},
"general": {
"by": "by",
"close": "Close",
"submit": "Submit",
"save": "Save",
@ -31,6 +32,8 @@
"today": "Today",
"yesterday": "Yesterday",
"tomorrow": "Tomorrow",
"used": "Used",
"reveal": "Reveal",
"error": {
"invalidUrl": "Invalid URL format",
"nameEmpty": "Name cannot be empty",
@ -83,6 +86,9 @@
}
},
"nav": {
"indexTitle": "Home",
"authorsTitle": "Authors",
"staffTitle": "Staff",
"login": "Login",
"signup": "Signup",
"user": {
@ -133,6 +139,10 @@
"javadocs": "JavaDocs",
"downloads": "Downloads",
"community": "Community"
},
"links": {
"createProject": "Create Project",
"createOrganization": "Create Organization"
}
},
"guidelines": {
@ -933,20 +943,71 @@
"auth": {
"settings": {
"profile": {
"header": "Profile"
"header": "Profile",
"avatar": "Avatar",
"tagline": "Tagline",
"social": "Social"
},
"account": {
"header": "Account"
"header": "Account",
"username": "Username",
"verifyEmail": "Verify email",
"currentPassword": "Current Password",
"newPassword": "New Password (Optional)"
},
"security": {
"header": "Security"
"header": "Security",
"authApp": "Authenticator App",
"devices": "Devices",
"button": {
"setupAuthApp": "Setup 2FA via authenticator app",
"setupSecurityKey": "Setup 2FA via security key",
"linkGithub": "Link a GitHub account",
"linkOther": "Link {0} account",
"unlinkAccount": "Unlink {0} account {1}"
},
"authAppSetup": {
"scan": "Scan the QR code on the right using your favorite authenticator app",
"cantScan": "Can't scan? Enter the secret listed below the image!",
"enterTotp": "Enter a TOTP code generated by your authenticator app in the box below.",
"verifyTotp": "Verify TOTP code and activate"
},
"securityKeys": {
"name": "Security Keys",
"keyName": "Name",
"unregister": "Unregister",
"rename": "Rename"
},
"backupCodes": {
"name": "Backup Codes",
"generateNew": "Generate new codes",
"modal": {
"title": "Confirm backup codes",
"needConfigure": "You need to configure backup codes before you can activate 2fa. Please save these codes securely!",
"confirm": "Confirm that you saved the backup codes by entering one of them below",
"backupCode": "Backup Code"
}
},
"unlinkOAuth": {
"cantUnlink": "You can't unlink your last oauth credential if you don't have a password set",
"modal": {
"title": "Successfully unlinked!",
"message": "Successfully unlinked your {0} account!",
"unlinkUrl": "Click here to remove the Hangar app from your {0} account"
}
}
},
"apiKeys": {
"header": "API keys"
},
"misc": {
"header": "Other",
"accentColor": "Accent Color"
"accentColor": "Accent Color",
"language": "Language",
"alert": {
"colorAlert": "The accent colors are mostly untested and full of contrast issues, proceed with caution!",
"languageAlert": "Translations are experimental!"
}
}
}
},

View File

@ -44,16 +44,16 @@ async function saveAccount() {
<div v-if="auth.user">
<PageTitle>{{ t("auth.settings.account.header") }}</PageTitle>
<form class="flex flex-col gap-2">
<InputText v-model="accountForm.username" label="Username" :rules="[required()]" />
<InputText v-model="accountForm.username" :label="t('auth.settings.account.username')" :rules="[required()]" />
<span class="text-sm opacity-85 -mt-1.5">Note that you can only change your username once every 30 days.</span>
<InputText v-model="accountForm.email" label="Email" autofill="username" autocomplete="username" :rules="[required(), email()]" />
<Button v-if="!settings?.emailConfirmed" class="w-max" size="small" :disabled="loading" @click.prevent="$emit('openEmailConfirmModal')">
Verify email
{{ t("auth.settings.account.verifyEmail") }}
</Button>
<template v-if="settings?.hasPassword">
<InputPassword
v-model="accountForm.currentPassword"
label="Current password"
:label="t('auth.settings.account.currentPassword')"
name="current-password"
autofill="current-password"
autocomplete="current-password"
@ -61,14 +61,14 @@ async function saveAccount() {
/>
<InputPassword
v-model="accountForm.newPassword"
label="New password (optional)"
:label="t('auth.settings.account.newPassword')"
name="new-password"
autofill="new-password"
autocomplete="new-password"
/>
</template>
<div v-if="error" class="text-red">{{ error }}</div>
<Button type="submit" class="w-max" :disabled="loading" @click.prevent="saveAccount">Save</Button>
<Button type="submit" class="w-max" :disabled="loading" @click.prevent="saveAccount">{{ t("general.save") }}</Button>
</form>
</div>
</template>

View File

@ -38,10 +38,10 @@ watch(locale, async (newLocale) => {
<template>
<div>
<PageTitle>{{ i18n.t("auth.settings.misc.header") }}</PageTitle>
<Alert type="warning" class="mb-4">The accent colors are mostly untested and full of contrast issues, proceed with caution!</Alert>
<Alert type="warning" class="mb-4">{{ i18n.t("auth.settings.misc.alert.colorAlert") }}</Alert>
<InputSelect v-model="accentColor" :values="accentColors" :label="i18n.t('auth.settings.misc.accentColor')" />
<Alert type="warning" class="my-4">Translations are experimental!</Alert>
<Alert type="warning" class="my-4">{{ i18n.t("auth.settings.misc.alert.languageAlert") }}</Alert>
<InputSelect v-model="locale" :values="languages" :label="i18n.t('auth.settings.misc.language')" />
</div>
</template>

View File

@ -51,7 +51,7 @@ async function saveProfile() {
<div v-if="auth.user">
<PageTitle>{{ t("auth.settings.profile.header") }}</PageTitle>
<h3 class="text-lg font-bold mb-2">Avatar</h3>
<h3 class="text-lg font-bold mb-2">{{ t("auth.settings.profile.avatar") }}</h3>
<div class="relative">
<UserAvatar :username="auth.user.name" :avatar-url="auth.user.avatarUrl" />
<AvatarChangeModal :avatar="auth.user.avatarUrl" :action="`users/${auth.user.name}/settings/avatar`">
@ -61,15 +61,15 @@ async function saveProfile() {
</AvatarChangeModal>
</div>
<h3 class="text-lg font-bold mt-4 mb-2">Tagline</h3>
<InputText v-model="profileForm.tagline" label="Tagline" counter :maxlength="useBackendData.validations.userTagline.max" />
<h3 class="text-lg font-bold mt-4 mb-2">{{ t("auth.settings.profile.tagline") }}</h3>
<InputText v-model="profileForm.tagline" :label="t('auth.settings.profile.avatar')" counter :maxlength="useBackendData.validations.userTagline.max" />
<h3 class="text-lg font-bold mt-4">Social</h3>
<h3 class="text-lg font-bold mt-4">{{ t("auth.settings.profile.social") }}</h3>
<div v-for="(link, idx) in profileForm.socials" :key="link[0]" class="flex items-center mt-2">
<span class="w-25">{{ linkTypes.find((e) => e.value === link[0])?.text }}</span>
<div class="w-75">
<InputText v-if="link[0] === 'website'" v-model="link[1]" label="URL" :rules="[required(), validUrl()]" />
<InputText v-else v-model="link[1]" label="Username" :rules="[required()]" />
<InputText v-else v-model="link[1]" :label="t('auth.settings.account.username')" :rules="[required()]" />
</div>
<IconMdiBin class="ml-2 w-6 h-6 cursor-pointer hover:color-red" @click="removeLink(idx)" />
</div>
@ -78,10 +78,10 @@ async function saveProfile() {
<Button button-type="secondary" @click.prevent="addLink">Add link</Button>
</div>
<div class="w-75">
<InputSelect v-model="linkType" :values="linkTypes" label="Type" />
<InputSelect v-model="linkType" :values="linkTypes" :label="t('project.settings.links.typeField')" />
</div>
</div>
<Button type="submit" class="w-max mt-2" :disabled="loading" @click.prevent="saveProfile">Save</Button>
<Button type="submit" class="w-max mt-2" :disabled="loading" @click.prevent="saveProfile">{{ t("general.save") }}</Button>
</div>
</template>

View File

@ -266,17 +266,17 @@ function closeUnlinkModal() {
<template>
<div v-if="auth.user">
<PageTitle>{{ t("auth.settings.security.header") }}</PageTitle>
<h3 class="text-lg font-bold mb-2">Authenticator App</h3>
<h3 class="text-lg font-bold mb-2">{{ t("auth.settings.security.authApp") }}</h3>
<Button v-if="settings?.hasTotp" :disabled="loading" @click="unlinkTotp">Unlink totp</Button>
<Button v-else-if="!totpData" :disabled="loading" @click="setupTotp">Setup 2FA via authenticator app</Button>
<Button v-else-if="!totpData" :disabled="loading" @click="setupTotp"> {{ t("auth.settings.security.button.setupAuthApp") }} </Button>
<div v-else class="flex lt-sm:flex-col gap-8">
<div class="flex flex-col gap-2 basis-1/2">
<p>Scan the QR code on the right using your favorite authenticator app</p>
<p>Can't scan? Enter the secret listed below the image!</p>
<p>{{ t("auth.settings.security.authAppSetup.scan") }}</p>
<p>{{ t("auth.settings.security.authAppSetup.cantScan") }}</p>
<div class="mt-auto flex flex-col gap-2">
<p>Enter a TOTP code generated by your authenticator app in the box below.</p>
<p>{{ t("auth.settings.security.authAppSetup.enterTotp") }}</p>
<InputText v-model="totpCode" label="TOTP Code" inputmode="numeric" :rules="[requiredIf()(() => totpData != undefined)]" />
<Button :disabled="loading || v.$invalid" @click="addTotp">Verify TOTP code and activate</Button>
<Button :disabled="loading || v.$invalid" @click="addTotp"> {{ t("auth.settings.security.authAppSetup.verifyTotp") }} </Button>
</div>
</div>
<div class="basis-1/2">
@ -285,12 +285,16 @@ function closeUnlinkModal() {
</div>
</div>
<h3 class="text-lg font-bold mt-4 mb-2">Security Keys</h3>
<h3 class="text-lg font-bold mt-4 mb-2">{{ t("auth.settings.security.securityKeys.name") }}</h3>
<ul v-if="settings?.authenticators">
<li v-for="authenticator in settings.authenticators" :key="authenticator.id" class="my-1">
{{ authenticator.displayName }} <small class="mr-2">(added at <PrettyTime :time="authenticator.addedAt" long />)</small>
<Button size="small" :disabled="loading" @click.prevent="unregisterAuthenticator(authenticator)">Unregister</Button>
<Button class="ml-2" size="small" :disabled="loading" @click.prevent="renameAuthenticatorModal(authenticator)">Rename</Button>
<Button size="small" :disabled="loading" @click.prevent="unregisterAuthenticator(authenticator)">
{{ t("auth.settings.security.securityKeys.unregister") }}
</Button>
<Button class="ml-2" size="small" :disabled="loading" @click.prevent="renameAuthenticatorModal(authenticator)">
{{ t("auth.settings.security.securityKeys.rename") }}
</Button>
</li>
<Modal
ref="authenticatorRenameModal"
@ -300,25 +304,35 @@ function closeUnlinkModal() {
v.$reset();
"
>
<InputText v-model="newAuthenticatorName" label="Name" :rules="[requiredIf()(() => authenticatorRenameModal?.isOpen)]" />
<Button class="mt-2" size="small" :disabled="loading" @click.prevent="renameAuthenticator">Rename</Button>
<InputText
v-model="newAuthenticatorName"
:label="t('auth.settings.security.securityKeys.keyName')"
:rules="[requiredIf()(() => authenticatorRenameModal?.isOpen)]"
/>
<Button class="mt-2" size="small" :disabled="loading" @click.prevent="renameAuthenticator">
{{ t("auth.settings.security.securityKeys.rename") }}
</Button>
</Modal>
</ul>
<div class="my-2">
<InputText v-model="authenticatorName" label="Name" :rules="[requiredIf()(() => totpData == undefined && !authenticatorRenameModal?.isOpen)]" />
<InputText
v-model="authenticatorName"
:label="t('auth.settings.security.securityKeys.keyName')"
:rules="[requiredIf()(() => totpData == undefined && !authenticatorRenameModal?.isOpen)]"
/>
</div>
<Button :disabled="loading" @click="addAuthenticator">Setup 2FA via security key</Button>
<Button :disabled="loading" @click="addAuthenticator"> {{ t("auth.settings.security.button.setupSecurityKey") }} </Button>
<template v-if="settings?.hasBackupCodes">
<h3 class="text-lg font-bold mt-4 mb-2">Backup Codes</h3>
<h3 class="text-lg font-bold mt-4 mb-2">{{ t("auth.settings.security.backupCodes.name") }}</h3>
<div v-if="showCodes" class="flex flex-wrap mt-2 mb-2">
<div v-for="code in codes" :key="code.code" class="basis-3/12">
<code>{{ code["used_at"] ? "Used" : code.code }}</code>
<code>{{ code["used_at"] ? t("general.used") : code.code }}</code>
</div>
</div>
<div class="flex gap-2">
<Button v-if="!showCodes" :disabled="loading" @click="revealCodes">Reveal</Button>
<Button :disabled="loading" @click="generateNewCodes">Generate new codes</Button>
<Button v-if="!showCodes" :disabled="loading" @click="revealCodes"> {{ t("general.reveal") }} </Button>
<Button :disabled="loading" @click="generateNewCodes"> {{ t("auth.settings.security.backupCodes.generateNew") }} </Button>
</div>
</template>
@ -327,9 +341,9 @@ function closeUnlinkModal() {
<Button v-for="provider in backendData.security.oauthProviders" :key="provider" :disabled="loading" @click="setupOAuth(provider)">
<template v-if="provider === 'github'">
<IconMdiGithub class="mr-1" />
Link a GitHub account
{{ t("auth.settings.security.button.linkGithub") }}
</template>
<template v-else> Link {{ provider }} account</template>
<template v-else> {{ t("auth.settings.security.button.linkOther", [provider]) }} </template>
</Button>
</div>
<div class="flex gap-2 mt-2">
@ -339,37 +353,42 @@ function closeUnlinkModal() {
:disabled="!settings?.hasPassword && settings?.oauthConnections.length === 1"
:title="
!settings?.hasPassword && settings?.oauthConnections.length === 1
? 'You can\'t unlink your last oauth credential if you don\'t have a password set'
? // ? t('auth.settings.security.unlinkOAuth.cantUnlink') Doesn't work
'You can\'t unlink your last oauth credential if you don\'t have a password set'
: undefined
"
@click="unlinkOAuth(credential.provider, credential.id)"
>
<template v-if="credential.provider === 'github'">
<IconMdiGithub class="mr-1" />
Unlink GitHub account {{ credential.name }}
{{ t("auth.settings.security.button.unlinkAccount", ["GitHub", credential.name]) }}
</template>
<template v-else> Unlink {{ credential.provider }} account {{ credential.name }}</template>
<template v-else> {{ t("auth.settings.security.button.unlinkAccount", [credential.provider, credential.name]) }} </template>
</Button>
</div>
<Modal ref="oauthModal" title="Successfully unlinked!" @close="closeUnlinkModal">
<p>Successfully unlinked your {{ currentlyUnlinkingProvider }} account!</p>
<Link :href="unlinkUrl" target="_blank">Click here to remove the Hangar app from your {{ currentlyUnlinkingProvider }} account</Link>
<Modal ref="oauthModal" :title="t('auth.settings.security.unlinkOAuth.modal.title')" @close="closeUnlinkModal">
<p>{{ t("auth.settings.security.unlinkOAuth.modal.message", [currentlyUnlinkingProvider]) }}</p>
<Link :href="unlinkUrl" target="_blank"> {{ t("auth.settings.security.unlinkOAuth.modal.unlinkUrl", [currentlyUnlinkingProvider]) }} </Link>
</Modal>
<Modal ref="backupCodeModal" title="Confirm backup codes" @close="backupCodeModal.isOpen = false">
You need to configure backup codes before you can activate 2fa. Please save these codes securely!
<Modal ref="backupCodeModal" :title="t('auth.settings.security.backupCodes.modal.title')" @close="backupCodeModal.isOpen = false">
{{ t("auth.settings.security.backupCodes.modal.needConfigure") }}
<div class="flex flex-wrap mt-2 mb-2">
<div v-for="code in codes" :key="code.code" class="basis-3/12">
<code>{{ code.code }}</code>
</div>
</div>
Confirm that you saved the backup codes by entering one of them below
<InputText v-model="backupCodeConfirm" label="Backup Code" :rules="[requiredIf()(backupCodeModal?.isOpen)]" />
<Button class="mt-2" :disabled="v.$invalid" @click="confirmAndRepeat">Confirm</Button>
{{ t("auth.settings.security.backupCodes.modal.confirm") }}
<InputText
v-model="backupCodeConfirm"
:label="t('auth.settings.security.backupCodes.modal.backupCode')"
:rules="[requiredIf()(backupCodeModal?.isOpen)]"
/>
<Button class="mt-2" :disabled="v.$invalid" @click="confirmAndRepeat">{{ t("general.confirm") }}</Button>
</Modal>
<h3 class="text-lg font-bold mt-4 mb-2">Devices</h3>
<h3 class="text-lg font-bold mt-4 mb-2">{{ t("auth.settings.security.devices") }}</h3>
<ComingSoon>
last login<br />
on revoke iphone<br />