From 102c23ec1ecad9bc7cd83f5b28cde0c583365a91 Mon Sep 17 00:00:00 2001 From: NGPixel Date: Sat, 27 May 2023 04:57:27 +0000 Subject: [PATCH] feat: locale system groundwork + various improvements --- .devcontainer/Dockerfile | 19 +- .devcontainer/wait-for.sh | 191 ++ .eslintrc.yml | 4 +- .gitignore | 5 +- .vscode/settings.json | 3 +- localazy.json | 13 + server/core/kernel.mjs | 2 + server/db/migrations/3.0.0.mjs | 51 +- server/graph/resolvers/localization.mjs | 6 +- server/graph/schemas/localization.graphql | 11 +- server/graph/schemas/site.graphql | 14 +- server/locales/en.json | 3 + server/locales/list.json | 9 - server/locales/metadata.mjs | 31 + server/models/locales.mjs | 60 + server/models/sites.mjs | 8 +- server/package-lock.json | 23 - server/package.json | 3 + server/web.mjs | 2 +- ux/package-lock.json | 125 +- ux/package.json | 8 +- .../icons/ultraviolet-cellular-network.svg | 1 + ux/src/App.vue | 38 +- ux/src/boot/i18n.js | 7 +- ux/src/components/EditorMarkdown.vue | 6 + .../EditorMarkdownUserSettingsOverlay.vue | 196 ++ ux/src/components/MainOverlayDialog.vue | 4 + ux/src/components/PagePropertiesDialog.vue | 4 +- ux/src/components/TreeBrowserDialog.vue | 4 +- ux/src/i18n/index.js | 5 - ux/src/i18n/locales/en.json | 1766 ----------------- ux/src/pages/AdminGeneral.vue | 61 +- ux/src/pages/AdminLocale.vue | 27 +- ux/src/stores/page.js | 6 +- ux/src/stores/site.js | 39 +- 35 files changed, 803 insertions(+), 1952 deletions(-) create mode 100644 .devcontainer/wait-for.sh create mode 100644 localazy.json delete mode 100644 server/locales/list.json create mode 100644 server/locales/metadata.mjs create mode 100644 ux/public/_assets/icons/ultraviolet-cellular-network.svg create mode 100644 ux/src/components/EditorMarkdownUserSettingsOverlay.vue delete mode 100644 ux/src/i18n/index.js delete mode 100644 ux/src/i18n/locales/en.json diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index b7f8b419..73a8f25a 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -59,10 +59,14 @@ RUN curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o / RUN echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian \ $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null -# Add ngrok source +# Add ngrok Source RUN curl -s https://ngrok-agent.s3.amazonaws.com/ngrok.asc | sudo tee /etc/apt/trusted.gpg.d/ngrok.asc > /dev/null && \ echo "deb https://ngrok-agent.s3.amazonaws.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/ngrok.list +# Add Localazy Source +RUN curl -sS https://dist.localazy.com/debian/pubkey.gpg | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/localazy.gpg && \ + echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/trusted.gpg.d/localazy.gpg] https://maven.localazy.com/repository/apt/ stable main" | sudo tee /etc/apt/sources.list.d/localazy.list + # Install the packages we need RUN apt-get update && export DEBIAN_FRONTEND=noninteractive && apt-get install -qy \ bash \ @@ -73,6 +77,7 @@ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive && apt-get install - less \ git \ gnupg2 \ + localazy \ nano \ netcat \ ngrok \ @@ -81,17 +86,17 @@ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive && apt-get install - wget # avoid million NPM install messages -ENV npm_config_loglevel warn +ENV npm_config_loglevel=warn # allow installing when the main user is root -ENV npm_config_unsafe_perm true +ENV npm_config_unsafe_perm=true # disable NPM funding messages -ENV npm_config_fund false +ENV npm_config_fund=false # Colorize the bash shell RUN sed -i 's/#force_color_prompt=/force_color_prompt=/' /root/.bashrc -# Fetch wait-for utility -ADD https://raw.githubusercontent.com/eficode/wait-for/v2.2.3/wait-for /usr/local/bin/ +# Copy wait-for utility +COPY wait-for.sh /usr/local/bin/wait-for RUN chmod +rx /usr/local/bin/wait-for # Copy the startup file @@ -102,3 +107,5 @@ RUN sed -i 's/\r$//' /docker-init.sh && \ # Create workspace RUN mkdir -p /workspace WORKDIR /workspace + +ENV NODE_ENV=development diff --git a/.devcontainer/wait-for.sh b/.devcontainer/wait-for.sh new file mode 100644 index 00000000..3c382ef7 --- /dev/null +++ b/.devcontainer/wait-for.sh @@ -0,0 +1,191 @@ +#!/bin/sh + +# The MIT License (MIT) +# +# Copyright (c) 2017 Eficode Oy +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +VERSION="2.2.3" + +set -- "$@" -- "$TIMEOUT" "$QUIET" "$PROTOCOL" "$HOST" "$PORT" "$result" +TIMEOUT=15 +QUIET=0 +# The protocol to make the request with, either "tcp" or "http" +PROTOCOL="tcp" + +echoerr() { + if [ "$QUIET" -ne 1 ]; then printf "%s\n" "$*" 1>&2; fi +} + +usage() { + exitcode="$1" + cat << USAGE >&2 +Usage: + $0 host:port|url [-t timeout] [-- command args] + -q | --quiet Do not output any status messages + -t TIMEOUT | --timeout=timeout Timeout in seconds, zero for no timeout + -v | --version Show the version of this tool + -- COMMAND ARGS Execute command with args after the test finishes +USAGE + exit "$exitcode" +} + +wait_for() { + case "$PROTOCOL" in + tcp) + if ! command -v nc >/dev/null; then + echoerr 'nc command is missing!' + exit 1 + fi + ;; + http) + if ! command -v wget >/dev/null; then + echoerr 'wget command is missing!' + exit 1 + fi + ;; + esac + + TIMEOUT_END=$(($(date +%s) + TIMEOUT)) + + while :; do + case "$PROTOCOL" in + tcp) + nc -w 1 -z "$HOST" "$PORT" > /dev/null 2>&1 + ;; + http) + wget --timeout=1 -q "$HOST" -O /dev/null > /dev/null 2>&1 + ;; + *) + echoerr "Unknown protocol '$PROTOCOL'" + exit 1 + ;; + esac + + result=$? + + if [ $result -eq 0 ] ; then + if [ $# -gt 7 ] ; then + for result in $(seq $(($# - 7))); do + result=$1 + shift + set -- "$@" "$result" + done + + TIMEOUT=$2 QUIET=$3 PROTOCOL=$4 HOST=$5 PORT=$6 result=$7 + shift 7 + exec "$@" + fi + exit 0 + fi + + if [ $TIMEOUT -ne 0 -a $(date +%s) -ge $TIMEOUT_END ]; then + echo "Operation timed out" >&2 + exit 1 + fi + + sleep 1 + done +} + +while :; do + case "$1" in + http://*|https://*) + HOST="$1" + PROTOCOL="http" + shift 1 + ;; + *:* ) + HOST=$(printf "%s\n" "$1"| cut -d : -f 1) + PORT=$(printf "%s\n" "$1"| cut -d : -f 2) + shift 1 + ;; + -v | --version) + echo $VERSION + exit + ;; + -q | --quiet) + QUIET=1 + shift 1 + ;; + -q-*) + QUIET=0 + echoerr "Unknown option: $1" + usage 1 + ;; + -q*) + QUIET=1 + result=$1 + shift 1 + set -- -"${result#-q}" "$@" + ;; + -t | --timeout) + TIMEOUT="$2" + shift 2 + ;; + -t*) + TIMEOUT="${1#-t}" + shift 1 + ;; + --timeout=*) + TIMEOUT="${1#*=}" + shift 1 + ;; + --) + shift + break + ;; + --help) + usage 0 + ;; + -*) + QUIET=0 + echoerr "Unknown option: $1" + usage 1 + ;; + *) + QUIET=0 + echoerr "Unknown argument: $1" + usage 1 + ;; + esac +done + +if ! [ "$TIMEOUT" -ge 0 ] 2>/dev/null; then + echoerr "Error: invalid timeout '$TIMEOUT'" + usage 3 +fi + +case "$PROTOCOL" in + tcp) + if [ "$HOST" = "" ] || [ "$PORT" = "" ]; then + echoerr "Error: you need to provide a host and port to test." + usage 2 + fi + ;; + http) + if [ "$HOST" = "" ]; then + echoerr "Error: you need to provide a host to test." + usage 2 + fi + ;; +esac + +wait_for "$@" diff --git a/.eslintrc.yml b/.eslintrc.yml index 658e2d73..36dd612b 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -1,13 +1,12 @@ extends: - requarks - plugin:vue/strongly-recommended - - plugin:cypress/recommended env: node: true jest: true parserOptions: parser: babel-eslint - ecmaVersion: 2017 + ecmaVersion: 2020 allowImportExportEverywhere: true globals: document: false @@ -22,3 +21,4 @@ ignorePatterns: - 'repo/**' - 'data/**' - 'logs/**' + - 'server/locales/**' diff --git a/.gitignore b/.gitignore index f0381073..fc694355 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,7 @@ npm-debug.log* # Config Files /config.yml +/localazy.keys.json # Data directories /repo @@ -40,4 +41,6 @@ test-results/ .scannerwork # Localization Resources -/server/locales/**/*.yml +/server/locales/*.json +!/server/locales/en.json +!/server/locales/list.json diff --git a/.vscode/settings.json b/.vscode/settings.json index 02371f93..b606fec0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,8 +11,9 @@ "source.fixAll.eslint": true }, "i18n-ally.localesPaths": [ - "ux/src/i18n/locales" + "server/locales", ], + "i18n-ally.pathMatcher": "{locale}.json", "i18n-ally.keystyle": "flat", "i18n-ally.sortKeys": true, "i18n-ally.enabledFrameworks": [ diff --git a/localazy.json b/localazy.json new file mode 100644 index 00000000..8c6faf52 --- /dev/null +++ b/localazy.json @@ -0,0 +1,13 @@ +{ + "upload": { + "folder": "server/locales", + "files": "en.json", + "type": "json" + }, + + "download": { + "folder": "server/locales", + "files": "${lang}.json", + "metadataFileJs": "metadata.mjs" + } +} diff --git a/server/core/kernel.mjs b/server/core/kernel.mjs index 3daa9e77..b33efd99 100644 --- a/server/core/kernel.mjs +++ b/server/core/kernel.mjs @@ -70,6 +70,8 @@ export default { * Post-Web Boot Sequence */ async postBootWeb() { + await WIKI.db.locales.refreshFromDisk() + await WIKI.db.analytics.refreshProvidersFromDisk() await WIKI.db.authentication.refreshStrategiesFromDisk() await WIKI.db.commentProviders.refreshProvidersFromDisk() diff --git a/server/db/migrations/3.0.0.mjs b/server/db/migrations/3.0.0.mjs index b8ef53eb..1eaff4d3 100644 --- a/server/db/migrations/3.0.0.mjs +++ b/server/db/migrations/3.0.0.mjs @@ -163,11 +163,14 @@ export async function up (knex) { }) // LOCALES ----------------------------- .createTable('locales', table => { - table.string('code', 5).notNullable().primary() - table.jsonb('strings') - table.boolean('isRTL').notNullable().defaultTo(false) + table.string('code', 10).notNullable().primary() table.string('name').notNullable() table.string('nativeName').notNullable() + table.string('language', 2).notNullable().index() + table.string('region', 2) + table.string('script', 4) + table.boolean('isRTL').notNullable().defaultTo(false) + table.jsonb('strings') table.integer('completeness').notNullable().defaultTo(0) table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now()) table.timestamp('updatedAt').notNullable().defaultTo(knex.fn.now()) @@ -207,7 +210,7 @@ export async function up (knex) { .createTable('pageLinks', table => { table.increments('id').primary() table.string('path').notNullable() - table.string('localeCode', 5).notNullable() + table.string('localeCode', 10).notNullable() }) // PAGES ------------------------------- .createTable('pages', table => { @@ -284,7 +287,7 @@ export async function up (knex) { table.string('fileName').notNullable().index() table.string('hash').notNullable().index() table.enu('type', ['folder', 'page', 'asset']).notNullable().index() - table.string('localeCode', 5).notNullable().defaultTo('en').index() + table.string('localeCode', 10).notNullable().defaultTo('en').index() table.string('title').notNullable() table.jsonb('meta').notNullable().defaultTo('{}') table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now()) @@ -295,6 +298,13 @@ export async function up (knex) { table.uuid('id').notNullable().primary() table.binary('data').notNullable() }) + // USER EDITOR SETTINGS ---------------- + .createTable('userEditorSettings', table => { + table.uuid('id').notNullable() + table.string('editor').notNullable() + table.jsonb('config').notNullable().defaultTo('{}') + table.primary(['id', 'editor']) + }) // USER KEYS --------------------------- .createTable('userKeys', table => { table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()')) @@ -359,7 +369,7 @@ export async function up (knex) { table.uuid('siteId').notNullable().references('id').inTable('sites').index() }) .table('pageHistory', table => { - table.string('localeCode', 5).references('code').inTable('locales') + table.string('localeCode', 10).references('code').inTable('locales') table.uuid('authorId').notNullable().references('id').inTable('users') table.uuid('siteId').notNullable().references('id').inTable('sites').index() }) @@ -368,7 +378,7 @@ export async function up (knex) { table.index(['path', 'localeCode']) }) .table('pages', table => { - table.string('localeCode', 5).references('code').inTable('locales').index() + table.string('localeCode', 10).references('code').inTable('locales').index() table.uuid('authorId').notNullable().references('id').inTable('users').index() table.uuid('creatorId').notNullable().references('id').inTable('users').index() table.uuid('ownerId').notNullable().references('id').inTable('users').index() @@ -387,9 +397,6 @@ export async function up (knex) { .table('userKeys', table => { table.uuid('userId').notNullable().references('id').inTable('users') }) - .table('users', table => { - table.string('localeCode', 5).references('code').inTable('locales').notNullable().defaultTo('en') - }) // ===================================== // DEFAULT DATA @@ -520,16 +527,6 @@ export async function up (knex) { } ]) - // -> DEFAULT LOCALE - - await knex('locales').insert({ - code: 'en', - strings: {}, - isRTL: false, - name: 'English', - nativeName: 'English' - }) - // -> DEFAULT SITE await knex('sites').insert({ @@ -544,6 +541,7 @@ export async function up (knex) { footerExtra: '', pageExtensions: ['md', 'html', 'txt'], pageCasing: true, + discoverable: false, defaults: { tocDepth: { min: 1, @@ -566,9 +564,10 @@ export async function up (knex) { follow: true }, authStrategies: [{ id: authModuleId, order: 0, isVisible: true }], - locale: 'en', - localeNamespacing: false, - localeNamespaces: [], + locales: { + primary: 'en', + active: ['en'] + }, assets: { logo: false, logoExt: 'svg', @@ -700,8 +699,7 @@ export async function up (knex) { timeFormat: '12h', appearance: 'site', cvd: 'none' - }, - localeCode: 'en' + } }, { id: userGuestId, @@ -718,8 +716,7 @@ export async function up (knex) { timeFormat: '12h', appearance: 'site', cvd: 'none' - }, - localeCode: 'en' + } } ]) diff --git a/server/graph/resolvers/localization.mjs b/server/graph/resolvers/localization.mjs index 9a7d61c0..6b1c1eb1 100644 --- a/server/graph/resolvers/localization.mjs +++ b/server/graph/resolvers/localization.mjs @@ -5,7 +5,7 @@ export default { Query: { async locales(obj, args, context, info) { let remoteLocales = await WIKI.cache.get('locales') - let localLocales = await WIKI.db.locales.query().select('code', 'isRTL', 'name', 'nativeName', 'createdAt', 'updatedAt', 'availability') + let localLocales = await WIKI.db.locales.query().select('code', 'isRTL', 'name', 'nativeName', 'createdAt', 'updatedAt', 'completeness') remoteLocales = remoteLocales || localLocales return _.map(remoteLocales, rl => { let isInstalled = _.some(localLocales, ['code', rl.code]) @@ -16,8 +16,8 @@ export default { } }) }, - translations (obj, args, context, info) { - return WIKI.lang.getByNamespace(args.locale, args.namespace) + localeStrings (obj, args, context, info) { + return WIKI.db.locales.getStrings(args.locale) } }, Mutation: { diff --git a/server/graph/schemas/localization.graphql b/server/graph/schemas/localization.graphql index 8bdcd6ef..64c5f921 100644 --- a/server/graph/schemas/localization.graphql +++ b/server/graph/schemas/localization.graphql @@ -4,7 +4,7 @@ extend type Query { locales: [LocalizationLocale] - translations(locale: String!, namespace: String!): [Translation] + localeStrings(locale: String!): JSON } extend type Mutation { @@ -25,7 +25,7 @@ extend type Mutation { # ----------------------------------------------- type LocalizationLocale { - availability: Int + completeness: Int code: String createdAt: Date installDate: Date @@ -33,10 +33,7 @@ type LocalizationLocale { isRTL: Boolean name: String nativeName: String + region: String + script: String updatedAt: Date } - -type Translation { - key: String - value: String -} diff --git a/server/graph/schemas/site.graphql b/server/graph/schemas/site.graphql index 8fce7324..bac323fd 100644 --- a/server/graph/schemas/site.graphql +++ b/server/graph/schemas/site.graphql @@ -65,11 +65,10 @@ type Site { sitemap: Boolean robots: SiteRobots features: SiteFeatures + discoverable: Boolean defaults: SiteDefaults uploads: SiteUploads - locale: String - localeNamespaces: [String] - localeNamespacing: Boolean + locales: SiteLocales editors: SiteEditors theme: SiteTheme } @@ -98,11 +97,9 @@ type SiteUploads { normalizeFilename: Boolean } -type SiteLocale { - locale: String - autoUpdate: Boolean - namespacing: Boolean - namespaces: [String] +type SiteLocales { + primary: String + active: [String] } type SiteEditors { @@ -184,6 +181,7 @@ input SiteUpdateInput { sitemap: Boolean robots: SiteRobotsInput features: SiteFeaturesInput + discoverable: Boolean defaults: SiteDefaultsInput uploads: SiteUploadsInput editors: SiteEditorsInput diff --git a/server/locales/en.json b/server/locales/en.json index fcd8ee0b..49d38e04 100644 --- a/server/locales/en.json +++ b/server/locales/en.json @@ -227,6 +227,9 @@ "admin.general.defaultTocDepth": "Default ToC Depth", "admin.general.defaultTocDepthHint": "The default minimum and maximum header levels to show in the table of contents.", "admin.general.defaults": "Site Defaults", + "admin.general.discoverable": "Make Discoverable in the Wiki Directory", + "admin.general.discoverableHint": "Add your wiki to the Wiki Directory so that it can be discovered by other users and wikis.", + "admin.general.discovery": "Discovery", "admin.general.displaySiteTitle": "Display Site Title", "admin.general.displaySiteTitleHint": "Should the site title be displayed next to the logo? If your logo isn't square and contain your brand name, turn this option off.", "admin.general.favicon": "Favicon", diff --git a/server/locales/list.json b/server/locales/list.json deleted file mode 100644 index adb3b377..00000000 --- a/server/locales/list.json +++ /dev/null @@ -1,9 +0,0 @@ -[ - { - "code": "en", - "name": "English", - "nativeName": "English", - "rtl": false, - "completeness": 100 - } -] diff --git a/server/locales/metadata.mjs b/server/locales/metadata.mjs new file mode 100644 index 00000000..00fd63db --- /dev/null +++ b/server/locales/metadata.mjs @@ -0,0 +1,31 @@ +const localazyMetadata = { + projectUrl: "https://localazy.com/p/wiki", + baseLocale: "en", + languages: [ + { + language: "en", + region: "", + script: "", + isRtl: false, + name: "English", + localizedName: "English", + pluralType: (n) => { return (n===1) ? "one" : "other"; } + } + ], + files: [ + { + cdnHash: "54b977214afbffe2ffeb07d0ccb03558e75e4408", + file: "file.json", + path: "", + library: "", + module: "", + buildType: "", + productFlavors: [], + cdnFiles: { + "en#": "https://delivery.localazy.com/_a7797965569058078203416ae5aa/_e0/54b977214afbffe2ffeb07d0ccb03558e75e4408/en/file.json" + } + } + ] +}; + +export default localazyMetadata; diff --git a/server/models/locales.mjs b/server/models/locales.mjs index 356384ad..a76e4a5e 100644 --- a/server/models/locales.mjs +++ b/server/models/locales.mjs @@ -1,4 +1,8 @@ import { Model } from 'objection' +import { find } from 'lodash-es' +import { stat, readFile } from 'node:fs/promises' +import path from 'node:path' +import { DateTime } from 'luxon' /** * Locales model @@ -36,6 +40,62 @@ export class Locale extends Model { this.updatedAt = new Date().toISOString() } + static async refreshFromDisk ({ force = false } = {}) { + try { + const localesMeta = (await import(`../locales/metadata.mjs`)).default + WIKI.logger.info(`Found ${localesMeta.languages.length} locales: [ OK ]`) + + const dbLocales = await WIKI.db.locales.query().select('code', 'updatedAt') + + for (const lang of localesMeta.languages) { + // -> Build filename + const langFilenameParts = [lang.language] + if (lang.region) { + langFilenameParts.push(lang.region) + } + if (lang.script) { + langFilenameParts.push(lang.script) + } + const langFilename = langFilenameParts.join('-') + + // -> Get DB version + const dbLang = find(dbLocales, ['code', langFilename]) + + // -> Get File version + const flPath = path.join(WIKI.SERVERPATH, `locales/${langFilename}.json`) + const flStat = await stat(flPath) + const flUpdatedAt = DateTime.fromJSDate(flStat.mtime) + + // -> Load strings + if (!dbLang || DateTime.fromJSDate(dbLang.updatedAt) < flUpdatedAt || force) { + WIKI.logger.debug(`Loading locale ${langFilename} into DB...`) + const flStrings = JSON.parse(await readFile(flPath, 'utf8')) + await WIKI.db.locales.query().insert({ + code: langFilename, + name: lang.name, + nativeName: lang.localizedName, + language: lang.language, + region: lang.region, + script: lang.script, + isRTL: lang.isRtl, + strings: flStrings + }).onConflict('code').merge(['strings', 'updatedAt']) + } else { + WIKI.logger.debug(`Locale ${langFilename} is newer in the DB. Skipping disk version.`) + } + } + } catch (err) { + WIKI.logger.warn(`Failed to load locales from disk: [ FAILED ]`) + WIKI.logger.warn(err) + return false + } + } + + static async getStrings (locale) { + const { strings } = await WIKI.db.locales.query().findOne('code', locale).column('strings') + return strings + } + static async getNavLocales({ cache = false } = {}) { return [] // if (!WIKI.config.lang.namespacing) { diff --git a/server/models/sites.mjs b/server/models/sites.mjs index d5d3868e..537ffda5 100644 --- a/server/models/sites.mjs +++ b/server/models/sites.mjs @@ -58,6 +58,7 @@ export class Site extends Model { footerExtra: '', pageExtensions: ['md', 'html', 'txt'], pageCasing: true, + discoverable: false, defaults: { tocDepth: { min: 1, @@ -79,9 +80,10 @@ export class Site extends Model { index: true, follow: true }, - locale: 'en', - localeNamespacing: false, - localeNamespaces: [], + locales: { + primary: 'en', + active: ['en'] + }, assets: { logo: false, logoExt: 'svg', diff --git a/server/package-lock.json b/server/package-lock.json index 2e6327ed..2663aacc 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -637,18 +637,6 @@ "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@graphql-tools/mock/node_modules/@graphql-tools/utils": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-9.2.1.tgz", - "integrity": "sha512-WUw506Ql6xzmOORlriNrD6Ugx+HjVgYxt9KCXD9mHAak+eaXSwuGGPyE60hy9xaDEoXKBsG7SkG69ybitaVl6A==", - "dependencies": { - "@graphql-typed-document-node/core": "^3.1.1", - "tslib": "^2.4.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" - } - }, "node_modules/@graphql-tools/schema": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-10.0.0.tgz", @@ -1556,17 +1544,6 @@ "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/apollo-server-core/node_modules/@graphql-tools/utils": { - "version": "8.9.0", - "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-8.9.0.tgz", - "integrity": "sha512-pjJIWH0XOVnYGXCqej8g/u/tsfV4LvLlj0eATKQu5zwnxd/TiTHq7Cg313qUPTFFHZ3PP5wJ15chYVtLDwaymg==", - "dependencies": { - "tslib": "^2.4.0" - }, - "peerDependencies": { - "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" - } - }, "node_modules/apollo-server-core/node_modules/value-or-promise": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/value-or-promise/-/value-or-promise-1.0.11.tgz", diff --git a/server/package.json b/server/package.json index 2e857594..744da960 100644 --- a/server/package.json +++ b/server/package.json @@ -185,6 +185,9 @@ "eslint-plugin-standard": "4.1.0", "nodemon": "2.0.22" }, + "overrides": { + "@graphql-tools/utils": "10.0.0" + }, "collective": { "type": "opencollective", "url": "https://opencollective.com/wikijs", diff --git a/server/web.mjs b/server/web.mjs index 6c4e1404..de82384e 100644 --- a/server/web.mjs +++ b/server/web.mjs @@ -206,7 +206,7 @@ export async function init () { id: currentSite.id, title: currentSite.config.title, darkMode: currentSite.config.theme.dark, - lang: currentSite.config.locale, + lang: currentSite.config.locales.primary, rtl: false, // TODO: handle RTL company: currentSite.config.company, contentLicense: currentSite.config.contentLicense diff --git a/ux/package-lock.json b/ux/package-lock.json index faa6f2af..1c5bd08a 100644 --- a/ux/package-lock.json +++ b/ux/package-lock.json @@ -38,7 +38,7 @@ "@tiptap/starter-kit": "2.0.3", "@tiptap/vue-3": "2.0.3", "apollo-upload-client": "17.0.0", - "browser-fs-access": "0.34.0", + "browser-fs-access": "0.34.1", "clipboard": "2.0.11", "codemirror": "5.65.11", "codemirror-asciidoc": "1.0.4", @@ -84,7 +84,7 @@ "quasar": "2.12.0", "slugify": "1.6.6", "socket.io-client": "4.6.1", - "tabulator-tables": "5.4.4", + "tabulator-tables": "5.5.0", "tippy.js": "6.3.7", "twemoji": "14.0.2", "uuid": "9.0.0", @@ -107,9 +107,9 @@ "eslint": "8.41.0", "eslint-config-standard": "17.0.0", "eslint-plugin-import": "2.27.5", - "eslint-plugin-n": "16.0.0", + "eslint-plugin-n": "15.7.0", "eslint-plugin-promise": "6.1.1", - "eslint-plugin-vue": "9.13.0" + "eslint-plugin-vue": "9.14.0" }, "engines": { "node": ">= 18.0", @@ -2136,9 +2136,9 @@ } }, "node_modules/browser-fs-access": { - "version": "0.34.0", - "resolved": "https://registry.npmjs.org/browser-fs-access/-/browser-fs-access-0.34.0.tgz", - "integrity": "sha512-F49NXUfjE9ZkDSF9Xr0PXPt89Sb9TIZDpxWEHOp0kYnT7RfciGLYcpHqXFFJ/+CiocOlT+5DOJ8Y7+XrKrzafw==" + "version": "0.34.1", + "resolved": "https://registry.npmjs.org/browser-fs-access/-/browser-fs-access-0.34.1.tgz", + "integrity": "sha512-HPaRf2yimp8kWSuWJXc8Mi78dPbDzfduA+Gyq14H4jlMvd6XNfIRm36Y2yRLaa4x0gwcGuepj4zf14oiTlxrxQ==" }, "node_modules/browserlist": { "version": "1.0.1", @@ -3372,23 +3372,47 @@ "ms": "^2.1.1" } }, - "node_modules/eslint-plugin-es-x": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-6.2.1.tgz", - "integrity": "sha512-uR34zUhZ9EBoiSD2DdV5kHLpydVEvwWqjteUr9sXRgJknwbKZJZhdJ7uFnaTtd+Nr/2G3ceJHnHXrFhJ67n3Tw==", + "node_modules/eslint-plugin-es": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-4.1.0.tgz", + "integrity": "sha512-GILhQTnjYE2WorX5Jyi5i4dz5ALWxBIdQECVQavL6s7cI76IZTDWleTHkxz/QT3kvcs2QlGHvKLYsSlPOlPXnQ==", "dev": true, "dependencies": { - "@eslint-community/eslint-utils": "^4.1.2", - "@eslint-community/regexpp": "^4.5.0" + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" }, "engines": { - "node": "^14.18.0 || >=16.0.0" + "node": ">=8.10.0" }, "funding": { - "url": "https://github.com/sponsors/ota-meshi" + "url": "https://github.com/sponsors/mysticatea" }, "peerDependencies": { - "eslint": ">=8" + "eslint": ">=4.19.1" + } + }, + "node_modules/eslint-plugin-es/node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/eslint-plugin-es/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" } }, "node_modules/eslint-plugin-import": { @@ -3447,22 +3471,22 @@ } }, "node_modules/eslint-plugin-n": { - "version": "16.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-16.0.0.tgz", - "integrity": "sha512-akkZTE3hsHBrq6CwmGuYCzQREbVUrA855kzcHqe6i0FLBkeY7Y/6tThCVkjUnjhvRBAlc+8lILcSe5QvvDpeZQ==", + "version": "15.7.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-15.7.0.tgz", + "integrity": "sha512-jDex9s7D/Qial8AGVIHq4W7NswpUD5DPDL2RH8Lzd9EloWUuvUkHfv4FRLMipH5q2UtyurorBkPeNi1wVWNh3Q==", "dev": true, "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", "builtins": "^5.0.1", - "eslint-plugin-es-x": "^6.1.0", + "eslint-plugin-es": "^4.1.0", + "eslint-utils": "^3.0.0", "ignore": "^5.1.1", - "is-core-module": "^2.12.0", + "is-core-module": "^2.11.0", "minimatch": "^3.1.2", - "resolve": "^1.22.2", - "semver": "^7.5.0" + "resolve": "^1.22.1", + "semver": "^7.3.8" }, "engines": { - "node": ">=16.0.0" + "node": ">=12.22.0" }, "funding": { "url": "https://github.com/sponsors/mysticatea" @@ -3483,9 +3507,9 @@ } }, "node_modules/eslint-plugin-vue": { - "version": "9.13.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.13.0.tgz", - "integrity": "sha512-aBz9A8WB4wmpnVv0pYUt86cmH9EkcwWzgEwecBxMoRNhQjTL5i4sqadnwShv/hOdr8Hbl8XANGV7dtX9UQIAyA==", + "version": "9.14.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.14.0.tgz", + "integrity": "sha512-4O7EuiqPGVQA1wYCzLvCzsBTv9JIPHLHhrf0k55DLzbwtmJbSw2TKS0G/l7pOwi9RWMSkjIT7ftChU5gZpgnJw==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.3.0", @@ -3519,6 +3543,33 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/eslint-visitor-keys": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", @@ -6419,6 +6470,18 @@ "node": ">= 0.4" } }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, "node_modules/register-service-worker": { "version": "1.7.2", "dev": true, @@ -7052,9 +7115,9 @@ "license": "MIT" }, "node_modules/tabulator-tables": { - "version": "5.4.4", - "resolved": "https://registry.npmjs.org/tabulator-tables/-/tabulator-tables-5.4.4.tgz", - "integrity": "sha512-WqPWLRwrD8UMISUjQqZyj6y9k3ajQBs7eJtRosbt9q8RRkZlYCJYGxLt3L44jEXaQCE5q5EJFbGeDUEDJa1a3w==" + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/tabulator-tables/-/tabulator-tables-5.5.0.tgz", + "integrity": "sha512-UVe26QIaGqFfaP5wfN51zF4Vy23MNTWyvM95bF7EWGsDRQm6b2MV0wiBzakVGFj28YAgz5PvEyfqLZF415PWxw==" }, "node_modules/tar-stream": { "version": "2.2.0", diff --git a/ux/package.json b/ux/package.json index fd200cab..736f4477 100644 --- a/ux/package.json +++ b/ux/package.json @@ -43,7 +43,7 @@ "@tiptap/starter-kit": "2.0.3", "@tiptap/vue-3": "2.0.3", "apollo-upload-client": "17.0.0", - "browser-fs-access": "0.34.0", + "browser-fs-access": "0.34.1", "clipboard": "2.0.11", "codemirror": "5.65.11", "codemirror-asciidoc": "1.0.4", @@ -89,7 +89,7 @@ "quasar": "2.12.0", "slugify": "1.6.6", "socket.io-client": "4.6.1", - "tabulator-tables": "5.4.4", + "tabulator-tables": "5.5.0", "tippy.js": "6.3.7", "twemoji": "14.0.2", "uuid": "9.0.0", @@ -112,9 +112,9 @@ "eslint": "8.41.0", "eslint-config-standard": "17.0.0", "eslint-plugin-import": "2.27.5", - "eslint-plugin-n": "16.0.0", + "eslint-plugin-n": "15.7.0", "eslint-plugin-promise": "6.1.1", - "eslint-plugin-vue": "9.13.0" + "eslint-plugin-vue": "9.14.0" }, "engines": { "node": ">= 18.0", diff --git a/ux/public/_assets/icons/ultraviolet-cellular-network.svg b/ux/public/_assets/icons/ultraviolet-cellular-network.svg new file mode 100644 index 00000000..03c3d5db --- /dev/null +++ b/ux/public/_assets/icons/ultraviolet-cellular-network.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ux/src/App.vue b/ux/src/App.vue index 48744a98..1e859139 100644 --- a/ux/src/App.vue +++ b/ux/src/App.vue @@ -10,6 +10,7 @@ import { useSiteStore } from 'src/stores/site' import { useUserStore } from 'src/stores/user' import { setCssVar, useQuasar } from 'quasar' import { useI18n } from 'vue-i18n' +import gql from 'graphql-tag' import '@mdi/font/css/materialdesignicons.css' @@ -27,7 +28,7 @@ const userStore = useUserStore() // I18N -const { t } = useI18n() +const i18n = useI18n({ useScope: 'global' }) // ROUTER @@ -88,6 +89,35 @@ async function applyTheme () { } } +// LOCALE + +async function fetchLocaleStrings (locale) { + try { + const resp = await APOLLO_CLIENT.query({ + query: gql` + query fetchLocaleStrings ( + $locale: String! + ) { + localeStrings ( + locale: $locale + ) + } + `, + fetchPolicy: 'cache-first', + variables: { + locale + } + }) + return resp?.data?.localeStrings + } catch (err) { + console.warn(err) + $q.notify({ + type: 'negative', + message: 'Failed to load locale strings.' + }) + } +} + // INIT SITE STORE if (typeof siteConfig !== 'undefined') { @@ -110,6 +140,10 @@ router.beforeEach(async (to, from) => { await siteStore.loadSite(window.location.hostname) console.info(`Using Site ID ${siteStore.id}`) } + // Locales + if (!i18n.availableLocales.includes('en')) { + i18n.setLocaleMessage('en', await fetchLocaleStrings('en')) + } // User Auth await userStore.refreshAuth() // User Profile @@ -128,7 +162,7 @@ EVENT_BUS.on('logout', () => { $q.notify({ type: 'positive', icon: 'las la-sign-out-alt', - message: t('auth.logoutSuccess') + message: i18n.t('auth.logoutSuccess') }) }) EVENT_BUS.on('applyTheme', () => { diff --git a/ux/src/boot/i18n.js b/ux/src/boot/i18n.js index 7506ff23..993529c0 100644 --- a/ux/src/boot/i18n.js +++ b/ux/src/boot/i18n.js @@ -1,12 +1,13 @@ import { boot } from 'quasar/wrappers' import { createI18n } from 'vue-i18n' -import messages from 'src/i18n' export default boot(({ app }) => { const i18n = createI18n({ legacy: false, - locale: 'en-US', - messages + locale: 'en', + fallbackLocale: 'en', + fallbackWarn: false, + messages: {} }) // Set i18n instance on app diff --git a/ux/src/components/EditorMarkdown.vue b/ux/src/components/EditorMarkdown.vue index c5082bbd..752b39ba 100644 --- a/ux/src/components/EditorMarkdown.vue +++ b/ux/src/components/EditorMarkdown.vue @@ -449,6 +449,10 @@ function processContent (newContent) { }) } +function openEditorSettings () { + siteStore.$patch({ overlay: 'EditorMarkdownConfig' }) +} + // MOUNTED onMounted(async () => { @@ -601,6 +605,7 @@ onMounted(async () => { }) EVENT_BUS.on('insertAsset', insertAssetClb) + EVENT_BUS.on('openEditorSettings', openEditorSettings) // this.$root.$on('editorInsert', opts => { // switch (opts.kind) { @@ -637,6 +642,7 @@ onMounted(async () => { onBeforeUnmount(() => { EVENT_BUS.off('insertAsset', insertAssetClb) + EVENT_BUS.off('openEditorSettings', openEditorSettings) if (editor) { editor.dispose() } diff --git a/ux/src/components/EditorMarkdownUserSettingsOverlay.vue b/ux/src/components/EditorMarkdownUserSettingsOverlay.vue new file mode 100644 index 00000000..dcbcc256 --- /dev/null +++ b/ux/src/components/EditorMarkdownUserSettingsOverlay.vue @@ -0,0 +1,196 @@ + + + diff --git a/ux/src/components/MainOverlayDialog.vue b/ux/src/components/MainOverlayDialog.vue index da28f4ab..1b75fff8 100644 --- a/ux/src/components/MainOverlayDialog.vue +++ b/ux/src/components/MainOverlayDialog.vue @@ -19,6 +19,10 @@ import { useSiteStore } from '../stores/site' import LoadingGeneric from './LoadingGeneric.vue' const overlays = { + EditorMarkdownConfig: defineAsyncComponent({ + loader: () => import('./EditorMarkdownUserSettingsOverlay.vue'), + loadingComponent: LoadingGeneric + }), FileManager: defineAsyncComponent({ loader: () => import('./FileManager.vue'), loadingComponent: LoadingGeneric diff --git a/ux/src/components/PagePropertiesDialog.vue b/ux/src/components/PagePropertiesDialog.vue index ee006d41..b585c239 100644 --- a/ux/src/components/PagePropertiesDialog.vue +++ b/ux/src/components/PagePropertiesDialog.vue @@ -32,8 +32,8 @@ q-card.page-properties-dialog ) q-scroll-area( ref='scrollArea' - :thumb-style='siteStore.thumbStyle' - :bar-style='siteStore.barStyle' + :thumb-style='siteStore.scrollStyle.thumb' + :bar-style='siteStore.scrollStyle.bar' style='height: calc(100% - 50px);' ) q-card-section(id='refCardInfo') diff --git a/ux/src/components/TreeBrowserDialog.vue b/ux/src/components/TreeBrowserDialog.vue index 39731f38..13487ec1 100644 --- a/ux/src/components/TreeBrowserDialog.vue +++ b/ux/src/components/TreeBrowserDialog.vue @@ -37,7 +37,7 @@ q-dialog(ref='dialogRef', @hide='onDialogHide') q-icon(:name='item.icon', size='sm') q-item-section q-item-label {{item.title}} - .page-save-dialog-path.font-robotomono {{folderPath}} + .page-save-dialog-path.font-robotomono {{currentFolderPath}} q-list.q-py-sm q-item blueprint-icon(icon='new-document') @@ -202,7 +202,7 @@ const barStyle = { // COMPUTED -const folderPath = computed(() => { +const currentFolderPath = computed(() => { if (!state.currentFolderId) { return '/' } else { diff --git a/ux/src/i18n/index.js b/ux/src/i18n/index.js deleted file mode 100644 index 1e05841a..00000000 --- a/ux/src/i18n/index.js +++ /dev/null @@ -1,5 +0,0 @@ -import en from './locales/en.json' - -export default { - 'en-US': en -} diff --git a/ux/src/i18n/locales/en.json b/ux/src/i18n/locales/en.json deleted file mode 100644 index be059a2c..00000000 --- a/ux/src/i18n/locales/en.json +++ /dev/null @@ -1,1766 +0,0 @@ -{ - "admin.adminArea": "Administration Area", - "admin.analytics.providerConfiguration": "Provider Configuration", - "admin.analytics.providerNoConfiguration": "This provider has no configuration options you can modify.", - "admin.analytics.providers": "Providers", - "admin.analytics.refreshSuccess": "List of providers refreshed successfully.", - "admin.analytics.saveSuccess": "Analytics configuration saved successfully", - "admin.analytics.subtitle": "Add analytics and tracking tools to your wiki", - "admin.analytics.title": "Analytics", - "admin.api.copyKeyTitle": "Copy API Key", - "admin.api.createInvalidData": "Some fields are missing or have invalid data.", - "admin.api.createSuccess": "API Key created successfully.", - "admin.api.disableButton": "Disable API", - "admin.api.disabled": "API Disabled", - "admin.api.enableButton": "Enable API", - "admin.api.enabled": "API Enabled", - "admin.api.expiration180d": "180 days", - "admin.api.expiration1y": "1 year", - "admin.api.expiration30d": "30 days", - "admin.api.expiration3y": "3 years", - "admin.api.expiration90d": "90 days", - "admin.api.groupSelected": "Use {group} group permissions", - "admin.api.groupsMissing": "You must select at least 1 group for this key.", - "admin.api.groupsSelected": "Use permissions from {count} groups", - "admin.api.headerCreated": "Created", - "admin.api.headerExpiration": "Expiration", - "admin.api.headerKeyEnding": "Key Ending", - "admin.api.headerLastUpdated": "Last Updated", - "admin.api.headerName": "Name", - "admin.api.headerRevoke": "Revoke", - "admin.api.key": "API Key", - "admin.api.nameInvalidChars": "Key name has invalid characters.", - "admin.api.nameMissing": "Key name is missing.", - "admin.api.newKeyButton": "New API Key", - "admin.api.newKeyCopyWarn": "Copy the key shown below as {bold}", - "admin.api.newKeyCopyWarnBold": "it will NOT be shown again.", - "admin.api.newKeyExpiration": "Expiration", - "admin.api.newKeyExpirationHint": "You can still revoke a key anytime regardless of the expiration.", - "admin.api.newKeyFullAccess": "Full Access", - "admin.api.newKeyGroup": "Group", - "admin.api.newKeyGroupError": "You must select a group.", - "admin.api.newKeyGroupHint": "The API key will have the same permissions as the selected group.", - "admin.api.newKeyGroupPermissions": "or use group permissions...", - "admin.api.newKeyGuestGroupError": "The guests group cannot be used for API keys.", - "admin.api.newKeyName": "Name", - "admin.api.newKeyNameError": "Name is missing or invalid.", - "admin.api.newKeyNameHint": "Purpose of this key", - "admin.api.newKeyPermissionScopes": "Permission Scopes", - "admin.api.newKeySuccess": "API key created successfully.", - "admin.api.newKeyTitle": "New API Key", - "admin.api.noKeyInfo": "No API keys have been generated yet.", - "admin.api.none": "There are no API keys yet.", - "admin.api.permissionGroups": "Group Permissions", - "admin.api.refreshSuccess": "List of API keys has been refreshed.", - "admin.api.revoke": "Revoke", - "admin.api.revokeConfirm": "Revoke API Key?", - "admin.api.revokeConfirmText": "Are you sure you want to revoke key {name}? This action cannot be undone!", - "admin.api.revokeSuccess": "The key has been revoked successfully.", - "admin.api.revoked": "Revoked", - "admin.api.revokedHint": "This key has been revoked and can no longer be used.", - "admin.api.subtitle": "Manage keys to access the API", - "admin.api.title": "API Access", - "admin.api.toggleStateDisabledSuccess": "API has been disabled successfully.", - "admin.api.toggleStateEnabledSuccess": "API has been enabled successfully.", - "admin.approval.title": "Approvals", - "admin.audit.title": "Audit Log", - "admin.auth.activeStrategies": "Active Strategies", - "admin.auth.addStrategy": "Add Strategy", - "admin.auth.allowedWebOrigins": "Allowed Web Origins", - "admin.auth.autoEnrollGroups": "Assign to group", - "admin.auth.autoEnrollGroupsHint": "Automatically assign new users to these groups.", - "admin.auth.callbackUrl": "Callback URL / Redirect URI", - "admin.auth.configReference": "Configuration Reference", - "admin.auth.configReferenceSubtitle": "Some strategies may require some configuration values to be set on your provider. These are provided for reference only and may not be needed by the current strategy.", - "admin.auth.displayName": "Display Name", - "admin.auth.displayNameHint": "The title shown to the end user for this authentication strategy.", - "admin.auth.domainsWhitelist": "Limit to specific email domains", - "admin.auth.domainsWhitelistHint": "A list of domains authorized to register. The user email address domain must match one of these to gain access.", - "admin.auth.enabled": "Enabled", - "admin.auth.enabledForced": "This strategy cannot be disabled.", - "admin.auth.enabledHint": "Should this strategy be available to sites for login.", - "admin.auth.force2fa": "Force all users to use Two-Factor Authentication (2FA)", - "admin.auth.force2faHint": "Users will be required to setup 2FA the first time they login and cannot be disabled by the user.", - "admin.auth.globalAdvSettings": "Global Advanced Settings", - "admin.auth.loginUrl": "Login URL", - "admin.auth.logoutUrl": "Logout URL", - "admin.auth.refreshSuccess": "List of strategies has been refreshed.", - "admin.auth.registration": "Registration", - "admin.auth.saveSuccess": "Authentication configuration saved successfully.", - "admin.auth.security": "Security", - "admin.auth.selfRegistration": "Allow self-registration", - "admin.auth.selfRegistrationHint": "Allow any user successfully authorized by the strategy to access the wiki.", - "admin.auth.siteUrlNotSetup": "You must set a valid {siteUrl} first! Click on {general} in the left sidebar.", - "admin.auth.status": "Status", - "admin.auth.strategies": "Strategies", - "admin.auth.strategyConfiguration": "Strategy Configuration", - "admin.auth.strategyIsEnabled": "Active", - "admin.auth.strategyIsEnabledHint": "Are users able to login using this strategy?", - "admin.auth.strategyNoConfiguration": "This strategy has no configuration options you can modify.", - "admin.auth.strategyState": "This strategy is {state} {locked}", - "admin.auth.strategyStateActive": "active", - "admin.auth.strategyStateInactive": "not active", - "admin.auth.strategyStateLocked": "and cannot be disabled.", - "admin.auth.subtitle": "Configure the authentication settings of your wiki", - "admin.auth.title": "Authentication", - "admin.auth.vendor": "Vendor", - "admin.auth.vendorWebsite": "Website", - "admin.blocks.title": "Content Blocks", - "admin.comments.provider": "Provider", - "admin.comments.providerConfig": "Provider Configuration", - "admin.comments.providerNoConfig": "This provider has no configuration options you can modify.", - "admin.comments.subtitle": "Add discussions to your wiki pages", - "admin.comments.title": "Comments", - "admin.contribute.title": "Donate", - "admin.dashboard.contributeHelp": "We need your help!", - "admin.dashboard.contributeLearnMore": "Learn More", - "admin.dashboard.contributeSubtitle": "Wiki.js is a free and open source project. There are several ways you can contribute to the project.", - "admin.dashboard.groups": "Groups", - "admin.dashboard.lastLogins": "Last Logins", - "admin.dashboard.mostPopularPages": "Most Popular Pages", - "admin.dashboard.pages": "Pages", - "admin.dashboard.recentPages": "Recent Pages", - "admin.dashboard.subtitle": "Wiki.js", - "admin.dashboard.title": "Dashboard", - "admin.dashboard.users": "Users", - "admin.dashboard.versionLatest": "You are running the latest version.", - "admin.dashboard.versionNew": "A new version is available: {version}", - "admin.dev.flags.title": "Flags", - "admin.dev.graphiql.title": "GraphiQL", - "admin.dev.title": "Developer Tools", - "admin.dev.voyager.title": "Voyager", - "admin.editors.apiDescription": "Document your REST / GraphQL APIs.", - "admin.editors.apiName": "API Docs Editor", - "admin.editors.asciidocDescription": "Use the AsciiDoc syntax to write content. Includes real-time preview.", - "admin.editors.asciidocName": "AsciiDoc Editor", - "admin.editors.blogDescription": "Write a series of posts over time.", - "admin.editors.blogName": "Blog Editor", - "admin.editors.channelDescription": "Create discussion channels to collaborate in real-time with your team.", - "admin.editors.channelName": "Discussion Channels", - "admin.editors.configuration": "Configuration", - "admin.editors.markdown.allowHTML": "Allow HTML", - "admin.editors.markdown.allowHTMLHint": "Allow HTML tags in content.", - "admin.editors.markdown.general": "General", - "admin.editors.markdown.kroki": "Kroki", - "admin.editors.markdown.krokiHint": "Enable Kroki Diagrams Parser", - "admin.editors.markdown.krokiServerUrl": "Kroki Server URL", - "admin.editors.markdown.krokiServerUrlHint": "URL to the Kroki server used for image generation.", - "admin.editors.markdown.latexEngine": "LaTeX Engine", - "admin.editors.markdown.latexEngineHint": "Which engine to use to process TeX/LaTeX expressions.", - "admin.editors.markdown.lineBreaks": "Auto Line Breaks", - "admin.editors.markdown.lineBreaksHint": "Automatically add linebreaks within paragraphs.", - "admin.editors.markdown.linkify": "Auto-linking", - "admin.editors.markdown.linkifyHint": "Automatically convert URLs into clickable links.", - "admin.editors.markdown.multimdTable": "MultiMarkdown Table", - "admin.editors.markdown.multimdTableHint": "Enable support for MultiMarkdown Table features.", - "admin.editors.markdown.plantuml": "PlantUML", - "admin.editors.markdown.plantumlHint": "Enable PlantUML Parser", - "admin.editors.markdown.plantumlServerUrl": "PlantUML Server URL", - "admin.editors.markdown.plantumlServerUrlHint": "URL to the PlantUML server used for image generation.", - "admin.editors.markdown.quotes": "Quotes Style", - "admin.editors.markdown.quotesHint": "When typographer is enabled. Double + single quotes replacement pairs. e.g. «»„“ for Russian, „“‚‘ for German, etc.", - "admin.editors.markdown.saveSuccess": "Markdown editor configuration saved successfully.", - "admin.editors.markdown.tabWidth": "Code Block Tab Width", - "admin.editors.markdown.tabWidthHint": "Amount of spaces for each tab in code blocks.", - "admin.editors.markdown.typographer": "Typographer", - "admin.editors.markdown.typographerHint": "Enable some language-neutral replacement + quotes beautification.", - "admin.editors.markdown.underline": "Underline Emphasis", - "admin.editors.markdown.underlineHint": "Enable text underlining by using _underline_ syntax.", - "admin.editors.markdownDescription": "Use the Markdown syntax to write content. Includes real-time preview and code completion features.", - "admin.editors.markdownName": "Markdown Editor", - "admin.editors.redirectDescription": "Create redirections to other pages / external links.", - "admin.editors.redirectName": "Redirection", - "admin.editors.saveSuccess": "Editors state saved successfully.", - "admin.editors.subtitle": "Manage editors and their configuration", - "admin.editors.title": "Editors", - "admin.editors.useRenderingPipeline": "Uses the rendering pipeline.", - "admin.editors.wysiwygDescription": "A visual WYSIWYG editor. The recommended editor for non-technical users.", - "admin.editors.wysiwygName": "Visual Editor", - "admin.extensions.incompatible": "not compatible", - "admin.extensions.install": "Install", - "admin.extensions.installFailed": "Failed to install extension.", - "admin.extensions.installSuccess": "Extension installed successfully.", - "admin.extensions.installed": "Installed", - "admin.extensions.installing": "Installing extension...", - "admin.extensions.installingHint": "This may take a while depending on your server.", - "admin.extensions.instructions": "Instructions", - "admin.extensions.instructionsHint": "Must be installed manually", - "admin.extensions.reinstall": "Reinstall", - "admin.extensions.requiresSharp": "Requires Sharp extension", - "admin.extensions.subtitle": "Install extensions for extra functionality", - "admin.extensions.title": "Extensions", - "admin.flags.advanced.hint": "Set custom configuration flags. Note that all values are public to all users! Do not insert senstive data.", - "admin.flags.advanced.label": "Custom Configuration", - "admin.flags.authDebug.hint": "Log detailed debug info of all login / registration attempts.", - "admin.flags.authDebug.label": "Auth Debug", - "admin.flags.experimental.hint": "Enable unstable / unfinished features. DO NOT enable in a production environment!", - "admin.flags.experimental.label": "Experimental Features", - "admin.flags.saveSuccess": "Flags have been updated successfully.", - "admin.flags.sqlLog.hint": "Log all queries made to the database to console.", - "admin.flags.sqlLog.label": "SQL Query Logging", - "admin.flags.subtitle": "Low-level system flags for debugging or experimental purposes", - "admin.flags.title": "Flags", - "admin.flags.warn.hint": "Doing so may result in data loss, performance issues or a broken installation!", - "admin.flags.warn.label": "Do NOT enable these flags unless you know what you're doing!", - "admin.general.allowComments": "Allow Comments", - "admin.general.allowCommentsHint": "Can users leave comments on pages? Can be restricted using Page Rules.", - "admin.general.allowContributions": "Allow Contributions", - "admin.general.allowContributionsHint": "Can users with read access permissions propose changes for pages? Can be restricted using Page Rules.", - "admin.general.allowProfile": "Allow Profile Editing", - "admin.general.allowProfileHint": "Can users edit their own profile? If profile data is managed by an external identity provider, you should turn this off.", - "admin.general.allowRatings": "Allow Ratings", - "admin.general.allowRatingsHint": "Can users leave ratings on pages? Can be restricted using Page Rules.", - "admin.general.allowSearch": "Allow Search", - "admin.general.allowSearchHint": "Can users search for content they have read access to?", - "admin.general.companyName": "Company / Organization Name", - "admin.general.companyNameHint": "Name to use when displaying copyright notice in the footer. Leave empty to hide.", - "admin.general.contentLicense": "Content License", - "admin.general.contentLicenseHint": "License shown in the footer of all content pages.", - "admin.general.defaultDateFormat": "Default Date Format", - "admin.general.defaultDateFormatHint": "The default date format for new users.", - "admin.general.defaultTimeFormat": "Default Time Format", - "admin.general.defaultTimeFormat12h": "12 hour", - "admin.general.defaultTimeFormat24h": "24 hour", - "admin.general.defaultTimeFormatHint": "The default time format for new users.", - "admin.general.defaultTimezone": "Default Timezone", - "admin.general.defaultTimezoneHint": "The default timezone for new users.", - "admin.general.defaultTocDepth": "Default ToC Depth", - "admin.general.defaultTocDepthHint": "The default minimum and maximum header levels to show in the table of contents.", - "admin.general.defaults": "Site Defaults", - "admin.general.displaySiteTitle": "Display Site Title", - "admin.general.displaySiteTitleHint": "Should the site title be displayed next to the logo? If your logo isn't square and contain your brand name, turn this option off.", - "admin.general.favicon": "Favicon", - "admin.general.faviconHint": "Favicon image file, in SVG, PNG, JPG, WEBP or GIF format. Must be a square image.", - "admin.general.faviconUploadSuccess": "Site Favicon uploaded successfully.", - "admin.general.features": "Features", - "admin.general.footerCopyright": "Footer / Copyright", - "admin.general.footerExtra": "Additional Footer Text", - "admin.general.footerExtraHint": "Optionally add more content to the footer, such as additional copyright terms or mandatory regulatory info.", - "admin.general.general": "General", - "admin.general.logo": "Logo", - "admin.general.logoUpl": "Site Logo", - "admin.general.logoUplHint": "Logo image file, in SVG, PNG, JPG, WEBP or GIF format.", - "admin.general.logoUploadSuccess": "Site logo uploaded successfully.", - "admin.general.pageCasing": "Case Sensitive Paths", - "admin.general.pageCasingHint": "Treat paths with different casing as distinct pages.", - "admin.general.pageExtensions": "Page Extensions", - "admin.general.pageExtensionsHint": "A comma-separated list of URL extensions that will be treated as pages. For example, adding md will treat /foobar.md the same as /foobar.", - "admin.general.ratingsOff": "Off", - "admin.general.ratingsStars": "Stars", - "admin.general.ratingsThumbs": "Thumbs", - "admin.general.reasonForChange": "Reason for Change", - "admin.general.reasonForChangeHint": "Should users be prompted the reason for changes made to a page?", - "admin.general.reasonForChangeOff": "Off", - "admin.general.reasonForChangeOptional": "Optional", - "admin.general.reasonForChangeRequired": "Required", - "admin.general.saveSuccess": "Site configuration saved successfully.", - "admin.general.searchAllowFollow": "Allow Search Engines to Follow Links", - "admin.general.searchAllowFollowHint": "This sets the meta-robots property to follow or nofollow.", - "admin.general.searchAllowIndexing": "Allow Indexing by Search Engines", - "admin.general.searchAllowIndexingHint": "This sets the meta-robots property to index or noindex.", - "admin.general.senderEmailHint": "Email address of the sender.", - "admin.general.senderNameHint": "Name of the sender.", - "admin.general.siteBranding": "Site Branding", - "admin.general.siteDescription": "Site Description", - "admin.general.siteDescriptionHint": "Default description when none is provided for a page.", - "admin.general.siteHostname": "Site Hostname", - "admin.general.siteHostnameHint": "Hostname this site should respond to. Set * for catch-all / fallback domain.", - "admin.general.siteHostnameInvalid": "Invalid Hostname", - "admin.general.siteInfo": "Site Info", - "admin.general.siteTitle": "Site Title", - "admin.general.siteTitleHint": "Displayed in the top bar and appended to all pages meta title.", - "admin.general.siteTitleInvalidChars": "Site Title contains invalid characters.", - "admin.general.sitemap": "Allow Sitemap", - "admin.general.sitemapHint": "Make a sitemap.xml available to search engines with all pages accessible to guests.", - "admin.general.subtitle": "Main settings of your wiki", - "admin.general.title": "General", - "admin.general.uploadClear": "Clear", - "admin.general.uploadConflictBehavior": "Upload Conflict Behavior", - "admin.general.uploadConflictBehaviorHint": "How should uploads for a file that already exists be handled?", - "admin.general.uploadConflictBehaviorNew": "Append Time to Filename", - "admin.general.uploadConflictBehaviorOverwrite": "Overwrite", - "admin.general.uploadConflictBehaviorReject": "Reject", - "admin.general.uploadLogo": "Upload Logo", - "admin.general.uploadNormalizeFilename": "Normalize Filenames", - "admin.general.uploadNormalizeFilenameHint": "Automatically transform filenames to a standard URL-friendly format.", - "admin.general.uploadSizeHint": "An image of {size} pixels is recommended for best results.", - "admin.general.uploadTypesHint": "{typeList} or {lastType} files only", - "admin.general.uploads": "Uploads", - "admin.general.urlHandling": "URL Handling", - "admin.groups.assignUser": "Assign User", - "admin.groups.authBehaviors": "Authentication Behaviors", - "admin.groups.create": "New Group", - "admin.groups.createSuccess": "Group created successfully.", - "admin.groups.delete": "Delete Group", - "admin.groups.deleteConfirm": "Are you sure you want delete group {groupName}? Any user currently assigned to this group will be unassigned from it.", - "admin.groups.deleteConfirmWarn": "This action cannot be undone!", - "admin.groups.deleteSuccess": "Group was deleted successfully.", - "admin.groups.edit": "Edit Group", - "admin.groups.exportRules": "Export Rules", - "admin.groups.exportRulesNoneError": "This group has no rule to export!", - "admin.groups.filterUsers": "Filter...", - "admin.groups.general": "General", - "admin.groups.importFailed": "Something went wrong while importing this file. Making sure it is a valid rules JSON file.", - "admin.groups.importModeAdd": "Add to Existing", - "admin.groups.importModeReplace": "Replace All", - "admin.groups.importModeText": "Do you want to replace all existing rules with these ones or add these rules to the existing ones?", - "admin.groups.importModeTitle": "Add or replace?", - "admin.groups.importRules": "Import Rules", - "admin.groups.importSuccess": "Rules imported successfully!", - "admin.groups.info": "Group Info", - "admin.groups.name": "Group Name", - "admin.groups.nameHint": "Name of the group", - "admin.groups.overview": "Overview", - "admin.groups.permissions": "Permissions", - "admin.groups.redirectOnFirstLogin": "First-time Login Redirect", - "admin.groups.redirectOnFirstLoginHint": "Optionally redirect the user to a specific page when he/she login for the first time. Leave empty to use the site-defined value.", - "admin.groups.redirectOnLogin": "Redirect on Login", - "admin.groups.redirectOnLoginHint": "The path / URL where the user will be redirected upon successful login. Leave empty to use the site-defined value.", - "admin.groups.redirectOnLogout": "Redirect on Logout", - "admin.groups.redirectOnLogoutHint": "The path / URL where the user will be redirected upon logout. Leave empty to use the site-defined value.", - "admin.groups.refreshSuccess": "Groups refreshed successfully.", - "admin.groups.ruleAllow": "Allow", - "admin.groups.ruleDeny": "Deny", - "admin.groups.ruleForceAllow": "Force Allow", - "admin.groups.ruleLocales": "Locale(s)", - "admin.groups.ruleMatchEnd": "Path Ends With...", - "admin.groups.ruleMatchExact": "Path Is Exactly...", - "admin.groups.ruleMatchRegex": "Path Matches Regex...", - "admin.groups.ruleMatchStart": "Path Starts With...", - "admin.groups.ruleMatchTag": "Has Any Tag...", - "admin.groups.ruleMatchTagAll": "Has All Tags...", - "admin.groups.ruleSites": "Site(s)", - "admin.groups.ruleUntitled": "Untitled Rule", - "admin.groups.rules": "Rules", - "admin.groups.rulesNone": "This group doesn't have any rules yet.", - "admin.groups.selectedLocales": "Any Locale | {locale} locale only | {count} locales selected", - "admin.groups.selectedSites": "Any Site | 1 site selected | {count} sites selected", - "admin.groups.subtitle": "Manage user groups and permissions", - "admin.groups.title": "Groups", - "admin.groups.userCount": "User Count", - "admin.groups.users": "Users", - "admin.groups.usersCount": "0 user | 1 user | {count} users", - "admin.groups.usersNone": "This group doesn't have any user yet.", - "admin.icons.mandatory": "Used by the system and cannot be disabled.", - "admin.icons.reference": "Reference", - "admin.icons.subtitle": "Configure the icon packs available for use", - "admin.icons.title": "Icons", - "admin.icons.warnHint": "Only activate the icon packs you actually use.", - "admin.icons.warnLabel": "Enabling additional icon packs can significantly increase page load times!", - "admin.instances.activeConnections": "Active Connections", - "admin.instances.activeListeners": "Active Listeners", - "admin.instances.firstSeen": "First Seen", - "admin.instances.lastSeen": "Last Seen", - "admin.instances.subtitle": "View a list of active instances", - "admin.instances.title": "Instances", - "admin.locale.activeNamespaces": "Active Namespaces", - "admin.locale.autoUpdate.hint": "Automatically download updates to this locale as they become available.", - "admin.locale.autoUpdate.hintWithNS": "Automatically download updates to all namespaced locales enabled below.", - "admin.locale.autoUpdate.label": "Update Automatically", - "admin.locale.availability": "Availability", - "admin.locale.base.hint": "All UI text elements will be displayed in selected language.", - "admin.locale.base.label": "Site Locale", - "admin.locale.base.labelWithNS": "Base Locale", - "admin.locale.code": "Code", - "admin.locale.download": "Download", - "admin.locale.downloadNew": "Install New Locale", - "admin.locale.downloadTitle": "Download Locale", - "admin.locale.name": "Name", - "admin.locale.namespaces.hint": "Enables multiple language versions of the same page.", - "admin.locale.namespaces.label": "Multilingual Namespaces", - "admin.locale.namespacing": "Multilingual Namespacing", - "admin.locale.namespacingPrefixWarning.subtitle": "Paths without a locale code will be automatically redirected to the base locale defined above.", - "admin.locale.namespacingPrefixWarning.title": "The locale code will be prefixed to all paths. (e.g. /{langCode}/page-name)", - "admin.locale.nativeName": "Native Name", - "admin.locale.rtl": "RTL", - "admin.locale.settings": "Locale Settings", - "admin.locale.sideload": "Sideload Locale Package", - "admin.locale.sideloadHelp": "If you are not connected to the internet or cannot download locale files using the method above, you can instead sideload packages manually by uploading them below.", - "admin.locale.subtitle": "Set localization options for your wiki", - "admin.locale.title": "Locale", - "admin.logging.title": "Logging", - "admin.login.background": "Background Image", - "admin.login.backgroundHint": "Specify an image to use as the login background. PNG and JPG are supported, 1920x1080 recommended. Leave empty for default.", - "admin.login.bgUploadSuccess": "Login background image uploaded successfully.", - "admin.login.bypassScreen": "Bypass Login Screen", - "admin.login.bypassScreenHint": "Should the user be redirected automatically to the first authentication provider. Has no effect if the first provider is a username/password provider type.", - "admin.login.bypassUnauthorized": "Bypass Unauthorized Screen", - "admin.login.bypassUnauthorizedHint": "Always redirect the user to the login screen instead of showing an unauthorized error page when the user is not logged in.", - "admin.login.experience": "User Experience", - "admin.login.loginRedirect": "Login Redirect", - "admin.login.loginRedirectHint": "Optionally redirect the user to a specific page when he/she logins (except if first time login which is defined below). This can be overridden at the group level.", - "admin.login.logoutRedirect": "Logout Redirect", - "admin.login.logoutRedirectHint": "Optionally redirect the user to a specific page when he/she logouts. This can be overridden at the group level.", - "admin.login.providers": "Login Providers", - "admin.login.providersVisbleWarning": "Note that you can always temporarily show all hidden providers by adding ?all=1 to the url. This is useful to login as local admin while hiding it from normal users.", - "admin.login.saveSuccess": "Login configuration saved successfully.", - "admin.login.subtitle": "Configure the login user experience of your wiki site", - "admin.login.title": "Login", - "admin.login.welcomeRedirect": "First-time Login Redirect", - "admin.login.welcomeRedirectHint": "Optionally redirect the user to a specific page when he/she login for the first time. This can be overridden at the group level.", - "admin.mail.configuration": "Configuration", - "admin.mail.dkim": "DKIM (optional)", - "admin.mail.dkimDomainName": "Domain Name", - "admin.mail.dkimDomainNameHint": "Domain name used for DKIM validation.", - "admin.mail.dkimHint": "DKIM (DomainKeys Identified Mail) provides a layer of security on all emails sent from Wiki.js by providing the means for recipients to validate the domain name and ensure the message authenticity.", - "admin.mail.dkimKeySelector": "Key Selector", - "admin.mail.dkimKeySelectorHint": "Determines which key to use for DKIM in your DNS records.", - "admin.mail.dkimPrivateKey": "Private Key", - "admin.mail.dkimPrivateKeyHint": "Private key for the selector in PEM format", - "admin.mail.dkimUse": "Use DKIM", - "admin.mail.dkimUseHint": "Should DKIM be used when sending emails.", - "admin.mail.saveSuccess": "Configuration saved successfully.", - "admin.mail.sendTestSuccess": "A test email was sent successfully.", - "admin.mail.sender": "Sender", - "admin.mail.senderEmail": "Sender Email", - "admin.mail.senderName": "Sender Name", - "admin.mail.smtp": "SMTP Settings", - "admin.mail.smtpHost": "Host", - "admin.mail.smtpHostHint": "Hostname or IP address of the SMTP server.", - "admin.mail.smtpName": "Client Identifying Name", - "admin.mail.smtpNameHint": "An optional name to send to the SMTP server to identify your mailer. Leave empty to use server hostname. For Google Workspace customers, this should be your main domain name.", - "admin.mail.smtpPort": "Port", - "admin.mail.smtpPortHint": "Usually 465 (recommended), 587 or 25.", - "admin.mail.smtpPwd": "Password", - "admin.mail.smtpPwdHint": "Password used for authenticating to the SMTP server.", - "admin.mail.smtpTLS": "Secure (TLS)", - "admin.mail.smtpTLSHint": "Should be enabled when using port 465, otherwise turned off (587 or 25).", - "admin.mail.smtpUser": "Username", - "admin.mail.smtpUserHint": "Username used for authenticating to the SMTP server.", - "admin.mail.smtpVerifySSL": "Verify SSL Certificate", - "admin.mail.smtpVerifySSLHint": "Some hosts requires SSL certificate checking to be disabled. Leave enabled for proper security.", - "admin.mail.subtitle": "Configure mail settings", - "admin.mail.templateResetPwd": "Password Reset Email", - "admin.mail.templateWelcome": "Welcome Email", - "admin.mail.templates": "Mail Templates", - "admin.mail.test": "Send a test email", - "admin.mail.testHint": "Send a test email to ensure your SMTP configuration is working.", - "admin.mail.testRecipient": "Recipient Email Address", - "admin.mail.testRecipientHint": "Email address that should receive the test email.", - "admin.mail.testSend": "Send Email", - "admin.mail.title": "Mail", - "admin.nav.modules": "Modules", - "admin.nav.site": "Site", - "admin.nav.system": "System", - "admin.nav.users": "Users", - "admin.navigation.copyFromLocale": "Copy from locale...", - "admin.navigation.copyFromLocaleInfoText": "Select the locale from which items will be copied from. Items will be appended to the current list of items in the active locale.", - "admin.navigation.delete": "Delete {kind}", - "admin.navigation.divider": "Divider", - "admin.navigation.edit": "Edit {kind}", - "admin.navigation.emptyList": "Navigation is empty", - "admin.navigation.header": "Header", - "admin.navigation.icon": "Icon", - "admin.navigation.label": "Label", - "admin.navigation.link": "Link", - "admin.navigation.mode": "Navigation Mode", - "admin.navigation.modeCustom.description": "Static Navigation Menu + Site Tree Button", - "admin.navigation.modeCustom.title": "Custom Navigation", - "admin.navigation.modeNone.description": "Disable Site Navigation", - "admin.navigation.modeNone.title": "None", - "admin.navigation.modeSiteTree.description": "Classic Tree-based Navigation", - "admin.navigation.modeSiteTree.title": "Site Tree", - "admin.navigation.modeStatic.description": "Static Navigation Menu Only", - "admin.navigation.modeStatic.title": "Static Navigation", - "admin.navigation.navType.external": "External Link", - "admin.navigation.navType.externalblank": "External Link (New Window)", - "admin.navigation.navType.home": "Home", - "admin.navigation.navType.page": "Page", - "admin.navigation.navType.searchQuery": "Search Query", - "admin.navigation.noItemsText": "Click the Add button to add your first navigation item.", - "admin.navigation.noSelectionText": "Select a navigation item on the left.", - "admin.navigation.saveSuccess": "Navigation saved successfully.", - "admin.navigation.selectPageButton": "Select Page...", - "admin.navigation.sourceLocale": "Source Locale", - "admin.navigation.sourceLocaleHint": "The locale from which navigation items will be copied from.", - "admin.navigation.subtitle": "Manage the site navigation", - "admin.navigation.target": "Target", - "admin.navigation.targetType": "Target Type", - "admin.navigation.title": "Navigation", - "admin.navigation.untitled": "Untitled {kind}", - "admin.navigation.visibilityMode.all": "Visible to everyone", - "admin.navigation.visibilityMode.restricted": "Visible to select groups...", - "admin.pages.title": "Pages", - "admin.rendering.subtitle": "Configure the content rendering pipeline", - "admin.rendering.title": "Rendering", - "admin.scheduler.active": "Active", - "admin.scheduler.activeNone": "There are no active jobs at the moment.", - "admin.scheduler.attempt": "Attempt", - "admin.scheduler.cancelJob": "Cancel Job", - "admin.scheduler.cancelJobSuccess": "Job cancelled successfully.", - "admin.scheduler.completed": "Completed", - "admin.scheduler.completedIn": "Completed in {duration}", - "admin.scheduler.completedNone": "There are no recently completed job to display.", - "admin.scheduler.createdAt": "Created", - "admin.scheduler.createdBy": "by instance {instance}", - "admin.scheduler.cron": "Cron", - "admin.scheduler.error": "Error", - "admin.scheduler.failed": "Failed", - "admin.scheduler.failedNone": "There are no recently failed job to display.", - "admin.scheduler.interrupted": "Interrupted", - "admin.scheduler.pending": "Pending", - "admin.scheduler.result": "Result", - "admin.scheduler.retryJob": "Retry Job", - "admin.scheduler.retryJobSuccess": "Job has been rescheduled and will execute shortly.", - "admin.scheduler.schedule": "Schedule", - "admin.scheduler.scheduled": "Scheduled", - "admin.scheduler.scheduledNone": "There are no scheduled jobs at the moment.", - "admin.scheduler.startedAt": "Started", - "admin.scheduler.state": "State", - "admin.scheduler.subtitle": "View scheduled and completed jobs", - "admin.scheduler.title": "Scheduler", - "admin.scheduler.type": "Type", - "admin.scheduler.upcoming": "Upcoming", - "admin.scheduler.upcomingNone": "There are no upcoming job for the moment.", - "admin.scheduler.updatedAt": "Last Updated", - "admin.scheduler.useWorker": "Execution Mode", - "admin.scheduler.waitUntil": "Start", - "admin.search.configSaveSuccess": "Search engine configuration saved successfully.", - "admin.search.engineConfig": "Engine Configuration", - "admin.search.engineNoConfig": "This engine has no configuration options you can modify.", - "admin.search.indexRebuildSuccess": "Index rebuilt successfully.", - "admin.search.listRefreshSuccess": "List of search engines has been refreshed.", - "admin.search.rebuildIndex": "Rebuild Index", - "admin.search.searchEngine": "Search Engine", - "admin.search.subtitle": "Configure the search capabilities of your wiki", - "admin.search.title": "Search Engine", - "admin.security.cors": "CORS (Cross-Origin Resource Sharing)", - "admin.security.corsHostnames": "Hostnames Whitelist", - "admin.security.corsHostnamesHint": "Enter one hostname per line", - "admin.security.corsMode": "CORS Mode", - "admin.security.corsModeHint": "How the GraphQL server should handle preflight requests?", - "admin.security.corsRegex": "Regex Pattern", - "admin.security.corsRegexHint": "Pattern against which the request hostname is matched.", - "admin.security.disallowFloc": "Disallow Google FLoC", - "admin.security.disallowFlocHint": "Broadcast that this website should be opted-out of Google Federed Learning of Cohorts (FLoC). Recommended for privacy.", - "admin.security.disallowIframe": "Disallow iFrame Embedding", - "admin.security.disallowIframeHint": "Prevents other websites from embedding your wiki in an iframe. This provides clickjacking protection.", - "admin.security.disallowOpenRedirect": "Block Open Redirect", - "admin.security.disallowOpenRedirectHint": "Prevents user controlled URLs from directing to websites outside of your wiki. This provides Open Redirect protection.", - "admin.security.enforce2fa": "Enforce 2FA", - "admin.security.enforce2faHint": "Force all users to use Two-Factor Authentication when using an authentication provider with a user / password form.", - "admin.security.enforceHsts": "Enforce HSTS", - "admin.security.enforceHstsHint": "This ensures the connection cannot be established through an insecure HTTP connection.", - "admin.security.enforceSameOriginReferrerPolicy": "Enforce Same-Origin Referrer Policy", - "admin.security.enforceSameOriginReferrerPolicyHint": "Whether the referrer information should be included to requests to external endpoints.", - "admin.security.forceAssetDownload": "Force Asset Download for Unsafe Extensions", - "admin.security.forceAssetDownloadHint": "Should non-image files be forced as downloads when accessed directly. This prevents potential XSS attacks.", - "admin.security.hsts": "HSTS (HTTP Strict Transport Security)", - "admin.security.hstsDuration": "HSTS Max Age", - "admin.security.hstsDurationHint": "Defines the duration for which the server should only deliver content through HTTPS. It's a good idea to start with small values and make sure that nothing breaks on your wiki before moving to longer values.", - "admin.security.jwt": "JWT Configuration", - "admin.security.jwtAudience": "JWT Audience", - "admin.security.jwtAudienceHint": "Audience URN used in JWT issued upon login. Usually your domain name. (e.g. urn:your.domain.com)", - "admin.security.loginScreen": "Login Screen", - "admin.security.maxUploadBatch": "Max Files per Upload", - "admin.security.maxUploadBatchHint": "How many files can be uploaded in a single batch?", - "admin.security.maxUploadBatchSuffix": "files", - "admin.security.maxUploadSize": "Max Upload Size", - "admin.security.maxUploadSizeHint": "The maximum size for a single file. Final value in base 2.", - "admin.security.maxUploadSizeSuffix": "bytes", - "admin.security.saveSuccess": "Security configuration updated successfully.", - "admin.security.scanSVG": "Scan and Sanitize SVG Uploads", - "admin.security.scanSVGHint": "Should SVG uploads be scanned for vulnerabilities and stripped of any potentially unsafe content.", - "admin.security.subtitle": "Configure security settings", - "admin.security.title": "Security", - "admin.security.tokenEndpointAuthMethod": "Token Endpoint Authentication Method", - "admin.security.tokenExpiration": "Token Expiration", - "admin.security.tokenExpirationHint": "The expiration period of a token until it must be renewed. (default: 30m)", - "admin.security.tokenRenewalPeriod": "Token Renewal Period", - "admin.security.tokenRenewalPeriodHint": "The maximum period a token can be renewed when expired. (default: 14d)", - "admin.security.trustProxy": "Trust X-Forwarded-* Proxy Headers", - "admin.security.trustProxyHint": "Should be enabled when using a reverse-proxy like nginx, apache, CloudFlare, etc in front of Wiki.js. Turn off otherwise.", - "admin.security.uploads": "Uploads", - "admin.security.uploadsInfo": "These settings only affect Wiki.js. If you're using a reverse-proxy (e.g. nginx, Apache, Cloudflare), you must also change its settings to match.", - "admin.security.warn": "Make sure to understand the implications before turning on / off a security feature.", - "admin.sites.activate": "Activate Site", - "admin.sites.activateConfirm": "Are you sure you want activate site {siteTitle}? The site will become accessible to users with read access.", - "admin.sites.createSuccess": "Site created successfully.", - "admin.sites.deactivate": "Deactivate Site", - "admin.sites.deactivateConfirm": "Are you sure you want deactivate site {siteTitle}? The site will no longer be accessible to users.", - "admin.sites.delete": "Delete Site", - "admin.sites.deleteConfirm": "Are you sure you want delete site {siteTitle}? This will permanently delete all site content (including pages, history, comments and assets) and configuration!", - "admin.sites.deleteConfirmWarn": "This action cannot be undone!", - "admin.sites.deleteSuccess": "Site was deleted successfully.", - "admin.sites.edit": "Edit Site", - "admin.sites.hostname": "Hostname", - "admin.sites.hostnameHint": "Must be a fully-qualified domain name (e.g. wiki.example.com) or * for a catch-all site. Note that there can only be 1 catch-all site.", - "admin.sites.isActive": "Active", - "admin.sites.new": "New Site", - "admin.sites.refreshSuccess": "List of sites refreshed successfully.", - "admin.sites.subtitle": "Manage your wiki sites", - "admin.sites.title": "Sites", - "admin.ssl.currentState": "Current State", - "admin.ssl.domain": "Domain", - "admin.ssl.domainHint": "Enter the fully qualified domain pointing to your wiki. (e.g. wiki.example.com)", - "admin.ssl.expiration": "Certificate Expiration", - "admin.ssl.httpPort": "HTTP Port", - "admin.ssl.httpPortHint": "Non-SSL port the server will listen to for HTTP requests. Usually 80 or 3000.", - "admin.ssl.httpPortRedirect": "Redirect HTTP requests to HTTPS", - "admin.ssl.httpPortRedirectHint": "Will automatically redirect any requests on the HTTP port to HTTPS.", - "admin.ssl.httpPortRedirectSaveSuccess": "HTTP Redirection changed successfully.", - "admin.ssl.httpPortRedirectTurnOff": "Turn Off", - "admin.ssl.httpPortRedirectTurnOn": "Turn On", - "admin.ssl.httpsPort": "HTTPS Port", - "admin.ssl.httpsPortHint": "SSL port the server will listen to for HTTPS requests. Usually 443.", - "admin.ssl.ports": "Ports", - "admin.ssl.provider": "Provider", - "admin.ssl.providerCustomCertificate": "Custom Certificate", - "admin.ssl.providerDisabled": "Disabled", - "admin.ssl.providerHint": "Select Custom Certificate if you have your own certificate already.", - "admin.ssl.providerLetsEncrypt": "Let's Encrypt", - "admin.ssl.providerOptions": "Provider Options", - "admin.ssl.renewCertificate": "Renew Certificate", - "admin.ssl.renewCertificateLoadingSubtitle": "Do not leave this page.", - "admin.ssl.renewCertificateLoadingTitle": "Renewing Certificate...", - "admin.ssl.renewCertificateSuccess": "Certificate renewed successfully.", - "admin.ssl.status": "Certificate Status", - "admin.ssl.subscriberEmail": "Subscriber Email", - "admin.ssl.subtitle": "Manage SSL configuration", - "admin.ssl.title": "SSL", - "admin.ssl.writableConfigFileWarning": "Note that your config file must be writable in order to persist ports configuration.", - "admin.stats.title": "Statistics", - "admin.storage.actionRun": "Run", - "admin.storage.actions": "Actions", - "admin.storage.actionsInactiveWarn": "You must enable this storage target and apply changes before you can run actions.", - "admin.storage.addTarget": "Add Storage Target", - "admin.storage.assetDelivery": "Asset Delivery", - "admin.storage.assetDeliveryHint": "Select how uploaded assets should be delivered to the user. Note that not all storage origins support asset delivery and some can only be used for backup purposes.", - "admin.storage.assetDirectAccess": "Direct Access", - "admin.storage.assetDirectAccessHint": "Assets are accessed directly by the user using a secure / signed link. When enabled, takes priority over file streaming.", - "admin.storage.assetDirectAccessNotSupported": "Not supported by this storage target.", - "admin.storage.assetStreaming": "File Streaming", - "admin.storage.assetStreamingHint": "Assets will be streamed from the storage target, through the server, to the user.", - "admin.storage.assetStreamingNotSupported": "Not supported by this storage target.", - "admin.storage.assetsOnly": "Assets Only", - "admin.storage.cancelSetup": "Cancel", - "admin.storage.config": "Configuration", - "admin.storage.contentTypeDocuments": "Documents", - "admin.storage.contentTypeDocumentsHint": "Text or presentation documents in PDF, TXT, Word, Excel and Powerpoint formats.", - "admin.storage.contentTypeImages": "Images", - "admin.storage.contentTypeImagesHint": "Image Assets in JPG, PNG, GIF, WebP and SVG formats.", - "admin.storage.contentTypeLargeFiles": "Large Files", - "admin.storage.contentTypeLargeFilesDBWarn": "For performance reasons, large files should not be stored in the database. Consider using another storage type for these files.", - "admin.storage.contentTypeLargeFilesHint": "Large files such as videos, zip archives and binaries. Pages never fall into this category, irrespective of their size.", - "admin.storage.contentTypeLargeFilesThreshold": "Size Threshold", - "admin.storage.contentTypeOthers": "Others", - "admin.storage.contentTypeOthersHint": "Any other file types that don't match the other categories.", - "admin.storage.contentTypePages": "Pages", - "admin.storage.contentTypePagesHint": "Page content source, in Markdown, HTML or JSON format depending on the editor.", - "admin.storage.contentTypes": "Content Types", - "admin.storage.contentTypesHint": "Select the type of content that should be stored to this storage target:", - "admin.storage.currentState": "Current State", - "admin.storage.deliveryPaths": "Delivery Paths", - "admin.storage.deliveryPathsLegend": "Legend:", - "admin.storage.deliveryPathsPushToOrigin": "Push to Origin", - "admin.storage.deliveryPathsUser": "User", - "admin.storage.deliveryPathsUserRequest": "User Request", - "admin.storage.destroyConfirm": "Confirm Setup Reset", - "admin.storage.destroyConfirmInfo": "Are you sure you want to reset the storage target setup configuration? Note that this action cannot be undone and you will need to start the setup process over.", - "admin.storage.destroyingSetup": "Resetting storage target setup configuration...", - "admin.storage.enabled": "Enabled", - "admin.storage.enabledForced": "Cannot be disabled on the database target.", - "admin.storage.enabledHint": "Should this storage target be used for storing and accessing assets.", - "admin.storage.errorMsg": "Error Message", - "admin.storage.finishSetup": "Finish Setup", - "admin.storage.githubAccTypeOrg": "Organization", - "admin.storage.githubAccTypePersonal": "Personal", - "admin.storage.githubFinish": "Once you have installed the application on the GitHub repository of your choice, click the Finish Setup button below to validate the installation and start using it. Otherwise, click Destroy to delete any pending configuration and start over.", - "admin.storage.githubInstallApp": "Setup GitHub Connection - Step 2", - "admin.storage.githubInstallAppHint": "On the next screen, you will be prompted to install the app you just created onto one or more repositories. You may select a single one or all repositories.", - "admin.storage.githubOrg": "GitHub Organization", - "admin.storage.githubOrgHint": "Enter the GitHub organization account to be used.", - "admin.storage.githubPreparingManifest": "Preparing manifest...", - "admin.storage.githubPublicUrl": "Wiki Public URL", - "admin.storage.githubPublicUrlHint": "Enter the full URL to your wiki (e.g. https://wiki.example.com). Note that your wiki MUST be accessible from the internet!", - "admin.storage.githubRedirecting": "Redirecting to GitHub...", - "admin.storage.githubRepo": "GitHub Repository", - "admin.storage.githubRepoCreating": "Creating GitHub Repository...", - "admin.storage.githubRepoHint": "Enter the name of the repository to create on GitHub and use for this wiki:", - "admin.storage.githubSetupContinue": "Continue Setup", - "admin.storage.githubSetupDestroyFailed": "Failed to destroy setup configuration.", - "admin.storage.githubSetupDestroySuccess": "Setup configuration has been reset successfully.", - "admin.storage.githubSetupFailed": "GitHub Setup failed.", - "admin.storage.githubSetupInstallApp": "GitHub Connection Setup - Step 2", - "admin.storage.githubSetupInstallAppInfo": "On the next screen, you'll be prompted to install the app you just created onto one or more GitHub repositories.", - "admin.storage.githubSetupInstallAppReturn": "Once the installation on GitHub is completed, you will need to manually return to this page to finish the setup.", - "admin.storage.githubSetupInstallAppSelect": "IMPORTANT: Select only the repository that will be used to sync with this wiki.", - "admin.storage.githubSetupSuccess": "Success! Wiki.js is now connected to GitHub.", - "admin.storage.githubVerifying": "Verifying GitHub configuration...", - "admin.storage.inactiveTarget": "Inactive", - "admin.storage.lastSync": "Last synchronization {time}", - "admin.storage.lastSyncAttempt": "Last attempt was {time}", - "admin.storage.missingOrigin": "Missing Origin", - "admin.storage.noActions": "This storage target has no actions that you can execute.", - "admin.storage.noConfigOption": "This storage target has no configuration options you can modify.", - "admin.storage.noSyncModes": "This storage target has no synchronization options you can modify.", - "admin.storage.noTarget": "You don't have any active storage target.", - "admin.storage.notConfigured": "Not Configured", - "admin.storage.pagesAndAssets": "Pages and Assets", - "admin.storage.pagesOnly": "Pages Only", - "admin.storage.saveFailed": "Failed to save storage configuration.", - "admin.storage.saveSuccess": "Storage configuration saved successfully.", - "admin.storage.setup": "Setup", - "admin.storage.setupConfiguredHint": "This module is already configured. You can uninstall this module to start over.", - "admin.storage.setupHint": "This module requires a setup process to be completed in order to use it. Follow the instructions below to get started.", - "admin.storage.setupRequired": "Setup required", - "admin.storage.startSetup": "Start Setup", - "admin.storage.status": "Status", - "admin.storage.subtitle": "Set backup and sync targets for your content", - "admin.storage.sync": "Synchronization", - "admin.storage.syncDirBi": "Bi-directional", - "admin.storage.syncDirBiHint": "In bi-directional mode, content is first pulled from the storage target. Any newer content overwrites local content. New content since last sync is then pushed to the storage target, overwriting any content on target if present.", - "admin.storage.syncDirPull": "Pull from target", - "admin.storage.syncDirPullHint": "Content is always pulled from the storage target, overwriting any local content which already exists. This choice is usually reserved for single-use content import. Caution with this option as any local content will always be overwritten!", - "admin.storage.syncDirPush": "Push to target", - "admin.storage.syncDirPushHint": "Content is always pushed to the storage target, overwriting any existing content. This is safest choice for backup scenarios.", - "admin.storage.syncDirection": "Sync Direction", - "admin.storage.syncDirectionSubtitle": "Choose how content synchronization is handled for this storage target.", - "admin.storage.syncSchedule": "Sync Schedule", - "admin.storage.syncScheduleCurrent": "Currently set to every {schedule}.", - "admin.storage.syncScheduleDefault": "The default is every {schedule}.", - "admin.storage.syncScheduleHint": "For performance reasons, this storage target synchronize changes on an interval-based schedule, instead of on every change. Define at which interval should the synchronization occur.", - "admin.storage.targetConfig": "Target Configuration", - "admin.storage.targetState": "This storage target is {state}", - "admin.storage.targetStateActive": "active", - "admin.storage.targetStateInactive": "inactive", - "admin.storage.targets": "Targets", - "admin.storage.title": "Storage", - "admin.storage.uninstall": "Uninstall", - "admin.storage.unsupported": "Unsupported", - "admin.storage.useVersioning": "Use Versioning", - "admin.storage.useVersioningHint": "Should previous versions of assets be retained on the storage target.", - "admin.storage.vendor": "Vendor", - "admin.storage.vendorWebsite": "Website", - "admin.storage.versioning": "Asset Versioning", - "admin.storage.versioningForceEnabled": "Cannot be disabled on this storage target.", - "admin.storage.versioningHint": "Asset versioning allows for storage of all previous versions of the same file. Unless you have a requirement to store all versions of uploaded assets, it's recommended to leave this oftion off as it can consume significant storage space over time.", - "admin.storage.versioningNotSupported": "Not supported by this storage target.", - "admin.system.browser": "Browser", - "admin.system.browserHint": "The browser name and version.", - "admin.system.checkForUpdates": "Check", - "admin.system.checkUpdate": "Check / Upgrade", - "admin.system.checkingForUpdates": "Checking for Updates...", - "admin.system.client": "Client", - "admin.system.clientCookies": "Cookies Support", - "admin.system.clientCookiesHint": "Whether cookies are enabled on this browser.", - "admin.system.clientLanguage": "Primary Language", - "admin.system.clientLanguageHint": "The main language advertised by the browser.", - "admin.system.clientPlatform": "Platform", - "admin.system.clientPlatformHint": "The OS platform the browser is running on.", - "admin.system.clientViewport": "Viewport", - "admin.system.clientViewportHint": "The viewport dimensions available to the website.", - "admin.system.configFile": "Configuration File", - "admin.system.configFileHint": "The path to the Wiki.js configuration file.", - "admin.system.cpuCores": "CPU Cores", - "admin.system.cpuCoresHint": "The number of CPU cores available to Wiki.js.", - "admin.system.currentVersion": "Current Version", - "admin.system.currentVersionHint": "The currently installed version.", - "admin.system.database": "Database", - "admin.system.databaseHint": "The version of the database in use.", - "admin.system.databaseHost": "Database Host", - "admin.system.databaseHostHint": "The hostname used to access the database.", - "admin.system.dbPartialSupport": "Your database version is not fully supported. Some functionality may be limited or not work as expected.", - "admin.system.engines": "Server Engines", - "admin.system.hostInfo": "Server Host Information", - "admin.system.hostname": "Hostname", - "admin.system.hostnameHint": "The hostname of the server / container.", - "admin.system.latestVersion": "Latest Version", - "admin.system.latestVersionHint": "The latest version available to install.", - "admin.system.nodejsHint": "The version of Node.js installed.", - "admin.system.os": "Operating System", - "admin.system.osHint": "The OS Wiki.js is running on.", - "admin.system.published": "Published", - "admin.system.ramUsage": "RAM Usage: {used} / {total}", - "admin.system.refreshSuccess": "System Info has been refreshed.", - "admin.system.subtitle": "Information about your server / client", - "admin.system.title": "System Info", - "admin.system.totalRAM": "Total RAM", - "admin.system.totalRAMHint": "The total amount of RAM available to Wiki.js.", - "admin.system.upgrade": "Upgrade", - "admin.system.workingDirectory": "Working Directory", - "admin.system.workingDirectoryHint": "The directory path where Wiki.js is currently running from.", - "admin.tags.date": "Created {created} and last updated {updated}.", - "admin.tags.delete": "Delete this tag", - "admin.tags.deleteConfirm": "Delete Tag?", - "admin.tags.deleteConfirmText": "Are you sure you want to delete tag {tag}? The tag will also be unlinked from all pages.", - "admin.tags.deleteSuccess": "Tag deleted successfully.", - "admin.tags.edit": "Edit Tag", - "admin.tags.emptyList": "No tags to display.", - "admin.tags.filter": "Filter...", - "admin.tags.label": "Label", - "admin.tags.noItemsText": "Add a tag to a page to get started.", - "admin.tags.noSelectionText": "Select a tag from the list on the left.", - "admin.tags.refreshSuccess": "Tags have been refreshed.", - "admin.tags.saveSuccess": "Tag has been saved successfully.", - "admin.tags.subtitle": "Manage page tags", - "admin.tags.tag": "Tag", - "admin.tags.title": "Tags", - "admin.tags.viewLinkedPages": "View Linked Pages", - "admin.terminal.clear": "Clear", - "admin.terminal.command": "Command", - "admin.terminal.connect": "Connect", - "admin.terminal.connectError": "Connection Error:", - "admin.terminal.connected": "Connected.", - "admin.terminal.connecting": "Connecting to server...", - "admin.terminal.disconnect": "Disconnect", - "admin.terminal.disconnected": "Disconnected.", - "admin.terminal.logs": "Logs", - "admin.terminal.subtitle": "View process logs in real-time", - "admin.terminal.title": "Terminal", - "admin.theme.accentColor": "Accent Color", - "admin.theme.accentColorHint": "The accent color for elements that need to stand out or grab the user attention.", - "admin.theme.appearance": "Appearance", - "admin.theme.baseFont": "Base Font", - "admin.theme.baseFontHint": "The font used across the site for the interface.", - "admin.theme.bodyHtmlInjection": "Body HTML Injection", - "admin.theme.bodyHtmlInjectionHint": "HTML code to be injected just before the closing body tag.", - "admin.theme.codeBlocks": "Code Blocks", - "admin.theme.codeBlocksAppearance": "Code Blocks Appearance", - "admin.theme.codeBlocksAppearanceHint": "The color theme used to display code blocks on pages.", - "admin.theme.codeInjection": "Code Injection", - "admin.theme.contentFont": "Content Font", - "admin.theme.contentFontHint": "The font used specifically for page content.", - "admin.theme.contentWidth": "Content Width", - "admin.theme.contentWidthHint": "Should the content use all available viewport space or stay centered.", - "admin.theme.cssOverride": "CSS Override", - "admin.theme.cssOverrideHint": "CSS code to inject after system default CSS. Injecting too much CSS code can result in poor page load performance! CSS will automatically be minified.", - "admin.theme.cssOverrideWarning": "{caution} When adding styles for page content, you must scope them to the {cssClass} class. Omitting this could break the layout of the editor!", - "admin.theme.cssOverrideWarningCaution": "CAUTION:", - "admin.theme.darkMode": "Dark Mode", - "admin.theme.darkModeHint": "Not recommended for accessibility. Can always be turned off by the user.", - "admin.theme.downloadAuthor": "Author", - "admin.theme.downloadDownload": "Download", - "admin.theme.downloadName": "Name", - "admin.theme.downloadThemes": "Download Themes", - "admin.theme.fonts": "Fonts", - "admin.theme.headHtmlInjection": "Head HTML Injection", - "admin.theme.headHtmlInjectionHint": "HTML code to be injected just before the closing head tag. Usually for script tags.", - "admin.theme.headerColor": "Header Color", - "admin.theme.headerColorHint": "The background color for the site top header. Does not apply to the administration area.", - "admin.theme.iconset": "Icon Set", - "admin.theme.iconsetHint": "Set of icons to use for the sidebar navigation.", - "admin.theme.layout": "Layout", - "admin.theme.options": "Theme Options", - "admin.theme.primaryColor": "Primary Color", - "admin.theme.primaryColorHint": "The main color for primary action buttons and most form elements.", - "admin.theme.reduceMotion": "Reduce Motion", - "admin.theme.reduceMotionHint": "Disable most site animations. This setting is automatically enforced when the reduced motion flag is enabled on the user OS.", - "admin.theme.resetDefaults": "Reset Defaults", - "admin.theme.saveSuccess": "Theme configuration saved successfully!", - "admin.theme.secondaryColor": "Secondary Color", - "admin.theme.secondaryColorHint": "The alternate color for secondary action buttons and for some other elements.", - "admin.theme.showPrintBtn": "Show Print Button", - "admin.theme.showPrintBtnHint": "Should the print button be displayed on all pages. Note that this doesn't prevent the user from printing the page using the system dialog.", - "admin.theme.showSharingMenu": "Show Sharing Menu", - "admin.theme.showSharingMenuHint": "Should the sharing menu be displayed on all pages.", - "admin.theme.sidebarColor": "Sidebar Color", - "admin.theme.sidebarColorHint": "The background color for the side navigation menu on content pages. Does not apply to the administration area.", - "admin.theme.sidebarPosition": "Sidebar Position", - "admin.theme.sidebarPositionHint": "On which side should the main site navigation sidebar be displayed.", - "admin.theme.siteTheme": "Site Theme", - "admin.theme.siteThemeHint": "Themes affect how content pages are displayed. Other site sections (such as the editor or admin area) are not affected.", - "admin.theme.subtitle": "Modify the look & feel of your wiki", - "admin.theme.title": "Theme", - "admin.theme.tocPosition": "TOC Position", - "admin.theme.tocPositionHint": "On which side should the Table of Contents sidebar be displayed for content pages.", - "admin.users.active": "Active", - "admin.users.activity": "Activity", - "admin.users.appearance": "Site Appearance", - "admin.users.assignGroup": "Assign Group", - "admin.users.auth": "Authentication", - "admin.users.authProvider": "Provider", - "admin.users.authProviderId": "Provider Id", - "admin.users.authentication": "Authentication", - "admin.users.ban": "Ban User", - "admin.users.banHint": "Block the user from signing in and invalidate any active sessions.", - "admin.users.banned": "Banned", - "admin.users.basicInfo": "Basic Info", - "admin.users.changePassword": "Change Password", - "admin.users.changePasswordHint": "Change the user password. Note that the current password cannot be recovered.", - "admin.users.changePasswordSuccess": "User password was updated successfully.", - "admin.users.create": "Create User", - "admin.users.createInvalidData": "Cannot create user as some fields are invalid or missing.", - "admin.users.createKeepOpened": "Keep dialog opened after create", - "admin.users.createSuccess": "User created successfully!", - "admin.users.createdAt": "Created on {date}", - "admin.users.darkMode": "Dark Mode", - "admin.users.darkModeHint": "Display the user interface using dark mode.", - "admin.users.dateFormat": "Date Format", - "admin.users.dateFormatHint": "How dates should be formatted when displayed to the user.", - "admin.users.defaults": "Manage User Defaults", - "admin.users.defaultsSaveSuccess": "User defaults saved successfully.", - "admin.users.delete": "Delete User", - "admin.users.deleteConfirmForeignNotice": "Note that you cannot delete a user that already created content. You must instead either deactivate the user or delete all content that was created by that user.", - "admin.users.deleteConfirmReplaceWarn": "Any content (pages, uploads, comments, etc.) that was created by this user will be reassigned to the user selected below. It is recommended to create a dummy target user (e.g. Deleted User) if you don't want the content to be reassigned to any current active user.", - "admin.users.deleteConfirmText": "Are you sure you want to delete user {username}?", - "admin.users.deleteConfirmTitle": "Delete User?", - "admin.users.deleteHint": "Permanently remove the user from the database. This action cannot be undone!", - "admin.users.displayName": "Display Name", - "admin.users.edit": "Edit User", - "admin.users.email": "Email", - "admin.users.emailHint": "Email address of the user.", - "admin.users.emailInvalid": "Email address is invalid.", - "admin.users.emailMissing": "Email address is missing.", - "admin.users.extendedMetadata": "Extended Metadata", - "admin.users.groupAlreadyAssigned": "User is already assigned to this group.", - "admin.users.groupAssign": "Assign", - "admin.users.groupAssignNotice": "Note that you cannot assign users to the Administrators or Guests groups from this panel.", - "admin.users.groupSelected": "Assign to {group}", - "admin.users.groups": "Groups", - "admin.users.groupsMissing": "You must assign the user to at least 1 group.", - "admin.users.groupsSelected": "Assign to {count} groups", - "admin.users.id": "ID", - "admin.users.inactive": "Inactive", - "admin.users.info": "User Info", - "admin.users.invalidJSON": "Invalid JSON", - "admin.users.jobTitle": "Job Title", - "admin.users.jobTitleHint": "The job title of the user.", - "admin.users.joined": "Joined", - "admin.users.lastLoginAt": "Last login {date}", - "admin.users.lastUpdated": "Last Updated", - "admin.users.linkedAccounts": "Linked Accounts", - "admin.users.linkedProviders": "Linked Providers", - "admin.users.loading": "Loading User...", - "admin.users.location": "Location", - "admin.users.locationHint": "The city / country of the user or the office location.", - "admin.users.metadata": "Metadata", - "admin.users.minimumGroupRequired": "Cannot unassign because user must be assigned to at least 1 group.", - "admin.users.mustChangePwd": "Must Change Password", - "admin.users.mustChangePwdHint": "User will be prompted to choose a new password upon login.", - "admin.users.name": "Display Name", - "admin.users.nameHint": "Usually the full name or nickname of the user.", - "admin.users.nameInvalidChars": "Name has invalid characters.", - "admin.users.nameMissing": "Name is missing.", - "admin.users.newPassword": "New Password", - "admin.users.noGroupAssigned": "This user is not assigned to any group yet. You must assign at least 1 group to a user.", - "admin.users.noGroupSelected": "You must select a group first.", - "admin.users.noLinkedProviders": "This user isn't linked to any authentication providers.", - "admin.users.noteHint": "Notes are not shown to the user and can only be edited here.", - "admin.users.notes": "Notes", - "admin.users.operations": "Operations", - "admin.users.overview": "Overview", - "admin.users.passAuth": "Password Authentication", - "admin.users.password": "Password", - "admin.users.passwordMissing": "Password is missing.", - "admin.users.passwordTooShort": "Password is too short.", - "admin.users.preferences": "User Preferences", - "admin.users.profile": "User Profile", - "admin.users.pronouns": "Pronouns", - "admin.users.pronounsHint": "The pronouns used to address this user.", - "admin.users.pwdAuthActive": "Can Use Password Authentication", - "admin.users.pwdAuthActiveHint": "Whether the user can login using the password authentication.", - "admin.users.pwdAuthRestrict": "Restrict Password Authentication", - "admin.users.pwdAuthRestrictHint": "Prevent the user from using password authentication for login.", - "admin.users.pwdNotSet": "Password Not Set", - "admin.users.pwdSet": "Password is set", - "admin.users.pwdStrengthGood": "Good", - "admin.users.pwdStrengthMedium": "Medium", - "admin.users.pwdStrengthPoor": "Poor", - "admin.users.pwdStrengthStrong": "Strong", - "admin.users.pwdStrengthWeak": "Weak", - "admin.users.refreshSuccess": "Users refreshed successfully.", - "admin.users.saveSuccess": "User saved successfully.", - "admin.users.selectGroup": "Select Group...", - "admin.users.sendWelcomeEmail": "Send Welcome Email", - "admin.users.sendWelcomeEmailAltHint": "An email will be sent to the user with link(s) to the wiki(s) the user has read access to.", - "admin.users.sendWelcomeEmailHint": "An email will be sent to the user with his login details.", - "admin.users.subtitle": "Manage Users", - "admin.users.tfa": "Two Factor Authentication (2FA)", - "admin.users.tfaInvalidate": "Invalidate 2FA", - "admin.users.tfaInvalidateConfirm": "Are you sure you want to invalidate the user current 2FA configuration? This action cannot be undone.", - "admin.users.tfaInvalidateHint": "Force the user to setup 2FA again. Any active configuration will no longer work.", - "admin.users.tfaInvalidateSuccess": "User TFA configuration has been invalidated.", - "admin.users.tfaNotSet": "2FA is awaiting setup", - "admin.users.tfaRequired": "Require 2FA", - "admin.users.tfaRequiredHint": "User will be forced to use 2FA during the next login. This setting will have no effect if 2FA is already enforced by the login provider.", - "admin.users.tfaSet": "2FA configured", - "admin.users.timeFormat": "Time Format", - "admin.users.timeFormatHint": "How time should be formatted when displayed to the user.", - "admin.users.timezone": "Timezone", - "admin.users.timezoneHint": "Used to adjust date and time displayed to the user.", - "admin.users.title": "Users", - "admin.users.toggle2FA": "Toggle 2FA", - "admin.users.unassignGroup": "Unassign from Group", - "admin.users.unban": "Unban User", - "admin.users.unbanHint": "Allow the user to sign in.", - "admin.users.unverified": "Unverified", - "admin.users.unverify": "Unverify User", - "admin.users.unverifyHint": "Set the user as unverified (state where the email has not been validated).", - "admin.users.updateUser": "Update User", - "admin.users.userActivateSuccess": "User has been activated successfully.", - "admin.users.userAlreadyAssignedToGroup": "User is already assigned to this group!", - "admin.users.userDeactivateSuccess": "User deactivated successfully.", - "admin.users.userTFADisableSuccess": "2FA was disabled successfully.", - "admin.users.userTFAEnableSuccess": "2FA was enabled successfully.", - "admin.users.userUpdateSuccess": "User updated successfully.", - "admin.users.userVerifySuccess": "User has been verified successfully.", - "admin.users.verified": "Verified", - "admin.users.verify": "Verify User", - "admin.users.verifyHint": "Set the user as verified (state where the email has been validated).", - "admin.utilities.authSubtitle": "Various tools for authentication / users", - "admin.utilities.authTitle": "Authentication", - "admin.utilities.cacheSubtitle": "Flush cache of various components", - "admin.utilities.cacheTitle": "Flush Cache", - "admin.utilities.contentSubtitle": "Various tools for pages", - "admin.utilities.contentTitle": "Content", - "admin.utilities.disconnectWS": "Disconnect WebSocket Sessions", - "admin.utilities.disconnectWSHint": "Force all active websocket connections to be closed.", - "admin.utilities.disconnectWSSuccess": "All active websocket connections have been terminated.", - "admin.utilities.export": "Export", - "admin.utilities.exportHint": "Export content to tarball for backup / migration.", - "admin.utilities.flushCache": "Flush Cache", - "admin.utilities.flushCacheHint": "Pages and Assets are cached to disk for better performance. You can flush the cache to force all content to be fetched from the DB again.", - "admin.utilities.graphEndpointSubtitle": "Change the GraphQL endpoint for Wiki.js", - "admin.utilities.graphEndpointTitle": "GraphQL Endpoint", - "admin.utilities.import": "Import", - "admin.utilities.importHint": "Import content from a tarball backup or a 2.X backup.", - "admin.utilities.importv1Subtitle": "Migrate data from a previous 1.x installation", - "admin.utilities.importv1Title": "Import from Wiki.js 1.x", - "admin.utilities.invalidAuthCertificates": "Invalidate Authentication Certificates", - "admin.utilities.invalidAuthCertificatesHint": "Regenerate the public and private keys used for authentication. This will instantly log everyone out.", - "admin.utilities.purgeHistory": "Purge History", - "admin.utilities.purgeHistoryHint": "Delete history (content versioning) older than the selected timeframe.", - "admin.utilities.purgeHistoryTimeframe": "Delete older than...", - "admin.utilities.scanPageProblems": "Scan for Page Problems", - "admin.utilities.scanPageProblemsHint": "Scan all pages for invalid, missing or corrupted data.", - "admin.utilities.subtitle": "Maintenance and miscellaneous tools", - "admin.utilities.telemetrySubtitle": "Enable/Disable telemetry or reset the client ID", - "admin.utilities.telemetryTitle": "Telemetry", - "admin.utilities.title": "Utilities", - "admin.utilities.tools": "Tools", - "admin.utitilies.purgeHistoryMonth": "1 Month | {count} Months", - "admin.utitilies.purgeHistoryToday": "Today", - "admin.utitilies.purgeHistoryYear": "1 Year | {count} Years", - "admin.webhooks.acceptUntrusted": "Accept untrusted SSL certificates", - "admin.webhooks.acceptUntrustedHint": "It is recommended that you leave this off for proper security.", - "admin.webhooks.authHeader": "Authentication Header", - "admin.webhooks.authHeaderHint": "(Optional) The value of the Authorization header to send along the request.", - "admin.webhooks.createInvalidData": "The webhook has some invalid or missing data.", - "admin.webhooks.createSuccess": "Webhook created successfully!", - "admin.webhooks.delete": "Delete Webhook", - "admin.webhooks.deleteConfirm": "Are you sure you want to delete webhook {name}?", - "admin.webhooks.deleteConfirmWarn": "This action cannot be undone!", - "admin.webhooks.deleteSuccess": "Webhook deleted successfully.", - "admin.webhooks.edit": "Edit Webhook", - "admin.webhooks.eventCreatePage": "Create a new page", - "admin.webhooks.eventDeleteAsset": "Delete an asset", - "admin.webhooks.eventDeleteComment": "Delete a comment", - "admin.webhooks.eventDeletePage": "Delete a page", - "admin.webhooks.eventEditAsset": "Edit an existing asset", - "admin.webhooks.eventEditComment": "Edit an existing comment", - "admin.webhooks.eventEditPage": "Update an existing page", - "admin.webhooks.eventNewComment": "Post a new comment", - "admin.webhooks.eventRenameAsset": "Rename / move an asset", - "admin.webhooks.eventRenamePage": "Rename / move a page", - "admin.webhooks.eventUploadAsset": "Upload a new asset", - "admin.webhooks.eventUserJoin": "Create / register a new user", - "admin.webhooks.eventUserLogin": "User logins", - "admin.webhooks.eventUserLogout": "User logouts", - "admin.webhooks.events": "Events", - "admin.webhooks.eventsMissing": "You must select at least 1 event.", - "admin.webhooks.eventsSelected": "No event selected | 1 event selected | {count} events selected", - "admin.webhooks.includeContent": "Include Content", - "admin.webhooks.includeContentHint": "Should the payload include content (e.g. the full page body). Make sure that your remote endpoint can accept large payloads!", - "admin.webhooks.includeMetadata": "Include Metadata", - "admin.webhooks.includeMetadataHint": "Should the payload include metadata such as title, description, author, etc.", - "admin.webhooks.nameInvalidChars": "The name contains invalid characters.", - "admin.webhooks.nameMissing": "A name for this webhook is required.", - "admin.webhooks.new": "New Webhook", - "admin.webhooks.none": "There are no webhooks yet.", - "admin.webhooks.stateError": "Failed", - "admin.webhooks.stateErrorExplain": "The last trigger failed to call the endpoint.", - "admin.webhooks.stateErrorHint": "The last event failed to call your endpoint. Click Edit for more details.", - "admin.webhooks.statePending": "Pending", - "admin.webhooks.statePendingHint": "Waiting for an event to trigger this webhook for the first time.", - "admin.webhooks.stateSuccess": "Healthy", - "admin.webhooks.stateSuccessHint": "The last webhook trigger completed successfully.", - "admin.webhooks.subtitle": "Manage webhooks to external services", - "admin.webhooks.title": "Webhooks", - "admin.webhooks.typeAsset": "asset", - "admin.webhooks.typeComment": "comment", - "admin.webhooks.typePage": "page", - "admin.webhooks.typeUser": "user", - "admin.webhooks.updateSuccess": "Webhook updated successfully.", - "admin.webhooks.url": "URL", - "admin.webhooks.urlHint": "Enter the remote endpoint URL", - "admin.webhooks.urlInvalidChars": "The URL contains invalid characters.", - "admin.webhooks.urlMissing": "The URL is missing or is not valid.", - "auth.actions.login": "Log In", - "auth.actions.register": "Register", - "auth.changePwd.currentPassword": "Current Password", - "auth.changePwd.instructions": "You must choose a new password:", - "auth.changePwd.loading": "Changing password...", - "auth.changePwd.newPassword": "New Password", - "auth.changePwd.newPasswordVerify": "Verify New Password", - "auth.changePwd.proceed": "Change Password", - "auth.changePwd.subtitle": "Choose a new password", - "auth.enterCredentials": "Enter your credentials", - "auth.errors.forgotPassword": "Missing or invalid email address.", - "auth.errors.invalidEmail": "Email is invalid.", - "auth.errors.invalidLogin": "Invalid Login", - "auth.errors.invalidLoginMsg": "The email or password is invalid.", - "auth.errors.invalidName": "Name is invalid.", - "auth.errors.invalidUserEmail": "Invalid User Email", - "auth.errors.login": "Missing or invalid login fields.", - "auth.errors.loginError": "Login error", - "auth.errors.missingEmail": "Email is missing.", - "auth.errors.missingName": "Name is missing.", - "auth.errors.missingPassword": "Password is missing.", - "auth.errors.missingUsername": "Username is missing.", - "auth.errors.missingVerifyPassword": "Password Verification is missing.", - "auth.errors.notYetAuthorized": "You have not been authorized to login to this site yet.", - "auth.errors.passwordTooShort": "Password is too short.", - "auth.errors.passwordsNotMatch": "Passwords do not match.", - "auth.errors.register": "One or more fields are invalid.", - "auth.errors.tfaMissing": "Missing or incomplete security code.", - "auth.errors.tooManyAttempts": "Too many attempts!", - "auth.errors.tooManyAttemptsMsg": "You've made too many failed attempts in a short period of time, please try again {time}.", - "auth.errors.userNotFound": "User not found", - "auth.fields.email": "Email Address", - "auth.fields.emailUser": "Email / Username", - "auth.fields.name": "Name", - "auth.fields.password": "Password", - "auth.fields.username": "Username", - "auth.fields.verifyPassword": "Verify Password", - "auth.forgotPasswordCancel": "Cancel", - "auth.forgotPasswordLink": "Forgot Password", - "auth.forgotPasswordLoading": "Requesting password reset...", - "auth.forgotPasswordSubtitle": "Enter your email address to receive the instructions to reset your password:", - "auth.forgotPasswordSuccess": "Check your emails for password reset instructions!", - "auth.forgotPasswordTitle": "Forgot your password", - "auth.genericError": "Authentication is unavailable.", - "auth.invalidEmail": "Email address is invalid.", - "auth.invalidEmailUsername": "Enter a valid email / username.", - "auth.invalidPassword": "Enter a valid password.", - "auth.login.title": "Login", - "auth.loginRequired": "Login required", - "auth.loginSuccess": "Login Successful! Redirecting...", - "auth.loginUsingStrategy": "Login using {strategy}", - "auth.logoutSuccess": "You've been logged out successfully.", - "auth.missingEmail": "Missing email address.", - "auth.missingName": "Name is missing.", - "auth.missingPassword": "Missing password.", - "auth.nameTooLong": "Name is too long.", - "auth.nameTooShort": "Name is too short.", - "auth.orLoginUsingStrategy": "or login using...", - "auth.passwordNotMatch": "Both passwords do not match.", - "auth.passwordTooShort": "Password is too short.", - "auth.pleaseWait": "Please wait", - "auth.registerCheckEmail": "Check your emails to activate your account.", - "auth.registerSubTitle": "Fill-in the form below to create an account.", - "auth.registerSuccess": "Account created successfully!", - "auth.registerTitle": "Create an account", - "auth.registering": "Creating account...", - "auth.selectAuthProvider": "Sign in with", - "auth.sendResetPassword": "Reset Password", - "auth.signingIn": "Signing In...", - "auth.switchToLogin.link": "Back to Login", - "auth.switchToRegister.link": "Create an Account", - "auth.tfa.subtitle": "Security code required:", - "auth.tfa.verifyToken": "Verify", - "auth.tfaFormTitle": "Enter the security code generated from your trusted device:", - "auth.tfaSetupInstrFirst": "1) Scan the QR code below from your mobile 2FA application:", - "auth.tfaSetupInstrSecond": "2) Enter the security code generated from your trusted device:", - "auth.tfaSetupTitle": "Your administrator has required Two-Factor Authentication (2FA) to be enabled on your account.", - "common.actions.activate": "Activate", - "common.actions.add": "Add", - "common.actions.apply": "Apply", - "common.actions.browse": "Browse...", - "common.actions.cancel": "Cancel", - "common.actions.clear": "Clear", - "common.actions.close": "Close", - "common.actions.commit": "Commit", - "common.actions.confirm": "Confirm", - "common.actions.copy": "Copy", - "common.actions.copyURL": "Copy URL", - "common.actions.create": "Create", - "common.actions.deactivate": "Deactivate", - "common.actions.delete": "Delete", - "common.actions.discard": "Discard", - "common.actions.discardChanges": "Discard Changes", - "common.actions.download": "Download", - "common.actions.duplicate": "Duplicate", - "common.actions.edit": "Edit", - "common.actions.exit": "Exit", - "common.actions.fetch": "Fetch", - "common.actions.filter": "Filter", - "common.actions.generate": "Generate", - "common.actions.howItWorks": "How it works", - "common.actions.insert": "Insert", - "common.actions.login": "Login", - "common.actions.manage": "Manage", - "common.actions.move": "Move", - "common.actions.moveTo": "Move To", - "common.actions.new": "New", - "common.actions.newFolder": "New Folder", - "common.actions.newPage": "New Page", - "common.actions.ok": "OK", - "common.actions.optimize": "Optimize", - "common.actions.page": "Page", - "common.actions.preview": "Preview", - "common.actions.proceed": "Proceed", - "common.actions.properties": "Properties", - "common.actions.refresh": "Refresh", - "common.actions.rename": "Rename", - "common.actions.returnToTop": "Return to top", - "common.actions.save": "Save", - "common.actions.saveChanges": "Save Changes", - "common.actions.select": "Select", - "common.actions.update": "Update", - "common.actions.upload": "Upload", - "common.actions.view": "View", - "common.actions.viewDocs": "View Documentation", - "common.clipboard.failure": "Failed to copy to clipboard.", - "common.clipboard.success": "Copied to clipboard successfully.", - "common.clipboard.uuid": "Copy UUID to clipboard.", - "common.clipboard.uuidFailure": "Failed to copy UUID to clipboard.", - "common.clipboard.uuidSuccess": "Copied UUID to clipboard successfully.", - "common.comments.beFirst": "Be the first to comment.", - "common.comments.contentMissingError": "Comment is empty or too short!", - "common.comments.deleteConfirmTitle": "Confirm Delete", - "common.comments.deletePermanentWarn": "This action cannot be undone!", - "common.comments.deleteSuccess": "Comment was deleted successfully.", - "common.comments.deleteWarn": "Are you sure you want to permanently delete this comment?", - "common.comments.fieldContent": "Comment Content", - "common.comments.fieldEmail": "Your Email Address", - "common.comments.fieldName": "Your Name", - "common.comments.loading": "Loading comments...", - "common.comments.markdownFormat": "Markdown Format", - "common.comments.modified": "modified {reldate}", - "common.comments.newComment": "New Comment", - "common.comments.newPlaceholder": "Write a new comment...", - "common.comments.none": "No comments yet.", - "common.comments.postComment": "Post Comment", - "common.comments.postSuccess": "New comment posted successfully.", - "common.comments.postingAs": "Posting as {name}", - "common.comments.sdTitle": "Talk", - "common.comments.title": "Comments", - "common.comments.updateComment": "Update Comment", - "common.comments.updateSuccess": "Comment was updated successfully.", - "common.comments.viewDiscussion": "View Discussion", - "common.datetime": "{date} 'at' {time}", - "common.duration.days": "Day(s)", - "common.duration.every": "Every", - "common.duration.hours": "Hour(s)", - "common.duration.minutes": "Minute(s)", - "common.duration.months": "Month(s)", - "common.duration.years": "Year(s)", - "common.error.generic.hint": "Oops, something went wrong...", - "common.error.generic.title": "Unexpected Error", - "common.error.notfound.hint": "That page doesn't exist or is not available.", - "common.error.notfound.title": "Not Found", - "common.error.title": "Error", - "common.error.unauthorized.hint": "You don't have the required permissions to access this page.", - "common.error.unauthorized.title": "Unauthorized", - "common.error.unexpected": "An unexpected error occurred.", - "common.error.unknownsite.hint": "There's no wiki site at this host.", - "common.error.unknownsite.title": "Unknown Site", - "common.field.createdOn": "Created On", - "common.field.id": "ID", - "common.field.lastUpdated": "Last Updated", - "common.field.name": "Name", - "common.field.task": "Task", - "common.field.title": "Title", - "common.footerCopyright": "© {year} {company}. All rights reserved.", - "common.footerGeneric": "Powered by {link}, an open source project.", - "common.footerLicense": "Content is available under the {license}, by {company}.", - "common.footerPoweredBy": "Powered by {link}", - "common.header.account": "Account", - "common.header.admin": "Administration", - "common.header.assets": "Assets", - "common.header.browseTags": "Browse by Tags", - "common.header.currentPage": "Current Page", - "common.header.delete": "Delete", - "common.header.duplicate": "Duplicate", - "common.header.edit": "Edit", - "common.header.history": "History", - "common.header.home": "Home", - "common.header.imagesFiles": "Images & Files", - "common.header.language": "Language", - "common.header.login": "Login", - "common.header.logout": "Logout", - "common.header.move": "Move / Rename", - "common.header.myWiki": "My Wiki", - "common.header.newPage": "New Page", - "common.header.pageActions": "Page Actions", - "common.header.profile": "Profile", - "common.header.search": "Search...", - "common.header.searchClose": "Close", - "common.header.searchCopyLink": "Copy Search Link", - "common.header.searchDidYouMean": "Did you mean...", - "common.header.searchHint": "Type at least 2 characters to start searching...", - "common.header.searchLoading": "Searching...", - "common.header.searchNoResult": "No pages matching your query.", - "common.header.searchResultsCount": "Found {total} results", - "common.header.siteMap": "Site Map", - "common.header.view": "View", - "common.header.viewSource": "View Source", - "common.license.alr": "All Rights Reserved", - "common.license.cc0": "Public Domain", - "common.license.ccby": " Creative Commons Attribution License", - "common.license.ccbync": "Creative Commons Attribution-NonCommercial License", - "common.license.ccbyncnd": "Creative Commons Attribution-NonCommercial-NoDerivs License", - "common.license.ccbyncsa": "Creative Commons Attribution-NonCommercial-ShareAlike License", - "common.license.ccbynd": "Creative Commons Attribution-NoDerivs License", - "common.license.ccbysa": "Creative Commons Attribution-ShareAlike License", - "common.license.none": "None", - "common.loading": "Loading...", - "common.modernBrowser": "modern browser", - "common.newpage.create": "Create Page", - "common.newpage.goback": "Go back", - "common.newpage.subtitle": "Would you like to create it now?", - "common.newpage.title": "This page does not exist yet.", - "common.notfound.gohome": "Home", - "common.notfound.subtitle": "This page does not exist.", - "common.notfound.title": "Not Found", - "common.outdatedBrowserWarning": "Your browser is outdated. Upgrade to a {modernBrowser}.", - "common.page.bookmark": "Bookmark", - "common.page.delete": "Delete Page", - "common.page.deleteSubtitle": "The page can be restored from the administration area.", - "common.page.deleteTitle": "Are you sure you want to delete page {title}?", - "common.page.editPage": "Edit Page", - "common.page.global": "Global", - "common.page.id": "ID {id}", - "common.page.lastEditedBy": "Last edited by", - "common.page.loading": "Loading Page...", - "common.page.printFormat": "Print Format", - "common.page.private": "Private", - "common.page.published": "Published", - "common.page.returnNormalView": "Return to Normal View", - "common.page.share": "Share", - "common.page.tags": "Tags", - "common.page.tagsMatching": "Pages matching tags", - "common.page.toc": "Table of Contents", - "common.page.unpublished": "Unpublished", - "common.page.unpublishedWarning": "This page is not published.", - "common.page.versionId": "Version ID {id}", - "common.page.viewingSource": "Viewing source of page {path}", - "common.page.viewingSourceVersion": "Viewing source as of {date} of page {path}", - "common.pageSelector.createTitle": "Select New Page Location", - "common.pageSelector.folderEmptyWarning": "This folder is empty.", - "common.pageSelector.moveTitle": "Move / Rename Page Location", - "common.pageSelector.pages": "Pages", - "common.pageSelector.selectTitle": "Select a Page", - "common.pageSelector.virtualFolders": "Virtual Folders", - "common.password.average": "Average", - "common.password.good": "Good", - "common.password.poor": "Poor", - "common.password.strong": "Strong", - "common.password.weak": "Weak", - "common.sidebar.browse": "Browse", - "common.sidebar.currentDirectory": "Current Directory", - "common.sidebar.mainMenu": "Main Menu", - "common.sidebar.root": "(root)", - "common.unauthorized.action.create": "You cannot create the page.", - "common.unauthorized.action.download": "You cannot download the page content.", - "common.unauthorized.action.downloadVersion": "You cannot download the content for this page version.", - "common.unauthorized.action.edit": "You cannot edit the page.", - "common.unauthorized.action.history": "You cannot view the page history.", - "common.unauthorized.action.source": "You cannot view the page source.", - "common.unauthorized.action.sourceVersion": "You cannot view the source of this version of the page.", - "common.unauthorized.action.view": "You cannot view this page.", - "common.unauthorized.goback": "Go Back", - "common.unauthorized.login": "Login As...", - "common.unauthorized.title": "Unauthorized", - "common.user.search": "Search User", - "common.user.searchPlaceholder": "Search Users...", - "common.welcome.createhome": "Create Home Page", - "common.welcome.subtitle": "Let's get started and create the home page.", - "common.welcome.title": "Welcome to your wiki!", - "editor.assets.deleteAsset": "Delete Asset", - "editor.assets.deleteAssetConfirm": "Are you sure you want to delete asset", - "editor.assets.deleteAssetWarn": "This action cannot be undone!", - "editor.assets.deleteSuccess": "Asset deleted successfully.", - "editor.assets.fetchImage": "Fetch Remote Image", - "editor.assets.fileCount": "{count} files", - "editor.assets.folderCreateSuccess": "Asset folder created successfully.", - "editor.assets.folderEmpty": "This asset folder is empty.", - "editor.assets.folderName": "Folder Name", - "editor.assets.folderNameNamingRules": "Must follow the asset folder {namingRules}.", - "editor.assets.folderNameNamingRulesLink": "naming rules", - "editor.assets.headerActions": "Actions", - "editor.assets.headerAdded": "Added", - "editor.assets.headerFileSize": "File Size", - "editor.assets.headerFilename": "Filename", - "editor.assets.headerId": "ID", - "editor.assets.headerType": "Type", - "editor.assets.imageAlign": "Image Alignment", - "editor.assets.newFolder": "New Folder", - "editor.assets.noUploadError": "You must choose a file to upload first!", - "editor.assets.refreshSuccess": "List of assets refreshed successfully.", - "editor.assets.renameAsset": "Rename Asset", - "editor.assets.renameAssetSubtitle": "Enter the new name for this asset:", - "editor.assets.renameSuccess": "Asset renamed successfully.", - "editor.assets.title": "Assets", - "editor.assets.uploadAssets": "Upload Assets", - "editor.assets.uploadAssetsDropZone": "Browse or Drop files here...", - "editor.assets.uploadFailed": "File upload failed.", - "editor.backToEditor": "Back to Editor", - "editor.ckeditor.stats": "{chars} chars, {words} words", - "editor.conflict.editable": "(editable)", - "editor.conflict.infoGeneric": "A more recent version of this page was saved by {authorName}, {date}", - "editor.conflict.leftPanelInfo": "Your current edit, based on page version from {date}", - "editor.conflict.localVersion": "Local Version {refEditable}", - "editor.conflict.overwrite.description": "Are you sure you want to replace your current version with the latest remote content? {refEditsLost}", - "editor.conflict.overwrite.editsLost": "Your current edits will be lost.", - "editor.conflict.overwrite.title": "Overwrite with Remote Version?", - "editor.conflict.pageDescription": "Description:", - "editor.conflict.pageTitle": "Title:", - "editor.conflict.readonly": "(read-only)", - "editor.conflict.remoteVersion": "Remote Version {refReadOnly}", - "editor.conflict.rightPanelInfo": "Last edited by {authorName}, {date}", - "editor.conflict.title": "Resolve Save Conflict", - "editor.conflict.useLocal": "Use Local", - "editor.conflict.useLocalHint": "Use content in the left panel", - "editor.conflict.useRemote": "Use Remote", - "editor.conflict.useRemoteHint": "Discard local changes and use latest version", - "editor.conflict.viewLatestVersion": "View Latest Version", - "editor.conflict.warning": "Save conflict! Another user has already modified this page.", - "editor.conflict.whatToDo": "What do you want to do?", - "editor.conflict.whatToDoLocal": "Use your current local version and ignore the latest changes.", - "editor.conflict.whatToDoRemote": "Use the remote version (latest) and discard your changes.", - "editor.createPage": "Create Page", - "editor.markup.admonitionDanger": "Danger / Important Admonition", - "editor.markup.admonitionInfo": "Info / Note Admonition", - "editor.markup.admonitionSuccess": "Tip / Success Admonition", - "editor.markup.admonitionWarning": "Warning Admonition", - "editor.markup.blockquote": "Blockquote", - "editor.markup.blockquoteAdmonitions": "Blockquote / Admonition", - "editor.markup.blockquoteError": "Error Blockquote", - "editor.markup.blockquoteInfo": "Info Blockquote", - "editor.markup.blockquoteSuccess": "Success Blockquote", - "editor.markup.blockquoteWarning": "Warning Blockquote", - "editor.markup.bold": "Bold", - "editor.markup.distractionFreeMode": "Distraction Free Mode", - "editor.markup.header": "Header", - "editor.markup.headerLevel": "Header {level}", - "editor.markup.heading": "Heading {level}", - "editor.markup.inlineCode": "Inline Code", - "editor.markup.insertAssets": "Insert Assets", - "editor.markup.insertBlock": "Insert Block", - "editor.markup.insertCodeBlock": "Insert Code Block", - "editor.markup.insertDiagram": "Insert Diagram", - "editor.markup.insertEmoji": "Insert Emoji", - "editor.markup.insertFootnote": "Insert Footnote", - "editor.markup.insertHorizontalBar": "Insert Horizontal Bar", - "editor.markup.insertLink": "Insert Link", - "editor.markup.insertMathExpression": "Insert Math Expression", - "editor.markup.insertTable": "Insert Table", - "editor.markup.insertTabset": "Insert Tabset", - "editor.markup.insertVideoAudio": "Insert Video / Audio", - "editor.markup.italic": "Italic", - "editor.markup.keyboardKey": "Keyboard Key", - "editor.markup.markdownFormattingHelp": "Markdown Formatting Help", - "editor.markup.noSelectionError": "Text must be selected first!", - "editor.markup.orderedList": "Ordered List", - "editor.markup.strikethrough": "Strikethrough", - "editor.markup.subscript": "Subscript", - "editor.markup.superscript": "Superscript", - "editor.markup.tableHelper": "Table Helper", - "editor.markup.taskList": "Task List", - "editor.markup.taskListChecked": "Checked List Item", - "editor.markup.taskListUnchecked": "Unchecked List Item", - "editor.markup.togglePreviewPane": "Hide / Show Preview Pane", - "editor.markup.toggleSpellcheck": "Toggle Spellcheck", - "editor.markup.unorderedList": "Unordered List", - "editor.page": "Page", - "editor.pageData.data": "Data", - "editor.pageData.dragDropHint": "Drag-n-drop fields from the left onto this area to build your template structure.", - "editor.pageData.duplicateTemplateKeys": "One or more fields have duplicate unique keys!", - "editor.pageData.emptyTemplateStructure": "Template Structure is empty!", - "editor.pageData.fieldTypeBoolean": "Boolean", - "editor.pageData.fieldTypeHeader": "Header", - "editor.pageData.fieldTypeImage": "Image", - "editor.pageData.fieldTypeLink": "Link", - "editor.pageData.fieldTypeNumber": "Number", - "editor.pageData.fieldTypeText": "Text", - "editor.pageData.invalidTemplateKeys": "One or more fields have missing or invalid unique keys!", - "editor.pageData.invalidTemplateLabels": "One or more fields have missing or invalid labels!", - "editor.pageData.invalidTemplateName": "Template name is missing or invalid!", - "editor.pageData.label": "Label", - "editor.pageData.manageTemplates": "Manage Templates", - "editor.pageData.noTemplate": "You don't have any template yet. Create one to get started.", - "editor.pageData.selectTemplateAbove": "Select a template to edit from the dropdown menu above.", - "editor.pageData.template": "Template", - "editor.pageData.templateDeleteConfirmText": "Any page currently using this template will revert to basic template rendering.", - "editor.pageData.templateDeleteConfirmTitle": "Are you sure?", - "editor.pageData.templateFullRowTypes": "Full Row Types", - "editor.pageData.templateKeyValueTypes": "Key-Value Types", - "editor.pageData.templateStructure": "Structure", - "editor.pageData.templateTitle": "Template Title", - "editor.pageData.templateUntitled": "Untitled Template", - "editor.pageData.title": "Page Data", - "editor.pageData.uniqueKey": "Unique Key", - "editor.pageRel.button": "Button Appearance", - "editor.pageRel.caption": "Caption (optional)", - "editor.pageRel.center": "Center", - "editor.pageRel.label": "Label", - "editor.pageRel.left": "Left", - "editor.pageRel.position": "Button Position", - "editor.pageRel.preview": "Preview", - "editor.pageRel.right": "Right", - "editor.pageRel.selectIcon": "Select Icon...", - "editor.pageRel.selectPage": "Select Page...", - "editor.pageRel.target": "Target", - "editor.pageRel.title": "Add Page Relation", - "editor.pageRel.titleEdit": "Edit Page Relation", - "editor.pageScripts.title": "Page Scripts", - "editor.props.alias": "Alias", - "editor.props.allowComments": "Allow Comments", - "editor.props.allowCommentsHint": "Enable commenting abilities on this page.", - "editor.props.allowContributions": "Allow Contributions", - "editor.props.allowRatings": "Allow Ratings", - "editor.props.allowRatingsHint": "Enable rating capabilities on this page.", - "editor.props.dateRange": "Date Range", - "editor.props.dateRangeHint": "Select the start and end date for this page publication. The page will only be accessible to users with read access within the selected date range.", - "editor.props.draft": "Draft", - "editor.props.draftHint": "Visible to users with write access only.", - "editor.props.icon": "Icon", - "editor.props.info": "Info", - "editor.props.jsLoad": "Javascript - On Load", - "editor.props.jsLoadHint": "Execute javascript once the page is loaded", - "editor.props.jsUnload": "Javascript - On Unload", - "editor.props.jsUnloadHint": "Execute javascript before the page content is destroyed", - "editor.props.pageProperties": "Page Properties", - "editor.props.password": "Password", - "editor.props.passwordHint": "The page must be published and the user must have read access rights.", - "editor.props.publishState": "Publishing State", - "editor.props.published": "Published", - "editor.props.publishedHint": "Visible to all users with read access.", - "editor.props.relationAdd": "Add Relation...", - "editor.props.relationAddHint": "Add links to other pages in the footer (e.g. as part of a series of articles)", - "editor.props.relations": "Relations", - "editor.props.requirePassword": "Require Password", - "editor.props.scripts": "Scripts", - "editor.props.shortDescription": "Short Description", - "editor.props.showInTree": "Show in Site Navigation", - "editor.props.showSidebar": "Show Sidebar", - "editor.props.showTags": "Show Tags", - "editor.props.showToc": "Show Table of Contents", - "editor.props.sidebar": "Sidebar", - "editor.props.social": "Social", - "editor.props.styles": "CSS Styles", - "editor.props.stylesHint": "CSS Rules to add to the page", - "editor.props.tags": "Tags", - "editor.props.tagsHint": "Use tags to categorize your pages and make them easier to find.", - "editor.props.title": "Title", - "editor.props.tocMinMaxDepth": "Min/Max Depth", - "editor.props.visibility": "Visibility", - "editor.reasonForChange.field": "Reason", - "editor.reasonForChange.optional": "Enter a short description of the reason for this change. This is optional but recommended.", - "editor.reasonForChange.reasonMissing": "A reason is missing.", - "editor.reasonForChange.required": "You must provide a reason for this change. Enter a small description of what changed.", - "editor.reasonForChange.title": "Reason For Change", - "editor.renderPreview": "Render Preview", - "editor.save.createSuccess": "Page created successfully.", - "editor.save.error": "An error occurred while creating the page", - "editor.save.pleaseWait": "Please wait...", - "editor.save.processing": "Rendering", - "editor.save.saved": "Saved", - "editor.save.updateSuccess": "Page updated successfully.", - "editor.saveAndCloseTip": "Ctrl / Cmd + Click to save and close", - "editor.select.cannotChange": "This cannot be changed once the page is created.", - "editor.select.customView": "or create a custom view?", - "editor.select.title": "Which editor do you want to use for this page?", - "editor.settings": "Editor Settings", - "editor.tableEditor.title": "Table Editor", - "editor.togglePreviewPane": "Toggle Preview Pane", - "editor.toggleScrollSync": "Toggle Scroll Sync", - "editor.unsaved.body": "You have unsaved changes. Are you sure you want to leave the editor and discard any modifications you made since the last save?", - "editor.unsaved.title": "Discard Unsaved Changes?", - "editor.unsavedWarning": "You have unsaved edits. Are you sure you want to leave the editor?", - "fileman.7zFileType": "7zip Archive", - "fileman.aacFileType": "AAC Audio File", - "fileman.aiFileType": "Adobe Illustrator Document", - "fileman.aifFileType": "AIF Audio File", - "fileman.apkFileType": "Android Package", - "fileman.aviFileType": "AVI Video File", - "fileman.binFileType": "Binary File", - "fileman.bz2FileType": "BZIP2 Archive", - "fileman.copyURLSuccess": "URL has been copied to the clipboard.", - "fileman.createFolderInvalidData": "One or more fields are invalid.", - "fileman.createFolderSuccess": "Folder created successfully.", - "fileman.cssFileType": "Cascade Style Sheet", - "fileman.csvFileType": "Comma Separated Values Document", - "fileman.dataFileType": "Data File", - "fileman.detailsAssetSize": "File Size", - "fileman.detailsAssetType": "Type", - "fileman.detailsPageCreated": "Created", - "fileman.detailsPageEditor": "Editor", - "fileman.detailsPageType": "Type", - "fileman.detailsPageUpdated": "Last Updated", - "fileman.detailsTitle": "Title", - "fileman.dmgFileType": "Apple Disk Image File", - "fileman.docxFileType": "Microsoft Word Document", - "fileman.epsFileType": "EPS Image", - "fileman.exeFileType": "Windows Executable", - "fileman.flacFileType": "FLAC Audio File", - "fileman.folderChildrenCount": "Empty folder | 1 child | {count} children", - "fileman.folderCreate": "New Folder", - "fileman.folderFileName": "Path Name", - "fileman.folderFileNameHint": "URL friendly version of the folder name. Must consist of lowercase alphanumerical or hypen characters only.", - "fileman.folderFileNameInvalid": "Invalid Characters in Folder Path Name. Lowercase alphanumerical and hyphen characters only.", - "fileman.folderFileNameMissing": "Missing Folder Path Name", - "fileman.folderRename": "Rename Folder", - "fileman.folderTitle": "Title", - "fileman.folderTitleInvalidChars": "Invalid Characters in Folder Name", - "fileman.folderTitleMissing": "Missing Folder Title", - "fileman.gifFileType": "Animated GIF Image", - "fileman.gzFileType": "GZipped Archive", - "fileman.heicFileType": "HEIC Image", - "fileman.icoFileType": "Icon File", - "fileman.icsFileType": "ICS Calendar Event", - "fileman.isoFileType": "Optical Disk Image File", - "fileman.jpegFileType": "JPEG Image", - "fileman.jpgFileType": "JPEG Image", - "fileman.jsonFileType": "JSON Document", - "fileman.m4aFileType": "M4A Audio File", - "fileman.markdownPageType": "Markdown Page", - "fileman.midFileType": "MIDI Audio File", - "fileman.movFileType": "QuickTime Video File", - "fileman.mp3FileType": "MP3 Audio File", - "fileman.mp4FileType": "MP4 Video File", - "fileman.mpegFileType": "MPEG Video File", - "fileman.mpgFileType": "MPEG Video File", - "fileman.oggFileType": "OGG Audio File", - "fileman.otfFileType": "OpenType Font File", - "fileman.pdfFileType": "PDF Document", - "fileman.pngFileType": "PNG Image", - "fileman.pptxFileType": "Microsoft Powerpoint Presentation", - "fileman.psdFileType": "Adobe Photoshop Document", - "fileman.rarFileType": "RAR Archive", - "fileman.renameFolderInvalidData": "One or more fields are invalid.", - "fileman.renameFolderSuccess": "Folder renamed successfully.", - "fileman.svgFileType": "Scalable Vector Graphic", - "fileman.tarFileType": "TAR Archive", - "fileman.tgzFileType": "Gzipped TAR Archive", - "fileman.tifFileType": "TIFF Image", - "fileman.title": "File Manager", - "fileman.ttfFileType": "TrueType Font File", - "fileman.txtFileType": "Text Document", - "fileman.unknownFileType": "{type} file", - "fileman.uploadSuccess": "File(s) uploaded successfully.", - "fileman.viewOptions": "View Options", - "fileman.wavFileType": "WAV Audio File", - "fileman.wmaFileType": "WMA Audio File", - "fileman.wmvFileType": "WMV Video File", - "fileman.woff2FileType": "Web Open Font File", - "fileman.woffFileType": "Web Open Font File", - "fileman.xlstFileType": "Microsoft Excel Document", - "fileman.xmlFileType": "XML Document", - "fileman.xzFileType": "XZ Archive", - "fileman.zipFileType": "ZIP Archive", - "folderDeleteDialog.confirm": "Are you sure you want to delete folder {name} and all its content?", - "folderDeleteDialog.deleteSuccess": "Folder has been deleted successfully.", - "folderDeleteDialog.folderId": "Folder ID {id}", - "folderDeleteDialog.title": "Confirm Delete Folder", - "history.restore.confirmButton": "Restore", - "history.restore.confirmText": "Are you sure you want to restore this page content as it was on {date}? This version will be copied on top of the current history. As such, newer versions will still be preserved.", - "history.restore.confirmTitle": "Restore page version?", - "history.restore.success": "Page version restored succesfully!", - "pageDeleteDialog.confirm": "Are you sure you want to delete the page {name}?", - "pageDeleteDialog.deleteSuccess": "Page deleted successfully.", - "pageDeleteDialog.pageId": "Page ID {id}", - "pageDeleteDialog.title": "Confirm Page Deletion", - "pageSaveDialog.displayModePath": "Browse Using Paths", - "pageSaveDialog.displayModeTitle": "Browse Using Titles", - "pageSaveDialog.title": "Save As...", - "profile.accessibility": "Accessibility", - "profile.activity": "Activity", - "profile.appearance": "Site Appearance", - "profile.appearanceDark": "Dark", - "profile.appearanceDefault": "Site Default", - "profile.appearanceHint": "Use the light or dark theme.", - "profile.appearanceLight": "Light", - "profile.auth": "Authentication", - "profile.authChangePassword": "Change Password", - "profile.authInfo": "Your account is associated with the following authentication methods:", - "profile.authLoadingFailed": "Failed to load authentication methods.", - "profile.authModifyTfa": "Modify 2FA", - "profile.authSetTfa": "Set 2FA", - "profile.avatar": "Avatar", - "profile.avatarClearFailed": "Failed to clear profile picture.", - "profile.avatarClearSuccess": "Profile picture cleared successfully.", - "profile.avatarUploadDisabled": "Your avatar is set by your organization and cannot be changed.", - "profile.avatarUploadFailed": "Failed to upload user profile picture.", - "profile.avatarUploadHint": "For best results, use a 180x180 image of type JPG or PNG.", - "profile.avatarUploadSuccess": "Profile picture uploaded successfully.", - "profile.avatarUploadTitle": "Upload your user profile picture.", - "profile.cvd": "Color Vision Deficiency", - "profile.cvdDeuteranopia": "Deuteranopia", - "profile.cvdHint": "Alter the color scheme of certain UI elements to account for certain color vision dificiencies.", - "profile.cvdNone": "None", - "profile.cvdProtanopia": "Protanopia", - "profile.cvdTritanopia": "Tritanopia", - "profile.darkMode": "Dark Mode", - "profile.darkModeHint": "Change the appareance of the site to a dark theme.", - "profile.dateFormat": "Date Format", - "profile.dateFormatHint": "Set your preferred format to display dates.", - "profile.displayName": "Display Name", - "profile.displayNameHint": "Your full name; shown when authoring content (e.g. pages, comments, etc.).", - "profile.editDisabledDescription": "Your wiki administrator has disabled profile editing.", - "profile.editDisabledTitle": "Profile info is managed by your organization.", - "profile.email": "Email Address", - "profile.emailHint": "The email address used for login.", - "profile.groups": "Groups", - "profile.groupsInfo": "You're currently part of the following groups:", - "profile.groupsLoadingFailed": "Failed to load groups.", - "profile.groupsNone": "You're not part of any group.", - "profile.jobTitle": "Job Title", - "profile.jobTitleHint": "Your position in your organization; shown on your profile page.", - "profile.localeDefault": "Locale Default", - "profile.location": "Location", - "profile.locationHint": "Your city and country; shown on your profile page.", - "profile.myInfo": "My Info", - "profile.notifications": "Notifications", - "profile.pages.emptyList": "No pages to display.", - "profile.pages.headerCreatedAt": "Created", - "profile.pages.headerPath": "Path", - "profile.pages.headerTitle": "Title", - "profile.pages.headerUpdatedAt": "Last Updated", - "profile.pages.refreshSuccess": "Page list has been refreshed.", - "profile.pages.subtitle": "List of pages I created or last modified", - "profile.pages.title": "Pages", - "profile.preferences": "Preferences", - "profile.pronouns": "Pronouns", - "profile.pronounsHint": "Let people know which pronouns should they use when referring to you.", - "profile.save.success": "Profile saved successfully.", - "profile.saveFailed": "Failed to save profile changes.", - "profile.saveSuccess": "Profile saved successfully.", - "profile.saving": "Saving profile...", - "profile.subtitle": "My personal info", - "profile.timeFormat": "Time Format", - "profile.timeFormat12h": "12 hour", - "profile.timeFormat24h": "24 hour", - "profile.timeFormatHint": "Set your preferred format to display time.", - "profile.timezone": "Timezone", - "profile.timezoneHint": "Set your timezone to display local time correctly.", - "profile.title": "Profile", - "profile.uploadNewAvatar": "Upload New Image", - "profile.viewPublicProfile": "View Public Profile", - "tags.clearSelection": "Clear Selection", - "tags.currentSelection": "Current Selection", - "tags.locale": "Locale", - "tags.localeAny": "Any", - "tags.noResults": "Couldn't find any page with the selected tags.", - "tags.noResultsWithFilter": "Couldn't find any page matching the current filtering options.", - "tags.orderBy": "Order By", - "tags.orderByField.ID": "ID", - "tags.orderByField.creationDate": "Creation Date", - "tags.orderByField.lastModified": "Last Modified", - "tags.orderByField.path": "Path", - "tags.orderByField.title": "Title", - "tags.pageLastUpdated": "Last Updated {date}", - "tags.retrievingResultsLoading": "Retrieving page results...", - "tags.searchWithinResultsPlaceholder": "Search within results...", - "tags.selectOneMoreTags": "Select one or more tags", - "tags.selectOneMoreTagsHint": "Select one or more tags on the left.", - "welcome.admin": "Administration Area", - "welcome.createHome": "Create the homepage", - "welcome.homeDefault.content": "Write some content here...", - "welcome.homeDefault.description": "Welcome to my wiki!", - "welcome.homeDefault.title": "Home", - "welcome.subtitle": "Let's get started...", - "welcome.title": "Welcome to Wiki.js!" -} diff --git a/ux/src/pages/AdminGeneral.vue b/ux/src/pages/AdminGeneral.vue index 7842228d..731b3788 100644 --- a/ux/src/pages/AdminGeneral.vue +++ b/ux/src/pages/AdminGeneral.vue @@ -228,6 +228,31 @@ q-page.admin-general :options='reasonForChangeModes' ) + //- ----------------------- + //- Defaults + //- ----------------------- + q-card.q-pb-sm.q-mt-md(v-if='state.config.defaults') + q-card-section + .text-subtitle1 {{t('admin.general.defaults')}} + q-item + blueprint-icon(icon='depth') + q-item-section + q-item-label {{t(`admin.general.defaultTocDepth`)}} + q-item-label(caption) {{t(`admin.general.defaultTocDepthHint`)}} + q-item-section.col-auto.q-pl-sm(style='min-width: 180px;') + .text-caption {{t('editor.props.tocMinMaxDepth')}} #[strong (H{{state.config.defaults.tocDepth.min}} → H{{state.config.defaults.tocDepth.max}})] + q-range( + v-model='state.config.defaults.tocDepth' + :min='1' + :max='6' + color='primary' + :left-label-value='`H` + state.config.defaults.tocDepth.min' + :right-label-value='`H` + state.config.defaults.tocDepth.max' + snap + label + markers + ) + .col-12.col-lg-5 //- ----------------------- //- Logo @@ -316,29 +341,24 @@ q-page.admin-general .text-caption.q-ml-sm Dolor sit amet... //- ----------------------- - //- Defaults + //- Discovery //- ----------------------- - q-card.q-pb-sm.q-mt-md(v-if='state.config.defaults') + q-card.q-pb-sm.q-mt-md q-card-section - .text-subtitle1 {{t('admin.general.defaults')}} - q-item - blueprint-icon(icon='depth') + .text-subtitle1 {{t('admin.general.discovery')}} + q-item(tag='label') + blueprint-icon(icon='cellular-network') q-item-section - q-item-label {{t(`admin.general.defaultTocDepth`)}} - q-item-label(caption) {{t(`admin.general.defaultTocDepthHint`)}} - q-item-section.col-auto.q-pl-sm(style='min-width: 180px;') - .text-caption {{t('editor.props.tocMinMaxDepth')}} #[strong (H{{state.config.defaults.tocDepth.min}} → H{{state.config.defaults.tocDepth.max}})] - q-range( - v-model='state.config.defaults.tocDepth' - :min='1' - :max='6' + q-item-label {{t(`admin.general.discoverable`)}} + q-item-label(caption) {{t(`admin.general.discoverableHint`)}} + q-item-section(avatar) + q-toggle( + v-model='state.config.discoverable' color='primary' - :left-label-value='`H` + state.config.defaults.tocDepth.min' - :right-label-value='`H` + state.config.defaults.tocDepth.max' - snap - label - markers - ) + checked-icon='las la-check' + unchecked-icon='las la-times' + :aria-label='t(`admin.general.discoverable`)' + ) //- ----------------------- //- Uploads @@ -519,6 +539,7 @@ const state = reactive({ reasonForChange: 'off', profile: false }, + discoverable: false, defaults: { timezone: '', dateFormat: '', @@ -618,6 +639,7 @@ async function load () { reasonForChange search } + discoverable defaults { tocDepth { min @@ -687,6 +709,7 @@ async function save () { profile: state.config.features?.profile ?? false, search: state.config.features?.search ?? false }, + discoverable: state.config.discoverable ?? false, defaults: { tocDepth: { min: state.config.defaults?.tocDepth?.min ?? 1, diff --git a/ux/src/pages/AdminLocale.vue b/ux/src/pages/AdminLocale.vue index 114252ac..b2c451d9 100644 --- a/ux/src/pages/AdminLocale.vue +++ b/ux/src/pages/AdminLocale.vue @@ -60,7 +60,7 @@ q-page.admin-locale q-item-section q-select( outlined - v-model='state.selectedLocale' + v-model='state.primary' :options='installedLocales' option-value='code' option-label='name' @@ -185,9 +185,8 @@ useMeta({ const state = reactive({ loading: 0, locales: [], - selectedLocale: 'en', - namespacing: false, - namespaces: [] + primary: 'en', + active: [] }) // COMPUTED @@ -224,7 +223,7 @@ async function load () { query: gql` query getLocales ($siteId: UUID!) { locales { - availability + completeness code createdAt isInstalled @@ -232,15 +231,18 @@ async function load () { isRTL name nativeName + region + script updatedAt } siteById( id: $siteId ) { id - locale - localeNamespacing - localeNamespaces + locales { + primary + active + } } } `, @@ -250,11 +252,10 @@ async function load () { fetchPolicy: 'network-only' }) state.locales = cloneDeep(resp?.data?.locales) - state.selectedLocale = cloneDeep(resp?.data?.siteById?.locale) - state.namespacing = cloneDeep(resp?.data?.siteById?.localeNamespacing) - state.namespaces = cloneDeep(resp?.data?.siteById?.localeNamespaces) - if (!state.namespaces.includes(state.selectedLocale)) { - state.namespaces.push(state.selectedLocale) + state.primary = cloneDeep(resp?.data?.siteById?.locales?.primary) + state.active = cloneDeep(resp?.data?.siteById?.locales?.active ?? []) + if (!state.active.includes(state.primary)) { + state.active.push(state.primary) } $q.loading.hide() state.loading-- diff --git a/ux/src/stores/page.js b/ux/src/stores/page.js index 2b73c7e0..e760cd2a 100644 --- a/ux/src/stores/page.js +++ b/ux/src/stores/page.js @@ -320,6 +320,8 @@ export const usePageStore = defineStore('page', { await editorStore.fetchConfigs() } + const noDefaultPath = Boolean(!path && path !== '') + // -> Init editor editorStore.$patch({ originPageId: editorStore.isActive ? editorStore.originPageId : this.id, // Don't replace if already in edit mode @@ -352,7 +354,9 @@ export const usePageStore = defineStore('page', { mode: 'edit' }) - this.router.push(`/_create/${editor}`) + if (noDefaultPath) { + this.router.push(`/_create/${editor}`) + } }, /** * PAGE - EDIT diff --git a/ux/src/stores/site.js b/ux/src/stores/site.js index 187ab7e3..057bdb5b 100644 --- a/ux/src/stores/site.js +++ b/ux/src/stores/site.js @@ -2,6 +2,8 @@ import { defineStore } from 'pinia' import gql from 'graphql-tag' import { clone } from 'lodash-es' +import { useUserStore } from './user' + export const useSiteStore = defineStore('site', { state: () => ({ routerLoading: false, @@ -53,25 +55,36 @@ export const useSiteStore = defineStore('site', { showSharingMenu: true, showPrintBtn: true }, - thumbStyle: { - right: '2px', - borderRadius: '5px', - backgroundColor: '#000', - width: '5px', - opacity: 0.15 - }, - barStyle: { - backgroundColor: '#FAFAFA', - width: '9px', - opacity: 1 - }, sideDialogShown: false, sideDialogComponent: '', docsBase: 'https://next.js.wiki/docs' }), getters: { overlayIsShown: (state) => Boolean(state.overlay), - sideNavIsDisabled: (state) => Boolean(state.theme.sidebarPosition === 'off') + sideNavIsDisabled: (state) => Boolean(state.theme.sidebarPosition === 'off'), + scrollStyle: (state) => { + const userStore = useUserStore() + let isDark = false + if (userStore.appearance === 'site') { + isDark = state.theme.dark + } else if (userStore.appearance === 'dark') { + isDark = true + } + return { + thumb: { + right: '2px', + borderRadius: '5px', + backgroundColor: isDark ? '#FFF' : '#000', + width: '5px', + opacity: isDark ? 0.25 : 0.15 + }, + bar: { + backgroundColor: isDark ? '#000' : '#FAFAFA', + width: '9px', + opacity: isDark ? 0.25 : 1 + } + } + } }, actions: { openFileManager (opts) {