diff --git a/.env.example b/.env.example index bae34f6..db21550 100644 --- a/.env.example +++ b/.env.example @@ -1,8 +1,5 @@ DATABASE_URL="postgres://drop:drop@127.0.0.1:5432/drop" -CLIENT_CERTIFICATES="./.data/ca" - -FS_BACKEND_PATH="./.data/objects" - GIANT_BOMB_API_KEY="" +EXTERNAL_URL="localhost:3000" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1ae20e5..5520209 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,6 +1,12 @@ name: CI -on: [pull_request, push] +on: + push: + branches: + - develop + pull_request: + branches: + - develop jobs: typecheck: diff --git a/.vscode/settings.json b/.vscode/settings.json index a7c41eb..76d084c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -31,6 +31,7 @@ ], "i18n-ally.extract.ignoredByFiles": { "pages/admin/library/sources/index.vue": ["Filesystem"], - "components/NewsArticleCreateButton.vue": ["[", "`", "Enter"] + "components/NewsArticleCreateButton.vue": ["[", "`", "Enter"], + "server/api/v1/auth/signin/simple.post.ts": ["boolean | undefined"] } } diff --git a/components/EmojiText.vue b/components/EmojiText.vue index ee90157..694f68a 100644 --- a/components/EmojiText.vue +++ b/components/EmojiText.vue @@ -9,9 +9,7 @@ const props = defineProps<{ emoji: string; }>(); -const emojiEl = ref(null); - const url = computed(() => { - return `/api/v1/emojis/${twemoji.convert.toCodePoint(props.emoji)}.svg`; + return `/twemoji/${twemoji.convert.toCodePoint(props.emoji)}.svg`; }); diff --git a/components/LanguageSelector.vue b/components/LanguageSelector.vue index 106439b..523b8e4 100644 --- a/components/LanguageSelector.vue +++ b/components/LanguageSelector.vue @@ -9,9 +9,10 @@ class="grid w-full cursor-default grid-cols-1 rounded-md bg-zinc-900 py-1.5 pr-2 pl-3 text-left text-zinc-300 outline-1 -outline-offset-1 outline-zinc-700 focus:outline-2 focus:-outline-offset-2 focus:outline-blue-600 sm:text-sm/6" > - {{ - localeToEmoji(wiredLocale) - }} + {{ currentLocaleInformation?.name ?? wiredLocale }} @@ -46,9 +47,10 @@ ]" >
- - {{ localeToEmoji(listLocale.code) }} - + l.code === locale)?.dir || "ltr", + }, + }); +} function localeToEmoji(local: string): string { switch (local) { + // Default locale case "en": - case "en-gb": - case "en-ca": - case "en-au": - case "en-us": { + case "en-us": return "🇺🇸"; - } - case "en-pirate": { + + case "en-gb": + return "🇬🇧"; + case "en-ca": + return "🇨🇦"; + case "en-au": + return "🇦🇺"; + case "en-pirate": return "🏴‍☠️"; - } + case "fr": + return "🇫🇷"; + case "de": + return "🇩🇪"; + case "es": + return "🇪🇸"; + case "it": + return "🇮🇹"; + case "zh": + return "🇨🇳"; + case "zh-tw": + return "🇹🇼"; default: { return "❓"; @@ -130,10 +161,10 @@ function localeToEmoji(local: string): string { const wiredLocale = computed({ get() { - return locale.value; + return currLocale.value; }, set(v) { - setLocale(v); + changeLocale(v); }, }); const currentLocaleInformation = computed(() => diff --git a/components/UserFooter.vue b/components/UserFooter.vue index 23a2ef3..b62cb77 100644 --- a/components/UserFooter.vue +++ b/components/UserFooter.vue @@ -2,6 +2,7 @@
+
@@ -24,6 +25,8 @@
+ +
@@ -86,6 +89,21 @@
+ +
+

+ + + + +

+
@@ -96,6 +114,8 @@ import { IconsDiscordLogo, IconsGithubLogo } from "#components"; const { t } = useI18n(); +const versionInfo = await $dropFetch("/api/v1"); + const navigation = { games: [ { name: t("store.recentlyAdded"), href: "#" }, diff --git a/components/UserHeader/UserWidget.vue b/components/UserHeader/UserWidget.vue index 59d2daf..35e238d 100644 --- a/components/UserHeader/UserWidget.vue +++ b/components/UserHeader/UserWidget.vue @@ -85,20 +85,21 @@ import { useObject } from "~/composables/objects"; import type { NavigationItem } from "~/composables/types"; const user = useUser(); -const { t } = useI18n(); -const navigation: NavigationItem[] = [ - user.value?.admin - ? { - label: t("userHeader.profile.admin"), - route: "/admin", - prefix: "", - } - : undefined, - { - label: t("userHeader.profile.settings"), - route: "/account", - prefix: "", - }, -].filter((e) => e !== undefined); +const navigation = computed(() => + [ + user.value?.admin + ? { + label: $t("userHeader.profile.admin"), + route: "/admin", + prefix: "", + } + : undefined, + { + label: $t("userHeader.profile.settings"), + route: "/account", + prefix: "", + }, + ].filter((e) => e !== undefined), +); diff --git a/i18n/i18n.config.ts b/i18n/i18n.config.ts index a960cfd..f702086 100644 --- a/i18n/i18n.config.ts +++ b/i18n/i18n.config.ts @@ -21,7 +21,15 @@ export default defineI18nConfig(() => { // https://vue-i18n.intlify.dev/guide/essentials/datetime.html datetimeFormats: { "en-us": defaultDateTimeFormat, + "en-gb": defaultDateTimeFormat, + "en-au": defaultDateTimeFormat, "en-pirate": defaultDateTimeFormat, + fr: defaultDateTimeFormat, + de: defaultDateTimeFormat, + it: defaultDateTimeFormat, + es: defaultDateTimeFormat, + zh: defaultDateTimeFormat, + "zh-tw": defaultDateTimeFormat, }, }; }); diff --git a/i18n/locales/de.json b/i18n/locales/de.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/i18n/locales/de.json @@ -0,0 +1 @@ +{} diff --git a/i18n/locales/en_au.json b/i18n/locales/en_au.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/i18n/locales/en_au.json @@ -0,0 +1 @@ +{} diff --git a/i18n/locales/en_gb.json b/i18n/locales/en_gb.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/i18n/locales/en_gb.json @@ -0,0 +1 @@ +{} diff --git a/i18n/locales/en_us.json b/i18n/locales/en_us.json index dbdfd55..a9bfdf6 100644 --- a/i18n/locales/en_us.json +++ b/i18n/locales/en_us.json @@ -107,6 +107,17 @@ "listItemPlaceholder": "list item" }, "errors": { + "auth": { + "method": { + "signinDisabled": "Sign in method not enabled" + }, + "invalidUserOrPass": "Invalid username or password.", + "disabled": "Invalid or disabled account. Please contact the server administrator.", + "invalidPassState": "Invalid password state. Please contact the server administrator.", + "inviteIdRequired": "id required in fetching invitation", + "invalidInvite": "Invalid or expired invitation", + "usernameTaken": "Username already taken." + }, "backHome": "{arrow} Back to home", "invalidBody": "Invalid request body: {0}", "inviteRequired": "Invitation required to sign up.", @@ -201,7 +212,8 @@ "discord": "Discord", "github": "GitHub" }, - "topSellers": "Top Sellers" + "topSellers": "Top Sellers", + "version": "Drop {version} {gitRef}" }, "header": { "admin": { @@ -218,17 +230,14 @@ "admin": { "description": "Manage the users on your Drop instance, and configure your authentication methods.", "authLink": "Authentication {arrow}", - "displayNameHeader": "Display Name", "usernameHeader": "Username", "emailHeader": "Email", "adminHeader": "Admin?", "authoptionsHeader": "Auth Options", "srEditLabel": "Edit", - "adminUserLabel": "Admin user", "normalUserLabel": "Normal user", - "authentication": { "title": "Authentication", "description": "Drop supports a variety of \"authentication mechanisms\". As you enable or disable them, they are shown on the sign in screen for users to select from. Click the dot menu to configure the authentication mechanism.", @@ -237,47 +246,33 @@ "disabled": "Disabled", "srOpenOptions": "Open options", "configure": "Configure", - "simple": "Simple (username/password)", "oidc": "OpenID Connect" }, - "simple": { "title": "Simple authentication", "description": "Simple authentication uses a system of 'invitations' to create users. You can create an invitation, and optionally specify a username or email for the user, and then it will generate a magic URL that can be used to create an account.", - "invitationTitle": "invitations", "createInvitation": "Create invitation", - "noUsernameEnforced": "No username enforced.", "noEmailEnforced": "No email enforced.", - "adminInvitation": "Admin invitation", "userInvitation": "User invitation", - "expires": "Expires: {expiry}", "neverExpires": "Never expires.", - "noInvitations": "No invitations.", - "inviteTitle": "Invite user to Drop", "inviteDescription": "Drop will generate a URL that you can send to the person you want to invite. You can optionally specify a username or email for them to use.", - "inviteUsernameLabel": "Username (optional)", "inviteUsernameFormat": "Must be 5 or more characters", "inviteUsernamePlaceholder": "myUsername", - "inviteEmailLabel": "Email address (optional)", "inviteEmailDescription": "Must be in the format user{'@'}example.com", "inviteEmailPlaceholder": "me{'@'}example.com", - "inviteAdminSwitchLabel": "Admin invitation", "inviteAdminSwitchDescription": "Create this user as an administrator", - "inviteExpiryLabel": "Expires", - "inviteButton": "Invite", - "invite3Days": "3 days", "inviteWeek": "1 week", "inviteMonth": "1 month", @@ -347,18 +342,14 @@ "imageCarouselEmpty": "No images added to the carousel yet.", "removeImageCarousel": "Remove image", "addCarouselNoImages": "No images to add.", - "imageLibrary": "Image library", "imageLibraryDescription": "Please note all images uploaded are accessible to all users through browser dev-tools.", "setBanner": "Set as banner", "setCover": "Set as cover", "deleteImage": "Delete image", - "currentBanner": "banner", "currentCover": "cover", - "addDescriptionNoImages": "No images to add.", - "editGameName": "Game Name", "editGameDescription": "Game Description" }, @@ -407,23 +398,19 @@ "scheduled": { "cleanupInvitationsName": "Clean up invitations", "cleanupInvitationsDescription": "Cleans up expired invitations from the database to save space.", - "cleanupObjectsName": "Clean up objects", "cleanupObjectsDescription": "Detects and deletes unreferenced and unused objects to save space.", - "cleanupSessionsName": "Clean up sessions.", "cleanupSessionsDescription": "Cleans up expired sessions to save space and ensure security.", - "checkUpdateName": "Check update.", "checkUpdateDescription": "Check if Drop has an update." }, - "runningTasksTitle": "Running tasks", "noTasksRunning": "No tasks currently running", "completedTasksTitle": "Completed tasks", "dailyScheduledTitle": "Daily scheduled tasks", + "weeklyScheduledTitle": "Weekly scheduled tasks", "viewTask": "View {arrow}", - "back": "{arrow} Back to Tasks" } }, diff --git a/i18n/locales/es.json b/i18n/locales/es.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/i18n/locales/es.json @@ -0,0 +1 @@ +{} diff --git a/i18n/locales/fr.json b/i18n/locales/fr.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/i18n/locales/fr.json @@ -0,0 +1 @@ +{} diff --git a/i18n/locales/it.json b/i18n/locales/it.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/i18n/locales/it.json @@ -0,0 +1 @@ +{} diff --git a/i18n/locales/zh.json b/i18n/locales/zh.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/i18n/locales/zh.json @@ -0,0 +1 @@ +{} diff --git a/i18n/locales/zh_tw.json b/i18n/locales/zh_tw.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/i18n/locales/zh_tw.json @@ -0,0 +1 @@ +{} diff --git a/layouts/admin.vue b/layouts/admin.vue index 04bb205..6dc3f37 100644 --- a/layouts/admin.vue +++ b/layouts/admin.vue @@ -170,36 +170,36 @@ import { useCurrentNavigationIndex } from "~/composables/current-page-engine"; import { ArrowLeftIcon } from "@heroicons/vue/16/solid"; import { XMarkIcon } from "@heroicons/vue/24/solid"; -const { t } = useI18n(); +const i18nHead = useLocaleHead(); const navigation: Array = [ - { label: t("home"), route: "/admin", prefix: "/admin", icon: HomeIcon }, + { label: $t("home"), route: "/admin", prefix: "/admin", icon: HomeIcon }, { - label: t("userHeader.links.library"), + label: $t("userHeader.links.library"), route: "/admin/library", prefix: "/admin/library", icon: ServerStackIcon, }, { - label: t("header.admin.users"), + label: $t("header.admin.users"), route: "/admin/users", prefix: "/admin/users", icon: UserGroupIcon, }, { - label: t("header.admin.tasks"), + label: $t("header.admin.tasks"), route: "/admin/task", prefix: "/admin/task", icon: RectangleStackIcon, }, { - label: t("settings"), + label: $t("settings"), route: "/admin/settings", prefix: "/admin/settings", icon: Cog6ToothIcon, }, { - label: t("header.back"), + label: $t("header.back"), route: "/store", prefix: ".", icon: ArrowLeftIcon, @@ -221,11 +221,12 @@ router.afterEach(() => { useHead({ htmlAttrs: { - lang: "en", + lang: i18nHead.value.htmlAttrs.lang, + // @ts-expect-error head.value.htmlAttrs.dir is not typed as strictly as it should be + dir: i18nHead.value.htmlAttrs.dir, }, - link: [], titleTemplate(title) { - return title ? t("adminTitleTemplate", [title]) : t("adminTitle"); + return title ? $t("adminTitleTemplate", [title]) : $t("adminTitle"); }, }); diff --git a/layouts/default.vue b/layouts/default.vue index 40b9307..8bbfc19 100644 --- a/layouts/default.vue +++ b/layouts/default.vue @@ -13,15 +13,20 @@