diff --git a/.dockerignore b/.dockerignore index 4abae2bf..615017f5 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,9 @@ +Dockerfile +.github +.vscode +*.md + +#### gitignore below # Nuxt dev/build outputs .output .data @@ -8,6 +14,7 @@ dist # Node dependencies node_modules +.yarn # Logs logs @@ -24,3 +31,13 @@ logs !.env.example .data + + +# deploy template +deploy-template/* + +!deploy-template/compose.yml + +# generated prisma client +/prisma/client +/prisma/validate diff --git a/.env.example b/.env.example index bae34f6a..7d708ff5 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="http://localhost:3000" diff --git a/.gitattributes b/.gitattributes index af3ad128..e3f08fc5 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,3 +2,4 @@ /.yarn/releases/* binary /.yarn/plugins/**/* binary /.pnp.* binary linguist-generated +* text=auto eol=lf diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..f04182eb --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,55 @@ +name: CI + +on: + push: + branches: + - develop + pull_request: + branches: + - develop + +permissions: + contents: read + +jobs: + typecheck: + name: Typecheck + runs-on: ubuntu-latest + steps: + - name: Check out the repo + uses: actions/checkout@v4 + with: + submodules: true + + - name: Setup Node.js environment + uses: actions/setup-node@v4 + with: + node-version: lts/* + cache: "yarn" + + - name: Install dependencies + run: yarn install --immutable --network-timeout 1000000 + + - name: Typecheck + run: yarn typecheck + + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - name: Check out the repo + uses: actions/checkout@v4 + with: + submodules: true + + - name: Setup Node.js environment + uses: actions/setup-node@v4 + with: + node-version: lts/* + cache: "yarn" + + - name: Install dependencies + run: yarn install --immutable --network-timeout 1000000 + + - name: Lint + run: yarn lint diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..ed29fb50 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,95 @@ +name: Release Workflow + +on: + workflow_dispatch: {} + release: + types: [published] + # This can be used to automatically publish nightlies at UTC nighttime + schedule: + - cron: "0 2 * * *" # run at 2 AM UTC + +permissions: + contents: read + +jobs: + web: + name: Push website Docker image to registry + runs-on: ubuntu-latest + permissions: + packages: write + contents: read + steps: + - name: Check out the repo + uses: actions/checkout@v4 + with: + submodules: true + fetch-tags: true + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Determine final version + id: get_final_ver + run: | + BASE_VER=v$(jq -r '.version' package.json) + TODAY=$(date +'%Y.%m.%d') + + echo "Today will be: $TODAY" + echo "today=$TODAY" >> $GITHUB_OUTPUT + + if [[ "${{ github.event_name }}" == "release" ]]; then + FINAL_VER="$BASE_VER" + else + FINAL_VER="${BASE_VER}-nightly.$TODAY" + fi + + echo "Drop's release tag will be: $FINAL_VER" + echo "final_ver=$FINAL_VER" >> $GITHUB_OUTPUT + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v3 + with: + buildkitd-flags: --debug + + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: | + ghcr.io/drop-OSS/drop + tags: | + type=schedule,pattern=nightly + type=schedule,pattern=nightly.${{ steps.get_final_ver.outputs.today }} + type=semver,pattern=v{{version}} + type=semver,pattern=v{{major}}.{{minor}} + type=semver,pattern=v{{major}} + type=ref,event=branch,prefix=branch- + type=ref,event=pr + type=sha + # set latest tag for stable releases + type=raw,value=latest,enable=${{ github.event_name == 'release' && github.event.release.prerelease == false }} + + - name: Build and push image + id: build-and-push + uses: docker/build-push-action@v6 + with: + context: . + push: true + provenance: mode=max + sbom: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + platforms: linux/amd64,linux/arm64 + cache-from: type=gha + cache-to: type=gha,mode=max + build-args: | + BUILD_DROP_VERSION=${{ steps.get_final_ver.outputs.final_ver }} diff --git a/.gitignore b/.gitignore index 915b82c5..7188be16 100644 --- a/.gitignore +++ b/.gitignore @@ -30,4 +30,8 @@ logs # deploy template deploy-template/* -!deploy-template/compose.yml \ No newline at end of file +!deploy-template/compose.yml + +# generated prisma client +/prisma/client +/prisma/validate \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 37893f59..de224957 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -29,3 +29,26 @@ build: docker image tag $IMAGE_NAME $PUBLISH_LATEST_IMAGE_NAME docker push $PUBLISH_IMAGE_NAME $PUBLISH_LATEST_IMAGE_NAME fi + +build-arm64: + stage: build + image: arm64v8/docker:latest + tags: + - aarch64 + variables: + IMAGE_NAME: $CI_REGISTRY_IMAGE/$CI_COMMIT_REF_NAME:$CI_COMMIT_SHORT_SHA-arm64 + LATEST_IMAGE_NAME: $CI_REGISTRY_IMAGE/$CI_COMMIT_REF_NAME:latest-arm64 + PUBLISH_IMAGE_NAME: $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG-arm64 + PUBLISH_LATEST_IMAGE_NAME: $CI_REGISTRY_IMAGE:latest-arm64 + script: + - docker build -t $IMAGE_NAME . --platform=linux/arm64 + - docker image tag $IMAGE_NAME $LATEST_IMAGE_NAME + - docker push $IMAGE_NAME + - docker push $LATEST_IMAGE_NAME + - | + if [ $CI_COMMIT_TAG ]; then + docker image tag $IMAGE_NAME $PUBLISH_IMAGE_NAME + docker image tag $IMAGE_NAME $PUBLISH_LATEST_IMAGE_NAME + docker push $PUBLISH_IMAGE_NAME + docker push $PUBLISH_LATEST_IMAGE_NAME + fi diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..3c9727cb --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +drop-base/ \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000..2fc7cb9e --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,12 @@ +{ + "recommendations": [ + "lokalise.i18n-ally", + "esbenp.prettier-vscode", + "Prisma.prisma", + "bradlc.vscode-tailwindcss", + "Vue.volar", + "arktypeio.arkdark", + "EditorConfig.EditorConfig", + "dbaeumer.vscode-eslint" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 87ca8efb..76d084c8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,18 +1,37 @@ { - "spellchecker.ignoreWordsList": [ - "mTLS", - "Wireguard" - ], - "sqltools.connections": [ - { - "previewLimit": 50, - "server": "localhost", - "port": 5432, - "driver": "PostgreSQL", - "name": "drop", - "database": "drop", - "username": "drop", - "password": "drop" - } - ] + "spellchecker.ignoreWordsList": ["mTLS", "Wireguard"], + "sqltools.connections": [ + { + "previewLimit": 50, + "server": "localhost", + "port": 5432, + "driver": "PostgreSQL", + "name": "drop", + "database": "drop", + "username": "drop", + "password": "drop" + } + ], + // allow autocomplete for ArkType expressions like "string | num" + "editor.quickSuggestions": { + "strings": "on" + }, + // prioritize ArkType's "type" for autoimports + "typescript.preferences.autoImportSpecifierExcludeRegexes": ["^(node:)?os$"], + // i18n Ally settings + "i18n-ally.sortKeys": true, + "i18n-ally.keepFulfilled": true, + "i18n-ally.extract.autoDetect": true, + "i18n-ally.localesPaths": ["i18n", "i18n/locales"], + "i18n-ally.keystyle": "nested", + "i18n-ally.extract.ignored": [ + "string >= 14", + "string.alphanumeric >= 5", + "/api/v1/admin/import/version/preload?id=${encodeURIComponent(\n gameId,\n )}&version=${encodeURIComponent(version)}" + ], + "i18n-ally.extract.ignoredByFiles": { + "pages/admin/library/sources/index.vue": ["Filesystem"], + "components/NewsArticleCreateButton.vue": ["[", "`", "Enter"], + "server/api/v1/auth/signin/simple.post.ts": ["boolean | undefined"] + } } diff --git a/.yarnrc b/.yarnrc deleted file mode 100644 index 9bcf9469..00000000 --- a/.yarnrc +++ /dev/null @@ -1 +0,0 @@ -"@drop:registry" "https://lab.deepcore.dev/api/v4/projects/57/packages/npm/" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f7a20b4e..5bb86362 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -41,7 +41,8 @@ TODO: Add Troubleshooting If not, look at the [Troubleshooting](https://github.com/Drop-OSS/docs/Troubleshooting) page for instructions on how to gather data to better debug your problem. --> -If you cannot find an existing issue, you can go ahead and create an issue with as much + +If you cannot find an existing issue, you can go ahead and create an issue with as much detail as you can provide. It should include the data gathered as indicated above, along with the following: @@ -69,7 +70,8 @@ maintainers) by mentioning their GitHub handle (starting with `@`) in your messa ### Getting started You should be familiar with the basics of -[contributing on GitHub](https://help.github.com/articles/using-pull-requests) +[contributing on GitHub](https://help.github.com/articles/using-pull-requests) + @@ -95,8 +97,8 @@ maintainers) by mentioning their GitHub handle (starting with `@`) in your messa ### You have an addition -We are absolutely accepting more contributions or features to drop, but please, make sure -that it is reasonable. Contributions that only cover a very small niche are likely to not +We are absolutely accepting more contributions or features to drop, but please, make sure +that it is reasonable. Contributions that only cover a very small niche are likely to not be added. Please be so kind as to [search](#use-the-search-luke) for any pending, merged or rejected Pull Requests @@ -109,7 +111,7 @@ maintainers) by mentioning their GitHub handle (starting with `@`) in your messa For any extensive change, such as API changes, you will have to find testers to +1 your PR. ----- +--- ## Use the Search, Luke @@ -126,7 +128,11 @@ to be sure your contribution has not already come up. If all fails, your thing has probably not been reported yet, so you can go ahead and [create an issue](#reporting-issues) or [submit a PR](#submitting-pull-requests). ----- +--- + +## Translation + +If you want to help translate Drop, we would love to have your help! You can do so on our weblate instance. Please make sure to read the [message format syntax](https://vue-i18n.intlify.dev/guide/essentials/syntax.html) page before starting. Failure to do so may result in your translations causing errors in Drop. ## Commit Guidelines @@ -142,7 +148,6 @@ type(scope)!: subject ``` - `type`: the type of the commit is one of the following: - - `feat`: new features. - `fix`: bug fixes. - `docs`: documentation changes. @@ -159,19 +164,19 @@ type(scope)!: subject - `scope`: section of the codebase that the commit makes changes to. If it makes changes to many sections, or if no section in particular is modified, leave blank without the parentheses. Examples: - - Commit that changes the `git` plugin: + ``` feat(git): add alias for `git commit` ``` - Commit that changes many plugins: + ``` style: fix inline declaration of arrays ``` For changes to plugins or themes, the scope should be the plugin or theme name: - - ✅ `fix(agnoster): commit subject` - ❌ `fix(theme/agnoster): commit subject` @@ -201,8 +206,8 @@ type(scope)!: subject to specify other details, you can use the commit body, but it won't be visible. Formatting tricks: the commit subject may contain: - - Links to related issues or PRs by writing `#issue`. This will be highlighted by the changelog tool: + ``` feat(archlinux): add support for aura AUR helper (#9467) ``` @@ -219,7 +224,7 @@ Try to keep the first commit line short. It's harder to do using this commit sty concise, and if you need more space, you can use the commit body. Try to make sure that the commit subject is clear and precise enough that users will know what changed by just looking at the changelog. ----- +--- + ## Reference -This contributing guide is adapted from the -[oh-my-zsh contribution guide](https://github.com/ohmyzsh/ohmyzsh/blob/master/CONTRIBUTING.md). -If there are any issues with this, please email admin@deepcore.dev. \ No newline at end of file + +This contributing guide is adapted from the +[oh-my-zsh contribution guide](https://github.com/ohmyzsh/ohmyzsh/blob/master/CONTRIBUTING.md). +If there are any issues with this, please email admin@deepcore.dev. diff --git a/Dockerfile b/Dockerfile index 33e714a3..19e6a846 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,28 +1,49 @@ -# pull pre-configured and updated build environment -FROM registry.deepcore.dev/drop-oss/drop-server-build-environment/main:latest AS build-system +# syntax=docker/dockerfile:1 -# setup workdir -RUN mkdir /build -WORKDIR /build +# Unified deps builder +FROM node:lts-alpine AS deps +WORKDIR /app +COPY package.json yarn.lock ./ +RUN --mount=type=cache,target=/root/.yarn YARN_CACHE_FOLDER=/root/.yarn yarn install --network-timeout 1000000 --ignore-scripts + +# Build for app +FROM node:lts-alpine AS build-system +# setup workdir - has to be the same filepath as app because fuckin' Prisma +WORKDIR /app + +ENV NODE_ENV=production +ENV NUXT_TELEMETRY_DISABLED=1 +ENV YARN_CACHE_FOLDER=/root/.yarn + +# add git so drop can determine its git ref at build +RUN apk add --no-cache git -# install dependencies and build -RUN corepack enable +# copy deps and rest of project files +COPY --from=deps /app/node_modules ./node_modules COPY . . -RUN NUXT_TELEMETRY_DISABLED=1 yarn install -RUN NUXT_TELEMETRY_DISABLED=1 yarn build -# create run environment for Drop -FROM node:lts-slim AS run-system +ARG BUILD_DROP_VERSION +ARG BUILD_GIT_REF + +# build +RUN --mount=type=cache,target=/root/.yarn yarn postinstall && \ + yarn build -RUN mkdir /app +# create run environment for Drop +FROM node:lts-alpine AS run-system WORKDIR /app -COPY --from=build-system /build/.output ./app -COPY --from=build-system /build/prisma ./prisma -COPY --from=build-system /build/build ./startup +ENV NODE_ENV=production +ENV NUXT_TELEMETRY_DISABLED=1 + +RUN --mount=type=cache,target=/root/.yarn YARN_CACHE_FOLDER=/root/.yarn yarn add --network-timeout 1000000 --no-lockfile --ignore-scripts prisma@6.11.1 + +COPY --from=build-system /app/package.json ./ +COPY --from=build-system /app/.output ./app +COPY --from=build-system /app/prisma ./prisma +COPY --from=build-system /app/build ./startup -# OpenSSL as a dependency for Drop (TODO: seperate build environment) -RUN apt-get update -y && apt-get install -y openssl -RUN yarn global add prisma +ENV LIBRARY="/library" +ENV DATA="/data" -CMD ["/app/startup/launch.sh"] \ No newline at end of file +CMD ["sh", "/app/startup/launch.sh"] diff --git a/README.md b/README.md index 33d1533a..69ae3078 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,16 @@
+ {{ $t("setup.auth.description") }} +
++ {{ $t("setup.auth.simple.description") }} +
++ {{ $t("setup.auth.openid.description") }} +
++ {{ props.message }} +
++ {{ $t("library.admin.sources.fsPathDesc") }} +
++ {{ $t("library.admin.sources.fsPathDesc") }} +
++
{{ message }}
- An error occurred while responding to your request. If you believe - this to be a bug, please report it. Try signing in and see if it - resolves the issue. + {{ $t("errors.occurred") }}
+
diff --git a/eslint.config.mjs b/eslint.config.mjs
new file mode 100644
index 00000000..0576c43b
--- /dev/null
+++ b/eslint.config.mjs
@@ -0,0 +1,33 @@
+// @ts-check
+import withNuxt from "./.nuxt/eslint.config.mjs";
+import eslintConfigPrettier from "eslint-config-prettier/flat";
+import vueI18n from "@intlify/eslint-plugin-vue-i18n";
+
+export default withNuxt([
+ eslintConfigPrettier,
+
+ // vue-i18n plugin
+ ...vueI18n.configs.recommended,
+ {
+ rules: {
+ // Optional.
+ "@intlify/vue-i18n/no-dynamic-keys": "error",
+ "@intlify/vue-i18n/no-unused-keys": [
+ "off",
+ {
+ extensions: [".js", ".vue", ".ts"],
+ },
+ ],
+ "@intlify/vue-i18n/no-missing-keys": "error",
+ },
+ settings: {
+ "vue-i18n": {
+ localeDir: "./i18n/locales/*.{json,json5,ts,js}", // extension is glob formatting!
+
+ // Specify the version of `vue-i18n` you are using.
+ // If not specified, the message will be parsed twice.
+ messageSyntaxVersion: "^11.0.0",
+ },
+ },
+ },
+]);
diff --git a/i18n/i18n.config.ts b/i18n/i18n.config.ts
new file mode 100644
index 00000000..f702086a
--- /dev/null
+++ b/i18n/i18n.config.ts
@@ -0,0 +1,35 @@
+export default defineI18nConfig(() => {
+ const defaultDateTimeFormat = {
+ short: {
+ year: "numeric",
+ month: "long",
+ day: "numeric",
+ },
+ long: {
+ year: "numeric",
+ month: "short",
+ day: "numeric",
+ weekday: "short",
+ hour: "numeric",
+ minute: "numeric",
+ },
+ } as const;
+
+ return {
+ // https://i18n.nuxtjs.org/docs/guide/locale-fallback
+ fallbackLocale: "en-us",
+ // 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/localeDetector.ts b/i18n/localeDetector.ts
new file mode 100644
index 00000000..d4250b60
--- /dev/null
+++ b/i18n/localeDetector.ts
@@ -0,0 +1,25 @@
+// https://i18n.nuxtjs.org/docs/guide/server-side-translations
+
+// Detect based on query, cookie, header
+export default defineI18nLocaleDetector((event, config) => {
+ // try to get locale from query
+ const query = tryQueryLocale(event, { lang: "" }); // disable locale default value with `lang` option
+ if (query) {
+ return query.toString();
+ }
+
+ // try to get locale from cookie
+ const cookie = tryCookieLocale(event, { lang: "", name: "i18n_redirected" }); // disable locale default value with `lang` option
+ if (cookie) {
+ return cookie.toString();
+ }
+
+ // try to get locale from header (`accept-header`)
+ const header = tryHeaderLocale(event, { lang: "" }); // disable locale default value with `lang` option
+ if (header) {
+ return header.toString();
+ }
+
+ // If the locale cannot be resolved up to this point, it is resolved with the value `defaultLocale` of the locale config passed to the function
+ return config.defaultLocale;
+});
diff --git a/i18n/locales/de.json b/i18n/locales/de.json
new file mode 100644
index 00000000..0967ef42
--- /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 00000000..27d8b005
--- /dev/null
+++ b/i18n/locales/en_au.json
@@ -0,0 +1,5 @@
+{
+ "setup": {
+ "welcome": "G'day."
+ }
+}
diff --git a/i18n/locales/en_gb.json b/i18n/locales/en_gb.json
new file mode 100644
index 00000000..0967ef42
--- /dev/null
+++ b/i18n/locales/en_gb.json
@@ -0,0 +1 @@
+{}
diff --git a/i18n/locales/en_pirate.json b/i18n/locales/en_pirate.json
new file mode 100644
index 00000000..36001dec
--- /dev/null
+++ b/i18n/locales/en_pirate.json
@@ -0,0 +1,482 @@
+{
+ "account": {
+ "devices": {
+ "capabilities": "Aye, yer Capabilities",
+ "lastConnected": "Last Linked",
+ "noDevices": "No contraptions tied to yer coffers, eh?",
+ "platform": "Ship",
+ "revoke": "Scuttle 'em",
+ "subheader": "Manage the contraptions allowed access to yer Drop booty.",
+ "title": "Contraptions"
+ },
+ "notifications": {
+ "all": "Gaze upon all {arrow}",
+ "desc": "View and manage yer messages from the crows' nest.",
+ "markAllAsRead": "Mark all as read, aye!",
+ "markAsRead": "Mark as read, matey!",
+ "none": "No messages, savvy?",
+ "notifications": "Crows' Nest",
+ "title": "Messages from the Crows' Nest",
+ "unread": "Unread Messages"
+ },
+ "settings": "Account Settings, savvy?",
+ "title": "Yer Own Coffer"
+ },
+ "actions": "Deeds",
+ "add": "Add",
+ "adminTitle": "Cap'n's Quarters - Drop",
+ "adminTitleTemplate": "{0} - Cap'n - Drop",
+ "auth": {
+ "callback": {
+ "authClient": "Grant passage to this scallywag?",
+ "authorize": "Grant Passage",
+ "authorizedClient": "Drop has granted passage to the scallywag. Ye may now shut this porthole.",
+ "issues": "Troubles be brewin', matey?",
+ "learn": "Learn more {arrow}",
+ "paste": "Scribble this code into the scallywag to carry on:",
+ "permWarning": "Grantin' this request allows \"{name}\" on \"{platform}\" to:",
+ "requestedAccess": "\"{name}\" has requested passage to yer Drop coffer.",
+ "success": "Shiver me timbers, it worked!"
+ },
+ "confirmPassword": "Confirm @:auth.password",
+ "displayName": "Yer Scallywag Name",
+ "email": "Salty Mail",
+ "password": "Secret Word",
+ "register": {
+ "confirmPasswordFormat": "Must be the same as above, savvy?",
+ "emailFormat": "Must be in the fashion of a true scallywag {'@'} example.com",
+ "passwordFormat": "Must be 14 or more marks, ye landlubber!",
+ "subheader": "Fill in yer details below to make yer mark.",
+ "title": "Forge yer Drop Mark",
+ "usernameFormat": "Must be 5 or more marks, and all lowercase, argh!"
+ },
+ "signin": {
+ "externalProvider": "Sign in with another ship's captain {arrow}",
+ "forgot": "Forgot yer secret word, eh?",
+ "noAccount": "No mark in the logbook? Beg a cap'n to make ye one, argh!",
+ "or": "OR",
+ "pageTitle": "Sign in to Drop, ye dog!",
+ "rememberMe": "Remember me, savvy?",
+ "signin": "Sign in, ye scurvy dog!",
+ "title": "Sign in to yer mark"
+ },
+ "signout": "Cast off!",
+ "username": "Scallywag Name"
+ },
+ "cancel": "Belay that!",
+ "chars": {
+ "arrow": "→",
+ "arrowBack": "←",
+ "quoted": "\"\"",
+ "srComma": ", {0}"
+ },
+ "common": {
+ "cannotUndo": "This deed cannot be undone, ye hear!",
+ "close": "Shut yer trap!",
+ "create": "Forge!",
+ "date": "Date",
+ "deleteConfirm": "Are ye sure ye want to scuttle \"{0}\", ye rogue?",
+ "divider": "{'|'}",
+ "edit": "Amend",
+ "friends": "Shipmates",
+ "groups": "Crews",
+ "insert": "Insert",
+ "name": "Name, argh!",
+ "noResults": "No plunder found!",
+ "save": "Stow it!",
+ "servers": "Ships",
+ "srLoading": "Loading, loading, argh...",
+ "tags": "Marks",
+ "today": "Today"
+ },
+ "delete": "Scuttle!",
+ "drop": {
+ "desc": "An open-source game distribution platform built for speed, flexibility and beauty, like a swift brigantine!",
+ "drop": "Drop"
+ },
+ "editor": {
+ "bold": "Bold, like a cannonball!",
+ "boldPlaceholder": "bold text, matey",
+ "code": "Code, ye scallywag!",
+ "codePlaceholder": "code, argh!",
+ "heading": "Heading, to the horizon!",
+ "headingPlaceholder": "heading, savvy?",
+ "italic": "Italic, like a wobbly deck!",
+ "italicPlaceholder": "italic text, arrr!",
+ "link": "Link, a chain to adventure!",
+ "linkPlaceholder": "link text, ye dog!",
+ "listItem": "List Item, for yer plunder!",
+ "listItemPlaceholder": "list item, eh?"
+ },
+ "errors": {
+ "backHome": "{arrow} Back to yer safe harbor",
+ "game": {
+ "banner": {
+ "description": "Drop failed to hoist the banner image: {0}",
+ "title": "Failed to hoist the banner image"
+ },
+ "carousel": {
+ "description": "Drop failed to update the image carousel: {0}",
+ "title": "Failed to update image carousel"
+ },
+ "cover": {
+ "description": "Drop failed to hoist the cover image: {0}",
+ "title": "Failed to hoist the cover image"
+ },
+ "deleteImage": {
+ "description": "Drop failed to scuttle the image: {0}",
+ "title": "Failed to scuttle the image"
+ },
+ "description": {
+ "description": "Drop failed to update the game description: {0}",
+ "title": "Failed to update game description"
+ },
+ "metadata": {
+ "description": "Drop failed to update the game's charts: {0}",
+ "title": "Failed to update yer charts"
+ }
+ },
+ "invalidBody": "Invalid request, ye barnacle-encrusted body: {0}",
+ "inviteRequired": "Invitation demanded to sign up, ye landlubber.",
+ "library": {
+ "add": {
+ "desc": "Drop couldn't add this game to yer treasure hoard: {0}",
+ "title": "Failed to add game to yer treasure hoard"
+ },
+ "collection": {
+ "create": {
+ "desc": "Drop couldn't forge yer collection, argh: {0}",
+ "title": "Failed to forge collection"
+ }
+ },
+ "source": {
+ "delete": {
+ "desc": "Drop couldn't scuttle this source: {0}",
+ "title": "Failed to scuttle treasure hoard source"
+ }
+ }
+ },
+ "news": {
+ "article": {
+ "delete": {
+ "desc": "Drop couldn't scuttle this article: {0}",
+ "title": "Failed to scuttle article"
+ }
+ }
+ },
+ "occurred": "An error occurred whilst answerin' yer plea. If ye believe this be a bug, report it, ye dog! Try signin' in and see if it clears the decks.",
+ "ohNo": "Blimey!",
+ "pageTitle": "{0} | Drop",
+ "revokeClient": "Failed to scuttle scallywag",
+ "revokeClientFull": "Failed to scuttle scallywag {0}",
+ "signIn": "Sign in {arrow}, ye scurvy dog!",
+ "support": "Support Discord, arrr!",
+ "unknown": "An unknown blunder occurred, savvy?",
+ "upload": {
+ "description": "Drop couldn't hoist the file: {0}",
+ "title": "Failed to hoist the file"
+ },
+ "version": {
+ "delete": {
+ "desc": "Drop met a squall whilst scuttlin' the version: {error}",
+ "title": "There was a squall whilst scuttlin' the version"
+ },
+ "order": {
+ "desc": "Drop met a squall whilst updatin' the version: {error}",
+ "title": "There was a squall whilst updatin' the version order"
+ }
+ }
+ },
+ "footer": {
+ "about": "About, savvy?",
+ "aboutDrop": "About Drop, argh!",
+ "comparison": "Comparison, matey!",
+ "docs": {
+ "client": "Scallywag's Docs",
+ "server": "Cap'n's Docs"
+ },
+ "documentation": "Charts and Scrolls",
+ "findGame": "Find a Game, ye dog!",
+ "footer": "Keel",
+ "games": "Games",
+ "social": {
+ "discord": "Discord, argh!",
+ "github": "GitHub, savvy?"
+ },
+ "topSellers": "Top Plunderers"
+ },
+ "header": {
+ "admin": {
+ "admin": "Cap'n",
+ "tasks": "Duties",
+ "users": "Crew"
+ },
+ "back": "Aft!",
+ "openSidebar": "Open the side-hatch!"
+ },
+ "helpUsTranslate": "Help us translate Drop {arrow}, argh!",
+ "highest": "highest",
+ "home": "Home Port",
+ "library": {
+ "addGames": "All Plunder",
+ "addToLib": "Add to Yer Treasure Hoard",
+ "admin": {
+ "detectedGame": "Drop has found new plunder to import, argh!",
+ "detectedVersion": "Drop has found new versions of this plunder to import, savvy!",
+ "game": {
+ "addCarouselNoImages": "No images to add, ye dog.",
+ "addDescriptionNoImages": "No images to add, argh.",
+ "addImageCarousel": "Add from image treasure hoard",
+ "currentBanner": "banner",
+ "currentCover": "cover",
+ "deleteImage": "Scuttle image",
+ "editGameDescription": "Plunder Description",
+ "editGameName": "Plunder Name",
+ "imageCarousel": "Image Carousel",
+ "imageCarouselDescription": "Customize what images and what order be shown on the store page, savvy.",
+ "imageCarouselEmpty": "No images added to the carousel yet, argh.",
+ "imageLibrary": "Image treasure hoard",
+ "imageLibraryDescription": "Please note all images hoisted be accessible to all crew through browser dev-tools, savvy.",
+ "removeImageCarousel": "Remove image",
+ "setBanner": "Set as banner",
+ "setCover": "Set as cover"
+ },
+ "gameLibrary": "Game Treasure Hoard",
+ "import": {
+ "import": "Import, ye dog!",
+ "link": "Import {arrow}",
+ "loading": "Loadin' plunder results, arrr...",
+ "search": "Search",
+ "searchPlaceholder": "Fallout 4, savvy?",
+ "selectDir": "Pick a directory, ye landlubber...",
+ "selectGame": "Pick plunder to import",
+ "selectGamePlaceholder": "Pick a game, ye dog...",
+ "selectGameSearch": "Pick game",
+ "selectPlatform": "Pick a ship, ye scallywag...",
+ "version": {
+ "advancedOptions": "Advanced options, savvy?",
+ "import": "Import version",
+ "installDir": "(install_dir)/",
+ "launchCmd": "Launch executable/command, argh!",
+ "launchDesc": "Executable to launch the game, matey!",
+ "launchPlaceholder": "game.exe, aye!",
+ "loadingVersion": "Loading version charts...",
+ "noAdv": "No advanced options for this rig, argh.",
+ "noVersions": "No versions to import, savvy!",
+ "platform": "Ship type",
+ "setupCmd": "Setup executable/command",
+ "setupDesc": "Ran once when the game is installed, ye hear!",
+ "setupMode": "Setup mode, savvy?",
+ "setupModeDesc": "When enabled, this version has no launch command, and merely runs the executable on the crew's computer. Useful for games that only give installers and not portable files, argh!",
+ "setupPlaceholder": "setup.exe, aye!",
+ "umuLauncherId": "UMU Launcher ID",
+ "umuOverride": "Override UMU Launcher Game ID",
+ "umuOverrideDesc": "By default, Drop uses a non-ID when launchin' with UMU Launcher. To get the right patches for some games, ye might have to set this field by hand, savvy.",
+ "updateMode": "Update mode, argh!",
+ "updateModeDesc": "When enabled, these files will be installed atop (overwritin') the previous version's. If many 'update modes' be chained together, they be applied in order, ye hear!",
+ "version": "Pick version to import"
+ },
+ "withoutMetadata": "Import without charts"
+ },
+ "metadataProvider": "Charts Provider",
+ "noGames": "No plunder imported, savvy!",
+ "openEditor": "Open in Editor {arrow}",
+ "openStore": "Open in Store, argh!",
+ "shortDesc": "Short Description",
+ "sources": {
+ "create": "Forge source",
+ "createDesc": "Drop will use this source to get to yer game treasure hoard, and make 'em available, argh.",
+ "desc": "Rig yer treasure hoard sources, where Drop will look for new plunder and versions to import, savvy.",
+ "fsDesc": "Imports games from a path on disk. Needs version-based folder structure, and backs archived games, ye hear!",
+ "fsPath": "Path",
+ "fsPathDesc": "An absolute path to yer game treasure hoard.",
+ "fsPathPlaceholder": "/mnt/games, aye!",
+ "link": "Sources {arrow}",
+ "nameDesc": "The name of yer source, for yer own reckonin', argh.",
+ "namePlaceholder": "My New Source, savvy?",
+ "sources": "Treasure Hoard Sources",
+ "typeDesc": "The type of yer source. Changes the demanded options, ye dog!",
+ "working": "Workin', eh?"
+ },
+ "subheader": "As ye add folders to yer treasure hoard sources, Drop will find 'em and ask ye to import 'em. Each game needs to be imported before ye can import a version, savvy.",
+ "title": "Treasure Hoards",
+ "version": {
+ "delta": "Upgrade mode",
+ "noVersions": "Ye have no versions of this plunder available, ye dog!",
+ "noVersionsAdded": "no versions added, argh!"
+ },
+ "versionPriority": "Version priority"
+ },
+ "back": "Aft to Treasure Hoard",
+ "collection": {
+ "addToNew": "Add to new collection",
+ "collections": "Collections",
+ "create": "Forge Collection",
+ "createDesc": "Collections can be used to sort yer plunder and find 'em easier, especially if ye have a grand treasure hoard, argh!",
+ "delete": "Scuttle Collection",
+ "namePlaceholder": "Collection name, matey!",
+ "noCollections": "No collections, savvy!",
+ "notFound": "Collection not found, argh!",
+ "subheader": "Add a new collection to sort yer plunder",
+ "title": "Collection"
+ },
+ "gameCount": "{0} plunder | {0} plunder | {0} plunder",
+ "inLib": "In Treasure Hoard",
+ "launcherOpen": "Open in Launcher, argh!",
+ "noGames": "No plunder in treasure hoard, savvy!",
+ "notFound": "Plunder not found, matey!",
+ "search": "Search treasure hoard, ye dog...",
+ "subheader": "Sort yer plunder into collections for easy access, and get to all yer plunder, savvy!"
+ },
+ "lowest": "lowest",
+ "news": {
+ "article": {
+ "add": "Add, ye dog!",
+ "content": "Content (Markdown), savvy!",
+ "create": "Forge New Article",
+ "editor": "Editor",
+ "editorGuide": "Use the quick ways above or scribble Markdown directly. Backs **bold**, *italic*, [links](url), and more, argh!",
+ "new": "New article, savvy!",
+ "preview": "Preview, matey!",
+ "shortDesc": "Short description",
+ "submit": "Submit, ye scurvy dog!",
+ "tagPlaceholder": "Add a mark, ye dog...",
+ "titles": "Title, argh!",
+ "uploadCover": "Hoist cover image"
+ },
+ "back": "Aft to News",
+ "checkLater": "Check back later for new charts, matey!",
+ "delete": "Scuttle Article",
+ "filter": {
+ "all": "All time, savvy!",
+ "month": "This moon",
+ "week": "This week",
+ "year": "This year, argh!"
+ },
+ "none": "No articles, savvy!",
+ "notFound": "Article not found, matey!",
+ "search": "Search articles, ye dog!",
+ "searchPlaceholder": "Search articles, argh...",
+ "subheader": "Stay up to date with the latest charts and announcements, savvy!",
+ "title": "Latest News from the High Seas"
+ },
+ "options": "Options, matey!",
+ "security": "Safety",
+ "selectLanguage": "Pick yer tongue",
+ "settings": "Settings",
+ "store": {
+ "commingSoon": "comin' soon, argh!",
+ "exploreMore": "Explore more {arrow}, ye dog!",
+ "images": "Plunder Images",
+ "lookAt": "Look at it, ye scurvy dog!",
+ "noGame": "no plunder",
+ "noImages": "No images, savvy!",
+ "openAdminDashboard": "Open in Cap'n's Quarters",
+ "platform": "Ship | Ship | Ships",
+ "rating": "Rating, argh!",
+ "readLess": "Click to read less, matey!",
+ "readMore": "Click to read more, ye dog!",
+ "recentlyAdded": "Recently Added Plunder",
+ "recentlyReleased": "Recently set sail",
+ "recentlyUpdated": "Recently Updated",
+ "released": "Released, argh!",
+ "reviews": "({0} Sea Tales)",
+ "title": "Store",
+ "view": "View in Store"
+ },
+ "tasks": {
+ "admin": {
+ "back": "{arrow} Aft to Duties",
+ "completedTasksTitle": "Duties completed",
+ "dailyScheduledTitle": "Daily scheduled duties",
+ "noTasksRunning": "No duties currently underway",
+ "runningTasksTitle": "Duties underway",
+ "scheduled": {
+ "checkUpdateDescription": "Check if Drop has new charts.",
+ "checkUpdateName": "Check for new charts.",
+ "cleanupInvitationsDescription": "Cleans up expired invitations from the logbook to save space, savvy.",
+ "cleanupInvitationsName": "Clean up invitations",
+ "cleanupObjectsDescription": "Finds and scuttles unreferenced and unused objects to save space, argh.",
+ "cleanupObjectsName": "Clean up objects",
+ "cleanupSessionsDescription": "Cleans up expired sessions to save space and keep ye safe, ye dog!",
+ "cleanupSessionsName": "Clean up sessions."
+ },
+ "viewTask": "View {arrow}"
+ }
+ },
+ "title": "Drop",
+ "titleTemplate": "{0} | Drop",
+ "todo": "Todo, argh!",
+ "type": "Type",
+ "upload": "Hoist!",
+ "uploadFile": "Hoist file",
+ "userHeader": {
+ "closeSidebar": "Close side-hatch!",
+ "links": {
+ "community": "Shipmates",
+ "library": "Treasure Hoard",
+ "news": "News from the High Seas"
+ },
+ "profile": {
+ "admin": "Cap'n's Quarters",
+ "settings": "Account settings, savvy!"
+ }
+ },
+ "users": {
+ "admin": {
+ "adminHeader": "Cap'n, eh?",
+ "adminUserLabel": "Cap'n of the crew",
+ "authLink": "Passage {arrow}",
+ "authentication": {
+ "configure": "Rig",
+ "description": "Drop backs many 'passage ways'. As ye enable or disable 'em, they show on the sign-in screen for the crew to pick. Click the dot menu to rig the passage way.",
+ "disabled": "Disabled",
+ "enabled": "Enabled",
+ "enabledKey": "Enabled, argh?",
+ "oidc": "OpenID Connect, savvy?",
+ "simple": "Simple (crew name/secret word)",
+ "srOpenOptions": "Open options",
+ "title": "Passage"
+ },
+ "authoptionsHeader": "Passage Options",
+ "description": "Manage the crew on yer Drop vessel, and set yer passage methods, savvy?",
+ "displayNameHeader": "Scallywag Name",
+ "emailHeader": "Salty Mail",
+ "normalUserLabel": "Common crewman",
+ "simple": {
+ "adminInvitation": "Cap'n's Invitation",
+ "createInvitation": "Forge Invitation",
+ "description": "Simple passage uses a system of 'invitations' to create crew. Ye can forge an invitation, and optionally name a crew name or salty mail for the crew, then it'll make a magic scroll that can be used to make a mark.",
+ "expires": "Expires: {expiry}",
+ "invitationTitle": "Invitations",
+ "invite3Days": "3 suns",
+ "invite6Months": "6 moons",
+ "inviteAdminSwitchDescription": "Make this crewman a cap'n, argh!",
+ "inviteAdminSwitchLabel": "Cap'n's invitation",
+ "inviteButton": "Invite, ye dog!",
+ "inviteDescription": "Drop will make a scroll ye can send to the scallywag ye want to invite. Ye can optionally name a crew name or salty mail for them to use.",
+ "inviteEmailDescription": "Must be in the fashion of a scallywag {'@'} example.com",
+ "inviteEmailLabel": "Salty mail address (optional)",
+ "inviteEmailPlaceholder": "me{'@'}example.com",
+ "inviteExpiryLabel": "Expires",
+ "inviteMonth": "1 moon",
+ "inviteNever": "Never",
+ "inviteTitle": "Invite crew to Drop",
+ "inviteUsernameFormat": "Must be 5 or more marks",
+ "inviteUsernameLabel": "Crew Name (optional)",
+ "inviteUsernamePlaceholder": "myScallywagName",
+ "inviteWeek": "1 week",
+ "inviteYear": "1 year",
+ "neverExpires": "Never expires, savvy.",
+ "noEmailEnforced": "No salty mail forced, matey.",
+ "noInvitations": "No invitations, argh.",
+ "noUsernameEnforced": "No crew name forced, argh.",
+ "title": "Simple passage",
+ "userInvitation": "Crewman's Invitation"
+ },
+ "srEditLabel": "Amend",
+ "usernameHeader": "Crew Name"
+ }
+ },
+ "welcome": "Ahoy, Welcome!"
+}
diff --git a/i18n/locales/en_us.json b/i18n/locales/en_us.json
new file mode 100644
index 00000000..58e16c9a
--- /dev/null
+++ b/i18n/locales/en_us.json
@@ -0,0 +1,622 @@
+{
+ "account": {
+ "devices": {
+ "capabilities": "Capabilities",
+ "lastConnected": "Last Connected",
+ "noDevices": "No devices connected to your account.",
+ "platform": "Platform",
+ "revoke": "Revoke",
+ "subheader": "Manage the devices authorized to access your Drop account.",
+ "title": "Devices"
+ },
+ "notifications": {
+ "all": "View all {arrow}",
+ "desc": "View and manage your notifications.",
+ "markAllAsRead": "Mark all as read",
+ "markAsRead": "Mark as read",
+ "none": "No notifications",
+ "notifications": "Notifications",
+ "title": "Notifications",
+ "unread": "Unread Notifications"
+ },
+ "settings": "Settings",
+ "title": "Account Settings"
+ },
+ "actions": "Actions",
+ "add": "Add",
+ "adminTitle": "Admin Dashboard - Drop",
+ "adminTitleTemplate": "{0} - Admin - Drop",
+ "auth": {
+ "callback": {
+ "authClient": "Authorize client?",
+ "authorize": "Authorize",
+ "authorizedClient": "Drop has successfully authorized the client. You may now close this window.",
+ "issues": "Having issues?",
+ "learn": "Learn more {arrow}",
+ "paste": "Paste this code into the client to continue:",
+ "permWarning": "Accepting this request will allow \"{name}\" on \"{platform}\" to:",
+ "requestedAccess": "\"{name}\" has requested access to your Drop account.",
+ "success": "Successful!"
+ },
+ "confirmPassword": "Confirm @:auth.password",
+ "displayName": "Display Name",
+ "email": "Email",
+ "password": "Password",
+ "register": {
+ "confirmPasswordFormat": "Must be the same as above",
+ "emailFormat": "Must be in the format user{'@'}example.com",
+ "passwordFormat": "Must be 14 or more characters",
+ "subheader": "Fill in your details below to create your account.",
+ "title": "Create your Drop account",
+ "usernameFormat": "Must be 5 or more characters, and lowercase"
+ },
+ "signin": {
+ "externalProvider": "Sign in with external provider {arrow}",
+ "forgot": "Forgot password?",
+ "noAccount": "Don't have an account? Ask an admin to create one for you.",
+ "or": "OR",
+ "pageTitle": "Sign in to Drop",
+ "rememberMe": "Remember me",
+ "signin": "Sign in",
+ "title": "Sign in to your account"
+ },
+ "signout": "Signout",
+ "username": "Username"
+ },
+ "setup": {
+ "welcome": "Hey there.",
+ "welcomeDescription": "Welcome to Drop setup wizard. It will walk you through configuring Drop for the first time, and how it works.",
+ "finish": "Let's go {arrow}",
+ "noPage": "no page",
+ "auth": {
+ "title": "Authentication",
+ "description": "Authentication in Drop happens through multiple configured 'providers'. Each one can allow users to sign-in through their method. To get started, have at least one authentication provider enabled, and create an account through it.",
+ "docs": "Documentation {arrow}",
+ "enabled": "Enabled?",
+ "simple": {
+ "title": "Simple authentication",
+ "description": "Simple authentication uses username/password to authentication users. It is enabled by default if no other authentication provider is enabled.",
+ "register": "Register as admin {arrow}"
+ },
+ "openid": {
+ "title": "OpenID Connect",
+ "description": "OpenID Connect (OIDC) is an OAuth2 extension commonly supported. Drop requires OIDC configuration to be done via environment variables.",
+ "skip": "I have a user with OIDC"
+ }
+ },
+ "stages": {
+ "account": {
+ "name": "Setup your admin account.",
+ "description": "You need at least one account to start using Drop."
+ },
+ "library": {
+ "name": "Create a library.",
+ "description": "Add at least one library source to use Drop."
+ }
+ }
+ },
+ "cancel": "Cancel",
+ "chars": {
+ "arrow": "→",
+ "arrowBack": "←",
+ "quoted": "\"\"",
+ "srComma": ", {0}"
+ },
+ "common": {
+ "cannotUndo": "This action cannot be undone.",
+ "close": "Close",
+ "create": "Create",
+ "date": "Date",
+ "deleteConfirm": "Are you sure you want to delete \"{0}\"?",
+ "divider": "{'|'}",
+ "edit": "Edit",
+ "friends": "Friends",
+ "groups": "Groups",
+ "insert": "Insert",
+ "name": "Name",
+ "noResults": "No results",
+ "noSelected": "No items selected.",
+ "remove": "Remove",
+ "save": "Save",
+ "saved": "Saved",
+ "servers": "Servers",
+ "srLoading": "Loading...",
+ "tags": "Tags",
+ "today": "Today",
+ "add": "Add"
+ },
+ "delete": "Delete",
+ "drop": {
+ "desc": "An open-source game distribution platform built for speed, flexibility and beauty.",
+ "drop": "Drop"
+ },
+ "editor": {
+ "bold": "Bold",
+ "boldPlaceholder": "bold text",
+ "code": "Code",
+ "codePlaceholder": "code",
+ "heading": "Heading",
+ "headingPlaceholder": "heading",
+ "italic": "Italic",
+ "italicPlaceholder": "italic text",
+ "link": "Link",
+ "linkPlaceholder": "link text",
+ "listItem": "List Item",
+ "listItemPlaceholder": "list item"
+ },
+ "errors": {
+ "admin": {
+ "user": {
+ "delete": {
+ "desc": "Drop couldn't delete this user: {0}",
+ "title": "Failed to delete user"
+ }
+ }
+ },
+ "auth": {
+ "disabled": "Invalid or disabled account. Please contact the server administrator.",
+ "invalidInvite": "Invalid or expired invitation",
+ "invalidPassState": "Invalid password state. Please contact the server administrator.",
+ "invalidUserOrPass": "Invalid username or password.",
+ "inviteIdRequired": "id required in fetching invitation",
+ "method": {
+ "signinDisabled": "Sign in method not enabled"
+ },
+ "usernameTaken": "Username already taken."
+ },
+ "backHome": "{arrow} Back to home",
+ "game": {
+ "banner": {
+ "description": "Drop failed to update the banner image: {0}",
+ "title": "Failed to update the banner image"
+ },
+ "carousel": {
+ "description": "Drop failed to update the image carousel: {0}",
+ "title": "Failed to update image carousel"
+ },
+ "cover": {
+ "description": "Drop failed to update the cover image: {0}",
+ "title": "Failed to update the cover image"
+ },
+ "deleteImage": {
+ "description": "Drop failed to delete the image: {0}",
+ "title": "Failed to delete the image"
+ },
+ "description": {
+ "description": "Drop failed to update the game description: {0}",
+ "title": "Failed to update game description"
+ },
+ "metadata": {
+ "description": "Drop failed to update the game's metadata: {0}",
+ "title": "Failed to update metadata"
+ }
+ },
+ "invalidBody": "Invalid request body: {0}",
+ "inviteRequired": "Invitation required to sign up.",
+ "library": {
+ "add": {
+ "desc": "Drop couldn't add this game to your library: {0}",
+ "title": "Failed to add game to library"
+ },
+ "collection": {
+ "create": {
+ "desc": "Drop couldn't create your collection: {0}",
+ "title": "Failed to create collection"
+ }
+ },
+ "source": {
+ "delete": {
+ "desc": "Drop couldn't add delete this source: {0}",
+ "title": "Failed to delete library source"
+ }
+ }
+ },
+ "news": {
+ "article": {
+ "delete": {
+ "desc": "Drop couldn't delete this article: {0}",
+ "title": "Failed to delete article"
+ }
+ }
+ },
+ "occurred": "An error occurred while responding to your request. If you believe this to be a bug, please report it. Try signing in and see if it resolves the issue.",
+ "ohNo": "Oh no!",
+ "pageTitle": "{0} | Drop",
+ "revokeClient": "Failed to revoke client",
+ "revokeClientFull": "Failed to revoke client {0}",
+ "signIn": "Sign in {arrow}",
+ "support": "Support Discord",
+ "unknown": "An unknown error occurred",
+ "upload": {
+ "description": "Drop couldn't upload the file: {0}",
+ "title": "Failed to upload file"
+ },
+ "version": {
+ "delete": {
+ "desc": "Drop encountered an error while deleting the version: {error}",
+ "title": "There an error while deleting the version"
+ },
+ "order": {
+ "desc": "Drop encountered an error while updating the version: {error}",
+ "title": "There an error while updating the version order"
+ }
+ }
+ },
+ "footer": {
+ "about": "About",
+ "aboutDrop": "About Drop",
+ "comparison": "Comparison",
+ "docs": {
+ "client": "Client Docs",
+ "server": "Server Docs"
+ },
+ "documentation": "Documentation",
+ "findGame": "Find a Game",
+ "footer": "Footer",
+ "games": "Games",
+ "social": {
+ "discord": "Discord",
+ "github": "GitHub"
+ },
+ "topSellers": "Top Sellers",
+ "version": "Drop {version} {gitRef}"
+ },
+ "header": {
+ "admin": {
+ "admin": "Admin",
+ "metadata": "Meta",
+ "settings": "Settings",
+ "tasks": "Tasks",
+ "users": "Users"
+ },
+ "back": "Back",
+ "openSidebar": "Open sidebar"
+ },
+ "helpUsTranslate": "Help us translate Drop {arrow}",
+ "highest": "highest",
+ "home": "Home",
+ "library": {
+ "addGames": "All Games",
+ "addToLib": "Add to Library",
+ "admin": {
+ "detectedGame": "Drop has detected you have new games to import.",
+ "detectedVersion": "Drop has detected you have new verions of this game to import.",
+ "offlineTitle": "Game offline",
+ "offline": "Drop couldn't access this game.",
+ "game": {
+ "addCarouselNoImages": "No images to add.",
+ "addDescriptionNoImages": "No images to add.",
+ "addImageCarousel": "Add from image library",
+ "currentBanner": "banner",
+ "currentCover": "cover",
+ "deleteImage": "Delete image",
+ "editGameDescription": "Game Description",
+ "editGameName": "Game Name",
+ "imageCarousel": "Image Carousel",
+ "imageCarouselDescription": "Customise what images and what order are shown on the store page.",
+ "imageCarouselEmpty": "No images added to the carousel yet.",
+ "imageLibrary": "Image library",
+ "imageLibraryDescription": "Please note all images uploaded are accessible to all users through browser dev-tools.",
+ "removeImageCarousel": "Remove image",
+ "setBanner": "Set as banner",
+ "setCover": "Set as cover"
+ },
+ "gameLibrary": "Game Library",
+ "import": {
+ "import": "Import",
+ "link": "Import {arrow}",
+ "loading": "Loading game results...",
+ "search": "Search",
+ "searchPlaceholder": "Fallout 4",
+ "selectDir": "Please select a directory...",
+ "selectGame": "Select game to import",
+ "selectGamePlaceholder": "Please select a game...",
+ "selectGameSearch": "Select game",
+ "selectPlatform": "Please select a platform...",
+ "bulkImportTitle": "Bulk import mode",
+ "bulkImportDescription": "When on, this page won't redirect you to the import task, so you can import multiple games in succession.",
+ "version": {
+ "advancedOptions": "Advanced options",
+ "import": "Import version",
+ "installDir": "(install_dir)/",
+ "launchCmd": "Launch executable/command",
+ "launchDesc": "Executable to launch the game",
+ "launchPlaceholder": "game.exe",
+ "loadingVersion": "Loading version metadata...",
+ "noAdv": "No advanced options for this configuration.",
+ "noVersions": "No versions to import",
+ "platform": "Version platform",
+ "setupCmd": "Setup executable/command",
+ "setupDesc": "Ran once when the game is installed",
+ "setupMode": "Setup mode",
+ "setupModeDesc": "When enabled, this version does not have a launch command, and simply runs the executable on the user's computer. Useful for games that only distribute installer and not portable files.",
+ "setupPlaceholder": "setup.exe",
+ "umuLauncherId": "UMU Launcher ID",
+ "umuOverride": "Override UMU Launcher Game ID",
+ "umuOverrideDesc": "By default, Drop uses a non-ID when launching with UMU Launcher. In order to get the right patches for some games, you may have to manually set this field.",
+ "updateMode": "Update mode",
+ "updateModeDesc": "When enabled, these files will be installed on top of (overwriting) the previous version's. If multiple \"update modes\" are chained together, they are applied in order.",
+ "version": "Select version to import"
+ },
+ "withoutMetadata": "Import without metadata"
+ },
+ "metadataProvider": "Metadata provider",
+ "noGames": "No games imported",
+ "openEditor": "Open in Editor {arrow}",
+ "openStore": "Open in Store",
+ "shortDesc": "Short Description",
+ "sources": {
+ "create": "Create source",
+ "edit": "Edit source",
+ "createDesc": "Drop will use this source to access your game library, and make them available.",
+ "desc": "Configure your library sources, where Drop will look for new games and versions to import.",
+ "fsDesc": "Imports games from a path on disk. Requires version-based folder structure, and supports archived games.",
+ "fsFlatDesc": "Imports games from a path on disk, but without a separate version subfolder. Useful when migrating an existing library to Drop.",
+ "fsPath": "Path",
+ "fsPathDesc": "An absolute path to your game library.",
+ "fsPathPlaceholder": "/mnt/games",
+ "link": "Sources {arrow}",
+ "nameDesc": "The name of your source, for reference.",
+ "namePlaceholder": "My New Source",
+ "sources": "Library Sources",
+ "typeDesc": "The type of your source. Changes the required options.",
+ "working": "Working?"
+ },
+ "subheader": "As you add folders to your library sources, Drop will detect it and prompt you to import it. Each game needs to be imported before you can import a version.",
+ "title": "Libraries",
+ "version": {
+ "delta": "Upgrade mode",
+ "noVersions": "You have no versions of this game available.",
+ "noVersionsAdded": "no versions added"
+ },
+ "versionPriority": "Version priority",
+ "metadata": {
+ "tags": {
+ "title": "Tags",
+ "description": "Tags are automatically created from imported genres. You can add custom tags to add categorisation to your game library.",
+ "action": "Manage {arrow}",
+ "create": "Create",
+ "modal": {
+ "title": "Create Tag",
+ "description": "Create a tag to organize your library."
+ }
+ },
+ "companies": {
+ "title": "Companies",
+ "description": "Companies organize games by who they were developed or published by.",
+ "action": "Manage {arrow}",
+ "search": "Search companies...",
+ "searchGames": "Search company games...",
+ "noCompanies": "No companies",
+ "noGames": "No games",
+ "editor": {
+ "libraryTitle": "Game Library",
+ "libraryDescription": "Add, remove, or customise what this company has developed and/or published.",
+ "action": "Add Game {plus}",
+ "published": "Published",
+ "developed": "Developed",
+ "uploadIcon": "Upload icon",
+ "uploadBanner": "Upload banner",
+ "noDescription": "(no description)"
+ },
+ "addGame": {
+ "title": "Connect game to this company",
+ "description": "Pick a game to add to the company, and whether it should be listed as a developer, publisher, or both.",
+ "publisher": "Publisher?",
+ "developer": "Developer?",
+ "noGames": "No games to add"
+ },
+ "modals": {
+ "nameTitle": "Edit company name",
+ "nameDescription": "Edit the company's name. Used to match to new game imports.",
+ "shortDeckTitle": "Edit company description",
+ "shortDeckDescription": "Edit the company's description. Doesn't affect long (markdown) description.",
+ "websiteTitle": "Edit company website",
+ "websiteDescription": "Edit the company's website. Note: this will be a link, and won't have redirect protection."
+ }
+ }
+ }
+ },
+ "back": "Back to Library",
+ "collection": {
+ "addToNew": "Add to new collection",
+ "collections": "Collections",
+ "create": "Create Collection",
+ "createDesc": "Collections can used to organise your games and find them more easily, especially if you have a large library.",
+ "delete": "Delete Collection",
+ "namePlaceholder": "Collection name",
+ "noCollections": "No collections",
+ "notFound": "Collection not found",
+ "subheader": "Add a new collection to organize your games",
+ "title": "Collection"
+ },
+ "gameCount": "{0} games | {0} game | {0} games",
+ "inLib": "In Library",
+ "launcherOpen": "Open in Launcher",
+ "noGames": "No games in library",
+ "notFound": "Game not found",
+ "search": "Search library...",
+ "subheader": "Organize your games into collections for easy access, and access all your games."
+ },
+ "lowest": "lowest",
+ "news": {
+ "article": {
+ "add": "Add",
+ "content": "Content (Markdown)",
+ "create": "Create New Article",
+ "editor": "Editor",
+ "editorGuide": "Use the shortcuts above or write Markdown directly. Supports **bold**, *italic*, [links](url), and more.",
+ "new": "New article",
+ "preview": "Preview",
+ "shortDesc": "Short description",
+ "submit": "Submit",
+ "tagPlaceholder": "Add a tag...",
+ "titles": "Title",
+ "uploadCover": "Upload cover image"
+ },
+ "back": "Back to News",
+ "checkLater": "Check back later for updates.",
+ "delete": "Delete Article",
+ "filter": {
+ "all": "All time",
+ "month": "This month",
+ "week": "This week",
+ "year": "This year"
+ },
+ "none": "No articles",
+ "notFound": "Article not found",
+ "search": "Search articles",
+ "searchPlaceholder": "Search articles...",
+ "subheader": "Stay up to date with the latest updates and announcements.",
+ "title": "Latest News"
+ },
+ "options": "Options",
+ "security": "Security",
+ "selectLanguage": "Select language",
+ "settings": {
+ "admin": {
+ "description": "Configure Drop settings",
+ "store": {
+ "dropGameAltPlaceholder": "Example Game icon",
+ "dropGameDescriptionPlaceholder": "This is an example game. It will be replaced if you import a game.",
+ "dropGameNamePlaceholder": "Example Game",
+ "showGamePanelTextDecoration": "Show title and description on game tiles (default: on)",
+ "title": "Store"
+ },
+ "title": "Settings"
+ }
+ },
+ "store": {
+ "about": "About",
+ "commingSoon": "coming soon",
+ "developers": "Developers | Developer | Developers",
+ "exploreMore": "Explore more {arrow}",
+ "featured": "Featured",
+ "images": "Game Images",
+ "lookAt": "Check it out",
+ "noDevelopers": "No developers",
+ "noGame": "no game",
+ "noImages": "No images",
+ "noPublishers": "No publishers.",
+ "noTags": "No tags",
+ "openAdminDashboard": "Open in Admin Dashboard",
+ "platform": "Platform | Platform | Platforms",
+ "publishers": "Publishers | Publisher | Publishers",
+ "rating": "Rating",
+ "readLess": "Click to read less",
+ "readMore": "Click to read more",
+ "recentlyAdded": "Recently Added",
+ "recentlyReleased": "Recently released",
+ "recentlyUpdated": "Recently Updated",
+ "released": "Released",
+ "reviews": "({0} Reviews)",
+ "tags": "Tags",
+ "title": "Store",
+ "view": {
+ "sort": "Sort",
+ "srFilters": "Filters",
+ "srGames": "Games",
+ "srViewGrid": "View grid"
+ },
+ "viewInStore": "View in Store",
+ "website": "Website"
+ },
+ "tasks": {
+ "admin": {
+ "back": "{arrow} Back to Tasks",
+ "completedTasksTitle": "Completed tasks",
+ "dailyScheduledTitle": "Daily scheduled tasks",
+ "noTasksRunning": "No tasks currently running",
+ "runningTasksTitle": "Running tasks",
+ "scheduled": {
+ "checkUpdateDescription": "Check if Drop has an update.",
+ "checkUpdateName": "Check update.",
+ "cleanupInvitationsDescription": "Cleans up expired invitations from the database to save space.",
+ "cleanupInvitationsName": "Clean up invitations",
+ "cleanupObjectsDescription": "Detects and deletes unreferenced and unused objects to save space.",
+ "cleanupObjectsName": "Clean up objects",
+ "cleanupSessionsDescription": "Cleans up expired sessions to save space and ensure security.",
+ "cleanupSessionsName": "Clean up sessions."
+ },
+ "viewTask": "View {arrow}",
+ "weeklyScheduledTitle": "Weekly scheduled tasks"
+ }
+ },
+ "title": "Drop",
+ "titleTemplate": "{0} - Drop",
+ "todo": "Todo",
+ "type": "Type",
+ "upload": "Upload",
+ "uploadFile": "Upload file",
+ "userHeader": {
+ "closeSidebar": "Close sidebar",
+ "links": {
+ "community": "Community",
+ "library": "Library",
+ "news": "News"
+ },
+ "profile": {
+ "admin": "Admin Dashboard",
+ "settings": "Account settings"
+ }
+ },
+ "users": {
+ "admin": {
+ "adminHeader": "Admin?",
+ "adminUserLabel": "Admin user",
+ "authentication": {
+ "configure": "Configure",
+ "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.",
+ "disabled": "Disabled",
+ "enabled": "Enabled",
+ "enabledKey": "Enabled?",
+ "oidc": "OpenID Connect",
+ "simple": "Simple (username/password)",
+ "srOpenOptions": "Open options",
+ "title": "Authentication"
+ },
+ "authLink": "Authentication {arrow}",
+ "authoptionsHeader": "Auth Options",
+ "delete": "Delete",
+ "deleteUser": "Delete user {0}",
+ "description": "Manage the users on your Drop instance, and configure your authentication methods.",
+ "displayNameHeader": "Display Name",
+ "emailHeader": "Email",
+ "normalUserLabel": "Normal user",
+ "simple": {
+ "adminInvitation": "Admin invitation",
+ "createInvitation": "Create invitation",
+ "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.",
+ "expires": "Expires: {expiry}",
+ "invitationTitle": "invitations",
+ "invite3Days": "3 days",
+ "invite6Months": "6 months",
+ "inviteAdminSwitchDescription": "Create this user as an administrator",
+ "inviteAdminSwitchLabel": "Admin invitation",
+ "inviteButton": "Invite",
+ "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.",
+ "inviteEmailDescription": "Must be in the format user{'@'}example.com",
+ "inviteEmailLabel": "Email address (optional)",
+ "inviteEmailPlaceholder": "me{'@'}example.com",
+ "inviteExpiryLabel": "Expires",
+ "inviteMonth": "1 month",
+ "inviteNever": "Never",
+ "inviteTitle": "Invite user to Drop",
+ "inviteUsernameFormat": "Must be 5 or more characters",
+ "inviteUsernameLabel": "Username (optional)",
+ "inviteUsernamePlaceholder": "myUsername",
+ "inviteWeek": "1 week",
+ "inviteYear": "1 year",
+ "neverExpires": "Never expires.",
+ "noEmailEnforced": "No email enforced.",
+ "noInvitations": "No invitations.",
+ "noUsernameEnforced": "No username enforced.",
+ "title": "Simple authentication",
+ "userInvitation": "User invitation"
+ },
+ "srEditLabel": "Edit",
+ "usernameHeader": "Username"
+ }
+ },
+ "welcome": "American, Welcome!"
+}
diff --git a/i18n/locales/es.json b/i18n/locales/es.json
new file mode 100644
index 00000000..0967ef42
--- /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 00000000..0967ef42
--- /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 00000000..0967ef42
--- /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 00000000..0967ef42
--- /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 00000000..0967ef42
--- /dev/null
+++ b/i18n/locales/zh_tw.json
@@ -0,0 +1 @@
+{}
diff --git a/layouts/admin.vue b/layouts/admin.vue
index 52ca79fb..24cef9b6 100644
--- a/layouts/admin.vue
+++ b/layouts/admin.vue
@@ -2,43 +2,83 @@