diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index 742f443f9c..a5ce6a5d56 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -16,14 +16,28 @@ permissions:
packages: write
jobs:
- release:
- runs-on: ubuntu-latest
+ build:
+ strategy:
+ matrix:
+ include:
+ - os: ubuntu-latest
+ platforms: linux/amd64
+ required: true
+ - os: ubuntu-24.04-arm
+ platforms: linux/arm64
+ required: true
+ - os: ubuntu-24.04-arm
+ platforms: linux/arm/v7
+ required: false
+ continue-on-error: ${{ !matrix.required }}
+ runs-on: ${{ matrix.os }}
steps:
-
- - uses: actions/checkout@v4
-
- - name: Set up QEMU
- uses: docker/setup-qemu-action@v3
+ - name: Prepare
+ run: |
+ platform=${{ matrix.platforms }}
+ echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
+ echo "IMAGE=ghcr.io/${GITHUB_REPOSITORY@L}" >> $GITHUB_ENV
+ - uses: actions/checkout@v5
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
@@ -34,24 +48,11 @@ jobs:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
-
- - name: Get current date in NST
- run: echo "CURRENT_DATE_NST=$(date +'%Y%m%d-%H%M%S' -d '-3 hours -30 minutes')" >> $GITHUB_ENV
-
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
- images: ghcr.io/${{ github.repository }}
- tags: |
- type=semver,pattern={{version}}
- type=semver,pattern={{major}}.{{minor}}
- type=semver,pattern={{major}}.x
- type=raw,value=latest,enable={{is_default_branch}}
- type=ref,event=branch,enable=${{ github.event.repository.default_branch != github.ref }}
- type=raw,value=${{ env.CURRENT_DATE_NST }}
- flavor: |
- latest=true
+ images: ${{ env.IMAGE }}
- name: Cache node_modules
id: cache-node-modules
@@ -61,12 +62,73 @@ jobs:
key: var-cache-node-modules-${{ hashFiles('Dockerfile', 'install/package.json') }}
- name: Build and push Docker images
+ id: build
uses: docker/build-push-action@v6
with:
cache-from: type=gha
cache-to: type=gha,mode=min
context: .
file: ./Dockerfile
- platforms: linux/amd64,linux/arm64,linux/arm/v7
- push: true
- tags: ${{ steps.meta.outputs.tags }}
+ platforms: ${{ matrix.platforms }}
+ labels: ${{ steps.meta.outputs.labels }}
+ tags: ${{ env.IMAGE }}
+ outputs: type=image,push-by-digest=true,name-canonical=true,push=true
+ - name: Export digest
+ run: |
+ mkdir -p ${{ runner.temp }}/digests
+ digest="${{ steps.build.outputs.digest }}"
+ touch "${{ runner.temp }}/digests/${digest#sha256:}"
+
+ - name: Upload digest
+ uses: actions/upload-artifact@v4
+ with:
+ name: digests-${{ env.PLATFORM_PAIR }}
+ path: ${{ runner.temp }}/digests/*
+ if-no-files-found: error
+ retention-days: 1
+ merge:
+ runs-on: ubuntu-latest
+ needs:
+ - build
+ steps:
+ - name: Prepare
+ run: |
+ echo "IMAGE=ghcr.io/${GITHUB_REPOSITORY@L}" >> $GITHUB_ENV
+ echo "CURRENT_DATE_NST=$(date +'%Y%m%d-%H%M%S' -d '-3 hours -30 minutes')" >> $GITHUB_ENV
+ - name: Download digests
+ uses: actions/download-artifact@v4
+ with:
+ path: ${{ runner.temp }}/digests
+ pattern: digests-*
+ merge-multiple: true
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Login to GitHub Container Registry
+ uses: docker/login-action@v3
+ with:
+ registry: ghcr.io
+ username: ${{ github.repository_owner }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+ - name: Docker meta
+ id: meta
+ uses: docker/metadata-action@v5
+ with:
+ images: ${{ env.IMAGE }}
+ tags: |
+ type=semver,pattern={{version}}
+ type=semver,pattern={{major}}.{{minor}}
+ type=semver,pattern={{major}}.x
+ type=raw,value=latest,enable={{is_default_branch}}
+ type=ref,event=branch,enable=${{ github.event.repository.default_branch != github.ref }}
+ type=raw,value=${{ env.CURRENT_DATE_NST }}
+ flavor: |
+ latest=true
+ - name: Create manifest list and push
+ working-directory: ${{ runner.temp }}/digests
+ run: |
+ docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
+ $(printf '${{ env.IMAGE }}@sha256:%s ' *)
+ - name: Inspect image
+ run: |
+ docker buildx imagetools inspect ${{ env.IMAGE }}:${{ steps.meta.outputs.version }}
\ No newline at end of file
diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
index 43fdf33611..fe9710ac5a 100644
--- a/.github/workflows/test.yaml
+++ b/.github/workflows/test.yaml
@@ -63,7 +63,7 @@ jobs:
- 5432:5432
redis:
- image: 'redis:8.0.1'
+ image: 'redis:8.2.1'
# Set health checks to wait until redis has started
options: >-
--health-cmd "redis-cli ping"
@@ -81,7 +81,7 @@ jobs:
- 27017:27017
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- run: cp install/package.json package.json
diff --git a/.tx/config b/.tx/config
index 681e9a6709..d92404ea4f 100644
--- a/.tx/config
+++ b/.tx/config
@@ -51,6 +51,7 @@ trans.sv = public/language/sv/admin/admin.json
trans.th = public/language/th/admin/admin.json
trans.tr = public/language/tr/admin/admin.json
trans.uk = public/language/uk/admin/admin.json
+trans.ur = public/language/ur/admin/admin.json
trans.vi = public/language/vi/admin/admin.json
trans.zh_CN = public/language/zh-CN/admin/admin.json
trans.zh_TW = public/language/zh-TW/admin/admin.json
@@ -105,6 +106,7 @@ trans.sv = public/language/sv/admin/advanced/cache.json
trans.th = public/language/th/admin/advanced/cache.json
trans.tr = public/language/tr/admin/advanced/cache.json
trans.uk = public/language/uk/admin/advanced/cache.json
+trans.ur = public/language/ur/admin/advanced/cache.json
trans.vi = public/language/vi/admin/advanced/cache.json
trans.zh_CN = public/language/zh-CN/admin/advanced/cache.json
trans.zh_TW = public/language/zh-TW/admin/advanced/cache.json
@@ -159,6 +161,7 @@ trans.sv = public/language/sv/admin/advanced/database.json
trans.th = public/language/th/admin/advanced/database.json
trans.tr = public/language/tr/admin/advanced/database.json
trans.uk = public/language/uk/admin/advanced/database.json
+trans.ur = public/language/ur/admin/advanced/database.json
trans.vi = public/language/vi/admin/advanced/database.json
trans.zh_CN = public/language/zh-CN/admin/advanced/database.json
trans.zh_TW = public/language/zh-TW/admin/advanced/database.json
@@ -213,6 +216,7 @@ trans.sv = public/language/sv/admin/advanced/errors.json
trans.th = public/language/th/admin/advanced/errors.json
trans.tr = public/language/tr/admin/advanced/errors.json
trans.uk = public/language/uk/admin/advanced/errors.json
+trans.ur = public/language/ur/admin/advanced/errors.json
trans.vi = public/language/vi/admin/advanced/errors.json
trans.zh_CN = public/language/zh-CN/admin/advanced/errors.json
trans.zh_TW = public/language/zh-TW/admin/advanced/errors.json
@@ -267,6 +271,7 @@ trans.sv = public/language/sv/admin/advanced/events.json
trans.th = public/language/th/admin/advanced/events.json
trans.tr = public/language/tr/admin/advanced/events.json
trans.uk = public/language/uk/admin/advanced/events.json
+trans.ur = public/language/ur/admin/advanced/events.json
trans.vi = public/language/vi/admin/advanced/events.json
trans.zh_CN = public/language/zh-CN/admin/advanced/events.json
trans.zh_TW = public/language/zh-TW/admin/advanced/events.json
@@ -321,6 +326,7 @@ trans.sv = public/language/sv/admin/advanced/logs.json
trans.th = public/language/th/admin/advanced/logs.json
trans.tr = public/language/tr/admin/advanced/logs.json
trans.uk = public/language/uk/admin/advanced/logs.json
+trans.ur = public/language/ur/admin/advanced/logs.json
trans.vi = public/language/vi/admin/advanced/logs.json
trans.zh_CN = public/language/zh-CN/admin/advanced/logs.json
trans.zh_TW = public/language/zh-TW/admin/advanced/logs.json
@@ -375,6 +381,7 @@ trans.sv = public/language/sv/admin/appearance/customise.json
trans.th = public/language/th/admin/appearance/customise.json
trans.tr = public/language/tr/admin/appearance/customise.json
trans.uk = public/language/uk/admin/appearance/customise.json
+trans.ur = public/language/ur/admin/appearance/customise.json
trans.vi = public/language/vi/admin/appearance/customise.json
trans.zh_CN = public/language/zh-CN/admin/appearance/customise.json
trans.zh_TW = public/language/zh-TW/admin/appearance/customise.json
@@ -429,6 +436,7 @@ trans.sv = public/language/sv/admin/appearance/skins.json
trans.th = public/language/th/admin/appearance/skins.json
trans.tr = public/language/tr/admin/appearance/skins.json
trans.uk = public/language/uk/admin/appearance/skins.json
+trans.ur = public/language/ur/admin/appearance/skins.json
trans.vi = public/language/vi/admin/appearance/skins.json
trans.zh_CN = public/language/zh-CN/admin/appearance/skins.json
trans.zh_TW = public/language/zh-TW/admin/appearance/skins.json
@@ -483,6 +491,7 @@ trans.sv = public/language/sv/admin/appearance/themes.json
trans.th = public/language/th/admin/appearance/themes.json
trans.tr = public/language/tr/admin/appearance/themes.json
trans.uk = public/language/uk/admin/appearance/themes.json
+trans.ur = public/language/ur/admin/appearance/themes.json
trans.vi = public/language/vi/admin/appearance/themes.json
trans.zh_CN = public/language/zh-CN/admin/appearance/themes.json
trans.zh_TW = public/language/zh-TW/admin/appearance/themes.json
@@ -537,6 +546,7 @@ trans.sv = public/language/sv/admin/dashboard.json
trans.th = public/language/th/admin/dashboard.json
trans.tr = public/language/tr/admin/dashboard.json
trans.uk = public/language/uk/admin/dashboard.json
+trans.ur = public/language/ur/admin/dashboard.json
trans.vi = public/language/vi/admin/dashboard.json
trans.zh_CN = public/language/zh-CN/admin/dashboard.json
trans.zh_TW = public/language/zh-TW/admin/dashboard.json
@@ -591,6 +601,7 @@ trans.sv = public/language/sv/admin/development/info.json
trans.th = public/language/th/admin/development/info.json
trans.tr = public/language/tr/admin/development/info.json
trans.uk = public/language/uk/admin/development/info.json
+trans.ur = public/language/ur/admin/development/info.json
trans.vi = public/language/vi/admin/development/info.json
trans.zh_CN = public/language/zh-CN/admin/development/info.json
trans.zh_TW = public/language/zh-TW/admin/development/info.json
@@ -645,6 +656,7 @@ trans.sv = public/language/sv/admin/development/logger.json
trans.th = public/language/th/admin/development/logger.json
trans.tr = public/language/tr/admin/development/logger.json
trans.uk = public/language/uk/admin/development/logger.json
+trans.ur = public/language/ur/admin/development/logger.json
trans.vi = public/language/vi/admin/development/logger.json
trans.zh_CN = public/language/zh-CN/admin/development/logger.json
trans.zh_TW = public/language/zh-TW/admin/development/logger.json
@@ -699,6 +711,7 @@ trans.sv = public/language/sv/admin/extend/plugins.json
trans.th = public/language/th/admin/extend/plugins.json
trans.tr = public/language/tr/admin/extend/plugins.json
trans.uk = public/language/uk/admin/extend/plugins.json
+trans.ur = public/language/ur/admin/extend/plugins.json
trans.vi = public/language/vi/admin/extend/plugins.json
trans.zh_CN = public/language/zh-CN/admin/extend/plugins.json
trans.zh_TW = public/language/zh-TW/admin/extend/plugins.json
@@ -753,6 +766,7 @@ trans.sv = public/language/sv/admin/extend/rewards.json
trans.th = public/language/th/admin/extend/rewards.json
trans.tr = public/language/tr/admin/extend/rewards.json
trans.uk = public/language/uk/admin/extend/rewards.json
+trans.ur = public/language/ur/admin/extend/rewards.json
trans.vi = public/language/vi/admin/extend/rewards.json
trans.zh_CN = public/language/zh-CN/admin/extend/rewards.json
trans.zh_TW = public/language/zh-TW/admin/extend/rewards.json
@@ -807,6 +821,7 @@ trans.sv = public/language/sv/admin/extend/widgets.json
trans.th = public/language/th/admin/extend/widgets.json
trans.tr = public/language/tr/admin/extend/widgets.json
trans.uk = public/language/uk/admin/extend/widgets.json
+trans.ur = public/language/ur/admin/extend/widgets.json
trans.vi = public/language/vi/admin/extend/widgets.json
trans.zh_CN = public/language/zh-CN/admin/extend/widgets.json
trans.zh_TW = public/language/zh-TW/admin/extend/widgets.json
@@ -861,6 +876,7 @@ trans.sv = public/language/sv/admin/manage/admins-mods.json
trans.th = public/language/th/admin/manage/admins-mods.json
trans.tr = public/language/tr/admin/manage/admins-mods.json
trans.uk = public/language/uk/admin/manage/admins-mods.json
+trans.ur = public/language/ur/admin/manage/admins-mods.json
trans.vi = public/language/vi/admin/manage/admins-mods.json
trans.zh_CN = public/language/zh-CN/admin/manage/admins-mods.json
trans.zh_TW = public/language/zh-TW/admin/manage/admins-mods.json
@@ -915,6 +931,7 @@ trans.sv = public/language/sv/admin/manage/categories.json
trans.th = public/language/th/admin/manage/categories.json
trans.tr = public/language/tr/admin/manage/categories.json
trans.uk = public/language/uk/admin/manage/categories.json
+trans.ur = public/language/ur/admin/manage/categories.json
trans.vi = public/language/vi/admin/manage/categories.json
trans.zh_CN = public/language/zh-CN/admin/manage/categories.json
trans.zh_TW = public/language/zh-TW/admin/manage/categories.json
@@ -969,6 +986,7 @@ trans.sv = public/language/sv/admin/manage/digest.json
trans.th = public/language/th/admin/manage/digest.json
trans.tr = public/language/tr/admin/manage/digest.json
trans.uk = public/language/uk/admin/manage/digest.json
+trans.ur = public/language/ur/admin/manage/digest.json
trans.vi = public/language/vi/admin/manage/digest.json
trans.zh_CN = public/language/zh-CN/admin/manage/digest.json
trans.zh_TW = public/language/zh-TW/admin/manage/digest.json
@@ -1023,6 +1041,7 @@ trans.sv = public/language/sv/admin/manage/groups.json
trans.th = public/language/th/admin/manage/groups.json
trans.tr = public/language/tr/admin/manage/groups.json
trans.uk = public/language/uk/admin/manage/groups.json
+trans.ur = public/language/ur/admin/manage/groups.json
trans.vi = public/language/vi/admin/manage/groups.json
trans.zh_CN = public/language/zh-CN/admin/manage/groups.json
trans.zh_TW = public/language/zh-TW/admin/manage/groups.json
@@ -1077,6 +1096,7 @@ trans.sv = public/language/sv/admin/manage/privileges.json
trans.th = public/language/th/admin/manage/privileges.json
trans.tr = public/language/tr/admin/manage/privileges.json
trans.uk = public/language/uk/admin/manage/privileges.json
+trans.ur = public/language/ur/admin/manage/privileges.json
trans.vi = public/language/vi/admin/manage/privileges.json
trans.zh_CN = public/language/zh-CN/admin/manage/privileges.json
trans.zh_TW = public/language/zh-TW/admin/manage/privileges.json
@@ -1131,6 +1151,7 @@ trans.sv = public/language/sv/admin/manage/registration.json
trans.th = public/language/th/admin/manage/registration.json
trans.tr = public/language/tr/admin/manage/registration.json
trans.uk = public/language/uk/admin/manage/registration.json
+trans.ur = public/language/ur/admin/manage/registration.json
trans.vi = public/language/vi/admin/manage/registration.json
trans.zh_CN = public/language/zh-CN/admin/manage/registration.json
trans.zh_TW = public/language/zh-TW/admin/manage/registration.json
@@ -1185,6 +1206,7 @@ trans.sv = public/language/sv/admin/manage/tags.json
trans.th = public/language/th/admin/manage/tags.json
trans.tr = public/language/tr/admin/manage/tags.json
trans.uk = public/language/uk/admin/manage/tags.json
+trans.ur = public/language/ur/admin/manage/tags.json
trans.vi = public/language/vi/admin/manage/tags.json
trans.zh_CN = public/language/zh-CN/admin/manage/tags.json
trans.zh_TW = public/language/zh-TW/admin/manage/tags.json
@@ -1239,6 +1261,7 @@ trans.sv = public/language/sv/admin/manage/uploads.json
trans.th = public/language/th/admin/manage/uploads.json
trans.tr = public/language/tr/admin/manage/uploads.json
trans.uk = public/language/uk/admin/manage/uploads.json
+trans.ur = public/language/ur/admin/manage/uploads.json
trans.vi = public/language/vi/admin/manage/uploads.json
trans.zh_CN = public/language/zh-CN/admin/manage/uploads.json
trans.zh_TW = public/language/zh-TW/admin/manage/uploads.json
@@ -1293,6 +1316,7 @@ trans.sv = public/language/sv/admin/manage/user-custom-fields.json
trans.th = public/language/th/admin/manage/user-custom-fields.json
trans.tr = public/language/tr/admin/manage/user-custom-fields.json
trans.uk = public/language/uk/admin/manage/user-custom-fields.json
+trans.ur = public/language/ur/admin/manage/user-custom-fields.json
trans.vi = public/language/vi/admin/manage/user-custom-fields.json
trans.zh_CN = public/language/zh-CN/admin/manage/user-custom-fields.json
trans.zh_TW = public/language/zh-TW/admin/manage/user-custom-fields.json
@@ -1347,6 +1371,7 @@ trans.sv = public/language/sv/admin/manage/users.json
trans.th = public/language/th/admin/manage/users.json
trans.tr = public/language/tr/admin/manage/users.json
trans.uk = public/language/uk/admin/manage/users.json
+trans.ur = public/language/ur/admin/manage/users.json
trans.vi = public/language/vi/admin/manage/users.json
trans.zh_CN = public/language/zh-CN/admin/manage/users.json
trans.zh_TW = public/language/zh-TW/admin/manage/users.json
@@ -1401,6 +1426,7 @@ trans.sv = public/language/sv/admin/menu.json
trans.th = public/language/th/admin/menu.json
trans.tr = public/language/tr/admin/menu.json
trans.uk = public/language/uk/admin/menu.json
+trans.ur = public/language/ur/admin/menu.json
trans.vi = public/language/vi/admin/menu.json
trans.zh_CN = public/language/zh-CN/admin/menu.json
trans.zh_TW = public/language/zh-TW/admin/menu.json
@@ -1455,6 +1481,7 @@ trans.sv = public/language/sv/admin/settings/advanced.json
trans.th = public/language/th/admin/settings/advanced.json
trans.tr = public/language/tr/admin/settings/advanced.json
trans.uk = public/language/uk/admin/settings/advanced.json
+trans.ur = public/language/ur/admin/settings/advanced.json
trans.vi = public/language/vi/admin/settings/advanced.json
trans.zh_CN = public/language/zh-CN/admin/settings/advanced.json
trans.zh_TW = public/language/zh-TW/admin/settings/advanced.json
@@ -1509,6 +1536,7 @@ trans.sv = public/language/sv/admin/settings/activitypub.json
trans.th = public/language/th/admin/settings/activitypub.json
trans.tr = public/language/tr/admin/settings/activitypub.json
trans.uk = public/language/uk/admin/settings/activitypub.json
+trans.ur = public/language/ur/admin/settings/activitypub.json
trans.vi = public/language/vi/admin/settings/activitypub.json
trans.zh_CN = public/language/zh-CN/admin/settings/activitypub.json
trans.zh_TW = public/language/zh-TW/admin/settings/activitypub.json
@@ -1563,6 +1591,7 @@ trans.sv = public/language/sv/admin/settings/api.json
trans.th = public/language/th/admin/settings/api.json
trans.tr = public/language/tr/admin/settings/api.json
trans.uk = public/language/uk/admin/settings/api.json
+trans.ur = public/language/ur/admin/settings/api.json
trans.vi = public/language/vi/admin/settings/api.json
trans.zh_CN = public/language/zh-CN/admin/settings/api.json
trans.zh_TW = public/language/zh-TW/admin/settings/api.json
@@ -1617,6 +1646,7 @@ trans.sv = public/language/sv/admin/settings/chat.json
trans.th = public/language/th/admin/settings/chat.json
trans.tr = public/language/tr/admin/settings/chat.json
trans.uk = public/language/uk/admin/settings/chat.json
+trans.ur = public/language/ur/admin/settings/chat.json
trans.vi = public/language/vi/admin/settings/chat.json
trans.zh_CN = public/language/zh-CN/admin/settings/chat.json
trans.zh_TW = public/language/zh-TW/admin/settings/chat.json
@@ -1671,6 +1701,7 @@ trans.sv = public/language/sv/admin/settings/cookies.json
trans.th = public/language/th/admin/settings/cookies.json
trans.tr = public/language/tr/admin/settings/cookies.json
trans.uk = public/language/uk/admin/settings/cookies.json
+trans.ur = public/language/ur/admin/settings/cookies.json
trans.vi = public/language/vi/admin/settings/cookies.json
trans.zh_CN = public/language/zh-CN/admin/settings/cookies.json
trans.zh_TW = public/language/zh-TW/admin/settings/cookies.json
@@ -1725,6 +1756,7 @@ trans.sv = public/language/sv/admin/settings/email.json
trans.th = public/language/th/admin/settings/email.json
trans.tr = public/language/tr/admin/settings/email.json
trans.uk = public/language/uk/admin/settings/email.json
+trans.ur = public/language/ur/admin/settings/email.json
trans.vi = public/language/vi/admin/settings/email.json
trans.zh_CN = public/language/zh-CN/admin/settings/email.json
trans.zh_TW = public/language/zh-TW/admin/settings/email.json
@@ -1779,6 +1811,7 @@ trans.sv = public/language/sv/admin/settings/general.json
trans.th = public/language/th/admin/settings/general.json
trans.tr = public/language/tr/admin/settings/general.json
trans.uk = public/language/uk/admin/settings/general.json
+trans.ur = public/language/ur/admin/settings/general.json
trans.vi = public/language/vi/admin/settings/general.json
trans.zh_CN = public/language/zh-CN/admin/settings/general.json
trans.zh_TW = public/language/zh-TW/admin/settings/general.json
@@ -1833,6 +1866,7 @@ trans.sv = public/language/sv/admin/settings/group.json
trans.th = public/language/th/admin/settings/group.json
trans.tr = public/language/tr/admin/settings/group.json
trans.uk = public/language/uk/admin/settings/group.json
+trans.ur = public/language/ur/admin/settings/group.json
trans.vi = public/language/vi/admin/settings/group.json
trans.zh_CN = public/language/zh-CN/admin/settings/group.json
trans.zh_TW = public/language/zh-TW/admin/settings/group.json
@@ -1887,6 +1921,7 @@ trans.sv = public/language/sv/admin/settings/navigation.json
trans.th = public/language/th/admin/settings/navigation.json
trans.tr = public/language/tr/admin/settings/navigation.json
trans.uk = public/language/uk/admin/settings/navigation.json
+trans.ur = public/language/ur/admin/settings/navigation.json
trans.vi = public/language/vi/admin/settings/navigation.json
trans.zh_CN = public/language/zh-CN/admin/settings/navigation.json
trans.zh_TW = public/language/zh-TW/admin/settings/navigation.json
@@ -1941,6 +1976,7 @@ trans.sv = public/language/sv/admin/settings/notifications.json
trans.th = public/language/th/admin/settings/notifications.json
trans.tr = public/language/tr/admin/settings/notifications.json
trans.uk = public/language/uk/admin/settings/notifications.json
+trans.ur = public/language/ur/admin/settings/notifications.json
trans.vi = public/language/vi/admin/settings/notifications.json
trans.zh_CN = public/language/zh-CN/admin/settings/notifications.json
trans.zh_TW = public/language/zh-TW/admin/settings/notifications.json
@@ -1995,6 +2031,7 @@ trans.sv = public/language/sv/admin/settings/pagination.json
trans.th = public/language/th/admin/settings/pagination.json
trans.tr = public/language/tr/admin/settings/pagination.json
trans.uk = public/language/uk/admin/settings/pagination.json
+trans.ur = public/language/ur/admin/settings/pagination.json
trans.vi = public/language/vi/admin/settings/pagination.json
trans.zh_CN = public/language/zh-CN/admin/settings/pagination.json
trans.zh_TW = public/language/zh-TW/admin/settings/pagination.json
@@ -2049,6 +2086,7 @@ trans.sv = public/language/sv/admin/settings/post.json
trans.th = public/language/th/admin/settings/post.json
trans.tr = public/language/tr/admin/settings/post.json
trans.uk = public/language/uk/admin/settings/post.json
+trans.ur = public/language/ur/admin/settings/post.json
trans.vi = public/language/vi/admin/settings/post.json
trans.zh_CN = public/language/zh-CN/admin/settings/post.json
trans.zh_TW = public/language/zh-TW/admin/settings/post.json
@@ -2103,6 +2141,7 @@ trans.sv = public/language/sv/admin/settings/reputation.json
trans.th = public/language/th/admin/settings/reputation.json
trans.tr = public/language/tr/admin/settings/reputation.json
trans.uk = public/language/uk/admin/settings/reputation.json
+trans.ur = public/language/ur/admin/settings/reputation.json
trans.vi = public/language/vi/admin/settings/reputation.json
trans.zh_CN = public/language/zh-CN/admin/settings/reputation.json
trans.zh_TW = public/language/zh-TW/admin/settings/reputation.json
@@ -2157,6 +2196,7 @@ trans.sv = public/language/sv/admin/settings/sockets.json
trans.th = public/language/th/admin/settings/sockets.json
trans.tr = public/language/tr/admin/settings/sockets.json
trans.uk = public/language/uk/admin/settings/sockets.json
+trans.ur = public/language/ur/admin/settings/sockets.json
trans.vi = public/language/vi/admin/settings/sockets.json
trans.zh_CN = public/language/zh-CN/admin/settings/sockets.json
trans.zh_TW = public/language/zh-TW/admin/settings/sockets.json
@@ -2211,6 +2251,7 @@ trans.sv = public/language/sv/admin/settings/sounds.json
trans.th = public/language/th/admin/settings/sounds.json
trans.tr = public/language/tr/admin/settings/sounds.json
trans.uk = public/language/uk/admin/settings/sounds.json
+trans.ur = public/language/ur/admin/settings/sounds.json
trans.vi = public/language/vi/admin/settings/sounds.json
trans.zh_CN = public/language/zh-CN/admin/settings/sounds.json
trans.zh_TW = public/language/zh-TW/admin/settings/sounds.json
@@ -2265,6 +2306,7 @@ trans.sv = public/language/sv/admin/settings/tags.json
trans.th = public/language/th/admin/settings/tags.json
trans.tr = public/language/tr/admin/settings/tags.json
trans.uk = public/language/uk/admin/settings/tags.json
+trans.ur = public/language/ur/admin/settings/tags.json
trans.vi = public/language/vi/admin/settings/tags.json
trans.zh_CN = public/language/zh-CN/admin/settings/tags.json
trans.zh_TW = public/language/zh-TW/admin/settings/tags.json
@@ -2319,6 +2361,7 @@ trans.sv = public/language/sv/admin/settings/uploads.json
trans.th = public/language/th/admin/settings/uploads.json
trans.tr = public/language/tr/admin/settings/uploads.json
trans.uk = public/language/uk/admin/settings/uploads.json
+trans.ur = public/language/ur/admin/settings/uploads.json
trans.vi = public/language/vi/admin/settings/uploads.json
trans.zh_CN = public/language/zh-CN/admin/settings/uploads.json
trans.zh_TW = public/language/zh-TW/admin/settings/uploads.json
@@ -2373,6 +2416,7 @@ trans.sv = public/language/sv/admin/settings/user.json
trans.th = public/language/th/admin/settings/user.json
trans.tr = public/language/tr/admin/settings/user.json
trans.uk = public/language/uk/admin/settings/user.json
+trans.ur = public/language/ur/admin/settings/user.json
trans.vi = public/language/vi/admin/settings/user.json
trans.zh_CN = public/language/zh-CN/admin/settings/user.json
trans.zh_TW = public/language/zh-TW/admin/settings/user.json
@@ -2427,6 +2471,7 @@ trans.sv = public/language/sv/admin/settings/web-crawler.json
trans.th = public/language/th/admin/settings/web-crawler.json
trans.tr = public/language/tr/admin/settings/web-crawler.json
trans.uk = public/language/uk/admin/settings/web-crawler.json
+trans.ur = public/language/ur/admin/settings/web-crawler.json
trans.vi = public/language/vi/admin/settings/web-crawler.json
trans.zh_CN = public/language/zh-CN/admin/settings/web-crawler.json
trans.zh_TW = public/language/zh-TW/admin/settings/web-crawler.json
@@ -2481,6 +2526,7 @@ trans.sv = public/language/sv/themes/harmony.json
trans.th = public/language/th/themes/harmony.json
trans.tr = public/language/tr/themes/harmony.json
trans.uk = public/language/uk/themes/harmony.json
+trans.ur = public/language/ur/themes/harmony.json
trans.vi = public/language/vi/themes/harmony.json
trans.zh_CN = public/language/zh-CN/themes/harmony.json
trans.zh_TW = public/language/zh-TW/themes/harmony.json
@@ -2535,6 +2581,7 @@ trans.sv = public/language/sv/themes/persona.json
trans.th = public/language/th/themes/persona.json
trans.tr = public/language/tr/themes/persona.json
trans.uk = public/language/uk/themes/persona.json
+trans.ur = public/language/ur/themes/persona.json
trans.vi = public/language/vi/themes/persona.json
trans.zh_CN = public/language/zh-CN/themes/persona.json
trans.zh_TW = public/language/zh-TW/themes/persona.json
@@ -2589,6 +2636,7 @@ trans.sv = public/language/sv/aria.json
trans.th = public/language/th/aria.json
trans.tr = public/language/tr/aria.json
trans.uk = public/language/uk/aria.json
+trans.ur = public/language/ur/aria.json
trans.vi = public/language/vi/aria.json
trans.zh_CN = public/language/zh-CN/aria.json
trans.zh_TW = public/language/zh-TW/aria.json
@@ -2643,6 +2691,7 @@ trans.sv = public/language/sv/category.json
trans.th = public/language/th/category.json
trans.tr = public/language/tr/category.json
trans.uk = public/language/uk/category.json
+trans.ur = public/language/ur/category.json
trans.vi = public/language/vi/category.json
trans.zh_CN = public/language/zh-CN/category.json
trans.zh_TW = public/language/zh-TW/category.json
@@ -2697,6 +2746,7 @@ trans.sv = public/language/sv/email.json
trans.th = public/language/th/email.json
trans.tr = public/language/tr/email.json
trans.uk = public/language/uk/email.json
+trans.ur = public/language/ur/email.json
trans.vi = public/language/vi/email.json
trans.zh_CN = public/language/zh-CN/email.json
trans.zh_TW = public/language/zh-TW/email.json
@@ -2751,6 +2801,7 @@ trans.sv = public/language/sv/error.json
trans.th = public/language/th/error.json
trans.tr = public/language/tr/error.json
trans.uk = public/language/uk/error.json
+trans.ur = public/language/ur/error.json
trans.vi = public/language/vi/error.json
trans.zh_CN = public/language/zh-CN/error.json
trans.zh_TW = public/language/zh-TW/error.json
@@ -2858,6 +2909,7 @@ trans.sv = public/language/sv/global.json
trans.th = public/language/th/global.json
trans.tr = public/language/tr/global.json
trans.uk = public/language/uk/global.json
+trans.ur = public/language/ur/global.json
trans.vi = public/language/vi/global.json
trans.zh_CN = public/language/zh-CN/global.json
trans.zh_TW = public/language/zh-TW/global.json
@@ -2912,6 +2964,7 @@ trans.sv = public/language/sv/groups.json
trans.th = public/language/th/groups.json
trans.tr = public/language/tr/groups.json
trans.uk = public/language/uk/groups.json
+trans.ur = public/language/ur/groups.json
trans.vi = public/language/vi/groups.json
trans.zh_CN = public/language/zh-CN/groups.json
trans.zh_TW = public/language/zh-TW/groups.json
@@ -2966,6 +3019,7 @@ trans.sv = public/language/sv/ip-blacklist.json
trans.th = public/language/th/ip-blacklist.json
trans.tr = public/language/tr/ip-blacklist.json
trans.uk = public/language/uk/ip-blacklist.json
+trans.ur = public/language/ur/ip-blacklist.json
trans.vi = public/language/vi/ip-blacklist.json
trans.zh_CN = public/language/zh-CN/ip-blacklist.json
trans.zh_TW = public/language/zh-TW/ip-blacklist.json
@@ -3020,6 +3074,7 @@ trans.sv = public/language/sv/language.json
trans.th = public/language/th/language.json
trans.tr = public/language/tr/language.json
trans.uk = public/language/uk/language.json
+trans.ur = public/language/ur/language.json
trans.vi = public/language/vi/language.json
trans.zh_CN = public/language/zh-CN/language.json
trans.zh_TW = public/language/zh-TW/language.json
@@ -3074,6 +3129,7 @@ trans.sv = public/language/sv/login.json
trans.th = public/language/th/login.json
trans.tr = public/language/tr/login.json
trans.uk = public/language/uk/login.json
+trans.ur = public/language/ur/login.json
trans.vi = public/language/vi/login.json
trans.zh_CN = public/language/zh-CN/login.json
trans.zh_TW = public/language/zh-TW/login.json
@@ -3128,6 +3184,7 @@ trans.sv = public/language/sv/modules.json
trans.th = public/language/th/modules.json
trans.tr = public/language/tr/modules.json
trans.uk = public/language/uk/modules.json
+trans.ur = public/language/ur/modules.json
trans.vi = public/language/vi/modules.json
trans.zh_CN = public/language/zh-CN/modules.json
trans.zh_TW = public/language/zh-TW/modules.json
@@ -3182,6 +3239,7 @@ trans.sv = public/language/sv/notifications.json
trans.th = public/language/th/notifications.json
trans.tr = public/language/tr/notifications.json
trans.uk = public/language/uk/notifications.json
+trans.ur = public/language/ur/notifications.json
trans.vi = public/language/vi/notifications.json
trans.zh_CN = public/language/zh-CN/notifications.json
trans.zh_TW = public/language/zh-TW/notifications.json
@@ -3236,6 +3294,7 @@ trans.sv = public/language/sv/pages.json
trans.th = public/language/th/pages.json
trans.tr = public/language/tr/pages.json
trans.uk = public/language/uk/pages.json
+trans.ur = public/language/ur/pages.json
trans.vi = public/language/vi/pages.json
trans.zh_CN = public/language/zh-CN/pages.json
trans.zh_TW = public/language/zh-TW/pages.json
@@ -3290,6 +3349,7 @@ trans.sv = public/language/sv/post-queue.json
trans.th = public/language/th/post-queue.json
trans.tr = public/language/tr/post-queue.json
trans.uk = public/language/uk/post-queue.json
+trans.ur = public/language/ur/post-queue.json
trans.vi = public/language/vi/post-queue.json
trans.zh_CN = public/language/zh-CN/post-queue.json
trans.zh_TW = public/language/zh-TW/post-queue.json
@@ -3344,6 +3404,7 @@ trans.sv = public/language/sv/recent.json
trans.th = public/language/th/recent.json
trans.tr = public/language/tr/recent.json
trans.uk = public/language/uk/recent.json
+trans.ur = public/language/ur/recent.json
trans.vi = public/language/vi/recent.json
trans.zh_CN = public/language/zh-CN/recent.json
trans.zh_TW = public/language/zh-TW/recent.json
@@ -3398,6 +3459,7 @@ trans.sv = public/language/sv/register.json
trans.th = public/language/th/register.json
trans.tr = public/language/tr/register.json
trans.uk = public/language/uk/register.json
+trans.ur = public/language/ur/register.json
trans.vi = public/language/vi/register.json
trans.zh_CN = public/language/zh-CN/register.json
trans.zh_TW = public/language/zh-TW/register.json
@@ -3452,6 +3514,7 @@ trans.sv = public/language/sv/reset_password.json
trans.th = public/language/th/reset_password.json
trans.tr = public/language/tr/reset_password.json
trans.uk = public/language/uk/reset_password.json
+trans.ur = public/language/ur/reset_password.json
trans.vi = public/language/vi/reset_password.json
trans.zh_CN = public/language/zh-CN/reset_password.json
trans.zh_TW = public/language/zh-TW/reset_password.json
@@ -3506,6 +3569,7 @@ trans.sv = public/language/sv/rewards.json
trans.th = public/language/th/rewards.json
trans.tr = public/language/tr/rewards.json
trans.uk = public/language/uk/rewards.json
+trans.ur = public/language/ur/rewards.json
trans.vi = public/language/vi/rewards.json
trans.zh_CN = public/language/zh-CN/rewards.json
trans.zh_TW = public/language/zh-TW/rewards.json
@@ -3560,6 +3624,7 @@ trans.sv = public/language/sv/search.json
trans.th = public/language/th/search.json
trans.tr = public/language/tr/search.json
trans.uk = public/language/uk/search.json
+trans.ur = public/language/ur/search.json
trans.vi = public/language/vi/search.json
trans.zh_CN = public/language/zh-CN/search.json
trans.zh_TW = public/language/zh-TW/search.json
@@ -3614,6 +3679,7 @@ trans.sv = public/language/sv/social.json
trans.th = public/language/th/social.json
trans.tr = public/language/tr/social.json
trans.uk = public/language/uk/social.json
+trans.ur = public/language/ur/social.json
trans.vi = public/language/vi/social.json
trans.zh_CN = public/language/zh-CN/social.json
trans.zh_TW = public/language/zh-TW/social.json
@@ -3668,6 +3734,7 @@ trans.sv = public/language/sv/success.json
trans.th = public/language/th/success.json
trans.tr = public/language/tr/success.json
trans.uk = public/language/uk/success.json
+trans.ur = public/language/ur/success.json
trans.vi = public/language/vi/success.json
trans.zh_CN = public/language/zh-CN/success.json
trans.zh_TW = public/language/zh-TW/success.json
@@ -3722,6 +3789,7 @@ trans.sv = public/language/sv/tags.json
trans.th = public/language/th/tags.json
trans.tr = public/language/tr/tags.json
trans.uk = public/language/uk/tags.json
+trans.ur = public/language/ur/tags.json
trans.vi = public/language/vi/tags.json
trans.zh_CN = public/language/zh-CN/tags.json
trans.zh_TW = public/language/zh-TW/tags.json
@@ -3776,6 +3844,7 @@ trans.sv = public/language/sv/top.json
trans.th = public/language/th/top.json
trans.tr = public/language/tr/top.json
trans.uk = public/language/uk/top.json
+trans.ur = public/language/ur/top.json
trans.vi = public/language/vi/top.json
trans.zh_CN = public/language/zh-CN/top.json
trans.zh_TW = public/language/zh-TW/top.json
@@ -3830,6 +3899,7 @@ trans.sv = public/language/sv/topic.json
trans.th = public/language/th/topic.json
trans.tr = public/language/tr/topic.json
trans.uk = public/language/uk/topic.json
+trans.ur = public/language/ur/topic.json
trans.vi = public/language/vi/topic.json
trans.zh_CN = public/language/zh-CN/topic.json
trans.zh_TW = public/language/zh-TW/topic.json
@@ -3884,6 +3954,7 @@ trans.sv = public/language/sv/unread.json
trans.th = public/language/th/unread.json
trans.tr = public/language/tr/unread.json
trans.uk = public/language/uk/unread.json
+trans.ur = public/language/ur/unread.json
trans.vi = public/language/vi/unread.json
trans.zh_CN = public/language/zh-CN/unread.json
trans.zh_TW = public/language/zh-TW/unread.json
@@ -3938,6 +4009,7 @@ trans.sv = public/language/sv/uploads.json
trans.th = public/language/th/uploads.json
trans.tr = public/language/tr/uploads.json
trans.uk = public/language/uk/uploads.json
+trans.ur = public/language/ur/uploads.json
trans.vi = public/language/vi/uploads.json
trans.zh_CN = public/language/zh-CN/uploads.json
trans.zh_TW = public/language/zh-TW/uploads.json
@@ -3992,6 +4064,7 @@ trans.sv = public/language/sv/user.json
trans.th = public/language/th/user.json
trans.tr = public/language/tr/user.json
trans.uk = public/language/uk/user.json
+trans.ur = public/language/ur/user.json
trans.vi = public/language/vi/user.json
trans.zh_CN = public/language/zh-CN/user.json
trans.zh_TW = public/language/zh-TW/user.json
@@ -4046,6 +4119,7 @@ trans.sv = public/language/sv/users.json
trans.th = public/language/th/users.json
trans.tr = public/language/tr/users.json
trans.uk = public/language/uk/users.json
+trans.ur = public/language/ur/users.json
trans.vi = public/language/vi/users.json
trans.zh_CN = public/language/zh-CN/users.json
trans.zh_TW = public/language/zh-TW/users.json
@@ -4100,6 +4174,7 @@ trans.sv = public/language/sv/world.json
trans.th = public/language/th/world.json
trans.tr = public/language/tr/world.json
trans.uk = public/language/uk/world.json
+trans.ur = public/language/ur/world.json
trans.vi = public/language/vi/world.json
trans.zh_CN = public/language/zh-CN/world.json
trans.zh_TW = public/language/zh-TW/world.json
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d512874927..163d3d882c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,38 @@
+#### v4.4.6 (2025-08-06)
+
+##### Chores
+
+* incrementing version number - v4.4.5 (6f106923)
+* update changelog for v4.4.5 (de05dad2)
+* incrementing version number - v4.4.4 (d323af44)
+* incrementing version number - v4.4.3 (d354c2eb)
+* incrementing version number - v4.4.2 (55c510ae)
+* incrementing version number - v4.4.1 (5ae79b4e)
+* incrementing version number - v4.4.0 (0a75eee3)
+* incrementing version number - v4.3.2 (b92b5d80)
+* incrementing version number - v4.3.1 (308e6b9f)
+* incrementing version number - v4.3.0 (bff291db)
+* incrementing version number - v4.2.2 (17fecc24)
+* incrementing version number - v4.2.1 (852a270c)
+* incrementing version number - v4.2.0 (87581958)
+* incrementing version number - v4.1.1 (b2afbb16)
+* incrementing version number - v4.1.0 (36c80850)
+* incrementing version number - v4.0.6 (4a52fb2e)
+* incrementing version number - v4.0.5 (1792a62b)
+* incrementing version number - v4.0.4 (b1125cce)
+* incrementing version number - v4.0.3 (2b65c735)
+* incrementing version number - v4.0.2 (73fe5fcf)
+* incrementing version number - v4.0.1 (a461b758)
+* incrementing version number - v4.0.0 (c1eaee45)
+
+##### New Features
+
+* add new brite skin from bootswatch (567ed875)
+
+##### Bug Fixes
+
+* pass max-memory expose-gc as process args (d5f57af3)
+
#### v4.4.5 (2025-07-31)
##### Chores
diff --git a/docker-compose-pgsql.yml b/docker-compose-pgsql.yml
index 9011d0f92a..3e32023939 100644
--- a/docker-compose-pgsql.yml
+++ b/docker-compose-pgsql.yml
@@ -14,7 +14,7 @@ services:
- ./install/docker/setup.json:/usr/src/app/setup.json
postgres:
- image: postgres:17.5-alpine
+ image: postgres:17.6-alpine
restart: unless-stopped
environment:
POSTGRES_USER: nodebb
@@ -24,7 +24,7 @@ services:
- postgres-data:/var/lib/postgresql/data
redis:
- image: redis:8.0.1-alpine
+ image: redis:8.2.1-alpine
restart: unless-stopped
command: ['redis-server', '--appendonly', 'yes', '--loglevel', 'warning']
# command: ["redis-server", "--save", "60", "1", "--loglevel", "warning"] # uncomment if you want to use snapshotting instead of AOF
diff --git a/docker-compose-redis.yml b/docker-compose-redis.yml
index da31cd6886..98bceb6721 100644
--- a/docker-compose-redis.yml
+++ b/docker-compose-redis.yml
@@ -14,7 +14,7 @@ services:
- ./install/docker/setup.json:/usr/src/app/setup.json
redis:
- image: redis:8.0.1-alpine
+ image: redis:8.2.1-alpine
restart: unless-stopped
command: ['redis-server', '--appendonly', 'yes', '--loglevel', 'warning']
# command: ["redis-server", "--save", "60", "1", "--loglevel", "warning"] # uncomment if you want to use snapshotting instead of AOF
diff --git a/docker-compose.yml b/docker-compose.yml
index 637cecb0cd..ec096eb060 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -24,7 +24,7 @@ services:
- mongo-data:/data/db
- ./install/docker/mongodb-user-init.js:/docker-entrypoint-initdb.d/user-init.js
redis:
- image: redis:8.0.1-alpine
+ image: redis:8.2.1-alpine
restart: unless-stopped
command: ['redis-server', '--appendonly', 'yes', '--loglevel', 'warning']
# command: ['redis-server', '--save', '60', '1', '--loglevel', 'warning'] # uncomment if you want to use snapshotting instead of AOF
@@ -34,7 +34,7 @@ services:
- redis
postgres:
- image: postgres:17.5-alpine
+ image: postgres:17.6-alpine
restart: unless-stopped
environment:
POSTGRES_USER: nodebb
diff --git a/eslint.config.mjs b/eslint.config.mjs
index dd39fc1544..47cfa158f5 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -5,7 +5,7 @@ import publicConfig from 'eslint-config-nodebb/public';
import commonRules from 'eslint-config-nodebb/common';
import { defineConfig } from 'eslint/config';
-import stylisticJs from '@stylistic/eslint-plugin-js'
+import stylisticJs from '@stylistic/eslint-plugin'
import js from '@eslint/js';
import globals from 'globals';
diff --git a/install/data/defaults.json b/install/data/defaults.json
index b17bbbb135..b6ae97a3c7 100644
--- a/install/data/defaults.json
+++ b/install/data/defaults.json
@@ -76,7 +76,7 @@
"profile:keepAllUserImages": 0,
"gdpr_enabled": 1,
"allowProfileImageUploads": 1,
- "teaserPost": "last-reply",
+ "teaserPost": "last-post",
"showPostPreviewsOnHover": 1,
"allowPrivateGroups": 1,
"unreadCutoff": 2,
diff --git a/install/package.json b/install/package.json
index 97e4509313..2ad0782fac 100644
--- a/install/package.json
+++ b/install/package.json
@@ -29,7 +29,8 @@
},
"dependencies": {
"@adactive/bootstrap-tagsinput": "0.8.2",
- "@fontsource/inter": "5.2.5",
+ "@fontsource-utils/scss": "0.2.1",
+ "@fontsource/inter": "5.2.6",
"@fontsource/poppins": "5.2.6",
"@fortawesome/fontawesome-free": "6.7.2",
"@isaacs/ttlcache": "1.4.1",
@@ -38,41 +39,40 @@
"@textcomplete/contenteditable": "0.1.13",
"@textcomplete/core": "0.1.13",
"@textcomplete/textarea": "0.1.13",
- "ace-builds": "1.41.0",
+ "ace-builds": "1.43.3",
"archiver": "7.0.1",
"async": "3.2.6",
"autoprefixer": "10.4.21",
"bcryptjs": "3.0.2",
"benchpressjs": "2.5.5",
"body-parser": "2.2.0",
- "bootbox": "6.0.3",
- "bootstrap": "5.3.6",
- "bootswatch": "5.3.6",
+ "bootbox": "6.0.4",
+ "bootstrap": "5.3.8",
+ "bootswatch": "5.3.7",
"chalk": "4.1.2",
- "chart.js": "4.4.9",
+ "chart.js": "4.5.0",
"cli-graph": "3.2.2",
"clipboard": "2.0.11",
- "commander": "13.1.0",
+ "commander": "14.0.0",
"compare-versions": "6.1.1",
- "compression": "1.8.0",
+ "compression": "1.8.1",
"connect-flash": "0.1.1",
"connect-mongo": "5.1.0",
- "connect-multiparty": "2.2.0",
"connect-pg-simple": "10.0.0",
- "connect-redis": "8.0.3",
+ "connect-redis": "9.0.0",
"cookie-parser": "1.4.7",
- "cron": "4.3.0",
+ "cron": "4.3.3",
"cropperjs": "1.6.2",
"csrf-sync": "4.2.1",
"daemon": "1.1.0",
- "diff": "8.0.1",
- "esbuild": "0.25.4",
+ "diff": "8.0.2",
+ "esbuild": "0.25.9",
"express": "4.21.2",
- "express-session": "1.18.1",
+ "express-session": "1.18.2",
"express-useragent": "1.0.15",
"fetch-cookie": "3.1.0",
"file-loader": "6.2.0",
- "fs-extra": "11.3.0",
+ "fs-extra": "11.3.1",
"graceful-fs": "4.2.11",
"helmet": "7.2.0",
"html-to-text": "9.0.5",
@@ -91,47 +91,48 @@
"lru-cache": "11.1.0",
"mime": "3.0.0",
"mkdirp": "3.0.1",
- "mongodb": "6.16.0",
- "morgan": "1.10.0",
+ "mongodb": "6.19.0",
+ "morgan": "1.10.1",
"mousetrap": "1.6.5",
- "multiparty": "4.2.3",
+ "multer": "2.0.2",
"nconf": "0.13.0",
"nodebb-plugin-2factor": "7.5.10",
- "nodebb-plugin-composer-default": "10.2.51",
- "nodebb-plugin-dbsearch": "6.2.19",
- "nodebb-plugin-emoji": "6.0.2",
+ "nodebb-plugin-composer-default": "10.3.0",
+ "nodebb-plugin-dbsearch": "6.3.1",
+ "nodebb-plugin-emoji": "6.0.3",
"nodebb-plugin-emoji-android": "4.1.1",
"nodebb-plugin-markdown": "13.2.1",
"nodebb-plugin-mentions": "4.7.6",
"nodebb-plugin-spam-be-gone": "2.3.2",
- "nodebb-plugin-web-push": "0.7.4",
+ "nodebb-plugin-web-push": "0.7.5",
"nodebb-rewards-essentials": "1.0.2",
- "nodebb-theme-harmony": "2.1.15",
+ "nodebb-theme-harmony": "2.1.18",
"nodebb-theme-lavender": "7.1.19",
- "nodebb-theme-peace": "2.2.43",
+ "nodebb-theme-peace": "2.2.48",
"nodebb-theme-persona": "14.1.12",
- "nodebb-widget-essentials": "7.0.38",
- "nodemailer": "7.0.3",
+ "nodebb-widget-essentials": "7.0.40",
+ "nodemailer": "7.0.6",
"nprogress": "0.2.0",
"passport": "0.7.0",
"passport-http-bearer": "1.0.1",
"passport-local": "1.0.0",
- "pg": "8.16.0",
- "pg-cursor": "2.15.0",
- "postcss": "8.5.3",
+ "pg": "8.16.3",
+ "pg-cursor": "2.15.3",
+ "postcss": "8.5.6",
"postcss-clean": "1.2.0",
"progress-webpack-plugin": "1.0.16",
"prompt": "1.3.0",
- "ioredis": "5.6.1",
+ "redis": "5.8.2",
"rimraf": "6.0.1",
"rss": "1.2.2",
"rtlcss": "4.3.0",
"sanitize-html": "2.17.0",
- "sass": "1.88.0",
- "satori": "0.13.1",
+ "sass": "1.91.0",
+ "satori": "0.18.2",
+ "sbd": "^1.0.19",
"semver": "7.7.2",
- "serve-favicon": "2.5.0",
- "sharp": "0.32.6",
+ "serve-favicon": "2.5.1",
+ "sharp": "0.34.3",
"sitemap": "8.0.0",
"socket.io": "4.8.1",
"socket.io-client": "4.8.1",
@@ -144,12 +145,13 @@
"timeago": "1.6.7",
"tinycon": "0.6.8",
"toobusy-js": "0.5.1",
- "tough-cookie": "5.1.2",
- "validator": "13.15.0",
- "webpack": "5.99.8",
+ "tough-cookie": "6.0.0",
+ "undici": "^7.10.0",
+ "validator": "13.15.15",
+ "webpack": "5.101.3",
"webpack-merge": "6.0.1",
"winston": "3.17.0",
- "workerpool": "9.2.0",
+ "workerpool": "9.3.3",
"xml": "1.0.1",
"xregexp": "5.1.2",
"yargs": "17.7.2",
@@ -160,23 +162,23 @@
"@commitlint/cli": "19.8.1",
"@commitlint/config-angular": "19.8.1",
"coveralls": "3.1.1",
- "@eslint/js": "9.26.0",
- "@stylistic/eslint-plugin-js": "4.4.0",
- "eslint-config-nodebb": "1.1.5",
- "eslint-plugin-import": "2.31.0",
+ "@eslint/js": "9.34.0",
+ "@stylistic/eslint-plugin": "5.3.1",
+ "eslint-config-nodebb": "1.1.11",
+ "eslint-plugin-import": "2.32.0",
"grunt": "1.6.1",
"grunt-contrib-watch": "1.1.0",
"husky": "8.0.3",
"jsdom": "26.1.0",
- "lint-staged": "16.0.0",
- "mocha": "11.2.2",
+ "lint-staged": "16.1.5",
+ "mocha": "11.7.1",
"mocha-lcov-reporter": "1.3.0",
"mockdate": "3.0.5",
"nyc": "17.1.0",
- "smtp-server": "3.13.6"
+ "smtp-server": "3.14.0"
},
"optionalDependencies": {
- "sass-embedded": "1.88.0"
+ "sass-embedded": "1.91.0"
},
"resolutions": {
"*/jquery": "3.7.1"
diff --git a/loader.js b/loader.js
index 0f80697633..427c952ff4 100644
--- a/loader.js
+++ b/loader.js
@@ -106,6 +106,7 @@ function forkWorker(index, isPrimary) {
if (nconf.get('expose-gc')) {
execArgv.push('--expose-gc');
}
+
if (!ports[index]) {
return console.log(`[cluster] invalid port for worker : ${index} ports: ${ports.length}`);
}
diff --git a/public/language/ar/admin/dashboard.json b/public/language/ar/admin/dashboard.json
index b44c50d859..fe7d88a0c9 100644
--- a/public/language/ar/admin/dashboard.json
+++ b/public/language/ar/admin/dashboard.json
@@ -75,6 +75,7 @@
"graphs.page-views-registered": "زيارات الصفحات المسجلة",
"graphs.page-views-guest": "زيارات الصفحات للزوار",
"graphs.page-views-bot": "زيارات الصفحات الآلية",
+ "graphs.page-views-ap": "ActivityPub Page Views",
"graphs.unique-visitors": "زوار فريدين",
"graphs.registered-users": "مستخدمين مسجلين",
"graphs.guest-users": "المستخدمين الزوار",
diff --git a/public/language/ar/admin/manage/categories.json b/public/language/ar/admin/manage/categories.json
index 626e15e5bc..f2469f7414 100644
--- a/public/language/ar/admin/manage/categories.json
+++ b/public/language/ar/admin/manage/categories.json
@@ -1,11 +1,15 @@
{
"manage-categories": "Manage Categories",
"add-category": "Add category",
+ "add-local-category": "Add Local category",
+ "add-remote-category": "Add Remote category",
+ "remove": "Remove",
"jump-to": "Jump to...",
"settings": "اعدادات القسم",
"edit-category": "Edit Category",
"privileges": "الصلاحيات",
"back-to-categories": "Back to categories",
+ "id": "Category ID",
"name": "Category Name",
"handle": "Category Handle",
"handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.",
@@ -103,6 +107,8 @@
"alert.create-success": "Category successfully created!",
"alert.none-active": "You have no active categories.",
"alert.create": "Create a Category",
+ "alert.add": "Add a Category",
+ "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.
Note — The remote category may not reflect all topics published unless at least one local user tracks/watches it.",
"alert.confirm-purge": "
Do you really want to purge this category \"%1\"?
Purging a category will remove all topics and posts, and delete the category from the database. If you want to remove a category temporarily, you'll want to \"disable\" the category instead.
", "alert.purge-success": "Category purged!", "alert.copy-success": "Settings Copied!", diff --git a/public/language/ar/admin/settings/activitypub.json b/public/language/ar/admin/settings/activitypub.json index 94f9ad7822..1b00672e0f 100644 --- a/public/language/ar/admin/settings/activitypub.json +++ b/public/language/ar/admin/settings/activitypub.json @@ -18,6 +18,27 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.one,two,three)",
+ "rules.add": "Add New Rule",
+ "rules.type": "Type",
+ "rules.value": "Value",
+ "rules.cid": "Category",
+
+ "relays": "Relays",
+ "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.",
+ "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.",
+ "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.",
+ "relays.add": "Add New Relay",
+ "relays.relay": "Relay",
+ "relays.state": "State",
+ "relays.state-0": "Pending",
+ "relays.state-1": "Receiving only",
+ "relays.state-2": "Active",
+
"server-filtering": "Filtering",
"count": "This NodeBB is currently aware of %1 server(s)",
"server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.",
diff --git a/public/language/ar/error.json b/public/language/ar/error.json
index 4c9d77e775..4d2ea94cba 100644
--- a/public/language/ar/error.json
+++ b/public/language/ar/error.json
@@ -3,6 +3,7 @@
"invalid-json": "Invalid JSON",
"wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead",
"required-parameters-missing": "Required parameters were missing from this API call: %1",
+ "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.",
"not-logged-in": "لم تقم بتسجيل الدخول",
"account-locked": "تم حظر حسابك مؤقتًا.",
"search-requires-login": "البحث في المنتدى يتطلب حساب - الرجاء تسجيل الدخول أو التسجيل",
@@ -236,6 +237,7 @@
"socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later",
"invalid-plugin-id": "Invalid plugin ID",
"plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP",
+ "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin",
"plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled",
"plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.",
"theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP",
diff --git a/public/language/ar/modules.json b/public/language/ar/modules.json
index 1bf14cdc27..cb15105037 100644
--- a/public/language/ar/modules.json
+++ b/public/language/ar/modules.json
@@ -48,6 +48,7 @@
"chat.add-user": "Add User",
"chat.notification-settings": "Notification Settings",
"chat.default-notification-setting": "Default Notification Setting",
+ "chat.join-leave-messages": "Join/Leave Messages",
"chat.notification-setting-room-default": "Room Default",
"chat.notification-setting-none": "No notifications",
"chat.notification-setting-at-mention-only": "@mention only",
diff --git a/public/language/ar/social.json b/public/language/ar/social.json
index b36256840e..4e95bbb8ba 100644
--- a/public/language/ar/social.json
+++ b/public/language/ar/social.json
@@ -8,5 +8,7 @@
"log-in-with-facebook": "تسجيل الدخول باستخدام فيسبوك",
"continue-with-facebook": "التسجيل باستخدام فيسبوك",
"sign-in-with-linkedin": "Sign in with LinkedIn",
- "sign-up-with-linkedin": "Sign up with LinkedIn"
+ "sign-up-with-linkedin": "Sign up with LinkedIn",
+ "sign-in-with-wordpress": "Sign in with WordPress",
+ "sign-up-with-wordpress": "Sign up with WordPress"
}
\ No newline at end of file
diff --git a/public/language/az/admin/dashboard.json b/public/language/az/admin/dashboard.json
index 8e7b0253d4..ec74e422a9 100644
--- a/public/language/az/admin/dashboard.json
+++ b/public/language/az/admin/dashboard.json
@@ -75,6 +75,7 @@
"graphs.page-views-registered": "Səhifə Baxışları qeydə alınıb",
"graphs.page-views-guest": "Səhifə baxışı qonaq",
"graphs.page-views-bot": "Səhifə baxış botu",
+ "graphs.page-views-ap": "ActivityPub Page Views",
"graphs.unique-visitors": "Unikal ziyarətçilər",
"graphs.registered-users": "Qeydiyyatdan keçmiş istifadəçilər",
"graphs.guest-users": "Qonaqlar",
diff --git a/public/language/az/admin/manage/categories.json b/public/language/az/admin/manage/categories.json
index 460c4cd852..6540f6c4e9 100644
--- a/public/language/az/admin/manage/categories.json
+++ b/public/language/az/admin/manage/categories.json
@@ -1,11 +1,15 @@
{
"manage-categories": "Kateqoriyaları idarə et",
"add-category": "Kateqoriya əlavə et",
+ "add-local-category": "Add Local category",
+ "add-remote-category": "Add Remote category",
+ "remove": "Remove",
"jump-to": "Keç...",
"settings": "Kateqoriya parametrləri",
"edit-category": "Kateqoriyanı redaktə et",
"privileges": "İmtiyazlar",
"back-to-categories": "Kateqoriyalara qayıt",
+ "id": "Category ID",
"name": "Kateqoriya adı",
"handle": "Kateqoriya dəstəyi",
"handle.help": "Kateqoriya dəstəyiniz istifadəçi adına bənzər digər şəbəkələrdə bu kateqoriyanın təmsili kimi istifadə olunur. Kateqoriya sapı mövcud istifadəçi adı və ya istifadəçi qrupuna uyğun olmamalıdır.",
@@ -103,6 +107,8 @@
"alert.create-success": "Kateqoriya uğurla yaradıldı!",
"alert.none-active": "Aktiv kateqoriyalarınız yoxdur.",
"alert.create": "Kateqoriya yarat",
+ "alert.add": "Add a Category",
+ "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.Bu \"%1\" kateqoriyasını həqiqətən təmizləmək istəyirsiniz?
Kateqoriyanın təmizlənməsi bütün mövzuları və yazıları siləcək və kateqoriyanı verilənlər bazasından siləcək. Kateqoriyanı müvəqqəti olaraq silmək istəyirsinizsə, bunun əvəzinə kateqoriyanı \"deaktiv etmək\" istəyəcəksiniz.
", "alert.purge-success": "Kateqoriya təmizləndi!", "alert.copy-success": "Parametrlər kopyalandı!", diff --git a/public/language/az/admin/settings/activitypub.json b/public/language/az/admin/settings/activitypub.json index f7bb02e10b..db67fe8e2b 100644 --- a/public/language/az/admin/settings/activitypub.json +++ b/public/language/az/admin/settings/activitypub.json @@ -18,6 +18,27 @@ "probe-timeout": "Axtarış vaxtı (millisaniyə)", "probe-timeout-help": "(Defolt: 2000) Əgər axtarış sorğusu müəyyən edilmiş vaxt çərçivəsində cavab almazsa, onun əvəzinə istifadəçi birbaşa linkə göndəriləcək. Saytlar ləng cavab verirsə və əlavə vaxt vermək istəyirsinizsə, bu rəqəmi daha yüksək tənzimləyin.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.one,two,three)",
+ "rules.add": "Add New Rule",
+ "rules.type": "Type",
+ "rules.value": "Value",
+ "rules.cid": "Category",
+
+ "relays": "Relays",
+ "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.",
+ "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.",
+ "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.",
+ "relays.add": "Add New Relay",
+ "relays.relay": "Relay",
+ "relays.state": "State",
+ "relays.state-0": "Pending",
+ "relays.state-1": "Receiving only",
+ "relays.state-2": "Active",
+
"server-filtering": "Filtrlə",
"count": "Bu NodeBB hazırda %1 server(lər)dən xəbərdardır",
"server.filter-help": "NodeBB ilə federasiyaya mane olmaq istədiyiniz serverləri göstərin. Alternativ olaraq, bunun əvəzinə xüsusi serverlərlə federasiyaya seçimlə icazə verə bilərsiniz. Hər iki variant bir-birini istisna etsə də, dəstəklənir.",
diff --git a/public/language/az/error.json b/public/language/az/error.json
index 4c364364fb..62ca9934ff 100644
--- a/public/language/az/error.json
+++ b/public/language/az/error.json
@@ -3,6 +3,7 @@
"invalid-json": "Yanlış JSON",
"wrong-parameter-type": "`%1` mülkiyyəti üçün %3 növünün dəyəri gözlənilən idi, lakin bunun əvəzinə %2 alındı",
"required-parameters-missing": "Bu API çağırışında tələb olunan parametrlər yoxdur: %1",
+ "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.",
"not-logged-in": "Siz hesaba daxil olmamısınız.",
"account-locked": "Hesabınız müvəqqəti olaraq bloklanıb",
"search-requires-login": "Axtarış üçün hesab tələb olunur - zəhmət olmasa daxil olun və ya qeydiyyatdan keçin.",
@@ -236,6 +237,7 @@
"socket-reconnect-failed": "Hazırda serverə daxil olmaq mümkün deyil. Yenidən cəhd etmək üçün bura klikləyin və ya daha sonra yenidən cəhd edin",
"invalid-plugin-id": "Yanlış plagin identifikatoru",
"plugin-not-whitelisted": "Plugini quraşdırmaq mümkün deyil – yalnız NodeBB Paket Meneceri tərəfindən ağ siyahıya alınmış plaginlər ACP vasitəsilə quraşdırıla bilər",
+ "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin",
"plugin-installation-via-acp-disabled": "ACP vasitəsilə plagin quraşdırılması deaktiv edilib",
"plugins-set-in-configuration": "Sizə plagin vəziyyətini dəyişdirmək icazəsi verilmir, çünki onlar icra zamanı təyin olunur (config.json, ətraf mühit dəyişənləri və ya terminal arqumentləri), lütfən, bunun əvəzinə konfiqurasiyanı dəyişdirin.",
"theme-not-set-in-configuration": "Konfiqurasiyada aktiv plaginləri təyin edərkən, mövzuların dəyişdirilməsi ACP-də yeniləmədən əvvəl yeni mövzunun aktiv plaginlərin siyahısına əlavə edilməsini tələb edir.",
diff --git a/public/language/az/modules.json b/public/language/az/modules.json
index dad624a47f..b82661a7e1 100644
--- a/public/language/az/modules.json
+++ b/public/language/az/modules.json
@@ -48,6 +48,7 @@
"chat.add-user": "İstifadəçi əlavə et",
"chat.notification-settings": "Bildiriş parametrləri",
"chat.default-notification-setting": "Defolt bildiriş parametri",
+ "chat.join-leave-messages": "Join/Leave Messages",
"chat.notification-setting-room-default": "Defolt otaq",
"chat.notification-setting-none": "Bildiriş yoxdur",
"chat.notification-setting-at-mention-only": "yalnız @qeyd",
diff --git a/public/language/az/social.json b/public/language/az/social.json
index 42afa9db1a..e9010887fe 100644
--- a/public/language/az/social.json
+++ b/public/language/az/social.json
@@ -8,5 +8,7 @@
"log-in-with-facebook": "Facebook ilə daxil olun",
"continue-with-facebook": "Facebook ilə davam edin",
"sign-in-with-linkedin": "LinkedIn ilə daxil olun",
- "sign-up-with-linkedin": "LinkedIn ilə qeydiyyatdan keç"
+ "sign-up-with-linkedin": "LinkedIn ilə qeydiyyatdan keç",
+ "sign-in-with-wordpress": "Sign in with WordPress",
+ "sign-up-with-wordpress": "Sign up with WordPress"
}
\ No newline at end of file
diff --git a/public/language/bg/admin/dashboard.json b/public/language/bg/admin/dashboard.json
index d7839ed1ff..f749044a69 100644
--- a/public/language/bg/admin/dashboard.json
+++ b/public/language/bg/admin/dashboard.json
@@ -75,6 +75,7 @@
"graphs.page-views-registered": "Преглеждания на страниците от регистрирани потребители",
"graphs.page-views-guest": "Преглеждания на страниците от гости",
"graphs.page-views-bot": "Преглеждания на страниците от ботове",
+ "graphs.page-views-ap": "Преглеждания на страницата от ActivityPub",
"graphs.unique-visitors": "Уникални посетители",
"graphs.registered-users": "Регистрирани потребители",
"graphs.guest-users": "Гости",
diff --git a/public/language/bg/admin/manage/categories.json b/public/language/bg/admin/manage/categories.json
index 31531b4c16..51a23cb96b 100644
--- a/public/language/bg/admin/manage/categories.json
+++ b/public/language/bg/admin/manage/categories.json
@@ -1,11 +1,15 @@
{
"manage-categories": "Управление на категориите",
"add-category": "Добавяне на категория",
+ "add-local-category": "Добавяне на локална категория",
+ "add-remote-category": "Добавяне на отдалечена категория",
+ "remove": "Премахване",
"jump-to": "Прехвърляне към…",
"settings": "Настройки на категорията",
"edit-category": "Редактиране на категорията",
"privileges": "Правомощия",
"back-to-categories": "Назад към категориите",
+ "id": "Идентификатор на категорията",
"name": "Име на категорията",
"handle": "Идентификатор на категорията",
"handle.help": "Идентификаторът на категорията се ползва за представяне на тази категория в други мрежи, подобно на потребителското име. Този идентификатор не трябва да съвпада със съществуващо потребителско име или потребителска група.",
@@ -103,6 +107,8 @@
"alert.create-success": "Категорията е създадена успешно!",
"alert.none-active": "Нямате активни категории.",
"alert.create": "Създаване на категория",
+ "alert.add": "Добавяне на категория",
+ "alert.add-help": "Отдалечена категория може да бъде добавена в списъка с категории, като посочите нейния идентификатор.Наистина ли искате да изтриете категорията „%1“?
Изтриването на категорията ще премахне всички теми и публикации, и ще изтрие категорията от базата данни. Ако искате да премахнете категорията временно, можете просто да я „изключите“.
", "alert.purge-success": "Категорията е изтрита!", "alert.copy-success": "Настройките са копирани!", diff --git a/public/language/bg/admin/settings/activitypub.json b/public/language/bg/admin/settings/activitypub.json index 59c76176bd..83b33df719 100644 --- a/public/language/bg/admin/settings/activitypub.json +++ b/public/language/bg/admin/settings/activitypub.json @@ -18,6 +18,27 @@ "probe-timeout": "Време за изчакване на проверката (милисекунди)", "probe-timeout-help": "(По подразбиране: 2000) Ако проверката не получи отговор в рамките на зададеното време, потребителят ще бъде изпратен директно на адреса на връзката. Задайте по-голямо число, ако уеб сайтовете отговарят по-бавно и искате да им дадете повече време.", + "rules": "Категоризиране", + "rules-intro": "Съдържанието открито чрез ActivityPub може да бъде категоризирано автоматично следвайки определени правила (например дума отбелязана с диез)", + "rules.modal.title": "Как работи това", + "rules.modal.instructions": "Цялото входящо съдържание се проверява спрямо правилата и ако има съвпадения – те се преместват в избраната категория.едно,две,три)",
+ "rules.add": "Добавяне на ново правило",
+ "rules.type": "Тип",
+ "rules.value": "Стойност",
+ "rules.cid": "Категория",
+
+ "relays": "Препредавател",
+ "relays.intro": "Препредавателят подобрява отриването на съдържание за и от Вашият NodeBB. Абонирането за препредавател означава, че съдържанието получено от него ще бъде препредавано тук, а съдържанието публикувано тук, ще бъде излъчвано от него за останалите.",
+ "relays.warning": "Забележка: препредавателите могат да доставят огромно количество трафик, което може да увеличи разходите Ви за съхранение и обработка.",
+ "relays.litepub": "NodeBB използва стандарт за препредаване в стила на LitePub. Адресът, който въведете тук, трябва да завършва с /actor.",
+ "relays.add": "Добавяне на нов препредавател",
+ "relays.relay": "Препредавател",
+ "relays.state": "Състояние",
+ "relays.state-0": "В изчакване",
+ "relays.state-1": "Само приемане",
+ "relays.state-2": "Активен",
+
"server-filtering": "Филтриране",
"count": "Този NodeBB в момента знае за наличието на %1 сървър(а)",
"server.filter-help": "Посочете сървърите, с които не искате Вашият NodeBB да осъществява връзка. Или можете вместо това да посочите конкретни сървъри, с които разрешавате връзката. И двете възможности са налични, но може да изберете само една от тях.",
diff --git a/public/language/bg/error.json b/public/language/bg/error.json
index 78566e8bf1..94d12a36b1 100644
--- a/public/language/bg/error.json
+++ b/public/language/bg/error.json
@@ -3,6 +3,7 @@
"invalid-json": "Неправилен JSON",
"wrong-parameter-type": "За свойството `%1` се очакваше стойност от тип %3, но вместо това беше получено %2",
"required-parameters-missing": "Липсват задължителни параметри от това извикване към ППИ: %1",
+ "reserved-ip-address": "Мрежовите заявки до IP адреси от резервирани области не са позволени.",
"not-logged-in": "Изглежда не сте се вписали в системата.",
"account-locked": "Вашият акаунт беше заключен временно",
"search-requires-login": "Търсенето изисква регистриран акаунт! Моля, впишете се или се регистрирайте!",
@@ -236,6 +237,7 @@
"socket-reconnect-failed": "В момента сървърът е недостъпен. Натиснете тук, за да опитате отново, или опитайте пак по-късно.",
"invalid-plugin-id": "Грешен идентификатор на добавка",
"plugin-not-whitelisted": "Добавката не може да бъде инсталирана – само добавки, одобрени от пакетния мениджър на NodeBB могат да бъдат инсталирани чрез ACP",
+ "cannot-toggle-system-plugin": "Не можете да превключите състоянието на системна добавка",
"plugin-installation-via-acp-disabled": "Инсталирането на добавки чрез ACP е изключено",
"plugins-set-in-configuration": "Не можете да променяте състоянието на добавката, тъй като то се определя по време на работата ѝ (чрез config.json, променливи на средата или аргументи при изпълнение). Вместо това може да промените конфигурацията.",
"theme-not-set-in-configuration": "Когато определяте активните добавки в конфигурацията, промяната на темите изисква да се добави новата тема към активните добавки, преди актуализирането ѝ в ACP",
diff --git a/public/language/bg/modules.json b/public/language/bg/modules.json
index ba3da12344..170141d17d 100644
--- a/public/language/bg/modules.json
+++ b/public/language/bg/modules.json
@@ -48,6 +48,7 @@
"chat.add-user": "Добавяне на потребител",
"chat.notification-settings": "Настройки за известията",
"chat.default-notification-setting": "Стандартни настройки за известията",
+ "chat.join-leave-messages": "Съобщения за присъединяване/напускане",
"chat.notification-setting-room-default": "По подразбиране за стаята",
"chat.notification-setting-none": "Без известия",
"chat.notification-setting-at-mention-only": "Само @споменавания",
@@ -120,7 +121,7 @@
"bootbox.ok": "Добре",
"bootbox.cancel": "Отказ",
"bootbox.confirm": "Потвърждаване",
- "bootbox.submit": "Публикуване",
+ "bootbox.submit": "Изпращане",
"bootbox.send": "Изпращане",
"cover.dragging-title": "Наместване на снимката",
"cover.dragging-message": "Преместете снимката на желаното положение и натиснете „Запазване“",
diff --git a/public/language/bg/social.json b/public/language/bg/social.json
index 931e80c8c8..df4532b68e 100644
--- a/public/language/bg/social.json
+++ b/public/language/bg/social.json
@@ -8,5 +8,7 @@
"log-in-with-facebook": "Вписване с Facebook",
"continue-with-facebook": "Продължаване с Facebook",
"sign-in-with-linkedin": "Вписване с LinkedIn",
- "sign-up-with-linkedin": "Регистриране с LinkedIn"
+ "sign-up-with-linkedin": "Регистриране с LinkedIn",
+ "sign-in-with-wordpress": "Вписване с WordPress",
+ "sign-up-with-wordpress": "Регистриране с WordPress"
}
\ No newline at end of file
diff --git a/public/language/bn/admin/dashboard.json b/public/language/bn/admin/dashboard.json
index 6ad973f5f3..0be6d5866c 100644
--- a/public/language/bn/admin/dashboard.json
+++ b/public/language/bn/admin/dashboard.json
@@ -75,6 +75,7 @@
"graphs.page-views-registered": "Page Views Registered",
"graphs.page-views-guest": "Page Views Guest",
"graphs.page-views-bot": "Page Views Bot",
+ "graphs.page-views-ap": "ActivityPub Page Views",
"graphs.unique-visitors": "Unique Visitors",
"graphs.registered-users": "Registered Users",
"graphs.guest-users": "Guest Users",
diff --git a/public/language/bn/admin/manage/categories.json b/public/language/bn/admin/manage/categories.json
index f51152f22d..d66dd814a1 100644
--- a/public/language/bn/admin/manage/categories.json
+++ b/public/language/bn/admin/manage/categories.json
@@ -1,11 +1,15 @@
{
"manage-categories": "Manage Categories",
"add-category": "Add category",
+ "add-local-category": "Add Local category",
+ "add-remote-category": "Add Remote category",
+ "remove": "Remove",
"jump-to": "Jump to...",
"settings": "Category Settings",
"edit-category": "Edit Category",
"privileges": "Privileges",
"back-to-categories": "Back to categories",
+ "id": "Category ID",
"name": "Category Name",
"handle": "Category Handle",
"handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.",
@@ -103,6 +107,8 @@
"alert.create-success": "Category successfully created!",
"alert.none-active": "You have no active categories.",
"alert.create": "Create a Category",
+ "alert.add": "Add a Category",
+ "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.Do you really want to purge this category \"%1\"?
Purging a category will remove all topics and posts, and delete the category from the database. If you want to remove a category temporarily, you'll want to \"disable\" the category instead.
", "alert.purge-success": "Category purged!", "alert.copy-success": "Settings Copied!", diff --git a/public/language/bn/admin/settings/activitypub.json b/public/language/bn/admin/settings/activitypub.json index 94f9ad7822..1b00672e0f 100644 --- a/public/language/bn/admin/settings/activitypub.json +++ b/public/language/bn/admin/settings/activitypub.json @@ -18,6 +18,27 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.one,two,three)",
+ "rules.add": "Add New Rule",
+ "rules.type": "Type",
+ "rules.value": "Value",
+ "rules.cid": "Category",
+
+ "relays": "Relays",
+ "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.",
+ "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.",
+ "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.",
+ "relays.add": "Add New Relay",
+ "relays.relay": "Relay",
+ "relays.state": "State",
+ "relays.state-0": "Pending",
+ "relays.state-1": "Receiving only",
+ "relays.state-2": "Active",
+
"server-filtering": "Filtering",
"count": "This NodeBB is currently aware of %1 server(s)",
"server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.",
diff --git a/public/language/bn/error.json b/public/language/bn/error.json
index 19157e3724..3dfb852227 100644
--- a/public/language/bn/error.json
+++ b/public/language/bn/error.json
@@ -3,6 +3,7 @@
"invalid-json": "Invalid JSON",
"wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead",
"required-parameters-missing": "Required parameters were missing from this API call: %1",
+ "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.",
"not-logged-in": "আপনি লগিন করেননি",
"account-locked": "আপনার অ্যাকাউন্ট সাময়িকভাবে লক করা হয়েছে",
"search-requires-login": "Searching requires an account - please login or register.",
@@ -236,6 +237,7 @@
"socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later",
"invalid-plugin-id": "Invalid plugin ID",
"plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP",
+ "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin",
"plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled",
"plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.",
"theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP",
diff --git a/public/language/bn/modules.json b/public/language/bn/modules.json
index d010f1ad37..45ce1f0e8c 100644
--- a/public/language/bn/modules.json
+++ b/public/language/bn/modules.json
@@ -48,6 +48,7 @@
"chat.add-user": "Add User",
"chat.notification-settings": "Notification Settings",
"chat.default-notification-setting": "Default Notification Setting",
+ "chat.join-leave-messages": "Join/Leave Messages",
"chat.notification-setting-room-default": "Room Default",
"chat.notification-setting-none": "No notifications",
"chat.notification-setting-at-mention-only": "@mention only",
diff --git a/public/language/bn/social.json b/public/language/bn/social.json
index 2ba690a187..5b8dd99a46 100644
--- a/public/language/bn/social.json
+++ b/public/language/bn/social.json
@@ -8,5 +8,7 @@
"log-in-with-facebook": "Log in with Facebook",
"continue-with-facebook": "Continue with Facebook",
"sign-in-with-linkedin": "Sign in with LinkedIn",
- "sign-up-with-linkedin": "Sign up with LinkedIn"
+ "sign-up-with-linkedin": "Sign up with LinkedIn",
+ "sign-in-with-wordpress": "Sign in with WordPress",
+ "sign-up-with-wordpress": "Sign up with WordPress"
}
\ No newline at end of file
diff --git a/public/language/cs/admin/dashboard.json b/public/language/cs/admin/dashboard.json
index ad5fd7cd94..7fccda35b3 100644
--- a/public/language/cs/admin/dashboard.json
+++ b/public/language/cs/admin/dashboard.json
@@ -75,6 +75,7 @@
"graphs.page-views-registered": "Zobrazených stránek/registrovaní",
"graphs.page-views-guest": "Zobrazených stránek/hosté",
"graphs.page-views-bot": "Zobrazených stránek/bot",
+ "graphs.page-views-ap": "ActivityPub Page Views",
"graphs.unique-visitors": "Jedineční návštěvníci",
"graphs.registered-users": "Registrovaní uživatelé",
"graphs.guest-users": "Guest Users",
diff --git a/public/language/cs/admin/manage/categories.json b/public/language/cs/admin/manage/categories.json
index 6095eb1293..8a8942a9f1 100644
--- a/public/language/cs/admin/manage/categories.json
+++ b/public/language/cs/admin/manage/categories.json
@@ -1,11 +1,15 @@
{
"manage-categories": "Manage Categories",
"add-category": "Add category",
+ "add-local-category": "Add Local category",
+ "add-remote-category": "Add Remote category",
+ "remove": "Remove",
"jump-to": "Jump to...",
"settings": "Nastavení kategorie",
"edit-category": "Edit Category",
"privileges": "Oprávnění",
"back-to-categories": "Back to categories",
+ "id": "Category ID",
"name": "Název kategorie",
"handle": "Category Handle",
"handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.",
@@ -103,6 +107,8 @@
"alert.create-success": "Kategorie byla úspěšně vytvořena.",
"alert.none-active": "Nemáte žádné aktivní kategorie.",
"alert.create": "Vytvořit kategorii",
+ "alert.add": "Add a Category",
+ "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.Opravdu chcete vyčistit tuto kategorii \"%1\"?
Smazání kategorie vyjme všechny témata a příspěvky a odstraní kategorii z databáze. Pokud chcete vyjmout kategorii dočasně, raději místo toho kategorii „zakažte”.
", "alert.purge-success": "Kategorie byla vyčištěna.", "alert.copy-success": "Nastavení bylo zkopírováno.", diff --git a/public/language/cs/admin/menu.json b/public/language/cs/admin/menu.json index a7adf69e7f..9c04206f3f 100644 --- a/public/language/cs/admin/menu.json +++ b/public/language/cs/admin/menu.json @@ -10,7 +10,7 @@ "section-manage": "Spravovat", "manage/categories": "Kategorie", "manage/privileges": "Oprávnění", - "manage/tags": "Značky", + "manage/tags": "Štítky", "manage/users": "Uživatelé", "manage/admins-mods": "Správci a moderátoři", "manage/registration": "Registrační fronta", @@ -35,7 +35,7 @@ "settings/post": "Posts", "settings/chat": "Chats", "settings/pagination": "Stránkování", - "settings/tags": "Značky", + "settings/tags": "Štítky", "settings/notifications": "Oznámení", "settings/api": "API Access", "settings/activitypub": "Federation (ActivityPub)", diff --git a/public/language/cs/admin/settings/activitypub.json b/public/language/cs/admin/settings/activitypub.json index 94f9ad7822..1b00672e0f 100644 --- a/public/language/cs/admin/settings/activitypub.json +++ b/public/language/cs/admin/settings/activitypub.json @@ -18,6 +18,27 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.one,two,three)",
+ "rules.add": "Add New Rule",
+ "rules.type": "Type",
+ "rules.value": "Value",
+ "rules.cid": "Category",
+
+ "relays": "Relays",
+ "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.",
+ "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.",
+ "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.",
+ "relays.add": "Add New Relay",
+ "relays.relay": "Relay",
+ "relays.state": "State",
+ "relays.state-0": "Pending",
+ "relays.state-1": "Receiving only",
+ "relays.state-2": "Active",
+
"server-filtering": "Filtering",
"count": "This NodeBB is currently aware of %1 server(s)",
"server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.",
diff --git a/public/language/cs/admin/settings/advanced.json b/public/language/cs/admin/settings/advanced.json
index 6f65a2bc45..608e14ba8f 100644
--- a/public/language/cs/admin/settings/advanced.json
+++ b/public/language/cs/admin/settings/advanced.json
@@ -38,7 +38,7 @@
"sockets.settings": "WebSocket Settings",
"sockets.max-attempts": "Max Reconnection Attempts",
- "sockets.default-placeholder": "Default: %1",
+ "sockets.default-placeholder": "Výchozí: %1",
"sockets.delay": "Reconnection Delay",
"compression.settings": "Compression Settings",
diff --git a/public/language/cs/admin/settings/post.json b/public/language/cs/admin/settings/post.json
index b0627af793..5b757e31de 100644
--- a/public/language/cs/admin/settings/post.json
+++ b/public/language/cs/admin/settings/post.json
@@ -4,11 +4,11 @@
"sorting.post-default": "Výchozí třídění příspěvků",
"sorting.oldest-to-newest": "Od nejstarších po nejnovější",
"sorting.newest-to-oldest": "Od nejnovějších po nejstarší",
- "sorting.recently-replied": "Recently Replied",
+ "sorting.recently-replied": "Poslední příspěvky",
"sorting.recently-created": "Recently Created",
"sorting.most-votes": "Dle počtu hlasů",
"sorting.most-posts": "Dle počtu příspěvků",
- "sorting.most-views": "Most Views",
+ "sorting.most-views": "Nejvíce zobrazení",
"sorting.topic-default": "Výchozí třídění tématu",
"length": "Délka příspěvku",
"post-queue": "Příspěvky ve frontě",
diff --git a/public/language/cs/error.json b/public/language/cs/error.json
index be1a799f5b..4295b7d97a 100644
--- a/public/language/cs/error.json
+++ b/public/language/cs/error.json
@@ -3,6 +3,7 @@
"invalid-json": "Neplatný JSON",
"wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead",
"required-parameters-missing": "Required parameters were missing from this API call: %1",
+ "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.",
"not-logged-in": "Zdá se, že nejste přihlášen/a",
"account-locked": "Váš účet byl dočasně uzamknut",
"search-requires-login": "Pro hledání je vyžadován účet – přihlaste se nebo zaregistrujte.",
@@ -236,6 +237,7 @@
"socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later",
"invalid-plugin-id": "Invalid plugin ID",
"plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP",
+ "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin",
"plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled",
"plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.",
"theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP",
diff --git a/public/language/cs/global.json b/public/language/cs/global.json
index 8e2dd50230..e02fc73a56 100644
--- a/public/language/cs/global.json
+++ b/public/language/cs/global.json
@@ -37,7 +37,7 @@
"header.categories": "Kategorie",
"header.recent": "Nejnovější",
"header.unread": "Nepřečtené",
- "header.tags": "Značky",
+ "header.tags": "Štítky",
"header.popular": "Populární",
"header.top": "Nejlepší",
"header.users": "Uživatelé",
@@ -147,7 +147,7 @@
"copied": "Copied",
"user-search-prompt": "Pro hledání uživatelů, zde pište...",
"hidden": "Hidden",
- "sort": "Sort",
+ "sort": "Řazení",
"actions": "Actions",
"rss-feed": "RSS Feed",
"skip-to-content": "Skip to content"
diff --git a/public/language/cs/modules.json b/public/language/cs/modules.json
index f8d89bde37..57219f8b88 100644
--- a/public/language/cs/modules.json
+++ b/public/language/cs/modules.json
@@ -16,8 +16,8 @@
"chat.user-typing-n": "%1, %2 and %3 others are typing ...",
"chat.user-has-messaged-you": "%1 Vám napsal.",
"chat.replying-to": "Replying to %1",
- "chat.see-all": "All chats",
- "chat.mark-all-read": "Mark all read",
+ "chat.see-all": "Všechny konverzace",
+ "chat.mark-all-read": "Označit vše jako přečtené",
"chat.no-messages": "Vyberte příjemce k prohlédnutí historie zpráv.",
"chat.no-users-in-room": "Žádní uživatelé v místnosti.",
"chat.recent-chats": "Aktuální konverzace",
@@ -48,6 +48,7 @@
"chat.add-user": "Add User",
"chat.notification-settings": "Notification Settings",
"chat.default-notification-setting": "Default Notification Setting",
+ "chat.join-leave-messages": "Join/Leave Messages",
"chat.notification-setting-room-default": "Room Default",
"chat.notification-setting-none": "No notifications",
"chat.notification-setting-at-mention-only": "@mention only",
@@ -79,7 +80,7 @@
"composer.compose": "Napsat",
"composer.show-preview": "Ukázat náhled",
"composer.hide-preview": "Skrýt náhled",
- "composer.help": "Help",
+ "composer.help": "Nápověda",
"composer.user-said-in": "%1 řekl v %2:",
"composer.user-said": "%1 řekl:",
"composer.discard": "Jste si jisti, že chcete zrušit tento příspěvek?",
@@ -88,23 +89,23 @@
"composer.uploading": "Nahrávám %1",
"composer.formatting.bold": "Tučné",
"composer.formatting.italic": "Kurzíva",
- "composer.formatting.heading": "Heading",
- "composer.formatting.heading1": "Heading 1",
- "composer.formatting.heading2": "Heading 2",
- "composer.formatting.heading3": "Heading 3",
- "composer.formatting.heading4": "Heading 4",
- "composer.formatting.heading5": "Heading 5",
- "composer.formatting.heading6": "Heading 6",
+ "composer.formatting.heading": "Nadpis",
+ "composer.formatting.heading1": "Nadpis 1",
+ "composer.formatting.heading2": "Nadpis 2",
+ "composer.formatting.heading3": "Nadpis 3",
+ "composer.formatting.heading4": "Nadpis 4",
+ "composer.formatting.heading5": "Nadpis 5",
+ "composer.formatting.heading6": "Nadpis 6",
"composer.formatting.list": "Seznam",
"composer.formatting.strikethrough": "Přeškrtnutí",
"composer.formatting.code": "Kód",
"composer.formatting.link": "Odkaz",
- "composer.formatting.picture": "Image Link",
+ "composer.formatting.picture": "Odkaz na obrázek",
"composer.upload-picture": "Nahrát obrázek",
"composer.upload-file": "Nahrát soubor",
"composer.zen-mode": "Režim Zem",
"composer.select-category": "Vyberte kategorii",
- "composer.textarea.placeholder": "Enter your post content here, drag and drop images",
+ "composer.textarea.placeholder": "Sem vložte obsah příspěvku nebo přetáhněte obrázky",
"composer.post-queue-alert": "Hello👋!Do you really want to purge this category \"%1\"?
Purging a category will remove all topics and posts, and delete the category from the database. If you want to remove a category temporarily, you'll want to \"disable\" the category instead.
", "alert.purge-success": "Category purged!", "alert.copy-success": "Settings Copied!", diff --git a/public/language/da/admin/settings/activitypub.json b/public/language/da/admin/settings/activitypub.json index 360d4ebfcf..c868684859 100644 --- a/public/language/da/admin/settings/activitypub.json +++ b/public/language/da/admin/settings/activitypub.json @@ -18,6 +18,27 @@ "probe-timeout": "Opslag Ventetid (millisekunder)", "probe-timeout-help": "(Udgangspunkt: 2000) Hvis opslagsforespørgslen ikke modtager et svar inden for den angivne tidsramme, vil vil brugeren blive sendt til linket direkte i stedet for. Justér dette tal højere, hvis sider responderer langsomt og du gerne vil give dem ekstra tid.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.one,two,three)",
+ "rules.add": "Add New Rule",
+ "rules.type": "Type",
+ "rules.value": "Value",
+ "rules.cid": "Category",
+
+ "relays": "Relays",
+ "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.",
+ "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.",
+ "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.",
+ "relays.add": "Add New Relay",
+ "relays.relay": "Relay",
+ "relays.state": "State",
+ "relays.state-0": "Pending",
+ "relays.state-1": "Receiving only",
+ "relays.state-2": "Active",
+
"server-filtering": "Filtrering",
"count": "Denne NodeBB instans er lige nu bevidst om %1 server(e)",
"server.filter-help": "Specificér servere, som du gerne vil stoppe fra at føderere med din NodeBB instans. Alternativt, kan du vælge at selektivt tillade føderation med udvalgte servere i stedet. Begge muligheder er understøttet, men man kan kun vælge en metode ad gangen.",
diff --git a/public/language/da/error.json b/public/language/da/error.json
index 657c4a479d..9418e8663c 100644
--- a/public/language/da/error.json
+++ b/public/language/da/error.json
@@ -3,6 +3,7 @@
"invalid-json": "Invalid JSON",
"wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead",
"required-parameters-missing": "Required parameters were missing from this API call: %1",
+ "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.",
"not-logged-in": "Det ser ikke ud til at du er logget ind.",
"account-locked": "Din konto er blevet blokeret midlertidigt.",
"search-requires-login": "Du skal have en konto for at søge - log venligst ind eller registrer dig.",
@@ -236,6 +237,7 @@
"socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later",
"invalid-plugin-id": "Invalid plugin ID",
"plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP",
+ "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin",
"plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled",
"plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.",
"theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP",
diff --git a/public/language/da/modules.json b/public/language/da/modules.json
index 85a9e8fdfa..e8ceb340f2 100644
--- a/public/language/da/modules.json
+++ b/public/language/da/modules.json
@@ -48,6 +48,7 @@
"chat.add-user": "Add User",
"chat.notification-settings": "Notification Settings",
"chat.default-notification-setting": "Default Notification Setting",
+ "chat.join-leave-messages": "Join/Leave Messages",
"chat.notification-setting-room-default": "Room Default",
"chat.notification-setting-none": "No notifications",
"chat.notification-setting-at-mention-only": "@mention only",
diff --git a/public/language/da/social.json b/public/language/da/social.json
index d2dec7d2f0..308b0f1065 100644
--- a/public/language/da/social.json
+++ b/public/language/da/social.json
@@ -8,5 +8,7 @@
"log-in-with-facebook": "Log ind med Facebook",
"continue-with-facebook": "Fortsæt med Facebook",
"sign-in-with-linkedin": "Log ind med LinkedIn",
- "sign-up-with-linkedin": "Meld dig ind med LinkedIn"
+ "sign-up-with-linkedin": "Meld dig ind med LinkedIn",
+ "sign-in-with-wordpress": "Sign in with WordPress",
+ "sign-up-with-wordpress": "Sign up with WordPress"
}
\ No newline at end of file
diff --git a/public/language/de/admin/dashboard.json b/public/language/de/admin/dashboard.json
index 9229f720aa..4067ac8311 100644
--- a/public/language/de/admin/dashboard.json
+++ b/public/language/de/admin/dashboard.json
@@ -75,6 +75,7 @@
"graphs.page-views-registered": "Registrierte Seitenaufrufe",
"graphs.page-views-guest": "Seitenaufrufe von Gästen",
"graphs.page-views-bot": "Seitenaufrufe von Bots",
+ "graphs.page-views-ap": "ActivityPub Page Views",
"graphs.unique-visitors": "Verschiedene Besucher",
"graphs.registered-users": "Registrierte Benutzer",
"graphs.guest-users": "Gast-Benutzer",
diff --git a/public/language/de/admin/manage/categories.json b/public/language/de/admin/manage/categories.json
index 1cc1282011..327f17ae07 100644
--- a/public/language/de/admin/manage/categories.json
+++ b/public/language/de/admin/manage/categories.json
@@ -1,11 +1,15 @@
{
"manage-categories": "Kategorien verwalten",
"add-category": "Kategorie hinzufügen",
+ "add-local-category": "Add Local category",
+ "add-remote-category": "Add Remote category",
+ "remove": "Remove",
"jump-to": "Springen zu...",
"settings": "Kategorieeinstellungen",
"edit-category": "Kategorie bearbeiten",
"privileges": "Berechtigungen",
"back-to-categories": "Zurück zu Kategorien",
+ "id": "Category ID",
"name": "Kategoriename",
"handle": "Category Handle",
"handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.",
@@ -103,6 +107,8 @@
"alert.create-success": "Kategorie erfolgreich erstellt!",
"alert.none-active": "Du hast keine aktiven Kategorien.",
"alert.create": "Erstelle eine Kategorie",
+ "alert.add": "Add a Category",
+ "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.Möchtest du die Kategorie \"%1\" wirklich löschen?
Löschen einer Kategorie wird alle Themen und Beiträge zu entfernen, und die Kategorie aus der Datenbank löschen. Falls du eine Kategorie temporär entfernen möchstest, dann kannst du sie stattdessen \"deaktivieren\".",
"alert.purge-success": "Kategorie gelöscht!",
"alert.copy-success": "Einstellungen kopiert!",
diff --git a/public/language/de/admin/settings/activitypub.json b/public/language/de/admin/settings/activitypub.json
index 7475cf5d4b..f08220e9df 100644
--- a/public/language/de/admin/settings/activitypub.json
+++ b/public/language/de/admin/settings/activitypub.json
@@ -1,26 +1,47 @@
{
"intro-lead": "Was ist Föderation?",
- "intro-body": "NodeBB is able to communicate with other NodeBB instances that support it. This is achieved through a protocol called ActivityPub. If enabled, NodeBB will also be able to communicate with other apps and websites that use ActivityPub (e.g. Mastodon, Peertube, etc.)",
+ "intro-body": "NodeBB kann mit anderen NodeBB-Instanzen kommunizieren, die dies unterstützen. Dies geschieht über ein Protokoll namens ActivityPub. Wenn es aktiviert ist, kann NodeBB auch mit anderen Apps und Websites kommunizieren, die ActivityPub verwenden (z. B. Mastodon, Peertube usw.).",
"general": "Allgemein",
"pruning": "Inhaltsbereinigung",
- "content-pruning": "Days to keep remote content",
- "content-pruning-help": "Note that remote content that has received engagement (a reply or a upvote/downvote) will be preserved. (0 for disabled)",
- "user-pruning": "Days to cache remote user accounts",
- "user-pruning-help": "Remote user accounts will only be pruned if they have no posts. Otherwise they will be re-retrieved. (0 for disabled)",
+ "content-pruning": "Tage, an denen der Remote-Inhalt aufbewahrt werden soll.",
+ "content-pruning-help": "Inhalte von extern, die Interaktionen bekommen haben (z. B. eine Antwort oder ein Upvote/Downvote), bleiben erhalten. (0 = deaktiviert)",
+ "user-pruning": "Tage, um externe Benutzerkonten zwischenzuspeichern",
+ "user-pruning-help": "Externe Benutzerkonten werden nur gelöscht, wenn sie keine Beiträge haben. Andernfalls werden sie erneut abgerufen. (0 = deaktiviert)",
"enabled": "Föderation aktivieren",
- "enabled-help": "If enabled, will allow this NodeBB will be able to communicate with all Activitypub-enabled clients on the wider fediverse.",
- "allowLoopback": "Allow loopback processing",
- "allowLoopback-help": "Useful for debugging purposes only. You should probably leave this disabled.",
+ "enabled-help": "Wenn aktiviert, kann dieses NodeBB mit allen ActivityPub-fähigen Clients im weiteren Fediverse kommunizieren.",
+ "allowLoopback": "Loopback-Verarbeitung erlauben",
+ "allowLoopback-help": "Nur für Debugging-Zwecke nützlich. Sollte am besten deaktiviert bleiben.",
"probe": "In App öffnen",
- "probe-enabled": "Try to open ActivityPub-enabled resources in NodeBB",
- "probe-enabled-help": "If enabled, NodeBB will check every external link for an ActivityPub equivalent, and load it in NodeBB instead.",
- "probe-timeout": "Lookup Timeout (milliseconds)",
- "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.",
+ "probe-enabled": "Versuchen, ActivityPub-fähige Ressourcen in NodeBB zu öffnen",
+ "probe-enabled-help": "Wenn aktiviert, überprüft NodeBB jeden externen Link auf ein ActivityPub-Äquivalent und lädt diesen stattdessen in NodeBB.",
+ "probe-timeout": "Lookup-Timeout (Millisekunden)",
+ "probe-timeout-help": "(Standard: 2000) Wenn die Lookup-Anfrage innerhalb des festgelegten Zeitraums keine Antwort erhält, wird der Nutzer stattdessen direkt zum Link weitergeleitet. Erhöhe diesen Wert, wenn Seiten langsam reagieren und du mehr Zeit einräumen möchtest.",
+
+ "rules": "Kategorisierung",
+ "rules-intro": "Über ActivityPub entdeckte Inhalte können automatisch anhand bestimmter Regeln (z. B. Hashtags) kategorisiert werden.",
+ "rules.modal.title": "Wie es funktioniert",
+ "rules.modal.instructions": "Eingehende Inhalte werden mit diesen Kategorisierungsregeln abgeglichen, und passende Inhalte werden automatisch in die gewünschte Kategorie verschoben. Hinweis: Inhalte, die bereits kategorisiert sind (z. B. in einer externen Kategorie), durchlaufen diese Regeln nicht.",
+ "rules.modal.values-multiple": "Um mehrere Werte abzugleichen, Einträge mit einem Komma trennen (z. B. eins,zwei,drei).",
+ "rules.add": "Neue Regel hinzufügen",
+ "rules.type": "Typ",
+ "rules.value": "Wert",
+ "rules.cid": "Kategorie",
+
+ "relays": "Relays",
+ "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.",
+ "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.",
+ "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.",
+ "relays.add": "Add New Relay",
+ "relays.relay": "Relay",
+ "relays.state": "State",
+ "relays.state-0": "Pending",
+ "relays.state-1": "Receiving only",
+ "relays.state-2": "Active",
"server-filtering": "Filterung",
- "count": "This NodeBB is currently aware of %1 server(s)",
- "server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.",
- "server.filter-help-hostname": "Enter just the instance hostname below (e.g. example.org), separated by line breaks.",
- "server.filter-allow-list": "Use this as an Allow List instead"
+ "count": "Dieses NodeBB kennt derzeit %1 Server",
+ "server.filter-help": "Gib die Server an, die du von der Föderation mit deinem NodeBB ausschließen möchtest. Alternativ kannst du auch festlegen, dass die Föderation nur mit bestimmten Servern erlaubt ist. Beide Optionen werden unterstützt, schließen sich jedoch gegenseitig aus.",
+ "server.filter-help-hostname": "Gib unten nur den Instanz-Hostnamen ein (z. B. example.org), jeweils durch Zeilenumbrüche getrennt.",
+ "server.filter-allow-list": "Stattdessen als Allow-Liste verwenden"
}
\ No newline at end of file
diff --git a/public/language/de/error.json b/public/language/de/error.json
index 8c44aceccd..b292fdf3f1 100644
--- a/public/language/de/error.json
+++ b/public/language/de/error.json
@@ -3,6 +3,7 @@
"invalid-json": "Ungültiges JSON",
"wrong-parameter-type": "Für die Eigenschaft „%1“ wurde ein Wert vom Typ %3 erwartet, aber stattdessen wurde %2 empfangen",
"required-parameters-missing": "Bei diesem API-Aufruf fehlten erforderliche Parameter: %1",
+ "reserved-ip-address": "Netzwerkanfragen an reservierte IP-Bereiche sind nicht erlaubt.",
"not-logged-in": "Du bist nicht angemeldet.",
"account-locked": "Dein Konto wurde vorübergehend gesperrt.",
"search-requires-login": "Die Suche erfordert ein Konto, bitte einloggen oder registrieren.",
@@ -67,8 +68,8 @@
"no-chat-room": "Der Chatroom existiert nicht",
"no-privileges": "Du verfügst nicht über ausreichende Berechtigungen, um die Aktion durchzuführen.",
"category-disabled": "Kategorie ist deaktiviert",
- "post-deleted": "Post deleted",
- "topic-locked": "Topic locked",
+ "post-deleted": "Beitrag gelöscht",
+ "topic-locked": "Thema gesperrt",
"post-edit-duration-expired": "Entschuldigung, du darfst Beiträge nur %1 Sekunde(n) nach dem Veröffentlichen editieren.",
"post-edit-duration-expired-minutes": "Du darfst Beiträge lediglich innerhalb von %1 Minuten/n nach dem Erstellen editieren",
"post-edit-duration-expired-minutes-seconds": "Du darfst Beiträge lediglich innerhalb von %1 Minuten/n und %2 Sekunden nach dem Erstellen editieren",
@@ -154,9 +155,9 @@
"about-me-too-long": "Entschuldigung, dein \"über mich\" kann nicht länger als %1 Zeichen sein.",
"cant-chat-with-yourself": "Du kannst nicht mit dir selber chatten!",
"chat-restricted": "Dieser Benutzer hat seine Chatfunktion eingeschränkt. Du kannst nur mit diesem Benutzer chatten, wenn er dir folgt.",
- "chat-allow-list-user-already-added": "This user is already in your allow list",
- "chat-deny-list-user-already-added": "This user is already in your deny list",
- "chat-user-blocked": "You have been blocked by this user.",
+ "chat-allow-list-user-already-added": "Dieser Benutzer befindet sich bereits in deiner Allow-Liste.",
+ "chat-deny-list-user-already-added": "Dieser Benutzer befindet sich bereits in deiner Deny-Liste.",
+ "chat-user-blocked": "Du wurdest von diesem Benutzer blockiert.",
"chat-disabled": "Das Chatsystem deaktiviert",
"too-many-messages": "Du hast zu viele Nachrichten versandt, bitte warte eine Weile.",
"invalid-chat-message": "Ungültige Nachricht",
@@ -171,7 +172,7 @@
"cant-add-users-to-chat-room": "Kann Benutzer nicht zu Chatroom hinzufügen",
"cant-remove-users-from-chat-room": "Kann Benutzer nicht aus Chatroom entfernen.",
"chat-room-name-too-long": "Der Name des Chat-Raums ist zu lang. Die Namen dürfen nicht länger als %1 Zeichen sein.",
- "remote-chat-received-too-long": "You received a chat message from %1, but it was too long and was rejected.",
+ "remote-chat-received-too-long": "Du hast eine Chat-Nachricht von %1 erhalten, aber sie war zu lang und wurde abgelehnt.",
"already-voting-for-this-post": "Du hast diesen Beitrag bereits bewertet.",
"reputation-system-disabled": "Das Reputationssystem ist deaktiviert.",
"downvoting-disabled": "Downvotes sind deaktiviert.",
@@ -185,20 +186,20 @@
"not-enough-reputation-min-rep-signature": "Du benötigst %1 Reputation, um eine Signatur hinzuzufügen",
"not-enough-reputation-min-rep-profile-picture": "Du benötigst %1 Ruf, um ein Profilbild hinzuzufügen",
"not-enough-reputation-min-rep-cover-picture": "Du benötigst %1 Ruf, um ein Titelbild hinzuzufügen",
- "not-enough-reputation-custom-field": "You need %1 reputation for %2",
- "custom-user-field-value-too-long": "Custom field value too long, %1",
- "custom-user-field-select-value-invalid": "Custom field selected option is invalid, %1",
- "custom-user-field-invalid-text": "Custom field text is invalid, %1",
- "custom-user-field-invalid-link": "Custom field link is invalid, %1",
- "custom-user-field-invalid-number": "Custom field number is invalid, %1",
- "custom-user-field-invalid-date": "Custom field date is invalid, %1",
- "invalid-custom-user-field": "Invalid custom user field, \"%1\" is already used by NodeBB",
+ "not-enough-reputation-custom-field": "Du benötigst %1 Reputation für %2",
+ "custom-user-field-value-too-long": "Benutzerdefiniertes Feld zu lang, %1",
+ "custom-user-field-select-value-invalid": "Die ausgewählte Option im benutzerdefinierten Feld ist ungültig, %1",
+ "custom-user-field-invalid-text": "Der Text im benutzerdefinierten Feld ist ungültig, %1",
+ "custom-user-field-invalid-link": "Der Link im benutzerdefinierten Feld ist ungültig, %1",
+ "custom-user-field-invalid-number": "Die Zahl im benutzerdefinierten Feld ist ungültig, %1",
+ "custom-user-field-invalid-date": "Das Datum im benutzerdefinierten Feld ist ungültig, %1",
+ "invalid-custom-user-field": "Ungültiges benutzerdefiniertes Feld, %1 wird bereits von NodeBB verwendet",
"post-already-flagged": "Du hast diesen Beitrag bereits gemeldet",
"user-already-flagged": "Du hast diesen Benutzer bereits gemeldet",
"post-flagged-too-many-times": "Dieser Beitrag wurde bereits von anderen Benutzern gemeldet",
"user-flagged-too-many-times": "Dieser Benutzer wurde bereits von anderen Benutzern gemeldet",
- "too-many-post-flags-per-day": "You can only flag %1 post(s) per day",
- "too-many-user-flags-per-day": "You can only flag %1 user(s) per day",
+ "too-many-post-flags-per-day": "Du kannst pro Tag nur %1 Beitrag/Beiträge melden",
+ "too-many-user-flags-per-day": "Du kannst pro Tag nur %1 Benutzer melden",
"cant-flag-privileged": "Sie dürfen die Profile oder Inhalte von privilegierten Benutzern (Moderatoren/Globalmoderatoren/Admins) nicht kennzeichnen.",
"cant-locate-flag-report": "Meldung-Report kann nicht gefunden werden",
"self-vote": "Du kannst deine eigenen Beiträge nicht bewerten",
@@ -234,13 +235,14 @@
"already-unblocked": "Dieser Nutzer ist bereits entsperrt",
"no-connection": "Es scheint als gäbe es ein Problem mit deiner Internetverbindung",
"socket-reconnect-failed": "Der Server kann zurzeit nicht erreicht werden. Klicken Sie hier, um es erneut zu versuchen, oder versuchen Sie es später erneut",
- "invalid-plugin-id": "Invalid plugin ID",
+ "invalid-plugin-id": "Ungültige Plugin-ID",
"plugin-not-whitelisted": "Plugin kann nicht installiert werden – nur Plugins, die vom NodeBB Package Manager in die Whitelist aufgenommen wurden, können über den ACP installiert werden",
- "plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled",
+ "cannot-toggle-system-plugin": "Du kannst den Status eines System-Plugins nicht umschalten",
+ "plugin-installation-via-acp-disabled": "Die Plugin-Installation über das ACP ist deaktiviert",
"plugins-set-in-configuration": "Du darfst den Status der Plugins nicht ändern, da sie zur Laufzeit definiert werden (config.json, Umgebungsvariablen oder Terminalargumente). Bitte ändere stattdessen die Konfiguration.",
"theme-not-set-in-configuration": "Wenn in der Konfiguration aktive Plugins definiert werden, muss bei einem Themenwechsel das neue Thema zur Liste der aktiven Plugins hinzugefügt werden, bevor es im ACP aktualisiert wird.",
"topic-event-unrecognized": "Themenereignis „%1“ nicht erkannt",
- "category.handle-taken": "Category handle is already taken, please choose another.",
+ "category.handle-taken": "Kategorie-Handle ist bereits vergeben, bitte wähle ein anderes.",
"cant-set-child-as-parent": "Untergeordnete Kategorie kann nicht als übergeordnete Kategorie festgelegt werden",
"cant-set-self-as-parent": "Die aktuelle Kategorie kann nicht als übergeordnete Kategorie festgelegt werden",
"api.master-token-no-uid": "Ein Master-Token wurde ohne eine entsprechende `_uid` im Anfrage-Body empfangen",
@@ -254,11 +256,11 @@
"api.501": "Die Route, die Sie anrufen möchten, ist noch nicht implementiert. Bitte versuchen Sie es morgen erneut",
"api.503": "Die Route, die Sie anrufen möchten, ist derzeit aufgrund einer Serverkonfiguration nicht verfügbar",
"api.reauth-required": "Die angeforderte Ressource erfordert eine (Re-)Authentifizierung.",
- "activitypub.not-enabled": "Federation is not enabled on this server",
- "activitypub.invalid-id": "Unable to resolve the input id, likely as it is malformed.",
- "activitypub.get-failed": "Unable to retrieve the specified resource.",
- "activitypub.pubKey-not-found": "Unable to resolve public key, so payload verification cannot take place.",
- "activitypub.origin-mismatch": "The received object's origin does not match the sender's origin",
- "activitypub.actor-mismatch": "The received activity is being carried out by an actor that is different from expected.",
- "activitypub.not-implemented": "The request was denied because it or an aspect of it is not implemented by the recipient server"
+ "activitypub.not-enabled": "Die Föderation ist auf diesem Server nicht aktiviert",
+ "activitypub.invalid-id": "Die Eingabe-ID kann nicht aufgelöst werden, wahrscheinlich weil sie fehlerhaft ist.",
+ "activitypub.get-failed": "Die angegebene Ressource kann nicht abgerufen werden.",
+ "activitypub.pubKey-not-found": "Der öffentliche Schlüssel kann nicht aufgelöst werden, daher ist eine Überprüfung des Payloads nicht möglich.",
+ "activitypub.origin-mismatch": "Der Ursprung des empfangenen Objekts stimmt nicht mit dem Ursprung des Absenders überein",
+ "activitypub.actor-mismatch": "Die empfangene Aktivität wird von einem anderen Akteur ausgeführt als erwartet",
+ "activitypub.not-implemented": "Die Anfrage wurde abgelehnt, weil sie oder ein Teil davon vom empfangenden Server nicht implementiert ist"
}
\ No newline at end of file
diff --git a/public/language/de/modules.json b/public/language/de/modules.json
index 5edc6169e8..cf5c776242 100644
--- a/public/language/de/modules.json
+++ b/public/language/de/modules.json
@@ -48,6 +48,7 @@
"chat.add-user": "Benutzer hinzufügen",
"chat.notification-settings": "Benachrichtigungseinstellungen",
"chat.default-notification-setting": "Standardeinstellung für die Benachrichtigung",
+ "chat.join-leave-messages": "Join/Leave Messages",
"chat.notification-setting-room-default": "Raum Standard",
"chat.notification-setting-none": "Keine Benachrichtigungen",
"chat.notification-setting-at-mention-only": "@nur Erwähnung",
diff --git a/public/language/de/social.json b/public/language/de/social.json
index 0e8ce251b3..1ae8106ef9 100644
--- a/public/language/de/social.json
+++ b/public/language/de/social.json
@@ -8,5 +8,7 @@
"log-in-with-facebook": "Mit Facebook anmelden",
"continue-with-facebook": "Mit Facebook fortsetzen",
"sign-in-with-linkedin": "Mit LinkedIn anmelden",
- "sign-up-with-linkedin": "Mit LinkedIn registrieren"
+ "sign-up-with-linkedin": "Mit LinkedIn registrieren",
+ "sign-in-with-wordpress": "Sign in with WordPress",
+ "sign-up-with-wordpress": "Sign up with WordPress"
}
\ No newline at end of file
diff --git a/public/language/el/admin/dashboard.json b/public/language/el/admin/dashboard.json
index 6ad973f5f3..0be6d5866c 100644
--- a/public/language/el/admin/dashboard.json
+++ b/public/language/el/admin/dashboard.json
@@ -75,6 +75,7 @@
"graphs.page-views-registered": "Page Views Registered",
"graphs.page-views-guest": "Page Views Guest",
"graphs.page-views-bot": "Page Views Bot",
+ "graphs.page-views-ap": "ActivityPub Page Views",
"graphs.unique-visitors": "Unique Visitors",
"graphs.registered-users": "Registered Users",
"graphs.guest-users": "Guest Users",
diff --git a/public/language/el/admin/manage/categories.json b/public/language/el/admin/manage/categories.json
index f51152f22d..d66dd814a1 100644
--- a/public/language/el/admin/manage/categories.json
+++ b/public/language/el/admin/manage/categories.json
@@ -1,11 +1,15 @@
{
"manage-categories": "Manage Categories",
"add-category": "Add category",
+ "add-local-category": "Add Local category",
+ "add-remote-category": "Add Remote category",
+ "remove": "Remove",
"jump-to": "Jump to...",
"settings": "Category Settings",
"edit-category": "Edit Category",
"privileges": "Privileges",
"back-to-categories": "Back to categories",
+ "id": "Category ID",
"name": "Category Name",
"handle": "Category Handle",
"handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.",
@@ -103,6 +107,8 @@
"alert.create-success": "Category successfully created!",
"alert.none-active": "You have no active categories.",
"alert.create": "Create a Category",
+ "alert.add": "Add a Category",
+ "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.
Note — The remote category may not reflect all topics published unless at least one local user tracks/watches it.",
"alert.confirm-purge": "
Do you really want to purge this category \"%1\"?
Purging a category will remove all topics and posts, and delete the category from the database. If you want to remove a category temporarily, you'll want to \"disable\" the category instead.
", "alert.purge-success": "Category purged!", "alert.copy-success": "Settings Copied!", diff --git a/public/language/el/admin/settings/activitypub.json b/public/language/el/admin/settings/activitypub.json index 94f9ad7822..1b00672e0f 100644 --- a/public/language/el/admin/settings/activitypub.json +++ b/public/language/el/admin/settings/activitypub.json @@ -18,6 +18,27 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.one,two,three)",
+ "rules.add": "Add New Rule",
+ "rules.type": "Type",
+ "rules.value": "Value",
+ "rules.cid": "Category",
+
+ "relays": "Relays",
+ "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.",
+ "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.",
+ "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.",
+ "relays.add": "Add New Relay",
+ "relays.relay": "Relay",
+ "relays.state": "State",
+ "relays.state-0": "Pending",
+ "relays.state-1": "Receiving only",
+ "relays.state-2": "Active",
+
"server-filtering": "Filtering",
"count": "This NodeBB is currently aware of %1 server(s)",
"server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.",
diff --git a/public/language/el/error.json b/public/language/el/error.json
index 9043105827..70bedd30c8 100644
--- a/public/language/el/error.json
+++ b/public/language/el/error.json
@@ -3,6 +3,7 @@
"invalid-json": "Invalid JSON",
"wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead",
"required-parameters-missing": "Required parameters were missing from this API call: %1",
+ "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.",
"not-logged-in": "Φαίνεται πως δεν είσαι συνδεδεμένος/η.",
"account-locked": "Ο λογαριασμός σου έχει κλειδωθεί προσωρινά",
"search-requires-login": "Searching requires an account - please login or register.",
@@ -236,6 +237,7 @@
"socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later",
"invalid-plugin-id": "Invalid plugin ID",
"plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP",
+ "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin",
"plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled",
"plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.",
"theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP",
diff --git a/public/language/el/modules.json b/public/language/el/modules.json
index a1d1259471..2768fec8a4 100644
--- a/public/language/el/modules.json
+++ b/public/language/el/modules.json
@@ -48,6 +48,7 @@
"chat.add-user": "Add User",
"chat.notification-settings": "Notification Settings",
"chat.default-notification-setting": "Default Notification Setting",
+ "chat.join-leave-messages": "Join/Leave Messages",
"chat.notification-setting-room-default": "Room Default",
"chat.notification-setting-none": "No notifications",
"chat.notification-setting-at-mention-only": "@mention only",
diff --git a/public/language/el/social.json b/public/language/el/social.json
index 2ba690a187..5b8dd99a46 100644
--- a/public/language/el/social.json
+++ b/public/language/el/social.json
@@ -8,5 +8,7 @@
"log-in-with-facebook": "Log in with Facebook",
"continue-with-facebook": "Continue with Facebook",
"sign-in-with-linkedin": "Sign in with LinkedIn",
- "sign-up-with-linkedin": "Sign up with LinkedIn"
+ "sign-up-with-linkedin": "Sign up with LinkedIn",
+ "sign-in-with-wordpress": "Sign in with WordPress",
+ "sign-up-with-wordpress": "Sign up with WordPress"
}
\ No newline at end of file
diff --git a/public/language/en-GB/admin/dashboard.json b/public/language/en-GB/admin/dashboard.json
index 6ad973f5f3..0be6d5866c 100644
--- a/public/language/en-GB/admin/dashboard.json
+++ b/public/language/en-GB/admin/dashboard.json
@@ -75,6 +75,7 @@
"graphs.page-views-registered": "Page Views Registered",
"graphs.page-views-guest": "Page Views Guest",
"graphs.page-views-bot": "Page Views Bot",
+ "graphs.page-views-ap": "ActivityPub Page Views",
"graphs.unique-visitors": "Unique Visitors",
"graphs.registered-users": "Registered Users",
"graphs.guest-users": "Guest Users",
diff --git a/public/language/en-GB/admin/manage/categories.json b/public/language/en-GB/admin/manage/categories.json
index f51152f22d..d66dd814a1 100644
--- a/public/language/en-GB/admin/manage/categories.json
+++ b/public/language/en-GB/admin/manage/categories.json
@@ -1,11 +1,15 @@
{
"manage-categories": "Manage Categories",
"add-category": "Add category",
+ "add-local-category": "Add Local category",
+ "add-remote-category": "Add Remote category",
+ "remove": "Remove",
"jump-to": "Jump to...",
"settings": "Category Settings",
"edit-category": "Edit Category",
"privileges": "Privileges",
"back-to-categories": "Back to categories",
+ "id": "Category ID",
"name": "Category Name",
"handle": "Category Handle",
"handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.",
@@ -103,6 +107,8 @@
"alert.create-success": "Category successfully created!",
"alert.none-active": "You have no active categories.",
"alert.create": "Create a Category",
+ "alert.add": "Add a Category",
+ "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.Do you really want to purge this category \"%1\"?
Purging a category will remove all topics and posts, and delete the category from the database. If you want to remove a category temporarily, you'll want to \"disable\" the category instead.
", "alert.purge-success": "Category purged!", "alert.copy-success": "Settings Copied!", diff --git a/public/language/en-GB/admin/settings/activitypub.json b/public/language/en-GB/admin/settings/activitypub.json index 94f9ad7822..1b00672e0f 100644 --- a/public/language/en-GB/admin/settings/activitypub.json +++ b/public/language/en-GB/admin/settings/activitypub.json @@ -18,6 +18,27 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.one,two,three)",
+ "rules.add": "Add New Rule",
+ "rules.type": "Type",
+ "rules.value": "Value",
+ "rules.cid": "Category",
+
+ "relays": "Relays",
+ "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.",
+ "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.",
+ "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.",
+ "relays.add": "Add New Relay",
+ "relays.relay": "Relay",
+ "relays.state": "State",
+ "relays.state-0": "Pending",
+ "relays.state-1": "Receiving only",
+ "relays.state-2": "Active",
+
"server-filtering": "Filtering",
"count": "This NodeBB is currently aware of %1 server(s)",
"server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.",
diff --git a/public/language/en-GB/error.json b/public/language/en-GB/error.json
index ec51a88ead..ecb950dff9 100644
--- a/public/language/en-GB/error.json
+++ b/public/language/en-GB/error.json
@@ -4,6 +4,8 @@
"wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead",
"required-parameters-missing": "Required parameters were missing from this API call: %1",
+ "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.",
+
"not-logged-in": "You don't seem to be logged in.",
"account-locked": "Your account has been locked temporarily",
"search-requires-login": "Searching requires an account - please login or register.",
diff --git a/public/language/en-GB/modules.json b/public/language/en-GB/modules.json
index 29ba02726f..f4b473992a 100644
--- a/public/language/en-GB/modules.json
+++ b/public/language/en-GB/modules.json
@@ -48,6 +48,7 @@
"chat.add-user": "Add User",
"chat.notification-settings": "Notification Settings",
"chat.default-notification-setting": "Default Notification Setting",
+ "chat.join-leave-messages": "Join/Leave Messages",
"chat.notification-setting-room-default": "Room Default",
"chat.notification-setting-none": "No notifications",
"chat.notification-setting-at-mention-only": "@mention only",
diff --git a/public/language/en-GB/social.json b/public/language/en-GB/social.json
index 2ba690a187..5b8dd99a46 100644
--- a/public/language/en-GB/social.json
+++ b/public/language/en-GB/social.json
@@ -8,5 +8,7 @@
"log-in-with-facebook": "Log in with Facebook",
"continue-with-facebook": "Continue with Facebook",
"sign-in-with-linkedin": "Sign in with LinkedIn",
- "sign-up-with-linkedin": "Sign up with LinkedIn"
+ "sign-up-with-linkedin": "Sign up with LinkedIn",
+ "sign-in-with-wordpress": "Sign in with WordPress",
+ "sign-up-with-wordpress": "Sign up with WordPress"
}
\ No newline at end of file
diff --git a/public/language/en-US/admin/dashboard.json b/public/language/en-US/admin/dashboard.json
index 6ad973f5f3..0be6d5866c 100644
--- a/public/language/en-US/admin/dashboard.json
+++ b/public/language/en-US/admin/dashboard.json
@@ -75,6 +75,7 @@
"graphs.page-views-registered": "Page Views Registered",
"graphs.page-views-guest": "Page Views Guest",
"graphs.page-views-bot": "Page Views Bot",
+ "graphs.page-views-ap": "ActivityPub Page Views",
"graphs.unique-visitors": "Unique Visitors",
"graphs.registered-users": "Registered Users",
"graphs.guest-users": "Guest Users",
diff --git a/public/language/en-US/admin/manage/categories.json b/public/language/en-US/admin/manage/categories.json
index f51152f22d..d66dd814a1 100644
--- a/public/language/en-US/admin/manage/categories.json
+++ b/public/language/en-US/admin/manage/categories.json
@@ -1,11 +1,15 @@
{
"manage-categories": "Manage Categories",
"add-category": "Add category",
+ "add-local-category": "Add Local category",
+ "add-remote-category": "Add Remote category",
+ "remove": "Remove",
"jump-to": "Jump to...",
"settings": "Category Settings",
"edit-category": "Edit Category",
"privileges": "Privileges",
"back-to-categories": "Back to categories",
+ "id": "Category ID",
"name": "Category Name",
"handle": "Category Handle",
"handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.",
@@ -103,6 +107,8 @@
"alert.create-success": "Category successfully created!",
"alert.none-active": "You have no active categories.",
"alert.create": "Create a Category",
+ "alert.add": "Add a Category",
+ "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.Do you really want to purge this category \"%1\"?
Purging a category will remove all topics and posts, and delete the category from the database. If you want to remove a category temporarily, you'll want to \"disable\" the category instead.
", "alert.purge-success": "Category purged!", "alert.copy-success": "Settings Copied!", diff --git a/public/language/en-US/admin/settings/activitypub.json b/public/language/en-US/admin/settings/activitypub.json index 94f9ad7822..1b00672e0f 100644 --- a/public/language/en-US/admin/settings/activitypub.json +++ b/public/language/en-US/admin/settings/activitypub.json @@ -18,6 +18,27 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.one,two,three)",
+ "rules.add": "Add New Rule",
+ "rules.type": "Type",
+ "rules.value": "Value",
+ "rules.cid": "Category",
+
+ "relays": "Relays",
+ "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.",
+ "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.",
+ "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.",
+ "relays.add": "Add New Relay",
+ "relays.relay": "Relay",
+ "relays.state": "State",
+ "relays.state-0": "Pending",
+ "relays.state-1": "Receiving only",
+ "relays.state-2": "Active",
+
"server-filtering": "Filtering",
"count": "This NodeBB is currently aware of %1 server(s)",
"server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.",
diff --git a/public/language/en-US/error.json b/public/language/en-US/error.json
index 535a568d1e..c3bb2dc892 100644
--- a/public/language/en-US/error.json
+++ b/public/language/en-US/error.json
@@ -3,6 +3,7 @@
"invalid-json": "Invalid JSON",
"wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead",
"required-parameters-missing": "Required parameters were missing from this API call: %1",
+ "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.",
"not-logged-in": "You don't seem to be logged in.",
"account-locked": "Your account has been locked temporarily",
"search-requires-login": "Searching requires an account - please login or register.",
@@ -236,6 +237,7 @@
"socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later",
"invalid-plugin-id": "Invalid plugin ID",
"plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP",
+ "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin",
"plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled",
"plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.",
"theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP",
diff --git a/public/language/en-US/modules.json b/public/language/en-US/modules.json
index a1d1259471..2768fec8a4 100644
--- a/public/language/en-US/modules.json
+++ b/public/language/en-US/modules.json
@@ -48,6 +48,7 @@
"chat.add-user": "Add User",
"chat.notification-settings": "Notification Settings",
"chat.default-notification-setting": "Default Notification Setting",
+ "chat.join-leave-messages": "Join/Leave Messages",
"chat.notification-setting-room-default": "Room Default",
"chat.notification-setting-none": "No notifications",
"chat.notification-setting-at-mention-only": "@mention only",
diff --git a/public/language/en-US/social.json b/public/language/en-US/social.json
index 2ba690a187..5b8dd99a46 100644
--- a/public/language/en-US/social.json
+++ b/public/language/en-US/social.json
@@ -8,5 +8,7 @@
"log-in-with-facebook": "Log in with Facebook",
"continue-with-facebook": "Continue with Facebook",
"sign-in-with-linkedin": "Sign in with LinkedIn",
- "sign-up-with-linkedin": "Sign up with LinkedIn"
+ "sign-up-with-linkedin": "Sign up with LinkedIn",
+ "sign-in-with-wordpress": "Sign in with WordPress",
+ "sign-up-with-wordpress": "Sign up with WordPress"
}
\ No newline at end of file
diff --git a/public/language/en-x-pirate/admin/dashboard.json b/public/language/en-x-pirate/admin/dashboard.json
index 6ad973f5f3..0be6d5866c 100644
--- a/public/language/en-x-pirate/admin/dashboard.json
+++ b/public/language/en-x-pirate/admin/dashboard.json
@@ -75,6 +75,7 @@
"graphs.page-views-registered": "Page Views Registered",
"graphs.page-views-guest": "Page Views Guest",
"graphs.page-views-bot": "Page Views Bot",
+ "graphs.page-views-ap": "ActivityPub Page Views",
"graphs.unique-visitors": "Unique Visitors",
"graphs.registered-users": "Registered Users",
"graphs.guest-users": "Guest Users",
diff --git a/public/language/en-x-pirate/admin/manage/categories.json b/public/language/en-x-pirate/admin/manage/categories.json
index f51152f22d..d66dd814a1 100644
--- a/public/language/en-x-pirate/admin/manage/categories.json
+++ b/public/language/en-x-pirate/admin/manage/categories.json
@@ -1,11 +1,15 @@
{
"manage-categories": "Manage Categories",
"add-category": "Add category",
+ "add-local-category": "Add Local category",
+ "add-remote-category": "Add Remote category",
+ "remove": "Remove",
"jump-to": "Jump to...",
"settings": "Category Settings",
"edit-category": "Edit Category",
"privileges": "Privileges",
"back-to-categories": "Back to categories",
+ "id": "Category ID",
"name": "Category Name",
"handle": "Category Handle",
"handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.",
@@ -103,6 +107,8 @@
"alert.create-success": "Category successfully created!",
"alert.none-active": "You have no active categories.",
"alert.create": "Create a Category",
+ "alert.add": "Add a Category",
+ "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.Do you really want to purge this category \"%1\"?
Purging a category will remove all topics and posts, and delete the category from the database. If you want to remove a category temporarily, you'll want to \"disable\" the category instead.
", "alert.purge-success": "Category purged!", "alert.copy-success": "Settings Copied!", diff --git a/public/language/en-x-pirate/admin/settings/activitypub.json b/public/language/en-x-pirate/admin/settings/activitypub.json index 94f9ad7822..1b00672e0f 100644 --- a/public/language/en-x-pirate/admin/settings/activitypub.json +++ b/public/language/en-x-pirate/admin/settings/activitypub.json @@ -18,6 +18,27 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.one,two,three)",
+ "rules.add": "Add New Rule",
+ "rules.type": "Type",
+ "rules.value": "Value",
+ "rules.cid": "Category",
+
+ "relays": "Relays",
+ "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.",
+ "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.",
+ "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.",
+ "relays.add": "Add New Relay",
+ "relays.relay": "Relay",
+ "relays.state": "State",
+ "relays.state-0": "Pending",
+ "relays.state-1": "Receiving only",
+ "relays.state-2": "Active",
+
"server-filtering": "Filtering",
"count": "This NodeBB is currently aware of %1 server(s)",
"server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.",
diff --git a/public/language/en-x-pirate/error.json b/public/language/en-x-pirate/error.json
index 535a568d1e..c3bb2dc892 100644
--- a/public/language/en-x-pirate/error.json
+++ b/public/language/en-x-pirate/error.json
@@ -3,6 +3,7 @@
"invalid-json": "Invalid JSON",
"wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead",
"required-parameters-missing": "Required parameters were missing from this API call: %1",
+ "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.",
"not-logged-in": "You don't seem to be logged in.",
"account-locked": "Your account has been locked temporarily",
"search-requires-login": "Searching requires an account - please login or register.",
@@ -236,6 +237,7 @@
"socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later",
"invalid-plugin-id": "Invalid plugin ID",
"plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP",
+ "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin",
"plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled",
"plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.",
"theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP",
diff --git a/public/language/en-x-pirate/modules.json b/public/language/en-x-pirate/modules.json
index c78a052be8..fb7c802ccb 100644
--- a/public/language/en-x-pirate/modules.json
+++ b/public/language/en-x-pirate/modules.json
@@ -48,6 +48,7 @@
"chat.add-user": "Add User",
"chat.notification-settings": "Notification Settings",
"chat.default-notification-setting": "Default Notification Setting",
+ "chat.join-leave-messages": "Join/Leave Messages",
"chat.notification-setting-room-default": "Room Default",
"chat.notification-setting-none": "No notifications",
"chat.notification-setting-at-mention-only": "@mention only",
diff --git a/public/language/en-x-pirate/social.json b/public/language/en-x-pirate/social.json
index 2ba690a187..5b8dd99a46 100644
--- a/public/language/en-x-pirate/social.json
+++ b/public/language/en-x-pirate/social.json
@@ -8,5 +8,7 @@
"log-in-with-facebook": "Log in with Facebook",
"continue-with-facebook": "Continue with Facebook",
"sign-in-with-linkedin": "Sign in with LinkedIn",
- "sign-up-with-linkedin": "Sign up with LinkedIn"
+ "sign-up-with-linkedin": "Sign up with LinkedIn",
+ "sign-in-with-wordpress": "Sign in with WordPress",
+ "sign-up-with-wordpress": "Sign up with WordPress"
}
\ No newline at end of file
diff --git a/public/language/es/admin/dashboard.json b/public/language/es/admin/dashboard.json
index 791546b5ee..211df77bc0 100644
--- a/public/language/es/admin/dashboard.json
+++ b/public/language/es/admin/dashboard.json
@@ -75,6 +75,7 @@
"graphs.page-views-registered": "Vistas de la página registradas",
"graphs.page-views-guest": "Vistas de la página visitantes",
"graphs.page-views-bot": "Vistas de la página Bot",
+ "graphs.page-views-ap": "ActivityPub Page Views",
"graphs.unique-visitors": "Visitantes Unicos",
"graphs.registered-users": "Usuarios Registrados",
"graphs.guest-users": "Guest Users",
diff --git a/public/language/es/admin/manage/categories.json b/public/language/es/admin/manage/categories.json
index 23d64eb58a..74e14ce149 100644
--- a/public/language/es/admin/manage/categories.json
+++ b/public/language/es/admin/manage/categories.json
@@ -1,11 +1,15 @@
{
"manage-categories": "Manage Categories",
"add-category": "Add category",
+ "add-local-category": "Add Local category",
+ "add-remote-category": "Add Remote category",
+ "remove": "Remove",
"jump-to": "Jump to...",
"settings": "Configuración de Categoría",
"edit-category": "Edit Category",
"privileges": "Privilegios",
"back-to-categories": "Back to categories",
+ "id": "Category ID",
"name": "Nombre de Categoría",
"handle": "Identificador de categoría ",
"handle.help": "Tu identificador de categoría está siendo utilizado como representación de esta categoría a través de otras redes, similar al nombre de usuario. El identificador de la categoría no puede ser igual a un nombre de usuario o usuario de grupo existente.",
@@ -103,6 +107,8 @@
"alert.create-success": "¡Categoría creada con éxito!",
"alert.none-active": "No tienes categorías activas.",
"alert.create": "Crear una Categoría",
+ "alert.add": "Add a Category",
+ "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.¿Realmente quieres purgar esta categoría\"%1\"?
Purgar una categoría eliminará todos los temas y respuestas, y borrará la categoría de la base de datos. Si quieres eliminar una categoría temporalmente, deberías \"desactivar\" esa categoría en su lugar.
", "alert.purge-success": "¡Categoría purgada!", "alert.copy-success": "¡Configuración Copiada!", diff --git a/public/language/es/admin/manage/user-custom-fields.json b/public/language/es/admin/manage/user-custom-fields.json index dab10670d2..49096d7021 100644 --- a/public/language/es/admin/manage/user-custom-fields.json +++ b/public/language/es/admin/manage/user-custom-fields.json @@ -1,28 +1,28 @@ { - "title": "Manage Custom User Fields", - "create-field": "Create Field", - "edit-field": "Edit Field", - "manage-custom-fields": "Manage Custom Fields", - "type-of-input": "Type of input", - "key": "Key", - "name": "Name", - "icon": "Icon", - "type": "Type", - "min-rep": "Minimum Reputation", - "input-type-text": "Input (Text)", - "input-type-link": "Input (Link)", - "input-type-number": "Input (Number)", - "input-type-date": "Input (Date)", - "input-type-select": "Select", - "input-type-select-multi": "Select Multiple", - "select-options": "Options", + "title": "Gestionar campos personalizados del usuario", + "create-field": "Crear campo", + "edit-field": "Editar campo", + "manage-custom-fields": "Gestionar campos personalizados", + "type-of-input": "Tipo de campo", + "key": "Clave", + "name": "Nombre", + "icon": "Icono", + "type": "Tipo", + "min-rep": "Reputación mínima", + "input-type-text": "Campo (Texto)", + "input-type-link": "Campo (Link)", + "input-type-number": "Campo (Número)", + "input-type-date": "Campo (Fecha)", + "input-type-select": "Selector", + "input-type-select-multi": "Selector múltiple", + "select-options": "Opciones", "select-options-help": "Add one option per line for the select element", - "minimum-reputation": "Minimum reputation", + "minimum-reputation": "Reputación mínima", "minimum-reputation-help": "If a user has less than this value they won't be able to use this field", "delete-field-confirm-x": "Do you really want to delete custom field \"%1\"?", - "custom-fields-saved": "Custom fields saved", - "visibility": "Visibility", - "visibility-all": "Everyone can see the field", - "visibility-loggedin": "Only logged in users can see the field", + "custom-fields-saved": "Campos personalizados guardados", + "visibility": "Visibilidad", + "visibility-all": "Todo el mundo puede ver este campo", + "visibility-loggedin": "Solo usuarios logeados pueden ver este campo", "visibility-privileged": "Only privileged users like admins & moderators can see the field" } \ No newline at end of file diff --git a/public/language/es/admin/settings/activitypub.json b/public/language/es/admin/settings/activitypub.json index 6e6585b295..da972be67d 100644 --- a/public/language/es/admin/settings/activitypub.json +++ b/public/language/es/admin/settings/activitypub.json @@ -18,6 +18,27 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.one,two,three)",
+ "rules.add": "Add New Rule",
+ "rules.type": "Type",
+ "rules.value": "Value",
+ "rules.cid": "Category",
+
+ "relays": "Relays",
+ "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.",
+ "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.",
+ "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.",
+ "relays.add": "Add New Relay",
+ "relays.relay": "Relay",
+ "relays.state": "State",
+ "relays.state-0": "Pending",
+ "relays.state-1": "Receiving only",
+ "relays.state-2": "Active",
+
"server-filtering": "Filtering",
"count": "This NodeBB is currently aware of %1 server(s)",
"server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.",
diff --git a/public/language/es/error.json b/public/language/es/error.json
index b1e53c4474..27a8776929 100644
--- a/public/language/es/error.json
+++ b/public/language/es/error.json
@@ -3,6 +3,7 @@
"invalid-json": "JSON no válido",
"wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead",
"required-parameters-missing": "Required parameters were missing from this API call: %1",
+ "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.",
"not-logged-in": "No has iniciado sesión.",
"account-locked": "Tu cuenta ha sido bloqueada temporalmente.",
"search-requires-login": "¡Buscar requiere estar registrado! Por favor, entra o regístrate.",
@@ -236,6 +237,7 @@
"socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later",
"invalid-plugin-id": "ID de plugin inválido",
"plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP",
+ "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin",
"plugin-installation-via-acp-disabled": "Instalación de extensiones vía ACP está deshabilitada",
"plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.",
"theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP",
diff --git a/public/language/es/modules.json b/public/language/es/modules.json
index 1fd0eda552..be35e9c442 100644
--- a/public/language/es/modules.json
+++ b/public/language/es/modules.json
@@ -48,6 +48,7 @@
"chat.add-user": "Add User",
"chat.notification-settings": "Notification Settings",
"chat.default-notification-setting": "Default Notification Setting",
+ "chat.join-leave-messages": "Join/Leave Messages",
"chat.notification-setting-room-default": "Room Default",
"chat.notification-setting-none": "No notifications",
"chat.notification-setting-at-mention-only": "@mention only",
diff --git a/public/language/es/social.json b/public/language/es/social.json
index 84474c4ecb..bebb366304 100644
--- a/public/language/es/social.json
+++ b/public/language/es/social.json
@@ -8,5 +8,7 @@
"log-in-with-facebook": "Accede con Facebook",
"continue-with-facebook": "Regístrate con Facebook",
"sign-in-with-linkedin": "Iniciar sesión con LinkedIn",
- "sign-up-with-linkedin": "Registrarse con LinkedIn"
+ "sign-up-with-linkedin": "Registrarse con LinkedIn",
+ "sign-in-with-wordpress": "Sign in with WordPress",
+ "sign-up-with-wordpress": "Sign up with WordPress"
}
\ No newline at end of file
diff --git a/public/language/es/user.json b/public/language/es/user.json
index 66dfc6c8dd..76e9c3c181 100644
--- a/public/language/es/user.json
+++ b/public/language/es/user.json
@@ -105,10 +105,10 @@
"show-email": "Mostrar mi correo electrónico",
"show-fullname": "Mostrar mi nombre completo",
"restrict-chats": "Solo permitir mensajes de chat de usuarios a los que sigo",
- "disable-incoming-chats": "Disable incoming chat messages ",
+ "disable-incoming-chats": "Desactivar mensajes de chat entrantes",
"chat-allow-list": "Allow chat messages from the following users",
"chat-deny-list": "Deny chat messages from the following users",
- "chat-list-add-user": "Add user",
+ "chat-list-add-user": "Agregar usuario",
"digest-label": "Suscribirse al resumen",
"digest-description": "Suscribirse a actualizaciones por correo electrónico a este foro (nuevas notificaciones y temas) de acuerdo a una recurrencia definida",
"digest-off": "Apagado",
diff --git a/public/language/et/admin/dashboard.json b/public/language/et/admin/dashboard.json
index 6ad973f5f3..0be6d5866c 100644
--- a/public/language/et/admin/dashboard.json
+++ b/public/language/et/admin/dashboard.json
@@ -75,6 +75,7 @@
"graphs.page-views-registered": "Page Views Registered",
"graphs.page-views-guest": "Page Views Guest",
"graphs.page-views-bot": "Page Views Bot",
+ "graphs.page-views-ap": "ActivityPub Page Views",
"graphs.unique-visitors": "Unique Visitors",
"graphs.registered-users": "Registered Users",
"graphs.guest-users": "Guest Users",
diff --git a/public/language/et/admin/manage/categories.json b/public/language/et/admin/manage/categories.json
index f51152f22d..d66dd814a1 100644
--- a/public/language/et/admin/manage/categories.json
+++ b/public/language/et/admin/manage/categories.json
@@ -1,11 +1,15 @@
{
"manage-categories": "Manage Categories",
"add-category": "Add category",
+ "add-local-category": "Add Local category",
+ "add-remote-category": "Add Remote category",
+ "remove": "Remove",
"jump-to": "Jump to...",
"settings": "Category Settings",
"edit-category": "Edit Category",
"privileges": "Privileges",
"back-to-categories": "Back to categories",
+ "id": "Category ID",
"name": "Category Name",
"handle": "Category Handle",
"handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.",
@@ -103,6 +107,8 @@
"alert.create-success": "Category successfully created!",
"alert.none-active": "You have no active categories.",
"alert.create": "Create a Category",
+ "alert.add": "Add a Category",
+ "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.Do you really want to purge this category \"%1\"?
Purging a category will remove all topics and posts, and delete the category from the database. If you want to remove a category temporarily, you'll want to \"disable\" the category instead.
", "alert.purge-success": "Category purged!", "alert.copy-success": "Settings Copied!", diff --git a/public/language/et/admin/settings/activitypub.json b/public/language/et/admin/settings/activitypub.json index 94f9ad7822..1b00672e0f 100644 --- a/public/language/et/admin/settings/activitypub.json +++ b/public/language/et/admin/settings/activitypub.json @@ -18,6 +18,27 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.one,two,three)",
+ "rules.add": "Add New Rule",
+ "rules.type": "Type",
+ "rules.value": "Value",
+ "rules.cid": "Category",
+
+ "relays": "Relays",
+ "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.",
+ "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.",
+ "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.",
+ "relays.add": "Add New Relay",
+ "relays.relay": "Relay",
+ "relays.state": "State",
+ "relays.state-0": "Pending",
+ "relays.state-1": "Receiving only",
+ "relays.state-2": "Active",
+
"server-filtering": "Filtering",
"count": "This NodeBB is currently aware of %1 server(s)",
"server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.",
diff --git a/public/language/et/error.json b/public/language/et/error.json
index caffd57a57..7fd4027cec 100644
--- a/public/language/et/error.json
+++ b/public/language/et/error.json
@@ -3,6 +3,7 @@
"invalid-json": "Invalid JSON",
"wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead",
"required-parameters-missing": "Required parameters were missing from this API call: %1",
+ "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.",
"not-logged-in": "Sa ei ole sisse logitud",
"account-locked": "Su kasutaja on ajutiselt lukustatud",
"search-requires-login": "Otsing nõuab kasutajat - palun registreeruge või logige sisse.",
@@ -236,6 +237,7 @@
"socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later",
"invalid-plugin-id": "Invalid plugin ID",
"plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP",
+ "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin",
"plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled",
"plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.",
"theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP",
diff --git a/public/language/et/modules.json b/public/language/et/modules.json
index 1aa6c0d427..8a8c22cc93 100644
--- a/public/language/et/modules.json
+++ b/public/language/et/modules.json
@@ -48,6 +48,7 @@
"chat.add-user": "Add User",
"chat.notification-settings": "Notification Settings",
"chat.default-notification-setting": "Default Notification Setting",
+ "chat.join-leave-messages": "Join/Leave Messages",
"chat.notification-setting-room-default": "Room Default",
"chat.notification-setting-none": "No notifications",
"chat.notification-setting-at-mention-only": "@mention only",
diff --git a/public/language/et/social.json b/public/language/et/social.json
index 2ba690a187..5b8dd99a46 100644
--- a/public/language/et/social.json
+++ b/public/language/et/social.json
@@ -8,5 +8,7 @@
"log-in-with-facebook": "Log in with Facebook",
"continue-with-facebook": "Continue with Facebook",
"sign-in-with-linkedin": "Sign in with LinkedIn",
- "sign-up-with-linkedin": "Sign up with LinkedIn"
+ "sign-up-with-linkedin": "Sign up with LinkedIn",
+ "sign-in-with-wordpress": "Sign in with WordPress",
+ "sign-up-with-wordpress": "Sign up with WordPress"
}
\ No newline at end of file
diff --git a/public/language/fa-IR/admin/dashboard.json b/public/language/fa-IR/admin/dashboard.json
index 77ff6d56e8..b5f458d0c0 100644
--- a/public/language/fa-IR/admin/dashboard.json
+++ b/public/language/fa-IR/admin/dashboard.json
@@ -75,6 +75,7 @@
"graphs.page-views-registered": "Page Views Registered",
"graphs.page-views-guest": "Page Views Guest",
"graphs.page-views-bot": "ربات بازدید از صفحه",
+ "graphs.page-views-ap": "ActivityPub Page Views",
"graphs.unique-visitors": "بازدیدکنندگان منحصر به فرد",
"graphs.registered-users": "کاربران ثبت نام شده",
"graphs.guest-users": "کاربران مهمان",
diff --git a/public/language/fa-IR/admin/manage/categories.json b/public/language/fa-IR/admin/manage/categories.json
index 53f79fbd20..f80ae0e28e 100644
--- a/public/language/fa-IR/admin/manage/categories.json
+++ b/public/language/fa-IR/admin/manage/categories.json
@@ -1,11 +1,15 @@
{
"manage-categories": "Manage Categories",
"add-category": "Add category",
+ "add-local-category": "Add Local category",
+ "add-remote-category": "Add Remote category",
+ "remove": "Remove",
"jump-to": "Jump to...",
"settings": "تنظیمات دستهبندی",
"edit-category": "Edit Category",
"privileges": "Privileges",
"back-to-categories": "Back to categories",
+ "id": "Category ID",
"name": "نام دستهبندی",
"handle": "Category Handle",
"handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.",
@@ -103,6 +107,8 @@
"alert.create-success": "Category successfully created!",
"alert.none-active": "You have no active categories.",
"alert.create": "Create a Category",
+ "alert.add": "Add a Category",
+ "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.Do you really want to purge this category \"%1\"?
Purging a category will remove all topics and posts, and delete the category from the database. If you want to remove a category temporarily, you'll want to \"disable\" the category instead.
", "alert.purge-success": "Category purged!", "alert.copy-success": "Settings Copied!", diff --git a/public/language/fa-IR/admin/settings/activitypub.json b/public/language/fa-IR/admin/settings/activitypub.json index 94f9ad7822..1b00672e0f 100644 --- a/public/language/fa-IR/admin/settings/activitypub.json +++ b/public/language/fa-IR/admin/settings/activitypub.json @@ -18,6 +18,27 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.one,two,three)",
+ "rules.add": "Add New Rule",
+ "rules.type": "Type",
+ "rules.value": "Value",
+ "rules.cid": "Category",
+
+ "relays": "Relays",
+ "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.",
+ "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.",
+ "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.",
+ "relays.add": "Add New Relay",
+ "relays.relay": "Relay",
+ "relays.state": "State",
+ "relays.state-0": "Pending",
+ "relays.state-1": "Receiving only",
+ "relays.state-2": "Active",
+
"server-filtering": "Filtering",
"count": "This NodeBB is currently aware of %1 server(s)",
"server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.",
diff --git a/public/language/fa-IR/error.json b/public/language/fa-IR/error.json
index 827290a155..7660c66770 100644
--- a/public/language/fa-IR/error.json
+++ b/public/language/fa-IR/error.json
@@ -3,6 +3,7 @@
"invalid-json": "JSON نامعتبر",
"wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead",
"required-parameters-missing": "Required parameters were missing from this API call: %1",
+ "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.",
"not-logged-in": "وارد حساب کاربری نشدهاید.",
"account-locked": "حساب کاربری شما موقتاً مسدود شده است.",
"search-requires-login": "استفاده از جستجو نیازمند ورود با نامکاربری و رمزعبور است. لطفا ابتدا وارد شوید.",
@@ -236,6 +237,7 @@
"socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later",
"invalid-plugin-id": "Invalid plugin ID",
"plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP",
+ "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin",
"plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled",
"plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.",
"theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP",
diff --git a/public/language/fa-IR/modules.json b/public/language/fa-IR/modules.json
index ddd073ddbf..0115f29ae5 100644
--- a/public/language/fa-IR/modules.json
+++ b/public/language/fa-IR/modules.json
@@ -48,6 +48,7 @@
"chat.add-user": "Add User",
"chat.notification-settings": "Notification Settings",
"chat.default-notification-setting": "Default Notification Setting",
+ "chat.join-leave-messages": "Join/Leave Messages",
"chat.notification-setting-room-default": "Room Default",
"chat.notification-setting-none": "No notifications",
"chat.notification-setting-at-mention-only": "@mention only",
diff --git a/public/language/fa-IR/social.json b/public/language/fa-IR/social.json
index 2ba690a187..5b8dd99a46 100644
--- a/public/language/fa-IR/social.json
+++ b/public/language/fa-IR/social.json
@@ -8,5 +8,7 @@
"log-in-with-facebook": "Log in with Facebook",
"continue-with-facebook": "Continue with Facebook",
"sign-in-with-linkedin": "Sign in with LinkedIn",
- "sign-up-with-linkedin": "Sign up with LinkedIn"
+ "sign-up-with-linkedin": "Sign up with LinkedIn",
+ "sign-in-with-wordpress": "Sign in with WordPress",
+ "sign-up-with-wordpress": "Sign up with WordPress"
}
\ No newline at end of file
diff --git a/public/language/fi/admin/dashboard.json b/public/language/fi/admin/dashboard.json
index 83c4b42c58..57c63fdf9d 100644
--- a/public/language/fi/admin/dashboard.json
+++ b/public/language/fi/admin/dashboard.json
@@ -75,6 +75,7 @@
"graphs.page-views-registered": "Kirjautuneiden sivulatausta",
"graphs.page-views-guest": "Vieraiden sivulatausta",
"graphs.page-views-bot": "Bottien sivulatausta",
+ "graphs.page-views-ap": "ActivityPub Page Views",
"graphs.unique-visitors": "Ainutalaatuista kävijää",
"graphs.registered-users": "Rekisteröitynyttä käyttäjää",
"graphs.guest-users": "Vieraskäyttäjää",
diff --git a/public/language/fi/admin/manage/categories.json b/public/language/fi/admin/manage/categories.json
index d458c9c5e9..0b50fb20ad 100644
--- a/public/language/fi/admin/manage/categories.json
+++ b/public/language/fi/admin/manage/categories.json
@@ -1,11 +1,15 @@
{
"manage-categories": "Hallitse kategorioita",
"add-category": "Lisää kategoria",
+ "add-local-category": "Add Local category",
+ "add-remote-category": "Add Remote category",
+ "remove": "Remove",
"jump-to": "Siirry...",
"settings": "Kategoria-asetukset",
"edit-category": "Muokkaa kategoriaa",
"privileges": "Privileges",
"back-to-categories": "Palaa kategorioihin",
+ "id": "Category ID",
"name": "Kategorian nimi",
"handle": "Category Handle",
"handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.",
@@ -103,6 +107,8 @@
"alert.create-success": "Kategoria luotiin!",
"alert.none-active": "Sinulla ei ole aktiivisia kategorioita.",
"alert.create": "Luo kategoria.",
+ "alert.add": "Add a Category",
+ "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.Do you really want to purge this category \"%1\"?
Purging a category will remove all topics and posts, and delete the category from the database. If you want to remove a category temporarily, you'll want to \"disable\" the category instead.
", "alert.purge-success": "Kategoria poistettiin!", "alert.copy-success": "Asetukset kopioitiin!", diff --git a/public/language/fi/admin/settings/activitypub.json b/public/language/fi/admin/settings/activitypub.json index 94f9ad7822..8fc3168448 100644 --- a/public/language/fi/admin/settings/activitypub.json +++ b/public/language/fi/admin/settings/activitypub.json @@ -1,23 +1,44 @@ { - "intro-lead": "What is Federation?", - "intro-body": "NodeBB is able to communicate with other NodeBB instances that support it. This is achieved through a protocol called ActivityPub. If enabled, NodeBB will also be able to communicate with other apps and websites that use ActivityPub (e.g. Mastodon, Peertube, etc.)", - "general": "General", - "pruning": "Content Pruning", + "intro-lead": "Mikä on federaatio?", + "intro-body": "NodeBB pystyy yhdistämään muihin NodeBB instansseihin, jotka tukevat tätä ominaisuutta. Tämä on toteutettuActivityPub protokollaa käyttäen ja, jos se on aktivoitu, NodeBB pystyy yhdistämään myös toisten sovellusten ja sivustojen kanssa jotka tukevat ActivityPub Protokollaa( esim. Mastodon, Peertube jne.) ", + "general": "Yleinen", + "pruning": "Sisällön karsiminen", "content-pruning": "Days to keep remote content", "content-pruning-help": "Note that remote content that has received engagement (a reply or a upvote/downvote) will be preserved. (0 for disabled)", "user-pruning": "Days to cache remote user accounts", "user-pruning-help": "Remote user accounts will only be pruned if they have no posts. Otherwise they will be re-retrieved. (0 for disabled)", - "enabled": "Enable Federation", + "enabled": "Ota federointi käyttöön", "enabled-help": "If enabled, will allow this NodeBB will be able to communicate with all Activitypub-enabled clients on the wider fediverse.", "allowLoopback": "Allow loopback processing", "allowLoopback-help": "Useful for debugging purposes only. You should probably leave this disabled.", - "probe": "Open in App", + "probe": "Avaa sovelluksessa", "probe-enabled": "Try to open ActivityPub-enabled resources in NodeBB", "probe-enabled-help": "If enabled, NodeBB will check every external link for an ActivityPub equivalent, and load it in NodeBB instead.", "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.one,two,three)",
+ "rules.add": "Add New Rule",
+ "rules.type": "Type",
+ "rules.value": "Value",
+ "rules.cid": "Category",
+
+ "relays": "Relays",
+ "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.",
+ "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.",
+ "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.",
+ "relays.add": "Add New Relay",
+ "relays.relay": "Relay",
+ "relays.state": "State",
+ "relays.state-0": "Pending",
+ "relays.state-1": "Receiving only",
+ "relays.state-2": "Active",
+
"server-filtering": "Filtering",
"count": "This NodeBB is currently aware of %1 server(s)",
"server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.",
diff --git a/public/language/fi/category.json b/public/language/fi/category.json
index 1df5d1b218..2d2f4f99de 100644
--- a/public/language/fi/category.json
+++ b/public/language/fi/category.json
@@ -1,8 +1,8 @@
{
"category": "Kategoria",
"subcategories": "Alikategoria",
- "uncategorized": "Uncategorized",
- "uncategorized.description": "Topics that do not strictly fit in with any existing categories",
+ "uncategorized": "Kategoroimattomat",
+ "uncategorized.description": "Aiheet jotka eivät suoraan sovi minkään kategorian alle.",
"handle.description": "This category can be followed from the open social web via the handle %1",
"new-topic-button": "Uusi aihe",
"guest-login-post": "Kirjaudu sisään julkastaksesi",
diff --git a/public/language/fi/error.json b/public/language/fi/error.json
index 905a03415a..d7d1af89ea 100644
--- a/public/language/fi/error.json
+++ b/public/language/fi/error.json
@@ -3,6 +3,7 @@
"invalid-json": "Invalid JSON",
"wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead",
"required-parameters-missing": "Required parameters were missing from this API call: %1",
+ "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.",
"not-logged-in": "Et taida olla kirjautuneena sisään.",
"account-locked": "Käyttäjätilisi on lukittu väliaikaisesti",
"search-requires-login": "Haku vaatii tunnukset. Kirjaudu sisään tai luo tunnus.",
@@ -236,6 +237,7 @@
"socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later",
"invalid-plugin-id": "Invalid plugin ID",
"plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP",
+ "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin",
"plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled",
"plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.",
"theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP",
diff --git a/public/language/fi/modules.json b/public/language/fi/modules.json
index d68c1bbaef..f118e6568a 100644
--- a/public/language/fi/modules.json
+++ b/public/language/fi/modules.json
@@ -3,7 +3,7 @@
"chat.chatting-with": "Pikaviesti käyttäjälle",
"chat.placeholder": "Kirjoita pikaviesti ja raahaa kuvia tähän",
"chat.placeholder.mobile": "Kirjoita pikaviesti",
- "chat.placeholder.message-room": "Message #%1",
+ "chat.placeholder.message-room": "Viesti #%1",
"chat.scroll-up-alert": "Siirry uusimpaan viestiin",
"chat.usernames-and-x-others": "%1 & %2 others",
"chat.chat-with-usernames": "Keskustelu käyttäjän %1 kanssa",
@@ -42,12 +42,13 @@
"chat.private-rooms": "Private Rooms (%1)",
"chat.create-room": "Luo keskusteluhuone",
"chat.private.option": "Private (Only visible to users added to room)",
- "chat.public.option": "Public (Visible to every user in selected groups)",
+ "chat.public.option": "Julkinen (Kaikki valitusta ryhmästä voivat nähdä tämän)",
"chat.public.groups-help": "To create a chat room that is visible to all users select registered-users from the group list.",
"chat.manage-room": "Hallitse keskusteluhuonetta",
"chat.add-user": "Add User",
"chat.notification-settings": "Ilmoitusasetukset",
"chat.default-notification-setting": "Ilmoitusten oletusasetukset",
+ "chat.join-leave-messages": "Join/Leave Messages",
"chat.notification-setting-room-default": "Huoneen oletus",
"chat.notification-setting-none": "Ilmoituksia ei ole",
"chat.notification-setting-at-mention-only": "vain @maininta",
@@ -69,8 +70,8 @@
"chat.in-room": "In this room",
"chat.kick": "Kick",
"chat.show-ip": "Näytä IP-osoite",
- "chat.copy-text": "Copy Text",
- "chat.copy-link": "Copy Link",
+ "chat.copy-text": "Kopioi teksti",
+ "chat.copy-link": "Kopioi linkki",
"chat.owner": "Room Owner",
"chat.grant-rescind-ownership": "Grant/Rescind Ownership",
"chat.system.user-join": "%1 has joined the room ",
diff --git a/public/language/fi/recent.json b/public/language/fi/recent.json
index 93fbd99645..9e8f26e97b 100644
--- a/public/language/fi/recent.json
+++ b/public/language/fi/recent.json
@@ -8,6 +8,6 @@
"no-recent-topics": "Tuoreita aiheita ei ole.",
"no-popular-topics": "Ei päivityksiä suosituimmissa aiheissa",
"load-new-posts": "Load new posts",
- "uncategorized.title": "All known topics",
+ "uncategorized.title": "Kaikki aiheet",
"uncategorized.intro": "This page shows a chronological listing of every topic that this forum has received.Voulez-vous vraiment purger cette catégorie \"%1\" ?
Purger une catégorie va enlever tous les sujets et messages en supprimant la catégorie de la base de données. Si vous voulez seulement enlevez une catégorietemporairement, il faut plutôt \"désactiver\" la catégorie.",
"alert.purge-success": "Catégorie purgée !",
"alert.copy-success": "Paramètres copiés !",
diff --git a/public/language/fr/admin/manage/user-custom-fields.json b/public/language/fr/admin/manage/user-custom-fields.json
index dab10670d2..cd7ec8a52d 100644
--- a/public/language/fr/admin/manage/user-custom-fields.json
+++ b/public/language/fr/admin/manage/user-custom-fields.json
@@ -1,8 +1,8 @@
{
- "title": "Manage Custom User Fields",
- "create-field": "Create Field",
- "edit-field": "Edit Field",
- "manage-custom-fields": "Manage Custom Fields",
+ "title": "Gérer les champs utilisateur personnalisés",
+ "create-field": "Créer un champ",
+ "edit-field": "Editer un champ",
+ "manage-custom-fields": "Gérer les champs personnalisés",
"type-of-input": "Type of input",
"key": "Key",
"name": "Name",
diff --git a/public/language/fr/admin/settings/activitypub.json b/public/language/fr/admin/settings/activitypub.json
index 94f9ad7822..1b00672e0f 100644
--- a/public/language/fr/admin/settings/activitypub.json
+++ b/public/language/fr/admin/settings/activitypub.json
@@ -18,6 +18,27 @@
"probe-timeout": "Lookup Timeout (milliseconds)",
"probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.",
+ "rules": "Categorization",
+ "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)",
+ "rules.modal.title": "How it works",
+ "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.
N.B. Content that is already categorized (i.e. in a remote category) will not pass through these rules.",
+ "rules.modal.values-multiple": "To match multiple values, separate entries with a comma (e.g. one,two,three)",
+ "rules.add": "Add New Rule",
+ "rules.type": "Type",
+ "rules.value": "Value",
+ "rules.cid": "Category",
+
+ "relays": "Relays",
+ "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.",
+ "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.",
+ "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.",
+ "relays.add": "Add New Relay",
+ "relays.relay": "Relay",
+ "relays.state": "State",
+ "relays.state-0": "Pending",
+ "relays.state-1": "Receiving only",
+ "relays.state-2": "Active",
+
"server-filtering": "Filtering",
"count": "This NodeBB is currently aware of %1 server(s)",
"server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.",
diff --git a/public/language/fr/error.json b/public/language/fr/error.json
index 94ea74f7ab..d22c59af08 100644
--- a/public/language/fr/error.json
+++ b/public/language/fr/error.json
@@ -3,6 +3,7 @@
"invalid-json": "JSON invalide",
"wrong-parameter-type": "Une valeur de type %3 était attendue pour la propriété `%1`, mais %2 a été reçu à la place",
"required-parameters-missing": "Les paramètres requis étaient manquants dans cet appel d'API : %1",
+ "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.",
"not-logged-in": "Vous ne semblez pas être connecté.",
"account-locked": "Votre compte a été temporairement suspendu",
"search-requires-login": "Rechercher nécessite d'avoir un compte. Veuillez vous identifier ou vous enregistrer.",
@@ -236,6 +237,7 @@
"socket-reconnect-failed": "Serveur inaccessible pour le moment. Cliquez ici pour réessayer ou réessayez plus tard",
"invalid-plugin-id": "ID de plugin invalide",
"plugin-not-whitelisted": "Impossible d'installer le plugin, seuls les plugins mis en liste blanche dans le gestionnaire de packages NodeBB peuvent être installés via l'ACP",
+ "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin",
"plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled",
"plugins-set-in-configuration": "Vous n'êtes pas autorisé à modifier l'état des plugins car ils sont définis au moment de l'exécution (config.json, variables d'environnement ou arguments de terminal), veuillez plutôt modifier la configuration.",
"theme-not-set-in-configuration": "Lors de la définition des plugins actifs, le changement de thème nécessite d'ajouter le nouveau thème à la liste des plugins actifs avant de le mettre à jour dans l'ACP",
diff --git a/public/language/fr/modules.json b/public/language/fr/modules.json
index 475fd1d297..2b54930f35 100644
--- a/public/language/fr/modules.json
+++ b/public/language/fr/modules.json
@@ -48,6 +48,7 @@
"chat.add-user": "Ajouter un utilisateur",
"chat.notification-settings": "Paramètres de notification",
"chat.default-notification-setting": "Paramètres de notification par défaut",
+ "chat.join-leave-messages": "Join/Leave Messages",
"chat.notification-setting-room-default": "Salon par défaut",
"chat.notification-setting-none": "Aucune notification",
"chat.notification-setting-at-mention-only": "@mention seulement",
diff --git a/public/language/fr/social.json b/public/language/fr/social.json
index c0a67052a8..b2b66a6cf6 100644
--- a/public/language/fr/social.json
+++ b/public/language/fr/social.json
@@ -8,5 +8,7 @@
"log-in-with-facebook": "Connectez-vous avec Facebook",
"continue-with-facebook": "Continuer avec Facebook",
"sign-in-with-linkedin": "Connectez-vous avec LinkedIn",
- "sign-up-with-linkedin": "Inscrivez-vous avec LinkedIn"
+ "sign-up-with-linkedin": "Inscrivez-vous avec LinkedIn",
+ "sign-in-with-wordpress": "Sign in with WordPress",
+ "sign-up-with-wordpress": "Sign up with WordPress"
}
\ No newline at end of file
diff --git a/public/language/gl/admin/dashboard.json b/public/language/gl/admin/dashboard.json
index 6ad973f5f3..0be6d5866c 100644
--- a/public/language/gl/admin/dashboard.json
+++ b/public/language/gl/admin/dashboard.json
@@ -75,6 +75,7 @@
"graphs.page-views-registered": "Page Views Registered",
"graphs.page-views-guest": "Page Views Guest",
"graphs.page-views-bot": "Page Views Bot",
+ "graphs.page-views-ap": "ActivityPub Page Views",
"graphs.unique-visitors": "Unique Visitors",
"graphs.registered-users": "Registered Users",
"graphs.guest-users": "Guest Users",
diff --git a/public/language/gl/admin/manage/categories.json b/public/language/gl/admin/manage/categories.json
index f51152f22d..d66dd814a1 100644
--- a/public/language/gl/admin/manage/categories.json
+++ b/public/language/gl/admin/manage/categories.json
@@ -1,11 +1,15 @@
{
"manage-categories": "Manage Categories",
"add-category": "Add category",
+ "add-local-category": "Add Local category",
+ "add-remote-category": "Add Remote category",
+ "remove": "Remove",
"jump-to": "Jump to...",
"settings": "Category Settings",
"edit-category": "Edit Category",
"privileges": "Privileges",
"back-to-categories": "Back to categories",
+ "id": "Category ID",
"name": "Category Name",
"handle": "Category Handle",
"handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.",
@@ -103,6 +107,8 @@
"alert.create-success": "Category successfully created!",
"alert.none-active": "You have no active categories.",
"alert.create": "Create a Category",
+ "alert.add": "Add a Category",
+ "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.
Note — The remote category may not reflect all topics published unless at least one local user tracks/watches it.",
"alert.confirm-purge": "
Do you really want to purge this category \"%1\"?
Purging a category will remove all topics and posts, and delete the category from the database. If you want to remove a category temporarily, you'll want to \"disable\" the category instead.
", "alert.purge-success": "Category purged!", "alert.copy-success": "Settings Copied!", diff --git a/public/language/gl/admin/settings/activitypub.json b/public/language/gl/admin/settings/activitypub.json index 94f9ad7822..1b00672e0f 100644 --- a/public/language/gl/admin/settings/activitypub.json +++ b/public/language/gl/admin/settings/activitypub.json @@ -18,6 +18,27 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.one,two,three)",
+ "rules.add": "Add New Rule",
+ "rules.type": "Type",
+ "rules.value": "Value",
+ "rules.cid": "Category",
+
+ "relays": "Relays",
+ "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.",
+ "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.",
+ "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.",
+ "relays.add": "Add New Relay",
+ "relays.relay": "Relay",
+ "relays.state": "State",
+ "relays.state-0": "Pending",
+ "relays.state-1": "Receiving only",
+ "relays.state-2": "Active",
+
"server-filtering": "Filtering",
"count": "This NodeBB is currently aware of %1 server(s)",
"server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.",
diff --git a/public/language/gl/error.json b/public/language/gl/error.json
index 40b34fe8be..7669c41b8f 100644
--- a/public/language/gl/error.json
+++ b/public/language/gl/error.json
@@ -3,6 +3,7 @@
"invalid-json": "Invalid JSON",
"wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead",
"required-parameters-missing": "Required parameters were missing from this API call: %1",
+ "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.",
"not-logged-in": "Parece que estás desconectado.",
"account-locked": "A túa conta foi bloqueada temporalmente.",
"search-requires-login": "As buscas requiren unha conta. Por favor inicia sesión ou rexístrate.",
@@ -236,6 +237,7 @@
"socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later",
"invalid-plugin-id": "Invalid plugin ID",
"plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP",
+ "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin",
"plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled",
"plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.",
"theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP",
diff --git a/public/language/gl/modules.json b/public/language/gl/modules.json
index 437ec89919..02ae4bd868 100644
--- a/public/language/gl/modules.json
+++ b/public/language/gl/modules.json
@@ -48,6 +48,7 @@
"chat.add-user": "Add User",
"chat.notification-settings": "Notification Settings",
"chat.default-notification-setting": "Default Notification Setting",
+ "chat.join-leave-messages": "Join/Leave Messages",
"chat.notification-setting-room-default": "Room Default",
"chat.notification-setting-none": "No notifications",
"chat.notification-setting-at-mention-only": "@mention only",
diff --git a/public/language/gl/social.json b/public/language/gl/social.json
index 2ba690a187..5b8dd99a46 100644
--- a/public/language/gl/social.json
+++ b/public/language/gl/social.json
@@ -8,5 +8,7 @@
"log-in-with-facebook": "Log in with Facebook",
"continue-with-facebook": "Continue with Facebook",
"sign-in-with-linkedin": "Sign in with LinkedIn",
- "sign-up-with-linkedin": "Sign up with LinkedIn"
+ "sign-up-with-linkedin": "Sign up with LinkedIn",
+ "sign-in-with-wordpress": "Sign in with WordPress",
+ "sign-up-with-wordpress": "Sign up with WordPress"
}
\ No newline at end of file
diff --git a/public/language/he/admin/dashboard.json b/public/language/he/admin/dashboard.json
index bdaa869915..1304c758a1 100644
--- a/public/language/he/admin/dashboard.json
+++ b/public/language/he/admin/dashboard.json
@@ -75,6 +75,7 @@
"graphs.page-views-registered": "צפיות בדפים-רשומים",
"graphs.page-views-guest": "צפיות בדפים-אורחים",
"graphs.page-views-bot": "צפיות בדפים-בוטים",
+ "graphs.page-views-ap": "ActivityPub תצוגת דפים",
"graphs.unique-visitors": "מבקרים ייחודיים",
"graphs.registered-users": "משתמשים רשומים",
"graphs.guest-users": "משתמשים אורחים",
diff --git a/public/language/he/admin/manage/categories.json b/public/language/he/admin/manage/categories.json
index c6837046d1..e250ec3867 100644
--- a/public/language/he/admin/manage/categories.json
+++ b/public/language/he/admin/manage/categories.json
@@ -1,11 +1,15 @@
{
"manage-categories": "ניהול קטגוריות",
"add-category": "הוספת קטגוריה",
+ "add-local-category": "Add Local category",
+ "add-remote-category": "Add Remote category",
+ "remove": "Remove",
"jump-to": "קפיצה אל...",
"settings": "הגדרות קטגוריות",
"edit-category": "עריכת קטגוריה",
"privileges": "הרשאות",
"back-to-categories": "חזרה לרשימת הקטגוריות",
+ "id": "Category ID",
"name": "שם קטגוריה",
"handle": "מקשר קטגוריה",
"handle.help": "המקשר לקטגוריה שלך משמשת כייצוג של קטגוריה זו ברשתות אחרות, בדומה לשם משתמש. נקודת אחיזה בקטגוריה אינה יכולה להתאים לשם משתמש או קבוצת משתמשים קיימים.",
@@ -103,6 +107,8 @@
"alert.create-success": "קטגוריה נוצרה בהצלחה!",
"alert.none-active": "אין לכם קטגוריות פעילות.",
"alert.create": "יצירת קטגוריה",
+ "alert.add": "Add a Category",
+ "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.האם אתם בטוחים שאתם רוצים למחוק את קטגוריית \"%1\"?
מחיקת קטגוריה תסיר את כל הנושאים והפוסטים ותמחק את הקטגוריה ממסד הנתונים. אם ברצונכם להסיר את הקטגוריה באופן זמני, בחרו ב\"השבתת\" הקטגוריה.
", "alert.purge-success": "הקטגוריה נמחקה!", "alert.copy-success": "ההגדרות הועתקו!", diff --git a/public/language/he/admin/settings/activitypub.json b/public/language/he/admin/settings/activitypub.json index 912aab7975..7d7f2360cf 100644 --- a/public/language/he/admin/settings/activitypub.json +++ b/public/language/he/admin/settings/activitypub.json @@ -18,6 +18,27 @@ "probe-timeout": "פסק זמן לחיפוש (מילישניות)", "probe-timeout-help": "(ברירת מחדל: 2000) אם שאילתת החיפוש לא תקבל תגובה בתוך מסגרת הזמן שנקבעה, המשתמש יישלח ישירות לקישור. התאימו מספר זה גבוה יותר אם אתרים מגיבים באיטיות וברצונכם להקדיש זמן נוסף.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.one,two,three)",
+ "rules.add": "Add New Rule",
+ "rules.type": "Type",
+ "rules.value": "Value",
+ "rules.cid": "Category",
+
+ "relays": "Relays",
+ "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.",
+ "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.",
+ "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.",
+ "relays.add": "Add New Relay",
+ "relays.relay": "Relay",
+ "relays.state": "State",
+ "relays.state-0": "Pending",
+ "relays.state-1": "Receiving only",
+ "relays.state-2": "Active",
+
"server-filtering": "סינון",
"count": "NodeBB זה מודע כרגע ל-%1 שרתים",
"server.filter-help": "ציין שרתים שברצונך למנוע מהתאחדות עם ה-NodeBB שלך. לחלופין, אתה יכול לבחור באופן סלקטיבי פדרציה מאושרים עם שרתים ספציפיים, במקום זאת. שתי האפשרויות נתמכות, אם כי הן סותרות זו את זו.",
diff --git a/public/language/he/admin/settings/user.json b/public/language/he/admin/settings/user.json
index 0c0d227b39..3f1b6958b6 100644
--- a/public/language/he/admin/settings/user.json
+++ b/public/language/he/admin/settings/user.json
@@ -67,7 +67,7 @@
"disable-incoming-chats": "השבתת הודעות צ'אט נכנסות",
"outgoing-new-tab": "פתח קישורים חיצוניים בכרטיסייה חדשה",
"topic-search": "הפעל חיפוש בתוך נושא",
- "update-url-with-post-index": "עדכן את כתובת הURL עם מספר הפוסט הנוכחי בזמן גלישה בנושאים",
+ "update-url-with-post-index": "עדכן את כתובת ה-URL עם מספר הפוסט הנוכחי בזמן גלישה בנושאים",
"digest-freq": "הרשם לקבלת תקציר",
"digest-freq.off": "כבוי",
"digest-freq.daily": "יומי",
diff --git a/public/language/he/error.json b/public/language/he/error.json
index 82af4336b3..a492795106 100644
--- a/public/language/he/error.json
+++ b/public/language/he/error.json
@@ -3,6 +3,7 @@
"invalid-json": "אובייקט JSON לא תקין",
"wrong-parameter-type": "ערך מסוג %3 היה צפוי למאפיין `%1`, אבל %2 התקבל במקום זאת",
"required-parameters-missing": "פרמטרים נדרשים היו חסרים בקריאת API זו: %1",
+ "reserved-ip-address": "אין לבקש בקשות רשת לטווחי IP שמורים.",
"not-logged-in": "נראה שאינכם מחוברים למערכת.",
"account-locked": "חשבונכם נחסם באופן זמני",
"search-requires-login": "חיפוש מצריך חשבון - אנא הירשמו או התחברו.",
@@ -236,6 +237,7 @@
"socket-reconnect-failed": "לא ניתן להגיע לשרת בשלב זה. לחצו כאן כדי לנסות שוב, או נסו שוב במועד מאוחר יותר",
"invalid-plugin-id": "מזהה תוסף לא תקין",
"plugin-not-whitelisted": "לא ניתן להתקין את התוסף – ניתן להתקין דרך הניהול רק תוספים שנמצאים ברשימה הלבנה של מנהל החבילות של NodeBB.",
+ "cannot-toggle-system-plugin": "לא ניתן להחליף מצב של תוסף מערכת",
"plugin-installation-via-acp-disabled": "התקנת תוסף באמצעות ACP מושבתת",
"plugins-set-in-configuration": "לא ניתן לשנות את מצב התוסף כפי שהם מוגדרים בזמן ריצה (config.json, משתני סביבה או ארגומנטים של מסוף), שנו את התצורה במקום זאת.",
"theme-not-set-in-configuration": "כאשר מגדירים תוספים פעילים בתצורה, שינוי ערכות נושא מחייב הוספת ערכת הנושא החדשה לרשימת התוספים הפעילים לפני עדכון שלו ב-ACP",
diff --git a/public/language/he/global.json b/public/language/he/global.json
index 428cd81ac4..7ec0571727 100644
--- a/public/language/he/global.json
+++ b/public/language/he/global.json
@@ -32,7 +32,7 @@
"pagination.enter-index": "עבור למיקום פוסט",
"pagination.go-to-page": "ניווט לדף",
"pagination.page-x": "עמוד %1",
- "header.brand-logo": "לוגו מותג",
+ "header.brand-logo": "לוגו אתר",
"header.admin": "ניהול",
"header.categories": "קטגוריות",
"header.recent": "פוסטים אחרונים",
@@ -134,8 +134,8 @@
"upload": "העלאה",
"uploads": "העלאות",
"allowed-file-types": "פורמטי הקבצים המורשים הם %1",
- "unsaved-changes": "יש לכם שינויים שלא נשמרו. האם הנכם בטוחים שברצונכם להמשיך?",
- "reconnecting-message": "החיבור ל-%1 אבד, אנא המתינו בזמן שאנו מנסים לחבר אתכם מחדש",
+ "unsaved-changes": "יש שינויים שלא נשמרו. האם אתם בטוחים שברצונכם להמשיך?",
+ "reconnecting-message": "החיבור ל-%1 אבד, נא להמתין בזמן שאנו מנסים לחבר אתכם מחדש",
"play": "נגן",
"cookies.message": "אתר זה משתמש ב cookies על מנת לשפר את חוויות המשתמש.",
"cookies.accept": "קיבלתי!",
diff --git a/public/language/he/modules.json b/public/language/he/modules.json
index fb546965c1..8505d4ccba 100644
--- a/public/language/he/modules.json
+++ b/public/language/he/modules.json
@@ -48,6 +48,7 @@
"chat.add-user": "הוספת משתמש",
"chat.notification-settings": "הגדרות התראות",
"chat.default-notification-setting": "הגדרת ברירת מחדל להתראות",
+ "chat.join-leave-messages": "הצטרפות/השארת הודעות",
"chat.notification-setting-room-default": "ברירת המחדל של החדר",
"chat.notification-setting-none": "ללא התראות",
"chat.notification-setting-at-mention-only": "@אזכור בלבד",
diff --git a/public/language/he/social.json b/public/language/he/social.json
index 31633e056a..a08072bc9a 100644
--- a/public/language/he/social.json
+++ b/public/language/he/social.json
@@ -8,5 +8,7 @@
"log-in-with-facebook": "היכנס באמצעות Facebook",
"continue-with-facebook": "המשך בFacebook",
"sign-in-with-linkedin": "היכנס באמצעות LinkedIn",
- "sign-up-with-linkedin": "הירשם באמצעות LinkedIn"
+ "sign-up-with-linkedin": "הירשם באמצעות LinkedIn",
+ "sign-in-with-wordpress": "Sign in with WordPress",
+ "sign-up-with-wordpress": "Sign up with WordPress"
}
\ No newline at end of file
diff --git a/public/language/hr/admin/dashboard.json b/public/language/hr/admin/dashboard.json
index 1e4fedf429..e1c060d8b7 100644
--- a/public/language/hr/admin/dashboard.json
+++ b/public/language/hr/admin/dashboard.json
@@ -75,6 +75,7 @@
"graphs.page-views-registered": "Page Views Registered",
"graphs.page-views-guest": "Page Views Guest",
"graphs.page-views-bot": "Page Views Bot",
+ "graphs.page-views-ap": "ActivityPub Page Views",
"graphs.unique-visitors": "Jedninstveni posjetitelji",
"graphs.registered-users": "Registrirani korisnici",
"graphs.guest-users": "Guest Users",
diff --git a/public/language/hr/admin/manage/categories.json b/public/language/hr/admin/manage/categories.json
index 7375875737..f7169fb5d7 100644
--- a/public/language/hr/admin/manage/categories.json
+++ b/public/language/hr/admin/manage/categories.json
@@ -1,11 +1,15 @@
{
"manage-categories": "Manage Categories",
"add-category": "Add category",
+ "add-local-category": "Add Local category",
+ "add-remote-category": "Add Remote category",
+ "remove": "Remove",
"jump-to": "Jump to...",
"settings": "Postavke kategorije",
"edit-category": "Edit Category",
"privileges": "Privilegije",
"back-to-categories": "Back to categories",
+ "id": "Category ID",
"name": "Ime kategorije",
"handle": "Category Handle",
"handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.",
@@ -103,6 +107,8 @@
"alert.create-success": "Kategorija uspješno kreirana!",
"alert.none-active": "Nemate aktivnih kategorija.",
"alert.create": "Napravi kategoriju",
+ "alert.add": "Add a Category",
+ "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.Do you really want to purge this category \"%1\"?
Purging a category will remove all topics and posts, and delete the category from the database. If you want to remove a category temporarily, you'll want to \"disable\" the category instead.
", "alert.purge-success": "Kategorija odbačena!", "alert.copy-success": "Postavke kopirane!", diff --git a/public/language/hr/admin/settings/activitypub.json b/public/language/hr/admin/settings/activitypub.json index 94f9ad7822..1b00672e0f 100644 --- a/public/language/hr/admin/settings/activitypub.json +++ b/public/language/hr/admin/settings/activitypub.json @@ -18,6 +18,27 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.one,two,three)",
+ "rules.add": "Add New Rule",
+ "rules.type": "Type",
+ "rules.value": "Value",
+ "rules.cid": "Category",
+
+ "relays": "Relays",
+ "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.",
+ "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.",
+ "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.",
+ "relays.add": "Add New Relay",
+ "relays.relay": "Relay",
+ "relays.state": "State",
+ "relays.state-0": "Pending",
+ "relays.state-1": "Receiving only",
+ "relays.state-2": "Active",
+
"server-filtering": "Filtering",
"count": "This NodeBB is currently aware of %1 server(s)",
"server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.",
diff --git a/public/language/hr/category.json b/public/language/hr/category.json
index ff5b1cb9c5..a02822d8d7 100644
--- a/public/language/hr/category.json
+++ b/public/language/hr/category.json
@@ -5,7 +5,7 @@
"uncategorized.description": "Topics that do not strictly fit in with any existing categories",
"handle.description": "This category can be followed from the open social web via the handle %1",
"new-topic-button": "Nova Tema",
- "guest-login-post": "Prijavi se za objavu",
+ "guest-login-post": "Prijavite se da biste objavili",
"no-topics": "Nema tema u ovoj kategoriji. Biztosan szeretnéd teljesen törölni ezt a kategóriát \"%1\"?
Egy kategória teljes törlése eltávolítja a témaköröket és hozzászólásokat, valamint törli a kategóriát az adatbázisból. Amennyiben szeretnél egy kategóriát ideiglenesen törölni, használd a kategória \"kikapcsolása\" funkciót.
", "alert.purge-success": "Kategória törölve!", "alert.copy-success": "Beállítások másolva!", diff --git a/public/language/hu/admin/settings/activitypub.json b/public/language/hu/admin/settings/activitypub.json index 94f9ad7822..1b00672e0f 100644 --- a/public/language/hu/admin/settings/activitypub.json +++ b/public/language/hu/admin/settings/activitypub.json @@ -18,6 +18,27 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.one,two,three)",
+ "rules.add": "Add New Rule",
+ "rules.type": "Type",
+ "rules.value": "Value",
+ "rules.cid": "Category",
+
+ "relays": "Relays",
+ "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.",
+ "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.",
+ "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.",
+ "relays.add": "Add New Relay",
+ "relays.relay": "Relay",
+ "relays.state": "State",
+ "relays.state-0": "Pending",
+ "relays.state-1": "Receiving only",
+ "relays.state-2": "Active",
+
"server-filtering": "Filtering",
"count": "This NodeBB is currently aware of %1 server(s)",
"server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.",
diff --git a/public/language/hu/error.json b/public/language/hu/error.json
index 63e9d7f465..e337533e57 100644
--- a/public/language/hu/error.json
+++ b/public/language/hu/error.json
@@ -3,6 +3,7 @@
"invalid-json": "Érvénytelen JSON",
"wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead",
"required-parameters-missing": "Required parameters were missing from this API call: %1",
+ "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.",
"not-logged-in": "Úgy tűnik, nem vagy bejelentkezve.",
"account-locked": "A fiókod ideiglenesen zárolva lett.",
"search-requires-login": "A kereséshez fiók szükséges - kérlek, lépj be vagy regisztrálj.",
@@ -236,6 +237,7 @@
"socket-reconnect-failed": "Nem lehet elérni a szervert. Kattints ide az újra próbáláshoz vagy várj egy kicsit",
"invalid-plugin-id": "Érvénytelen plugin ID",
"plugin-not-whitelisted": "Ez a bővítmény nem telepíthető – csak olyan bővítmények telepíthetőek amiket a NodeBB Package Manager az ACP-n keresztül tud telepíteni",
+ "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin",
"plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled",
"plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.",
"theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP",
diff --git a/public/language/hu/modules.json b/public/language/hu/modules.json
index 8b50449c1d..a3544a3bf1 100644
--- a/public/language/hu/modules.json
+++ b/public/language/hu/modules.json
@@ -48,6 +48,7 @@
"chat.add-user": "Add User",
"chat.notification-settings": "Notification Settings",
"chat.default-notification-setting": "Default Notification Setting",
+ "chat.join-leave-messages": "Join/Leave Messages",
"chat.notification-setting-room-default": "Room Default",
"chat.notification-setting-none": "No notifications",
"chat.notification-setting-at-mention-only": "@mention only",
diff --git a/public/language/hu/social.json b/public/language/hu/social.json
index b0beb0b1cf..d5765bc529 100644
--- a/public/language/hu/social.json
+++ b/public/language/hu/social.json
@@ -8,5 +8,7 @@
"log-in-with-facebook": "Log in with Facebook",
"continue-with-facebook": "Continue with Facebook",
"sign-in-with-linkedin": "Belépés LinkedIn-el",
- "sign-up-with-linkedin": "Regisztráció LinkedIn-el"
+ "sign-up-with-linkedin": "Regisztráció LinkedIn-el",
+ "sign-in-with-wordpress": "Sign in with WordPress",
+ "sign-up-with-wordpress": "Sign up with WordPress"
}
\ No newline at end of file
diff --git a/public/language/hy/admin/dashboard.json b/public/language/hy/admin/dashboard.json
index 5e4f140474..b6eef0c2ee 100644
--- a/public/language/hy/admin/dashboard.json
+++ b/public/language/hy/admin/dashboard.json
@@ -75,6 +75,7 @@
"graphs.page-views-registered": "Գրանցված Էջի դիտումներ ",
"graphs.page-views-guest": "Էջի դիտումներ Հյուր",
"graphs.page-views-bot": "Էջի դիտումների բոտ",
+ "graphs.page-views-ap": "ActivityPub Page Views",
"graphs.unique-visitors": "Եզակի այցելուներ",
"graphs.registered-users": "Գրանցված օգտատերեր",
"graphs.guest-users": "Հյուր օգտատերեր",
diff --git a/public/language/hy/admin/manage/categories.json b/public/language/hy/admin/manage/categories.json
index 7e07ab83be..16ea33717d 100644
--- a/public/language/hy/admin/manage/categories.json
+++ b/public/language/hy/admin/manage/categories.json
@@ -1,11 +1,15 @@
{
"manage-categories": "Կառավարեք կատեգորիաները",
"add-category": "Ավելացնել Կատեգորիա",
+ "add-local-category": "Add Local category",
+ "add-remote-category": "Add Remote category",
+ "remove": "Remove",
"jump-to": "Անցնել դեպի․․․",
"settings": "Կատեգորիայի կարգավորումներ",
"edit-category": "Խմբագրել Կատեգորիան",
"privileges": "Արտոնություններ",
"back-to-categories": "Վերադառնալ կատեգորիաներ",
+ "id": "Category ID",
"name": "Կատեգորիայի անվանումը",
"handle": "Category Handle",
"handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.",
@@ -103,6 +107,8 @@
"alert.create-success": "Կատեգորիան հաջողությամբ ստեղծվեց:",
"alert.none-active": "Դուք չունեք ակտիվ կատեգորիաներ:",
"alert.create": "Ստեղծել կատեգորիա",
+ "alert.add": "Add a Category",
+ "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.one,two,three)",
+ "rules.add": "Add New Rule",
+ "rules.type": "Type",
+ "rules.value": "Value",
+ "rules.cid": "Category",
+
+ "relays": "Relays",
+ "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.",
+ "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.",
+ "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.",
+ "relays.add": "Add New Relay",
+ "relays.relay": "Relay",
+ "relays.state": "State",
+ "relays.state-0": "Pending",
+ "relays.state-1": "Receiving only",
+ "relays.state-2": "Active",
+
"server-filtering": "Filtering",
"count": "This NodeBB is currently aware of %1 server(s)",
"server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.",
diff --git a/public/language/hy/error.json b/public/language/hy/error.json
index 553a5f00b4..e6c4a3c18b 100644
--- a/public/language/hy/error.json
+++ b/public/language/hy/error.json
@@ -3,6 +3,7 @@
"invalid-json": "Անվավեր JSON",
"wrong-parameter-type": "«%1» հատկության համար սպասվում էր %3 տիպի արժեք, բայց փոխարենը ստացվեց %2",
"required-parameters-missing": "Պահանջվող պարամետրերը բացակայում էին այս API զանգից՝ %1",
+ "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.",
"not-logged-in": "Դուք, կարծես, մուտք չեք գործել:",
"account-locked": "Ձեր հաշիվը ժամանակավորապես արգելափակվել է",
"search-requires-login": "Որոնումը պահանջում է հաշիվ. խնդրում ենք մուտք գործել կամ գրանցվել:",
@@ -236,6 +237,7 @@
"socket-reconnect-failed": "Այս պահին հնարավոր չէ միանալ սերվերին: Սեղմեք այստեղ՝ նորից փորձելու համար, կամ ավելի ուշ նորից փորձեք",
"invalid-plugin-id": "Invalid plugin ID",
"plugin-not-whitelisted": "Հնարավոր չէ տեղադրել plugin – ACP-ի միջոցով կարող են տեղադրվել միայն NodeBB Package Manager-ի կողմից սպիտակ ցուցակում ներառված պլագինները",
+ "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin",
"plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled",
"plugins-set-in-configuration": "Ձեզ չի թույլատրվում փոխել plugin-ի վիճակը, քանի որ դրանք սահմանված են գործարկման ժամանակ (config.json, շրջակա միջավայրի փոփոխականներ կամ տերմինալի արգումենտներ), փոխարենը փոխեք կազմաձևը:",
"theme-not-set-in-configuration": "Կազմաձևում ակտիվ պլագիններ սահմանելիս, թեմաները փոխելիս անհրաժեշտ է ավելացնել նոր թեման ակտիվ հավելումների ցանկում՝ նախքան այն թարմացնելը ACP-ում:",
diff --git a/public/language/hy/modules.json b/public/language/hy/modules.json
index 2de14aeb1e..381dcdfa51 100644
--- a/public/language/hy/modules.json
+++ b/public/language/hy/modules.json
@@ -48,6 +48,7 @@
"chat.add-user": "Ավելացնել օգտատեր",
"chat.notification-settings": "Ծանուցման կարգավորումներ",
"chat.default-notification-setting": "Ծանուցման հիմնական կարգավորումներ",
+ "chat.join-leave-messages": "Join/Leave Messages",
"chat.notification-setting-room-default": "Սենյակի հիմնական վիճակ",
"chat.notification-setting-none": "Ծանուցումներ չկան",
"chat.notification-setting-at-mention-only": "@նշում միայն",
diff --git a/public/language/hy/social.json b/public/language/hy/social.json
index 1f88d125ba..e54b7c8a69 100644
--- a/public/language/hy/social.json
+++ b/public/language/hy/social.json
@@ -8,5 +8,7 @@
"log-in-with-facebook": "Մուտք գործեք Facebook-ով",
"continue-with-facebook": "Շարունակեք Facebook-ով",
"sign-in-with-linkedin": "Մուտք գործեք LinkedIn-ով",
- "sign-up-with-linkedin": "Գրանցվեք LinkedIn-ով"
+ "sign-up-with-linkedin": "Գրանցվեք LinkedIn-ով",
+ "sign-in-with-wordpress": "Sign in with WordPress",
+ "sign-up-with-wordpress": "Sign up with WordPress"
}
\ No newline at end of file
diff --git a/public/language/id/admin/dashboard.json b/public/language/id/admin/dashboard.json
index 6ad973f5f3..0be6d5866c 100644
--- a/public/language/id/admin/dashboard.json
+++ b/public/language/id/admin/dashboard.json
@@ -75,6 +75,7 @@
"graphs.page-views-registered": "Page Views Registered",
"graphs.page-views-guest": "Page Views Guest",
"graphs.page-views-bot": "Page Views Bot",
+ "graphs.page-views-ap": "ActivityPub Page Views",
"graphs.unique-visitors": "Unique Visitors",
"graphs.registered-users": "Registered Users",
"graphs.guest-users": "Guest Users",
diff --git a/public/language/id/admin/manage/categories.json b/public/language/id/admin/manage/categories.json
index f51152f22d..d66dd814a1 100644
--- a/public/language/id/admin/manage/categories.json
+++ b/public/language/id/admin/manage/categories.json
@@ -1,11 +1,15 @@
{
"manage-categories": "Manage Categories",
"add-category": "Add category",
+ "add-local-category": "Add Local category",
+ "add-remote-category": "Add Remote category",
+ "remove": "Remove",
"jump-to": "Jump to...",
"settings": "Category Settings",
"edit-category": "Edit Category",
"privileges": "Privileges",
"back-to-categories": "Back to categories",
+ "id": "Category ID",
"name": "Category Name",
"handle": "Category Handle",
"handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.",
@@ -103,6 +107,8 @@
"alert.create-success": "Category successfully created!",
"alert.none-active": "You have no active categories.",
"alert.create": "Create a Category",
+ "alert.add": "Add a Category",
+ "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.Do you really want to purge this category \"%1\"?
Purging a category will remove all topics and posts, and delete the category from the database. If you want to remove a category temporarily, you'll want to \"disable\" the category instead.
", "alert.purge-success": "Category purged!", "alert.copy-success": "Settings Copied!", diff --git a/public/language/id/admin/settings/activitypub.json b/public/language/id/admin/settings/activitypub.json index 94f9ad7822..1b00672e0f 100644 --- a/public/language/id/admin/settings/activitypub.json +++ b/public/language/id/admin/settings/activitypub.json @@ -18,6 +18,27 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.one,two,three)",
+ "rules.add": "Add New Rule",
+ "rules.type": "Type",
+ "rules.value": "Value",
+ "rules.cid": "Category",
+
+ "relays": "Relays",
+ "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.",
+ "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.",
+ "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.",
+ "relays.add": "Add New Relay",
+ "relays.relay": "Relay",
+ "relays.state": "State",
+ "relays.state-0": "Pending",
+ "relays.state-1": "Receiving only",
+ "relays.state-2": "Active",
+
"server-filtering": "Filtering",
"count": "This NodeBB is currently aware of %1 server(s)",
"server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.",
diff --git a/public/language/id/error.json b/public/language/id/error.json
index 7cc7b20821..21d344ce72 100644
--- a/public/language/id/error.json
+++ b/public/language/id/error.json
@@ -3,6 +3,7 @@
"invalid-json": "Invalid JSON",
"wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead",
"required-parameters-missing": "Required parameters were missing from this API call: %1",
+ "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.",
"not-logged-in": "Kamu terlihat belum login",
"account-locked": "Akun kamu dikunci sementara",
"search-requires-login": "Searching requires an account - please login or register.",
@@ -236,6 +237,7 @@
"socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later",
"invalid-plugin-id": "Invalid plugin ID",
"plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP",
+ "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin",
"plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled",
"plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.",
"theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP",
diff --git a/public/language/id/modules.json b/public/language/id/modules.json
index 5b15156ff3..86afe16b09 100644
--- a/public/language/id/modules.json
+++ b/public/language/id/modules.json
@@ -48,6 +48,7 @@
"chat.add-user": "Add User",
"chat.notification-settings": "Notification Settings",
"chat.default-notification-setting": "Default Notification Setting",
+ "chat.join-leave-messages": "Join/Leave Messages",
"chat.notification-setting-room-default": "Room Default",
"chat.notification-setting-none": "No notifications",
"chat.notification-setting-at-mention-only": "@mention only",
diff --git a/public/language/id/social.json b/public/language/id/social.json
index 2ba690a187..5b8dd99a46 100644
--- a/public/language/id/social.json
+++ b/public/language/id/social.json
@@ -8,5 +8,7 @@
"log-in-with-facebook": "Log in with Facebook",
"continue-with-facebook": "Continue with Facebook",
"sign-in-with-linkedin": "Sign in with LinkedIn",
- "sign-up-with-linkedin": "Sign up with LinkedIn"
+ "sign-up-with-linkedin": "Sign up with LinkedIn",
+ "sign-in-with-wordpress": "Sign in with WordPress",
+ "sign-up-with-wordpress": "Sign up with WordPress"
}
\ No newline at end of file
diff --git a/public/language/it/admin/dashboard.json b/public/language/it/admin/dashboard.json
index 691b2914e7..ceaff8c06e 100644
--- a/public/language/it/admin/dashboard.json
+++ b/public/language/it/admin/dashboard.json
@@ -75,6 +75,7 @@
"graphs.page-views-registered": "Pagine viste Registrati",
"graphs.page-views-guest": "Pagine viste Ospite",
"graphs.page-views-bot": "Pagine viste Bot",
+ "graphs.page-views-ap": "Visualizzazioni della pagina ActivityPub",
"graphs.unique-visitors": "Visitatori Unici",
"graphs.registered-users": "Utenti Registrati",
"graphs.guest-users": "Utenti ospiti",
diff --git a/public/language/it/admin/manage/categories.json b/public/language/it/admin/manage/categories.json
index 6ff6e53395..b9ad1c8be3 100644
--- a/public/language/it/admin/manage/categories.json
+++ b/public/language/it/admin/manage/categories.json
@@ -1,11 +1,15 @@
{
"manage-categories": "Gestisci categorie",
"add-category": "Aggiungi categoria",
+ "add-local-category": "Aggiungi categoria locale",
+ "add-remote-category": "Aggiungi categoria remota",
+ "remove": "Rimuovi",
"jump-to": "Vai a...",
"settings": "Impostazioni Categoria",
"edit-category": "Modifica categoria",
"privileges": "Privilegi",
"back-to-categories": "Torna alle categorie",
+ "id": "ID Categoria",
"name": "Nome Categoria",
"handle": "Pseudonimo categoria",
"handle.help": "Lo pseudonimo della categoria è utilizzato come rappresentazione di questa categoria in altre reti, in modo simile a un nome utente. Lo pseudonimo di una categoria non deve corrispondere a un nome utente o a un gruppo di utenti esistenti.",
@@ -103,6 +107,8 @@
"alert.create-success": "Categoria creata con successo!",
"alert.none-active": "Hai una categoria non attiva.",
"alert.create": "Crea una Categoria",
+ "alert.add": "Aggiungi una categoria",
+ "alert.add-help": "Le categorie remote possono essere aggiunte all'elenco delle categorie specificando il loro identificatore.Vuoi davvero eliminare definitivamente questa categoria \"%1\"?
Eliminare definitivamente una categoria rimuoverà tutte le discussioni e i post ed eliminerà la categoria dal database. Se vuoi rimuovere una categoria temporaneamente, puoi invece \"disabilitare\" la categoria.",
"alert.purge-success": "Categoria eliminata definitivamente!",
"alert.copy-success": "Impostazioni copiate!",
diff --git a/public/language/it/admin/settings/activitypub.json b/public/language/it/admin/settings/activitypub.json
index 23c19f82e1..ed794f82b7 100644
--- a/public/language/it/admin/settings/activitypub.json
+++ b/public/language/it/admin/settings/activitypub.json
@@ -18,6 +18,27 @@
"probe-timeout": "Timeout di ricerca (millisecondi)",
"probe-timeout-help": "(Predefinito: 2000) Se la query di ricerca non riceve una risposta entro l'intervallo di tempo impostato, l'utente l'utente sarà indirizzato direttamente al link. Aumenta questo numero se i siti rispondono lentamente e desideri concedere più tempo.",
+ "rules": "Categorizzazione",
+ "rules-intro": "I contenuti scoperti tramite ActivityPub possono essere categorizzati automaticamente in base a determinate regole (ad es. hashtag)",
+ "rules.modal.title": "Come funziona",
+ "rules.modal.instructions": "Tutti i contenuti in arrivo sono controllati in base a queste regole di categorizzazione e i contenuti corrispondenti sono automaticamente spostati nella categoria scelta.
N.B. Contenuti già categorizzati (ad es. in una categoria remota) non passerà attraverso queste regole.",
+ "rules.modal.values-multiple": "Per abbinare più valori, separa le voci con una virgola (es. uno,due,tre)",
+ "rules.add": "Aggiungi nuova regola",
+ "rules.type": "Tipo",
+ "rules.value": "Valore",
+ "rules.cid": "Categoria",
+
+ "relays": "Relays",
+ "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.",
+ "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.",
+ "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.",
+ "relays.add": "Add New Relay",
+ "relays.relay": "Relay",
+ "relays.state": "State",
+ "relays.state-0": "Pending",
+ "relays.state-1": "Receiving only",
+ "relays.state-2": "Active",
+
"server-filtering": "Filtraggio",
"count": "Questo NodeBB è attualmente a conoscenza di %1 server",
"server.filter-help": "Specifica i server a cui desideri impedire la federazione con il tuo NodeBB. In alternativa, puoi scegliere di consentire in modo selettivo la federazione con server specifici. Entrambe le opzioni sono supportate, anche se si escludono a vicenda.",
diff --git a/public/language/it/error.json b/public/language/it/error.json
index 1b862ddede..0fb310b196 100644
--- a/public/language/it/error.json
+++ b/public/language/it/error.json
@@ -3,6 +3,7 @@
"invalid-json": "JSON non valido",
"wrong-parameter-type": "Era previsto un valore di tipo %3 per la proprietà '%1', ma invece è stato ricevuto %2",
"required-parameters-missing": "I parametri richiesti sono mancanti in questa chiamata API: %1",
+ "reserved-ip-address": "Le richieste di rete agli intervalli IP riservati non sono consentite.",
"not-logged-in": "Non sembra che tu abbia effettuato l'accesso.",
"account-locked": "Il tuo account è stato bloccato temporaneamente",
"search-requires-login": "La ricerca richiede un account! Si prega di effettuare l'accesso o registrarsi!",
@@ -236,6 +237,7 @@
"socket-reconnect-failed": "Impossibile raggiungere il server al momento. Clicca qui per riprovare o riprova in un secondo momento",
"invalid-plugin-id": "ID plugin non valido",
"plugin-not-whitelisted": "Impossibile installare il plug-in & solo i plugin nella whitelist del Gestione Pacchetti di NodeBB possono essere installati tramite ACP",
+ "cannot-toggle-system-plugin": "Non puoi attivare/disattivare lo stato di un plugin di sistema.",
"plugin-installation-via-acp-disabled": "L'installazione dei plugin tramite ACP è disabilitata",
"plugins-set-in-configuration": "Non è possibile modificare lo stato dei plugin, poiché sono definiti in fase di esecuzione. (config.json, variabili ambientali o argomenti del terminale); modificare invece la configurazione.",
"theme-not-set-in-configuration": "Quando si definiscono i plugin attivi nella configurazione, la modifica dei temi richiede l'aggiunta del nuovo tema all'elenco dei plugin attivi prima di aggiornarlo nell'ACP",
diff --git a/public/language/it/modules.json b/public/language/it/modules.json
index 2646ad5d5f..c5b60f5c4b 100644
--- a/public/language/it/modules.json
+++ b/public/language/it/modules.json
@@ -48,6 +48,7 @@
"chat.add-user": "Aggiungi utente",
"chat.notification-settings": "Impostazioni di notifica",
"chat.default-notification-setting": "Impostazioni di notifica predefinite",
+ "chat.join-leave-messages": "Messaggi di iscrizione/uscita",
"chat.notification-setting-room-default": "Stanza predefinita",
"chat.notification-setting-none": "Nessuna notifica",
"chat.notification-setting-at-mention-only": "@solo menzione",
diff --git a/public/language/it/social.json b/public/language/it/social.json
index 4fb4c6bd3e..6868cad941 100644
--- a/public/language/it/social.json
+++ b/public/language/it/social.json
@@ -8,5 +8,7 @@
"log-in-with-facebook": "Accedi con Facebook",
"continue-with-facebook": "Continua con Facebook",
"sign-in-with-linkedin": "Accedi con LinkedIn",
- "sign-up-with-linkedin": "Registrati con LinkedIn"
+ "sign-up-with-linkedin": "Registrati con LinkedIn",
+ "sign-in-with-wordpress": "Accedi con WordPress",
+ "sign-up-with-wordpress": "Iscriviti a WordPress"
}
\ No newline at end of file
diff --git a/public/language/ja/admin/dashboard.json b/public/language/ja/admin/dashboard.json
index 60e2fad225..f9354ce880 100644
--- a/public/language/ja/admin/dashboard.json
+++ b/public/language/ja/admin/dashboard.json
@@ -75,6 +75,7 @@
"graphs.page-views-registered": "ページビュー登録済み",
"graphs.page-views-guest": "ページビューゲスト",
"graphs.page-views-bot": "ページビューBot",
+ "graphs.page-views-ap": "ActivityPub Page Views",
"graphs.unique-visitors": "ユニークな訪問者",
"graphs.registered-users": "登録したユーザー",
"graphs.guest-users": "Guest Users",
diff --git a/public/language/ja/admin/manage/categories.json b/public/language/ja/admin/manage/categories.json
index 0044cf2f6d..44252e8c1a 100644
--- a/public/language/ja/admin/manage/categories.json
+++ b/public/language/ja/admin/manage/categories.json
@@ -1,11 +1,15 @@
{
"manage-categories": "Manage Categories",
"add-category": "Add category",
+ "add-local-category": "Add Local category",
+ "add-remote-category": "Add Remote category",
+ "remove": "Remove",
"jump-to": "Jump to...",
"settings": "カテゴリ設定",
"edit-category": "Edit Category",
"privileges": "特権",
"back-to-categories": "Back to categories",
+ "id": "Category ID",
"name": "カテゴリ名",
"handle": "Category Handle",
"handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.",
@@ -103,6 +107,8 @@
"alert.create-success": "カテゴリが正常に作成されました!",
"alert.none-active": "アクティブなカテゴリがありません。",
"alert.create": "カテゴリを作成",
+ "alert.add": "Add a Category",
+ "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.
Note — The remote category may not reflect all topics published unless at least one local user tracks/watches it.",
"alert.confirm-purge": "
本当にこのカテゴリ \"%1\"を切り離しますか?
カテゴリをパージすると、すべてのスレッドと投稿が削除され、データベースからカテゴリが削除されます。一時的に em>カテゴリを削除する場合は、代わりにカテゴリを無効にすることをおすすめします。
", "alert.purge-success": "カテゴリが切り離されました!", "alert.copy-success": "設定をコピーしました。", diff --git a/public/language/ja/admin/settings/activitypub.json b/public/language/ja/admin/settings/activitypub.json index 94f9ad7822..1b00672e0f 100644 --- a/public/language/ja/admin/settings/activitypub.json +++ b/public/language/ja/admin/settings/activitypub.json @@ -18,6 +18,27 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.one,two,three)",
+ "rules.add": "Add New Rule",
+ "rules.type": "Type",
+ "rules.value": "Value",
+ "rules.cid": "Category",
+
+ "relays": "Relays",
+ "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.",
+ "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.",
+ "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.",
+ "relays.add": "Add New Relay",
+ "relays.relay": "Relay",
+ "relays.state": "State",
+ "relays.state-0": "Pending",
+ "relays.state-1": "Receiving only",
+ "relays.state-2": "Active",
+
"server-filtering": "Filtering",
"count": "This NodeBB is currently aware of %1 server(s)",
"server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.",
diff --git a/public/language/ja/error.json b/public/language/ja/error.json
index ce848d8b34..be0b4396d1 100644
--- a/public/language/ja/error.json
+++ b/public/language/ja/error.json
@@ -3,6 +3,7 @@
"invalid-json": "無効なJSON",
"wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead",
"required-parameters-missing": "Required parameters were missing from this API call: %1",
+ "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.",
"not-logged-in": "ログインしていません。",
"account-locked": "あなたのアカウントは一時的にロックされています",
"search-requires-login": "検索するにはアカウントが必要です - ログインするかアカウントを作成してください。",
@@ -236,6 +237,7 @@
"socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later",
"invalid-plugin-id": "Invalid plugin ID",
"plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP",
+ "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin",
"plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled",
"plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.",
"theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP",
diff --git a/public/language/ja/modules.json b/public/language/ja/modules.json
index e177131774..19b23706f7 100644
--- a/public/language/ja/modules.json
+++ b/public/language/ja/modules.json
@@ -48,6 +48,7 @@
"chat.add-user": "Add User",
"chat.notification-settings": "Notification Settings",
"chat.default-notification-setting": "Default Notification Setting",
+ "chat.join-leave-messages": "Join/Leave Messages",
"chat.notification-setting-room-default": "Room Default",
"chat.notification-setting-none": "No notifications",
"chat.notification-setting-at-mention-only": "@mention only",
diff --git a/public/language/ja/social.json b/public/language/ja/social.json
index 2ba690a187..5b8dd99a46 100644
--- a/public/language/ja/social.json
+++ b/public/language/ja/social.json
@@ -8,5 +8,7 @@
"log-in-with-facebook": "Log in with Facebook",
"continue-with-facebook": "Continue with Facebook",
"sign-in-with-linkedin": "Sign in with LinkedIn",
- "sign-up-with-linkedin": "Sign up with LinkedIn"
+ "sign-up-with-linkedin": "Sign up with LinkedIn",
+ "sign-in-with-wordpress": "Sign in with WordPress",
+ "sign-up-with-wordpress": "Sign up with WordPress"
}
\ No newline at end of file
diff --git a/public/language/ko/admin/dashboard.json b/public/language/ko/admin/dashboard.json
index b0dd16eb86..8828269697 100644
--- a/public/language/ko/admin/dashboard.json
+++ b/public/language/ko/admin/dashboard.json
@@ -75,6 +75,7 @@
"graphs.page-views-registered": "등록된 사용자 페이지 뷰",
"graphs.page-views-guest": "비회원 페이지 뷰",
"graphs.page-views-bot": "봇 페이지 뷰",
+ "graphs.page-views-ap": "ActivityPub Page Views",
"graphs.unique-visitors": "고유 방문자",
"graphs.registered-users": "등록된 사용자",
"graphs.guest-users": "비회원 사용자",
diff --git a/public/language/ko/admin/manage/categories.json b/public/language/ko/admin/manage/categories.json
index 2afb2d7a89..2625775110 100644
--- a/public/language/ko/admin/manage/categories.json
+++ b/public/language/ko/admin/manage/categories.json
@@ -1,11 +1,15 @@
{
"manage-categories": "카테고리 관리",
"add-category": "카테고리 추가",
+ "add-local-category": "Add Local category",
+ "add-remote-category": "Add Remote category",
+ "remove": "Remove",
"jump-to": "이동...",
"settings": "카테고리 설정",
"edit-category": "카테고리 수정",
"privileges": "권한",
"back-to-categories": "카테고리로 돌아가기",
+ "id": "Category ID",
"name": "카테고리 이름",
"handle": "Category Handle",
"handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.",
@@ -103,6 +107,8 @@
"alert.create-success": "카테고리를 성공적으로 생성했습니다!",
"alert.none-active": "활성화된 카테고리가 없습니다.",
"alert.create": "카테고리 만들기",
+ "alert.add": "Add a Category",
+ "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.정말로 이 카테고리 \"%1\"를 정리하시겠습니까?
카테고리를 정리하면 모든 토픽과 게시물이 제거되며 데이터베이스에서 카테고리가 삭제됩니다. 카테고리를 일시적으로 제거하려면 카테고리를 대신 \"비활성화\"해야 합니다.
", "alert.purge-success": "카테고리를 정리했습니다!", "alert.copy-success": "설정을 복사했습니다!", diff --git a/public/language/ko/admin/settings/activitypub.json b/public/language/ko/admin/settings/activitypub.json index 94f9ad7822..1b00672e0f 100644 --- a/public/language/ko/admin/settings/activitypub.json +++ b/public/language/ko/admin/settings/activitypub.json @@ -18,6 +18,27 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.one,two,three)",
+ "rules.add": "Add New Rule",
+ "rules.type": "Type",
+ "rules.value": "Value",
+ "rules.cid": "Category",
+
+ "relays": "Relays",
+ "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.",
+ "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.",
+ "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.",
+ "relays.add": "Add New Relay",
+ "relays.relay": "Relay",
+ "relays.state": "State",
+ "relays.state-0": "Pending",
+ "relays.state-1": "Receiving only",
+ "relays.state-2": "Active",
+
"server-filtering": "Filtering",
"count": "This NodeBB is currently aware of %1 server(s)",
"server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.",
diff --git a/public/language/ko/error.json b/public/language/ko/error.json
index b81d681962..80ee9f08e5 100644
--- a/public/language/ko/error.json
+++ b/public/language/ko/error.json
@@ -3,6 +3,7 @@
"invalid-json": "잘못된 JSON",
"wrong-parameter-type": "속성 `%1`에 대해 %3 유형의 값이 예상되었지만 대신 %2가 수신되었습니다",
"required-parameters-missing": "이 API 호출에서 필수 매개변수가 누락되었습니다: %1",
+ "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.",
"not-logged-in": "로그인되지 않았습니다.",
"account-locked": "계정이 일시적으로 잠겼습니다.",
"search-requires-login": "검색에는 계정이 필요합니다. 로그인하거나 등록하세요.",
@@ -236,6 +237,7 @@
"socket-reconnect-failed": "현재 서버에 연결할 수 없습니다. 여기를 클릭 후 다시 시도하거나 나중에 다시 시도하세요",
"invalid-plugin-id": "잘못된 플러그인 ID",
"plugin-not-whitelisted": "플러그인을 설치할 수 없습니다 - NodeBB 패키지 관리자에서 허용목록에 등록된 플러그인만 ACP를 통해 설치할 수 있습니다",
+ "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin",
"plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled",
"plugins-set-in-configuration": "실행 중에 정의된 플러그인 상태를 변경할 수 없습니다 (config.json, 환경 변수 또는 터미널 인수). 대신 구성을 수정하세요.",
"theme-not-set-in-configuration": "구성에서 활성 플러그인을 정의할 때 새 테마를 추가하기 전에 ACP에서 테마를 업데이트해야 합니다",
diff --git a/public/language/ko/modules.json b/public/language/ko/modules.json
index 090984bfd0..b79e2f7028 100644
--- a/public/language/ko/modules.json
+++ b/public/language/ko/modules.json
@@ -48,6 +48,7 @@
"chat.add-user": "사용자 추가",
"chat.notification-settings": "알림 설정",
"chat.default-notification-setting": "기본 알림 설정",
+ "chat.join-leave-messages": "Join/Leave Messages",
"chat.notification-setting-room-default": "방 기본값",
"chat.notification-setting-none": "알림 없음",
"chat.notification-setting-at-mention-only": "@언급만",
diff --git a/public/language/ko/social.json b/public/language/ko/social.json
index 49b91bdf69..2e38a019d2 100644
--- a/public/language/ko/social.json
+++ b/public/language/ko/social.json
@@ -8,5 +8,7 @@
"log-in-with-facebook": "Facebook으로 로그인",
"continue-with-facebook": "Facebook으로 계속하기",
"sign-in-with-linkedin": "LinkedIn으로 로그인",
- "sign-up-with-linkedin": "LinkedIn으로 가입"
+ "sign-up-with-linkedin": "LinkedIn으로 가입",
+ "sign-in-with-wordpress": "Sign in with WordPress",
+ "sign-up-with-wordpress": "Sign up with WordPress"
}
\ No newline at end of file
diff --git a/public/language/lt/admin/dashboard.json b/public/language/lt/admin/dashboard.json
index 6ad973f5f3..0be6d5866c 100644
--- a/public/language/lt/admin/dashboard.json
+++ b/public/language/lt/admin/dashboard.json
@@ -75,6 +75,7 @@
"graphs.page-views-registered": "Page Views Registered",
"graphs.page-views-guest": "Page Views Guest",
"graphs.page-views-bot": "Page Views Bot",
+ "graphs.page-views-ap": "ActivityPub Page Views",
"graphs.unique-visitors": "Unique Visitors",
"graphs.registered-users": "Registered Users",
"graphs.guest-users": "Guest Users",
diff --git a/public/language/lt/admin/manage/categories.json b/public/language/lt/admin/manage/categories.json
index f51152f22d..d66dd814a1 100644
--- a/public/language/lt/admin/manage/categories.json
+++ b/public/language/lt/admin/manage/categories.json
@@ -1,11 +1,15 @@
{
"manage-categories": "Manage Categories",
"add-category": "Add category",
+ "add-local-category": "Add Local category",
+ "add-remote-category": "Add Remote category",
+ "remove": "Remove",
"jump-to": "Jump to...",
"settings": "Category Settings",
"edit-category": "Edit Category",
"privileges": "Privileges",
"back-to-categories": "Back to categories",
+ "id": "Category ID",
"name": "Category Name",
"handle": "Category Handle",
"handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.",
@@ -103,6 +107,8 @@
"alert.create-success": "Category successfully created!",
"alert.none-active": "You have no active categories.",
"alert.create": "Create a Category",
+ "alert.add": "Add a Category",
+ "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.Do you really want to purge this category \"%1\"?
Purging a category will remove all topics and posts, and delete the category from the database. If you want to remove a category temporarily, you'll want to \"disable\" the category instead.
", "alert.purge-success": "Category purged!", "alert.copy-success": "Settings Copied!", diff --git a/public/language/lt/admin/settings/activitypub.json b/public/language/lt/admin/settings/activitypub.json index 94f9ad7822..1b00672e0f 100644 --- a/public/language/lt/admin/settings/activitypub.json +++ b/public/language/lt/admin/settings/activitypub.json @@ -18,6 +18,27 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.one,two,three)",
+ "rules.add": "Add New Rule",
+ "rules.type": "Type",
+ "rules.value": "Value",
+ "rules.cid": "Category",
+
+ "relays": "Relays",
+ "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.",
+ "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.",
+ "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.",
+ "relays.add": "Add New Relay",
+ "relays.relay": "Relay",
+ "relays.state": "State",
+ "relays.state-0": "Pending",
+ "relays.state-1": "Receiving only",
+ "relays.state-2": "Active",
+
"server-filtering": "Filtering",
"count": "This NodeBB is currently aware of %1 server(s)",
"server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.",
diff --git a/public/language/lt/error.json b/public/language/lt/error.json
index 1b1c92380d..5fbf1188c4 100644
--- a/public/language/lt/error.json
+++ b/public/language/lt/error.json
@@ -3,6 +3,7 @@
"invalid-json": "Nevalidus JSON",
"wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead",
"required-parameters-missing": "Required parameters were missing from this API call: %1",
+ "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.",
"not-logged-in": "Atrodo, kad jūs neesate prisijungęs.",
"account-locked": "Jūsų paskyra buvo laikinai užrakinta",
"search-requires-login": "Paieška reikalauja vartotojo - prašome prisijungti arba užsiregistruoti",
@@ -236,6 +237,7 @@
"socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later",
"invalid-plugin-id": "Invalid plugin ID",
"plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP",
+ "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin",
"plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled",
"plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.",
"theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP",
diff --git a/public/language/lt/modules.json b/public/language/lt/modules.json
index 6a1a9c3c7a..6f9cb47059 100644
--- a/public/language/lt/modules.json
+++ b/public/language/lt/modules.json
@@ -48,6 +48,7 @@
"chat.add-user": "Add User",
"chat.notification-settings": "Notification Settings",
"chat.default-notification-setting": "Default Notification Setting",
+ "chat.join-leave-messages": "Join/Leave Messages",
"chat.notification-setting-room-default": "Room Default",
"chat.notification-setting-none": "No notifications",
"chat.notification-setting-at-mention-only": "@mention only",
diff --git a/public/language/lt/social.json b/public/language/lt/social.json
index 2ba690a187..5b8dd99a46 100644
--- a/public/language/lt/social.json
+++ b/public/language/lt/social.json
@@ -8,5 +8,7 @@
"log-in-with-facebook": "Log in with Facebook",
"continue-with-facebook": "Continue with Facebook",
"sign-in-with-linkedin": "Sign in with LinkedIn",
- "sign-up-with-linkedin": "Sign up with LinkedIn"
+ "sign-up-with-linkedin": "Sign up with LinkedIn",
+ "sign-in-with-wordpress": "Sign in with WordPress",
+ "sign-up-with-wordpress": "Sign up with WordPress"
}
\ No newline at end of file
diff --git a/public/language/lv/admin/dashboard.json b/public/language/lv/admin/dashboard.json
index ee1358bb7b..4f55f41c5d 100644
--- a/public/language/lv/admin/dashboard.json
+++ b/public/language/lv/admin/dashboard.json
@@ -75,6 +75,7 @@
"graphs.page-views-registered": "Lapu skatījumi no lietotājiem",
"graphs.page-views-guest": "Lapu skatījumi no viesiem",
"graphs.page-views-bot": "Lapu skatījumi no botiem",
+ "graphs.page-views-ap": "ActivityPub Page Views",
"graphs.unique-visitors": "Unikālie apmeklētāji",
"graphs.registered-users": "Reģistrētie lietotāji",
"graphs.guest-users": "Guest Users",
diff --git a/public/language/lv/admin/manage/categories.json b/public/language/lv/admin/manage/categories.json
index 1358f338ac..fba8b697ad 100644
--- a/public/language/lv/admin/manage/categories.json
+++ b/public/language/lv/admin/manage/categories.json
@@ -1,11 +1,15 @@
{
"manage-categories": "Manage Categories",
"add-category": "Add category",
+ "add-local-category": "Add Local category",
+ "add-remote-category": "Add Remote category",
+ "remove": "Remove",
"jump-to": "Jump to...",
"settings": "Kategorijas iestatījumi",
"edit-category": "Edit Category",
"privileges": "Privilēģijas",
"back-to-categories": "Back to categories",
+ "id": "Category ID",
"name": "Kategorijas nosaukums",
"handle": "Category Handle",
"handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.",
@@ -103,6 +107,8 @@
"alert.create-success": "Kategorija veiksmīgi izveidota",
"alert.none-active": "Nav aktīvo kategoriju",
"alert.create": "Izveidot kategoriju",
+ "alert.add": "Add a Category",
+ "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.Vai tiešām vēlies iztīrīt šo kategoriju \"%1\"?
Iztukšojot kategoriju, tiks noņemti visi temati un raksti un kategorija tiks izdzēsta no datu bāzes. Ja vēlies īslaicīgi noņemt kategoriju, \"atspējo\" to.
", "alert.purge-success": "Kategorija iztīrīta!", "alert.copy-success": "Iestatījumi kopēti!", diff --git a/public/language/lv/admin/settings/activitypub.json b/public/language/lv/admin/settings/activitypub.json index 94f9ad7822..1b00672e0f 100644 --- a/public/language/lv/admin/settings/activitypub.json +++ b/public/language/lv/admin/settings/activitypub.json @@ -18,6 +18,27 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.one,two,three)",
+ "rules.add": "Add New Rule",
+ "rules.type": "Type",
+ "rules.value": "Value",
+ "rules.cid": "Category",
+
+ "relays": "Relays",
+ "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.",
+ "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.",
+ "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.",
+ "relays.add": "Add New Relay",
+ "relays.relay": "Relay",
+ "relays.state": "State",
+ "relays.state-0": "Pending",
+ "relays.state-1": "Receiving only",
+ "relays.state-2": "Active",
+
"server-filtering": "Filtering",
"count": "This NodeBB is currently aware of %1 server(s)",
"server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.",
diff --git a/public/language/lv/error.json b/public/language/lv/error.json
index e902e1e6b7..6195541875 100644
--- a/public/language/lv/error.json
+++ b/public/language/lv/error.json
@@ -3,6 +3,7 @@
"invalid-json": "Nederīgs JSON",
"wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead",
"required-parameters-missing": "Required parameters were missing from this API call: %1",
+ "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.",
"not-logged-in": "Šķiet, ka neesi ielogojies.",
"account-locked": "Tavs konts ir uz laiku bloķēts",
"search-requires-login": "Meklēšanai nepieciešams konts - lūdzu, ielogojies vai reģistrējies.",
@@ -236,6 +237,7 @@
"socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later",
"invalid-plugin-id": "Invalid plugin ID",
"plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP",
+ "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin",
"plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled",
"plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.",
"theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP",
diff --git a/public/language/lv/modules.json b/public/language/lv/modules.json
index 0c1a36c576..940d9e52a1 100644
--- a/public/language/lv/modules.json
+++ b/public/language/lv/modules.json
@@ -48,6 +48,7 @@
"chat.add-user": "Add User",
"chat.notification-settings": "Notification Settings",
"chat.default-notification-setting": "Default Notification Setting",
+ "chat.join-leave-messages": "Join/Leave Messages",
"chat.notification-setting-room-default": "Room Default",
"chat.notification-setting-none": "No notifications",
"chat.notification-setting-at-mention-only": "@mention only",
diff --git a/public/language/lv/social.json b/public/language/lv/social.json
index 2ba690a187..5b8dd99a46 100644
--- a/public/language/lv/social.json
+++ b/public/language/lv/social.json
@@ -8,5 +8,7 @@
"log-in-with-facebook": "Log in with Facebook",
"continue-with-facebook": "Continue with Facebook",
"sign-in-with-linkedin": "Sign in with LinkedIn",
- "sign-up-with-linkedin": "Sign up with LinkedIn"
+ "sign-up-with-linkedin": "Sign up with LinkedIn",
+ "sign-in-with-wordpress": "Sign in with WordPress",
+ "sign-up-with-wordpress": "Sign up with WordPress"
}
\ No newline at end of file
diff --git a/public/language/ms/admin/dashboard.json b/public/language/ms/admin/dashboard.json
index 6ad973f5f3..0be6d5866c 100644
--- a/public/language/ms/admin/dashboard.json
+++ b/public/language/ms/admin/dashboard.json
@@ -75,6 +75,7 @@
"graphs.page-views-registered": "Page Views Registered",
"graphs.page-views-guest": "Page Views Guest",
"graphs.page-views-bot": "Page Views Bot",
+ "graphs.page-views-ap": "ActivityPub Page Views",
"graphs.unique-visitors": "Unique Visitors",
"graphs.registered-users": "Registered Users",
"graphs.guest-users": "Guest Users",
diff --git a/public/language/ms/admin/manage/categories.json b/public/language/ms/admin/manage/categories.json
index f51152f22d..d66dd814a1 100644
--- a/public/language/ms/admin/manage/categories.json
+++ b/public/language/ms/admin/manage/categories.json
@@ -1,11 +1,15 @@
{
"manage-categories": "Manage Categories",
"add-category": "Add category",
+ "add-local-category": "Add Local category",
+ "add-remote-category": "Add Remote category",
+ "remove": "Remove",
"jump-to": "Jump to...",
"settings": "Category Settings",
"edit-category": "Edit Category",
"privileges": "Privileges",
"back-to-categories": "Back to categories",
+ "id": "Category ID",
"name": "Category Name",
"handle": "Category Handle",
"handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.",
@@ -103,6 +107,8 @@
"alert.create-success": "Category successfully created!",
"alert.none-active": "You have no active categories.",
"alert.create": "Create a Category",
+ "alert.add": "Add a Category",
+ "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.Do you really want to purge this category \"%1\"?
Purging a category will remove all topics and posts, and delete the category from the database. If you want to remove a category temporarily, you'll want to \"disable\" the category instead.
", "alert.purge-success": "Category purged!", "alert.copy-success": "Settings Copied!", diff --git a/public/language/ms/admin/settings/activitypub.json b/public/language/ms/admin/settings/activitypub.json index 94f9ad7822..1b00672e0f 100644 --- a/public/language/ms/admin/settings/activitypub.json +++ b/public/language/ms/admin/settings/activitypub.json @@ -18,6 +18,27 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.one,two,three)",
+ "rules.add": "Add New Rule",
+ "rules.type": "Type",
+ "rules.value": "Value",
+ "rules.cid": "Category",
+
+ "relays": "Relays",
+ "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.",
+ "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.",
+ "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.",
+ "relays.add": "Add New Relay",
+ "relays.relay": "Relay",
+ "relays.state": "State",
+ "relays.state-0": "Pending",
+ "relays.state-1": "Receiving only",
+ "relays.state-2": "Active",
+
"server-filtering": "Filtering",
"count": "This NodeBB is currently aware of %1 server(s)",
"server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.",
diff --git a/public/language/ms/error.json b/public/language/ms/error.json
index a1c4660876..26e1b5a310 100644
--- a/public/language/ms/error.json
+++ b/public/language/ms/error.json
@@ -3,6 +3,7 @@
"invalid-json": "Invalid JSON",
"wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead",
"required-parameters-missing": "Required parameters were missing from this API call: %1",
+ "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.",
"not-logged-in": "Anda tidak log masuk.",
"account-locked": "Akaun anda telah dikunci untuk seketika",
"search-requires-login": "Fungsi Carian perlukan akaun - sila log masuk atau daftar.",
@@ -236,6 +237,7 @@
"socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later",
"invalid-plugin-id": "Invalid plugin ID",
"plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP",
+ "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin",
"plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled",
"plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.",
"theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP",
diff --git a/public/language/ms/modules.json b/public/language/ms/modules.json
index a2de6c8f89..d3f710ccaa 100644
--- a/public/language/ms/modules.json
+++ b/public/language/ms/modules.json
@@ -48,6 +48,7 @@
"chat.add-user": "Add User",
"chat.notification-settings": "Notification Settings",
"chat.default-notification-setting": "Default Notification Setting",
+ "chat.join-leave-messages": "Join/Leave Messages",
"chat.notification-setting-room-default": "Room Default",
"chat.notification-setting-none": "No notifications",
"chat.notification-setting-at-mention-only": "@mention only",
diff --git a/public/language/ms/social.json b/public/language/ms/social.json
index 2ba690a187..5b8dd99a46 100644
--- a/public/language/ms/social.json
+++ b/public/language/ms/social.json
@@ -8,5 +8,7 @@
"log-in-with-facebook": "Log in with Facebook",
"continue-with-facebook": "Continue with Facebook",
"sign-in-with-linkedin": "Sign in with LinkedIn",
- "sign-up-with-linkedin": "Sign up with LinkedIn"
+ "sign-up-with-linkedin": "Sign up with LinkedIn",
+ "sign-in-with-wordpress": "Sign in with WordPress",
+ "sign-up-with-wordpress": "Sign up with WordPress"
}
\ No newline at end of file
diff --git a/public/language/nb/admin/dashboard.json b/public/language/nb/admin/dashboard.json
index 8662f49f25..2a2e0ee67b 100644
--- a/public/language/nb/admin/dashboard.json
+++ b/public/language/nb/admin/dashboard.json
@@ -75,6 +75,7 @@
"graphs.page-views-registered": "Sidevisninger for registrerte",
"graphs.page-views-guest": "Sidevisninger for gjester",
"graphs.page-views-bot": "Sidevisninger for botter",
+ "graphs.page-views-ap": "ActivityPub Page Views",
"graphs.unique-visitors": "Unike besøkende",
"graphs.registered-users": "Registrerte brukere",
"graphs.guest-users": "Gjestebrukere",
diff --git a/public/language/nb/admin/manage/categories.json b/public/language/nb/admin/manage/categories.json
index 472daa0fd0..0cf4511dd6 100644
--- a/public/language/nb/admin/manage/categories.json
+++ b/public/language/nb/admin/manage/categories.json
@@ -1,11 +1,15 @@
{
"manage-categories": "Administrer kategorier",
"add-category": "Legg til kategori",
+ "add-local-category": "Add Local category",
+ "add-remote-category": "Add Remote category",
+ "remove": "Remove",
"jump-to": "Hopp til...",
"settings": "Kategoriinnstillinger",
"edit-category": "Rediger kategori",
"privileges": "Rettigheter",
"back-to-categories": "Tilbake til kategorier",
+ "id": "Category ID",
"name": "Kategorinavn",
"handle": "Kategoristi",
"handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.",
@@ -103,6 +107,8 @@
"alert.create-success": "Kategori opprettet!",
"alert.none-active": "Du har ingen aktive kategorier.",
"alert.create": "Opprett en kategori",
+ "alert.add": "Add a Category",
+ "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.Vil du virkelig renske kategorien \"%1\"?
Rensking av en kategori vil fjerne alle tråder og innlegg, og slette kategorien fra databasen. Hvis du vil fjerne en kategori midlertidig, vil du \"deaktivere\" kategorien i stedet.
", "alert.purge-success": "Kategori renset!", "alert.copy-success": "Innstillinger kopiert!", diff --git a/public/language/nb/admin/manage/privileges.json b/public/language/nb/admin/manage/privileges.json index 28702994b2..2d655c05eb 100644 --- a/public/language/nb/admin/manage/privileges.json +++ b/public/language/nb/admin/manage/privileges.json @@ -35,8 +35,8 @@ "view-edit-history": "Vis redigeringshistorikk", "delete-posts": "Slett innlegg", "view-deleted": "Vis slettede innlegg", - "upvote-posts": "Tilrå innlegg", - "downvote-posts": "Ikke tilrå innlegg", + "upvote-posts": "Anbefal innlegg", + "downvote-posts": "Ikke anbefal innlegg", "delete-topics": "Slett emner", "purge": "Rensk", "moderate": "Moderere", diff --git a/public/language/nb/admin/menu.json b/public/language/nb/admin/menu.json index 6c51ad8ccf..d62ddede52 100644 --- a/public/language/nb/admin/menu.json +++ b/public/language/nb/admin/menu.json @@ -25,7 +25,7 @@ "settings/general": "Generelt", "settings/homepage": "Hjemmeside", "settings/navigation": "Navigasjon", - "settings/reputation": "Omdømme og flagg", + "settings/reputation": "Omdømme ", "settings/email": "E-post", "settings/user": "Brukere", "settings/group": "Grupper", @@ -33,7 +33,7 @@ "settings/uploads": "Opplastinger", "settings/languages": "Språk", "settings/post": "Innlegg", - "settings/chat": "Chatter", + "settings/chat": "Chat", "settings/pagination": "Sidetelling", "settings/tags": "Emneord", "settings/notifications": "Varsler", diff --git a/public/language/nb/admin/settings/activitypub.json b/public/language/nb/admin/settings/activitypub.json index 70161ed834..2d280b94e5 100644 --- a/public/language/nb/admin/settings/activitypub.json +++ b/public/language/nb/admin/settings/activitypub.json @@ -4,7 +4,7 @@ "general": "Generelt", "pruning": "Content Pruning", "content-pruning": "Days to keep remote content", - "content-pruning-help": "Note that remote content that has received engagement (a reply or a upvote/downvote) will be preserved. (0 for disabled)", + "content-pruning-help": "Merk at eksternt innhold som har fått respons (et svar eller en anbefaling), vil bli bevart. (0 for å deaktivere)", "user-pruning": "Days to cache remote user accounts", "user-pruning-help": "Remote user accounts will only be pruned if they have no posts. Otherwise they will be re-retrieved. (0 for disabled)", "enabled": "Enable Federation", @@ -18,6 +18,27 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.one,two,three)",
+ "rules.add": "Add New Rule",
+ "rules.type": "Type",
+ "rules.value": "Value",
+ "rules.cid": "Category",
+
+ "relays": "Relays",
+ "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.",
+ "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.",
+ "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.",
+ "relays.add": "Add New Relay",
+ "relays.relay": "Relay",
+ "relays.state": "State",
+ "relays.state-0": "Pending",
+ "relays.state-1": "Receiving only",
+ "relays.state-2": "Active",
+
"server-filtering": "Filtrering",
"count": "This NodeBB is currently aware of %1 server(s)",
"server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.",
diff --git a/public/language/nb/admin/settings/post.json b/public/language/nb/admin/settings/post.json
index 548446786d..61f21ea532 100644
--- a/public/language/nb/admin/settings/post.json
+++ b/public/language/nb/admin/settings/post.json
@@ -5,8 +5,8 @@
"sorting.oldest-to-newest": "Eldste til nyeste",
"sorting.newest-to-oldest": "Nyeste til eldste",
"sorting.recently-replied": "Sist besvart",
- "sorting.recently-created": "Nylig opprettet",
- "sorting.most-votes": "Flest stemmer",
+ "sorting.recently-created": "Sist opprettet",
+ "sorting.most-votes": "Flest anbefalinger",
"sorting.most-posts": "Flest innlegg",
"sorting.most-views": "Flest visninger",
"sorting.topic-default": "Standard trådsortering",
diff --git a/public/language/nb/admin/settings/reputation.json b/public/language/nb/admin/settings/reputation.json
index 9612efe855..ad1df9d37a 100644
--- a/public/language/nb/admin/settings/reputation.json
+++ b/public/language/nb/admin/settings/reputation.json
@@ -2,18 +2,18 @@
"reputation": "Omdømmeinnstillinger",
"disable": "Deaktiver omdømmesystem",
"disable-down-voting": "Deaktiver nedstemning",
- "upvote-visibility": "Synlighet for oppstemmer",
- "upvote-visibility-all": "Alle kan se oppstemmer",
- "upvote-visibility-loggedin": "Bare innloggede brukere kan se oppstemmer",
- "upvote-visibility-privileged": "Bare privilegerte brukere kan se oppstemmer",
- "downvote-visibility": "Synlighet for nedstemmer",
+ "upvote-visibility": "Synlighet for anbefalinger",
+ "upvote-visibility-all": "Alle kan se anbefalinger",
+ "upvote-visibility-loggedin": "Bare innloggede brukere kan se anbefalinger",
+ "upvote-visibility-privileged": "Bare privilegerte brukere kan se anbefalinger",
+ "downvote-visibility": "Synlighet for anbefalinger",
"downvote-visibility-all": "Alle kan se nedstemmer",
"downvote-visibility-loggedin": "Bare innloggede brukere kan se nedstemmer",
"downvote-visibility-privileged": "Bare privilegerte brukere kan se nedstemmer",
"thresholds": "Aktivitetsterskler",
- "min-rep-upvote": "Minimum omdømme for å stemme opp innlegg",
- "upvotes-per-day": " Tilrådinger per dag (sett til 0 for ubegrensede tilrådinger)",
- "upvotes-per-user-per-day": " Tilrådinger per bruker per dag (sett til 0 for ubegrensede tilrådinger)",
+ "min-rep-upvote": "Minimum omdømme for å anbefale innlegg",
+ "upvotes-per-day": "Anbefalinger per dag (sett til 0 for ubegrensede tilrådinger)",
+ "upvotes-per-user-per-day": "Anbefalinger per bruker per dag (sett til 0 for ubegrensede tilrådinger)",
"min-rep-downvote": "Minimum omdømme for å stemme ned innlegg",
"downvotes-per-day": "Nedstemmer per dag (sett til 0 for ubegrensede nedstemmer)",
"downvotes-per-user-per-day": "Nedstemmer per bruker per dag (sett til 0 for ubegrensede nedstemmer)",
@@ -25,11 +25,11 @@
"min-rep-profile-picture": "Minimum omdømme for å legge til \"Profilbilde\" i brukerprofil",
"min-rep-cover-picture": "Minimum omdømme for å legge til \"Omslagsbilde\" i brukerprofil",
- "flags": "Flagginnstillinger",
- "flags.limit-per-target": "Maksimalt antall ganger noe kan flagges",
+ "flags": "Raporteringsinnstillinger",
+ "flags.limit-per-target": "Maksimalt antall ganger noe kan rapporterest",
"flags.limit-per-target-placeholder": "Standard: 0",
"flags.limit-per-target-help": "Når et innlegg eller en bruker blir flagget flere ganger, regnes hvert ekstra flagg som en \"rapport\" og legges til det opprinnelige flagget. Sett dette alternativet til et tall større enn null for å begrense antall rapporter et element kan motta.",
- "flags.limit-post-flags-per-day": "Maksimalt antall innlegg som kan flagges per dag",
+ "flags.limit-post-flags-per-day": "Maksimalt antall innlegg som kan rapporteres per dag",
"flags.limit-post-flags-per-day-help": "Angi antall innlegg som kan flagges av en bruker innen en dag.",
"flags.limit-user-flags-per-day": "Maksimalt antall brukere som kan flagges per dag",
"flags.limit-user-flags-per-day-help": "Angi antall brukere som kan flagges av en bruker innen en dag.",
diff --git a/public/language/nb/category.json b/public/language/nb/category.json
index 2f274001a6..3fff71221d 100644
--- a/public/language/nb/category.json
+++ b/public/language/nb/category.json
@@ -1,13 +1,13 @@
{
"category": "Kategori",
"subcategories": "Underkategorier",
- "uncategorized": "Uncategorized",
- "uncategorized.description": "Topics that do not strictly fit in with any existing categories",
+ "uncategorized": "Ukategorisert",
+ "uncategorized.description": "Innlegg som ikke passer inn i noen av de eksisterende kategoriene.",
"handle.description": "This category can be followed from the open social web via the handle %1",
- "new-topic-button": "Nytt emne",
+ "new-topic-button": "Nytt innlegg",
"guest-login-post": "Logg inn for å publisere innlegg",
"no-topics": "Denne kategorien er foreløpig tom.Do you really want to purge this category \"%1\"?
Purging a category will remove all topics and posts, and delete the category from the database. If you want to remove a category temporarily, you'll want to \"disable\" the category instead.
", "alert.purge-success": "Category purged!", "alert.copy-success": "Settings Copied!", diff --git a/public/language/nl/admin/settings/activitypub.json b/public/language/nl/admin/settings/activitypub.json index 94f9ad7822..1b00672e0f 100644 --- a/public/language/nl/admin/settings/activitypub.json +++ b/public/language/nl/admin/settings/activitypub.json @@ -18,6 +18,27 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.one,two,three)",
+ "rules.add": "Add New Rule",
+ "rules.type": "Type",
+ "rules.value": "Value",
+ "rules.cid": "Category",
+
+ "relays": "Relays",
+ "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.",
+ "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.",
+ "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.",
+ "relays.add": "Add New Relay",
+ "relays.relay": "Relay",
+ "relays.state": "State",
+ "relays.state-0": "Pending",
+ "relays.state-1": "Receiving only",
+ "relays.state-2": "Active",
+
"server-filtering": "Filtering",
"count": "This NodeBB is currently aware of %1 server(s)",
"server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.",
diff --git a/public/language/nl/error.json b/public/language/nl/error.json
index 83670b19ba..662a161197 100644
--- a/public/language/nl/error.json
+++ b/public/language/nl/error.json
@@ -3,6 +3,7 @@
"invalid-json": "Ongeldige JSON",
"wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead",
"required-parameters-missing": "Required parameters were missing from this API call: %1",
+ "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.",
"not-logged-in": "Het lijkt erop dat je niet ingelogd bent.",
"account-locked": "Je account is tijdelijk vergrendeld",
"search-requires-login": "Zoeken vereist een account - meld je aan of registreer je om te zoeken.",
@@ -236,6 +237,7 @@
"socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later",
"invalid-plugin-id": "Invalid plugin ID",
"plugin-not-whitelisted": "Kan plugin niet installeren – alleen plugins toegestaan door de NodeBB Package Manager kunnen via de ACP geinstalleerd worden",
+ "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin",
"plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled",
"plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.",
"theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP",
diff --git a/public/language/nl/modules.json b/public/language/nl/modules.json
index cbe6bc5603..1e9e509560 100644
--- a/public/language/nl/modules.json
+++ b/public/language/nl/modules.json
@@ -48,6 +48,7 @@
"chat.add-user": "Add User",
"chat.notification-settings": "Notification Settings",
"chat.default-notification-setting": "Default Notification Setting",
+ "chat.join-leave-messages": "Join/Leave Messages",
"chat.notification-setting-room-default": "Room Default",
"chat.notification-setting-none": "No notifications",
"chat.notification-setting-at-mention-only": "@mention only",
diff --git a/public/language/nl/social.json b/public/language/nl/social.json
index 2ba690a187..5b8dd99a46 100644
--- a/public/language/nl/social.json
+++ b/public/language/nl/social.json
@@ -8,5 +8,7 @@
"log-in-with-facebook": "Log in with Facebook",
"continue-with-facebook": "Continue with Facebook",
"sign-in-with-linkedin": "Sign in with LinkedIn",
- "sign-up-with-linkedin": "Sign up with LinkedIn"
+ "sign-up-with-linkedin": "Sign up with LinkedIn",
+ "sign-in-with-wordpress": "Sign in with WordPress",
+ "sign-up-with-wordpress": "Sign up with WordPress"
}
\ No newline at end of file
diff --git a/public/language/nn-NO/admin/dashboard.json b/public/language/nn-NO/admin/dashboard.json
index c852084c8f..2f31eb632b 100644
--- a/public/language/nn-NO/admin/dashboard.json
+++ b/public/language/nn-NO/admin/dashboard.json
@@ -65,7 +65,7 @@
"on-categories": "I kategoriar",
"reading-posts": "Les innlegg",
"browsing-topics": "Blar gjennom emne",
- "recent": "Nyleg",
+ "recent": "Siste",
"unread": "Uleste",
"high-presence-topics": "Emne med høg aktivitet",
@@ -75,6 +75,7 @@
"graphs.page-views-registered": "Diagram over sidevisningar (registrerte)",
"graphs.page-views-guest": "Diagram over sidevisningar (gjestar)",
"graphs.page-views-bot": "Diagram over sidevisningar (botar)",
+ "graphs.page-views-ap": "ActivityPub Page Views",
"graphs.unique-visitors": "Diagram over unike besøkande",
"graphs.registered-users": "Diagram over registrerte brukarar",
"graphs.guest-users": "Diagram over gjestar",
diff --git a/public/language/nn-NO/admin/manage/categories.json b/public/language/nn-NO/admin/manage/categories.json
index 6d947aacb2..900971a999 100644
--- a/public/language/nn-NO/admin/manage/categories.json
+++ b/public/language/nn-NO/admin/manage/categories.json
@@ -1,11 +1,15 @@
{
"manage-categories": "Administrer kategoriar",
"add-category": "Legg til kategori",
+ "add-local-category": "Add Local category",
+ "add-remote-category": "Add Remote category",
+ "remove": "Remove",
"jump-to": "Hopp til",
"settings": "Innstillingar",
"edit-category": "Rediger kategori",
"privileges": "Rettar",
"back-to-categories": "Tilbake til kategoriar",
+ "id": "Category ID",
"name": "Namn",
"handle": "Kategori-sti",
"handle.help": " Kategori-stien din blir brukt som ein representasjon av denne kategorien på andre nettverk, som eit brukarnamn. Ein kategori-sti må ikkje samsvare med eit eksisterande brukarnamn eller ei brukargruppe.",
@@ -86,23 +90,25 @@
"federation.disabled": "Føderasjon er deaktivert for heile nettstaden, så innstillingar for kategori-føderasjon er for tida ikkje tilgjengelege.",
"federation.disabled-cta": "Føderasjonsinnstillingar →",
"federation.syncing-header": "Synkronisering",
- "federation.syncing-intro": "Ein kategori kan følgje ein “Gruppeaktør” via ActivityPub-protokollen. Dersom innhald blir mottatt frå ein av aktørane som er lista opp nedanfor, vil det automatisk bli lagt til i denne kategorien.",
- "federation.syncing-caveat": " Merk: Å konfigurere synkronisering her opprettar ein einvegs synkronisering. NodeBB prøver å abonnere på/følgje aktøren, men det motsette kan ikkje føresetjast.",
- "federation.syncing-none": "Denne kategorien følgjer for tida ingen.",
+ "federation.syncing-intro": "Ein kategori kan følge ein “Gruppeaktør” via ActivityPub-protokollen. Dersom innhald blir mottatt frå ein av aktørane som er lista opp nedanfor, vil det automatisk bli lagt til i denne kategorien.",
+ "federation.syncing-caveat": " Merk: Å konfigurere synkronisering her opprettar ein einvegs synkronisering. NodeBB prøver å abonnere på/følge aktøren, men det motsette kan ikkje føresetjast.",
+ "federation.syncing-none": "Denne kategorien følger for tida ingen.",
"federation.syncing-add": "Synkronser med...",
"federation.syncing-actorUri": "Aktør",
"federation.syncing-follow": "Følg",
"federation.syncing-unfollow": "Avfølg",
- "federation.followers": "Eksterne brukarar som følgjer denne kategorien",
+ "federation.followers": "Eksterne brukarar som følger denne kategorien",
"federation.followers-handle": "Sti",
"federation.followers-id": "ID",
- "federation.followers-none": "Ingen følgjarar.",
+ "federation.followers-none": "Ingen følgarar.",
"federation.followers-autofill": "Autofill",
"alert.created": "Oppretta",
"alert.create-success": "Kategori oppretta med suksess",
"alert.none-active": "Ingen aktive kategoriar",
"alert.create": "Opprett kategori",
+ "alert.add": "Add a Category",
+ "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.one,two,three)",
+ "rules.add": "Add New Rule",
+ "rules.type": "Type",
+ "rules.value": "Value",
+ "rules.cid": "Category",
+
+ "relays": "Relays",
+ "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.",
+ "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.",
+ "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.",
+ "relays.add": "Add New Relay",
+ "relays.relay": "Relay",
+ "relays.state": "State",
+ "relays.state-0": "Pending",
+ "relays.state-1": "Receiving only",
+ "relays.state-2": "Active",
+
"server-filtering": "Filtrer etter",
"count": "Denne NodeBB-en er for tida klar over %1 server(ar)",
"server.filter-help": "Spesifiser serverar du ønskjer å hindre frå å føderere med din NodeBB. Alternativt kan du velje å tillate føderasjon berre med spesifikke serverar. Begge alternativ er støtta, men dei er gjensidig utelukkande.",
diff --git a/public/language/nn-NO/admin/settings/post.json b/public/language/nn-NO/admin/settings/post.json
index 10d4b92671..3534e580a9 100644
--- a/public/language/nn-NO/admin/settings/post.json
+++ b/public/language/nn-NO/admin/settings/post.json
@@ -6,7 +6,7 @@
"sorting.newest-to-oldest": "Nyaste til eldste",
"sorting.recently-replied": "Sist svart",
"sorting.recently-created": "Nyleg oppretta",
- "sorting.most-votes": "Fleire stemmer",
+ "sorting.most-votes": "Flest anbefalingar",
"sorting.most-posts": "Fleire innlegg",
"sorting.most-views": "Fleire visningar",
"sorting.topic-default": "Standard sortering for emne",
@@ -39,7 +39,7 @@
"teaser.last-reply": "Siste svar",
"teaser.first": "Fyrste innlegg",
"showPostPreviewsOnHover": "Vis innlegg ved førhandsvising",
- "unread-and-recent": "Uleste og nylege",
+ "unread-and-recent": "Innstillingar for Uleste og Siste",
"unread.cutoff": "Avskjering for uleste",
"unread.min-track-last": "Minste sporingslengd for siste innlegg",
"recent.max-topics": "Maksimum tal emne nyleg",
diff --git a/public/language/nn-NO/admin/settings/reputation.json b/public/language/nn-NO/admin/settings/reputation.json
index bcf7b0cf51..d3ac095bc3 100644
--- a/public/language/nn-NO/admin/settings/reputation.json
+++ b/public/language/nn-NO/admin/settings/reputation.json
@@ -2,18 +2,18 @@
"reputation": "Omdømme",
"disable": "Deaktiver",
"disable-down-voting": "Deaktiver nedstemming",
- "upvote-visibility": "Synlegheit for oppstemmer",
- "upvote-visibility-all": "Alle kan sjå oppstemmer",
- "upvote-visibility-loggedin": "Berre innlogga brukarar kan sjå oppstemmer",
- "upvote-visibility-privileged": "Berre privilegerte brukarar kan sjå oppstemmer",
+ "upvote-visibility": "Synlegheit for anbefalingar",
+ "upvote-visibility-all": "Alle kan sjå anbefalingar",
+ "upvote-visibility-loggedin": "Berre innlogga brukarar kan sjå anbefalingar",
+ "upvote-visibility-privileged": "Berre privilegerte brukarar kan sjå anbefalingar",
"downvote-visibility": "Synlegheit for nedstemmer",
"downvote-visibility-all": "Alle kan sjå nedstemmer",
"downvote-visibility-loggedin": "Berre innlogga brukarar kan sjå nedstemmer",
"downvote-visibility-privileged": "Berre privilegerte brukarar kan sjå nedstemmer",
"thresholds": "Grenseverdiar",
- "min-rep-upvote": "Minimum omdømme for å tilrå",
- "upvotes-per-day": "Tilrådingar per dag",
- "upvotes-per-user-per-day": "Tilrådingar per brukar per dag",
+ "min-rep-upvote": "Minimum omdømme for å anbefale",
+ "upvotes-per-day": "Anbefalingar per dag",
+ "upvotes-per-user-per-day": "Anbefalingar per brukar per dag",
"min-rep-downvote": "Minimum omdømme for å stemme ned",
"downvotes-per-day": "Nedstemmer per dag",
"downvotes-per-user-per-day": "Nedstemmer per brukar per dag",
diff --git a/public/language/nn-NO/admin/settings/sounds.json b/public/language/nn-NO/admin/settings/sounds.json
index f83f14d0f6..71970eccc7 100644
--- a/public/language/nn-NO/admin/settings/sounds.json
+++ b/public/language/nn-NO/admin/settings/sounds.json
@@ -1,6 +1,6 @@
{
"notifications": "Varsel",
- "chat-messages": "Chatmeldingar",
+ "chat-messages": "Meldingar",
"play-sound": "Spel av lyd",
"incoming-message": "Innkommande melding",
"outgoing-message": "Utgåande melding",
diff --git a/public/language/nn-NO/admin/settings/user.json b/public/language/nn-NO/admin/settings/user.json
index 5dc36d2d55..77b367a63b 100644
--- a/public/language/nn-NO/admin/settings/user.json
+++ b/public/language/nn-NO/admin/settings/user.json
@@ -63,7 +63,7 @@
"default-user-settings": "Standardinnstillingar for brukar",
"show-email": "Vis e-post",
"show-fullname": "Vis fullt namn",
- "restrict-chat": "Avgrens chat",
+ "restrict-chat": "Tillat berre meldingar frå brukarar eg følger",
"disable-incoming-chats": "Disable incoming chat messages",
"outgoing-new-tab": "Utgåande lenkjer i ny fane",
"topic-search": "Emnesøk",
@@ -81,7 +81,7 @@
"default-notification-settings": "Standard varslingsinnstillingar",
"categoryWatchState": "Kategori-overvåking",
"categoryWatchState.tracking": "Sporing",
- "categoryWatchState.notwatching": "Ikkje overvåking",
+ "categoryWatchState.notwatching": "Følger ikkje",
"categoryWatchState.ignoring": "Ignorerer",
"restrictions-new": "Nye restriksjonar",
"restrictions.rep-threshold": "Omdømmegrense",
diff --git a/public/language/nn-NO/category.json b/public/language/nn-NO/category.json
index 22f6ffeaa3..6ef0275e7f 100644
--- a/public/language/nn-NO/category.json
+++ b/public/language/nn-NO/category.json
@@ -1,30 +1,30 @@
{
"category": "Kategori",
"subcategories": "Underkategoriar",
- "uncategorized": "Uncategorized",
- "uncategorized.description": "Topics that do not strictly fit in with any existing categories",
+ "uncategorized": "Ukategorisert",
+ "uncategorized.description": "Innlegg som ikkje passar helt inn i nokon av dei eksisterande kategoriane.",
"handle.description": "This category can be followed from the open social web via the handle %1",
- "new-topic-button": "Nytt emne",
+ "new-topic-button": "Nytt innlegg",
"guest-login-post": "Logg inn for å legge inn innlegg",
"no-topics": "Denne kategorien er foreløpig tom.Czy na pewno chcesz wymazać tą kategorię \"%1\"?
Wymazanie kategorii skasuje wszystkie tematy, posty oraz skasuję kategorię z bazy danych. Jeśli chcesz tymczasowousunąć kategorię, będziesz musiał \"wyłączyć\" kategorię.
", "alert.purge-success": "Kategoria wymazana!", "alert.copy-success": "Ustawienie skopiowane!", diff --git a/public/language/pl/admin/settings/activitypub.json b/public/language/pl/admin/settings/activitypub.json index 31aa6e7282..5ea1cccc2b 100644 --- a/public/language/pl/admin/settings/activitypub.json +++ b/public/language/pl/admin/settings/activitypub.json @@ -18,6 +18,27 @@ "probe-timeout": "Czas oczekiwania (milisekundy)", "probe-timeout-help": "(Domyślnie: 2000) O ile zapytanie nie doczeka się odpowiedzi w tym czasie to użytkownik otrzyma odnośnik bezpośredni. Możesz wydłużyć czas oczekiwania o ile strony działają wolno a mimo to chcesz dać im szansę.", + "rules": "Przyszeregowanie", + "rules-intro": "Zawartość odkrywana via ActivePub może być automatycznie przyszeregowana w oparciu m.in o hashtag", + "rules.modal.title": "Jak to działa", + "rules.modal.instructions": "Wszelka zawartość z zewnątrz jest sprawdzana pod kątem zasad przyszeregowania a zatem z automatu umieszczana we własciwych kategoriach.jeden,dwa,trzy)",
+ "rules.add": "Dodaj nową regułę",
+ "rules.type": "Typ",
+ "rules.value": "Wartość",
+ "rules.cid": "Kategoria",
+
+ "relays": "Przekaźniki",
+ "relays.intro": "Przekaźnik usprawnia odnajdowanie zawartości do i z Twojego NodeBB. Dodanie przekaźnika oznacza, że odebrana zawartość z jego udziałem jest przekierowywana w to miejsce i analogicznie z tym co ma być widoczne z zewnątrz.",
+ "relays.warning": "Uwaga: przekaźniki mogą tworzyć duży ruch a także zwiększyć wykorzystanie pamięci masowej a co za tym idzie kosztów.",
+ "relays.litepub": "NodeBB nawiązuje do LitePub jeśli idzie o przekaźniki. Wprowadzony URL powinien kończyć się znakiem /.",
+ "relays.add": "Dodaj nowy przekaźnik",
+ "relays.relay": "Przekaźnik",
+ "relays.state": "Stan",
+ "relays.state-0": "Oczekujący",
+ "relays.state-1": "Tylko odbiór",
+ "relays.state-2": "Aktywny",
+
"server-filtering": "Filtrowanie",
"count": "NodeBB obecnie wykrywa 1% serwerów",
"server.filter-help": "Określ serwery, z którymi nie chcesz spinać NodeBB w ramach fediverse. Alternatywnie możesz dobrać dozwolone serwery fediverse. Obie opcje są dostępne ale wybierz jedną z nich.",
diff --git a/public/language/pl/error.json b/public/language/pl/error.json
index 135608bb95..27cb6c9d42 100644
--- a/public/language/pl/error.json
+++ b/public/language/pl/error.json
@@ -3,6 +3,7 @@
"invalid-json": "Niewłaściwy JSON",
"wrong-parameter-type": "Wartość typu %3 była oczekiwania dla właściwości `%1`, ale %2 został dostarczony",
"required-parameters-missing": "Brakowało wymaganych parametrów w tym żądaniu API: %1",
+ "reserved-ip-address": "Wywołania sieciowe do zarezerwowanych zakresów IP nie są dozwolone.",
"not-logged-in": "Nie jesteś zalogowany(-a).",
"account-locked": "Twoje konto zostało tymczasowo zablokowane",
"search-requires-login": "Wyszukiwanie wymaga konta - zaloguj się lub zarejestruj.",
@@ -236,6 +237,7 @@
"socket-reconnect-failed": "W tej chwili nie można połączyć się z serwerem. Kliknij tutaj, aby spróbować ponownie, lub spróbuj ponownie później",
"invalid-plugin-id": "Niepoprawny identyfikator wtyczki",
"plugin-not-whitelisted": "Nie da się zainstalować tej wtyczki – tylko wtyczki z białej listy menadżera pakietów NodeBB mogą być instalowane przez ACP",
+ "cannot-toggle-system-plugin": "Nie można zmienić nastawu dla wtyczki systemowej",
"plugin-installation-via-acp-disabled": "Instalacja wtyczek przez ACP jest wyłączona",
"plugins-set-in-configuration": "Nie możesz zmienić stanu wtyczki, bo został on zdefiniowany przy uruchamianiu (config.json, zmienne środowiskowe lub argumenty z terminala). Zamiast tego zmień konfigurację.",
"theme-not-set-in-configuration": "Pamiętaj o zależności między aktywnymi wtyczkami a wystrojem, który ma z nimi współpracować.",
diff --git a/public/language/pl/modules.json b/public/language/pl/modules.json
index 65f5d143de..3e52bcddc8 100644
--- a/public/language/pl/modules.json
+++ b/public/language/pl/modules.json
@@ -48,6 +48,7 @@
"chat.add-user": "Dodaj użytkownika",
"chat.notification-settings": "Ustawienia powiadomień",
"chat.default-notification-setting": "Domyślne ustawienia powiadomień",
+ "chat.join-leave-messages": "Dołącz/Odłącz wiadomości",
"chat.notification-setting-room-default": "Domyślne dla pokoju",
"chat.notification-setting-none": "Brak powiadomień",
"chat.notification-setting-at-mention-only": "Tylko zawołania z użyciem @",
diff --git a/public/language/pl/social.json b/public/language/pl/social.json
index 25d33196d0..07559bcee7 100644
--- a/public/language/pl/social.json
+++ b/public/language/pl/social.json
@@ -8,5 +8,7 @@
"log-in-with-facebook": "Zaloguj się przez Facebook",
"continue-with-facebook": "Kontynuuj z Facebook",
"sign-in-with-linkedin": "Zaloguj się przez LinkedIn",
- "sign-up-with-linkedin": "Zarejestruj się przez LinkedIn"
+ "sign-up-with-linkedin": "Zarejestruj się przez LinkedIn",
+ "sign-in-with-wordpress": "Zaloguj z WordPress",
+ "sign-up-with-wordpress": "Zarejestruj z WordPress"
}
\ No newline at end of file
diff --git a/public/language/pt-BR/admin/advanced/database.json b/public/language/pt-BR/admin/advanced/database.json
index 918fb8babd..ebf2a34e13 100644
--- a/public/language/pt-BR/admin/advanced/database.json
+++ b/public/language/pt-BR/admin/advanced/database.json
@@ -17,7 +17,7 @@
"mongo.file-size": "Tamanho do Arquivo",
"mongo.resident-memory": "Resident Memory",
"mongo.virtual-memory": "Memória Virtual",
- "mongo.mapped-memory": "Mapped Memory",
+ "mongo.mapped-memory": "Memória Canalizada",
"mongo.bytes-in": "Bytes recebidos",
"mongo.bytes-out": "Bytes enviados",
"mongo.num-requests": "Quantidade de Requisições",
diff --git a/public/language/pt-BR/admin/dashboard.json b/public/language/pt-BR/admin/dashboard.json
index 49dd02ba69..0e42f09063 100644
--- a/public/language/pt-BR/admin/dashboard.json
+++ b/public/language/pt-BR/admin/dashboard.json
@@ -75,6 +75,7 @@
"graphs.page-views-registered": "Páginas Visualizadas por Registrados",
"graphs.page-views-guest": "Páginas Visualizadas por Visitantes",
"graphs.page-views-bot": "Páginas Visualizadas por Bot",
+ "graphs.page-views-ap": "ActivityPub Page Views",
"graphs.unique-visitors": "Visitantes Únicos",
"graphs.registered-users": "Usuários Registrados",
"graphs.guest-users": "Guest Users",
diff --git a/public/language/pt-BR/admin/manage/categories.json b/public/language/pt-BR/admin/manage/categories.json
index 28318bdae9..c07d5537a5 100644
--- a/public/language/pt-BR/admin/manage/categories.json
+++ b/public/language/pt-BR/admin/manage/categories.json
@@ -1,11 +1,15 @@
{
"manage-categories": "Manage Categories",
"add-category": "Add category",
+ "add-local-category": "Add Local category",
+ "add-remote-category": "Add Remote category",
+ "remove": "Remove",
"jump-to": "Jump to...",
"settings": "Configurações de Categorias",
"edit-category": "Edit Category",
"privileges": "Privilégios",
"back-to-categories": "Back to categories",
+ "id": "Category ID",
"name": "Nome da Categoria",
"handle": "Category Handle",
"handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.",
@@ -103,6 +107,8 @@
"alert.create-success": "Categoria criada com sucesso!",
"alert.none-active": "Você não possui categorias ativas.",
"alert.create": "Criar uma Categoria",
+ "alert.add": "Add a Category",
+ "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.Você realmente quer purgar esta categoria \"%1\"?
Purgar uma categoria removerá todos os tópicos e posts, e deletará a categoria do banco de dados. Se você quiser remover uma categoria temporariamente, ao invés de fazer isso nós recomendados que você \"desabilite\" a categoria.
", "alert.purge-success": "Categoria purgada!", "alert.copy-success": "Configurações Copiadas!", diff --git a/public/language/pt-BR/admin/settings/activitypub.json b/public/language/pt-BR/admin/settings/activitypub.json index 94f9ad7822..1b00672e0f 100644 --- a/public/language/pt-BR/admin/settings/activitypub.json +++ b/public/language/pt-BR/admin/settings/activitypub.json @@ -18,6 +18,27 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.one,two,three)",
+ "rules.add": "Add New Rule",
+ "rules.type": "Type",
+ "rules.value": "Value",
+ "rules.cid": "Category",
+
+ "relays": "Relays",
+ "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.",
+ "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.",
+ "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.",
+ "relays.add": "Add New Relay",
+ "relays.relay": "Relay",
+ "relays.state": "State",
+ "relays.state-0": "Pending",
+ "relays.state-1": "Receiving only",
+ "relays.state-2": "Active",
+
"server-filtering": "Filtering",
"count": "This NodeBB is currently aware of %1 server(s)",
"server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.",
diff --git a/public/language/pt-BR/error.json b/public/language/pt-BR/error.json
index 111b323f42..2fc1039862 100644
--- a/public/language/pt-BR/error.json
+++ b/public/language/pt-BR/error.json
@@ -3,6 +3,7 @@
"invalid-json": "JSON Inválido",
"wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead",
"required-parameters-missing": "Required parameters were missing from this API call: %1",
+ "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.",
"not-logged-in": "Você não parece estar logado.",
"account-locked": "Sua conta foi temporariamente bloqueada",
"search-requires-login": "É necessário ter uma conta para pesquisar - por favor efetue o login ou cadastre-se.",
@@ -236,6 +237,7 @@
"socket-reconnect-failed": "Não foi possível acessar o servidor neste momento. Clique aqui para tentar novamente ou tente novamente mais tarde",
"invalid-plugin-id": "Invalid plugin ID",
"plugin-not-whitelisted": "Não foi possível instalar o plugin - apenas os plug-ins permitidos pelo NodeBB Package Manager podem ser instalados através do ACP",
+ "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin",
"plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled",
"plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.",
"theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP",
diff --git a/public/language/pt-BR/global.json b/public/language/pt-BR/global.json
index 798c4ef50d..d3b69cd790 100644
--- a/public/language/pt-BR/global.json
+++ b/public/language/pt-BR/global.json
@@ -79,7 +79,7 @@
"upvoters": "Votos positivos",
"upvoted": "Votou positivamente",
"downvoters": "Votos negativos",
- "downvoted": "Votou negativamente",
+ "downvoted": "Rebaixou",
"views": "Visualizações",
"posters": "Posters",
"watching": "Watching",
@@ -136,7 +136,7 @@
"allowed-file-types": "Os tipos de arquivo permitidos são %1",
"unsaved-changes": "Você tem alterações não salvas. Tem certeza de que você deseja sair da página?",
"reconnecting-message": "Parece que a sua conexão com o %1 caiu. Por favor, aguarde enquanto tentamos reconectar.",
- "play": "Executar",
+ "play": "Tocar",
"cookies.message": "Este site usa cookies para garantir que você obtenha a melhor experiência em nosso site.",
"cookies.accept": "Entendi!",
"cookies.learn-more": "Saber Mais",
diff --git a/public/language/pt-BR/modules.json b/public/language/pt-BR/modules.json
index 64a27be0d1..823391370f 100644
--- a/public/language/pt-BR/modules.json
+++ b/public/language/pt-BR/modules.json
@@ -48,6 +48,7 @@
"chat.add-user": "Add User",
"chat.notification-settings": "Notification Settings",
"chat.default-notification-setting": "Default Notification Setting",
+ "chat.join-leave-messages": "Join/Leave Messages",
"chat.notification-setting-room-default": "Room Default",
"chat.notification-setting-none": "No notifications",
"chat.notification-setting-at-mention-only": "@mention only",
diff --git a/public/language/pt-BR/notifications.json b/public/language/pt-BR/notifications.json
index 0d94954404..6cc2461f7d 100644
--- a/public/language/pt-BR/notifications.json
+++ b/public/language/pt-BR/notifications.json
@@ -65,7 +65,7 @@
"new-register-multiple": "Há %1 pedidos de registro aguardando revisão.",
"flag-assigned-to-you": "A Sinalização %1 foi atribuída a você",
"post-awaiting-review": "Post aguardando revisão",
- "profile-exported": "%1 perfil exportado, clique para fazer download",
+ "profile-exported": "%1 dados do perfil exportado, clique para fazer download",
"posts-exported": "%1 posts exportados, clique para fazer download",
"uploads-exported": "%1 uploads exportados, clique para fazer download",
"users-csv-exported": "Usuários csv exportados, clique para fazer o download",
diff --git a/public/language/pt-BR/pages.json b/public/language/pt-BR/pages.json
index ed80563df7..098b314683 100644
--- a/public/language/pt-BR/pages.json
+++ b/public/language/pt-BR/pages.json
@@ -56,7 +56,7 @@
"account/watched": "Tópicos assistidos por %1",
"account/ignored": "Tópicos ignorados por %1",
"account/read": "Topics read by %1",
- "account/upvoted": "Posts votados positivamente por %1",
+ "account/upvoted": "Posts alavancados por %1",
"account/downvoted": "Posts votados negativamente por %1",
"account/best": "Melhores posts de %1",
"account/controversial": "Controversial posts made by %1",
diff --git a/public/language/pt-BR/social.json b/public/language/pt-BR/social.json
index 2ba690a187..5b8dd99a46 100644
--- a/public/language/pt-BR/social.json
+++ b/public/language/pt-BR/social.json
@@ -8,5 +8,7 @@
"log-in-with-facebook": "Log in with Facebook",
"continue-with-facebook": "Continue with Facebook",
"sign-in-with-linkedin": "Sign in with LinkedIn",
- "sign-up-with-linkedin": "Sign up with LinkedIn"
+ "sign-up-with-linkedin": "Sign up with LinkedIn",
+ "sign-in-with-wordpress": "Sign in with WordPress",
+ "sign-up-with-wordpress": "Sign up with WordPress"
}
\ No newline at end of file
diff --git a/public/language/pt-PT/admin/dashboard.json b/public/language/pt-PT/admin/dashboard.json
index 6f82ecb7e2..64fda774f5 100644
--- a/public/language/pt-PT/admin/dashboard.json
+++ b/public/language/pt-PT/admin/dashboard.json
@@ -75,6 +75,7 @@
"graphs.page-views-registered": "Visualizações de páginas por utilizadores registados",
"graphs.page-views-guest": "Visualizações de páginas por convidados",
"graphs.page-views-bot": "Visualizações de páginas por bots",
+ "graphs.page-views-ap": "ActivityPub Page Views",
"graphs.unique-visitors": "Visitantes únicos",
"graphs.registered-users": "Utilizadores Registados",
"graphs.guest-users": "Guest Users",
diff --git a/public/language/pt-PT/admin/manage/categories.json b/public/language/pt-PT/admin/manage/categories.json
index 352db735c2..75d054e475 100644
--- a/public/language/pt-PT/admin/manage/categories.json
+++ b/public/language/pt-PT/admin/manage/categories.json
@@ -1,11 +1,15 @@
{
"manage-categories": "Manage Categories",
"add-category": "Add category",
+ "add-local-category": "Add Local category",
+ "add-remote-category": "Add Remote category",
+ "remove": "Remove",
"jump-to": "Jump to...",
"settings": "Definições da Categoria",
"edit-category": "Edit Category",
"privileges": "Privilégios",
"back-to-categories": "Back to categories",
+ "id": "Category ID",
"name": "Nome da Categoria",
"handle": "Category Handle",
"handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.",
@@ -103,6 +107,8 @@
"alert.create-success": "Categoria criada com sucesso!",
"alert.none-active": "Não tens categorias ativas.",
"alert.create": "Criar uma Categoria",
+ "alert.add": "Add a Category",
+ "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.Tens a certeza que pretendes eliminar definitivamente esta categoria \"%1\"?
\nEliminar uma categoria irá remover todos os tópicos e publicações e eliminar a categoria da base de dados. Se pretendes remover temporariamente uma categoria, em vez disso podes apenas \"desativar\" essa categoria.
", "alert.purge-success": "Categoria eliminada!", "alert.copy-success": "Definições Copiadas!", diff --git a/public/language/pt-PT/admin/settings/activitypub.json b/public/language/pt-PT/admin/settings/activitypub.json index 94f9ad7822..1b00672e0f 100644 --- a/public/language/pt-PT/admin/settings/activitypub.json +++ b/public/language/pt-PT/admin/settings/activitypub.json @@ -18,6 +18,27 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.one,two,three)",
+ "rules.add": "Add New Rule",
+ "rules.type": "Type",
+ "rules.value": "Value",
+ "rules.cid": "Category",
+
+ "relays": "Relays",
+ "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.",
+ "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.",
+ "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.",
+ "relays.add": "Add New Relay",
+ "relays.relay": "Relay",
+ "relays.state": "State",
+ "relays.state-0": "Pending",
+ "relays.state-1": "Receiving only",
+ "relays.state-2": "Active",
+
"server-filtering": "Filtering",
"count": "This NodeBB is currently aware of %1 server(s)",
"server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.",
diff --git a/public/language/pt-PT/error.json b/public/language/pt-PT/error.json
index 95fa2edfa4..fa0418c41c 100644
--- a/public/language/pt-PT/error.json
+++ b/public/language/pt-PT/error.json
@@ -3,6 +3,7 @@
"invalid-json": "JSON inválido",
"wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead",
"required-parameters-missing": "Required parameters were missing from this API call: %1",
+ "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.",
"not-logged-in": "Não tens sessão iniciada.",
"account-locked": "A sua conta foi bloqueada temporariamente",
"search-requires-login": "A pesquisa requer uma conta de utilizador - por favor inicia sessão ou cria uma conta.",
@@ -236,6 +237,7 @@
"socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later",
"invalid-plugin-id": "Invalid plugin ID",
"plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP",
+ "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin",
"plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled",
"plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.",
"theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP",
diff --git a/public/language/pt-PT/modules.json b/public/language/pt-PT/modules.json
index 5b3018d4fa..6e918bcc11 100644
--- a/public/language/pt-PT/modules.json
+++ b/public/language/pt-PT/modules.json
@@ -48,6 +48,7 @@
"chat.add-user": "Add User",
"chat.notification-settings": "Notification Settings",
"chat.default-notification-setting": "Default Notification Setting",
+ "chat.join-leave-messages": "Join/Leave Messages",
"chat.notification-setting-room-default": "Room Default",
"chat.notification-setting-none": "No notifications",
"chat.notification-setting-at-mention-only": "@mention only",
diff --git a/public/language/pt-PT/social.json b/public/language/pt-PT/social.json
index 2ba690a187..5b8dd99a46 100644
--- a/public/language/pt-PT/social.json
+++ b/public/language/pt-PT/social.json
@@ -8,5 +8,7 @@
"log-in-with-facebook": "Log in with Facebook",
"continue-with-facebook": "Continue with Facebook",
"sign-in-with-linkedin": "Sign in with LinkedIn",
- "sign-up-with-linkedin": "Sign up with LinkedIn"
+ "sign-up-with-linkedin": "Sign up with LinkedIn",
+ "sign-in-with-wordpress": "Sign in with WordPress",
+ "sign-up-with-wordpress": "Sign up with WordPress"
}
\ No newline at end of file
diff --git a/public/language/ro/admin/dashboard.json b/public/language/ro/admin/dashboard.json
index 6ad973f5f3..0be6d5866c 100644
--- a/public/language/ro/admin/dashboard.json
+++ b/public/language/ro/admin/dashboard.json
@@ -75,6 +75,7 @@
"graphs.page-views-registered": "Page Views Registered",
"graphs.page-views-guest": "Page Views Guest",
"graphs.page-views-bot": "Page Views Bot",
+ "graphs.page-views-ap": "ActivityPub Page Views",
"graphs.unique-visitors": "Unique Visitors",
"graphs.registered-users": "Registered Users",
"graphs.guest-users": "Guest Users",
diff --git a/public/language/ro/admin/manage/categories.json b/public/language/ro/admin/manage/categories.json
index f51152f22d..d66dd814a1 100644
--- a/public/language/ro/admin/manage/categories.json
+++ b/public/language/ro/admin/manage/categories.json
@@ -1,11 +1,15 @@
{
"manage-categories": "Manage Categories",
"add-category": "Add category",
+ "add-local-category": "Add Local category",
+ "add-remote-category": "Add Remote category",
+ "remove": "Remove",
"jump-to": "Jump to...",
"settings": "Category Settings",
"edit-category": "Edit Category",
"privileges": "Privileges",
"back-to-categories": "Back to categories",
+ "id": "Category ID",
"name": "Category Name",
"handle": "Category Handle",
"handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.",
@@ -103,6 +107,8 @@
"alert.create-success": "Category successfully created!",
"alert.none-active": "You have no active categories.",
"alert.create": "Create a Category",
+ "alert.add": "Add a Category",
+ "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.Do you really want to purge this category \"%1\"?
Purging a category will remove all topics and posts, and delete the category from the database. If you want to remove a category temporarily, you'll want to \"disable\" the category instead.
", "alert.purge-success": "Category purged!", "alert.copy-success": "Settings Copied!", diff --git a/public/language/ro/admin/settings/activitypub.json b/public/language/ro/admin/settings/activitypub.json index 94f9ad7822..1b00672e0f 100644 --- a/public/language/ro/admin/settings/activitypub.json +++ b/public/language/ro/admin/settings/activitypub.json @@ -18,6 +18,27 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.one,two,three)",
+ "rules.add": "Add New Rule",
+ "rules.type": "Type",
+ "rules.value": "Value",
+ "rules.cid": "Category",
+
+ "relays": "Relays",
+ "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.",
+ "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.",
+ "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.",
+ "relays.add": "Add New Relay",
+ "relays.relay": "Relay",
+ "relays.state": "State",
+ "relays.state-0": "Pending",
+ "relays.state-1": "Receiving only",
+ "relays.state-2": "Active",
+
"server-filtering": "Filtering",
"count": "This NodeBB is currently aware of %1 server(s)",
"server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.",
diff --git a/public/language/ro/error.json b/public/language/ro/error.json
index 472aa0fa53..abc63f3b06 100644
--- a/public/language/ro/error.json
+++ b/public/language/ro/error.json
@@ -3,6 +3,7 @@
"invalid-json": "Invalid JSON",
"wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead",
"required-parameters-missing": "Required parameters were missing from this API call: %1",
+ "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.",
"not-logged-in": "Se pare ca nu ești logat.",
"account-locked": "Contul tău a fost blocat temporar",
"search-requires-login": "Pentru a cauta ai nevoie de un cont. Logheaza-te sau autentifica-te.",
@@ -236,6 +237,7 @@
"socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later",
"invalid-plugin-id": "Invalid plugin ID",
"plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP",
+ "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin",
"plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled",
"plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.",
"theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP",
diff --git a/public/language/ro/modules.json b/public/language/ro/modules.json
index e45d834abc..980870abbc 100644
--- a/public/language/ro/modules.json
+++ b/public/language/ro/modules.json
@@ -48,6 +48,7 @@
"chat.add-user": "Add User",
"chat.notification-settings": "Notification Settings",
"chat.default-notification-setting": "Default Notification Setting",
+ "chat.join-leave-messages": "Join/Leave Messages",
"chat.notification-setting-room-default": "Room Default",
"chat.notification-setting-none": "No notifications",
"chat.notification-setting-at-mention-only": "@mention only",
diff --git a/public/language/ro/social.json b/public/language/ro/social.json
index 2ba690a187..5b8dd99a46 100644
--- a/public/language/ro/social.json
+++ b/public/language/ro/social.json
@@ -8,5 +8,7 @@
"log-in-with-facebook": "Log in with Facebook",
"continue-with-facebook": "Continue with Facebook",
"sign-in-with-linkedin": "Sign in with LinkedIn",
- "sign-up-with-linkedin": "Sign up with LinkedIn"
+ "sign-up-with-linkedin": "Sign up with LinkedIn",
+ "sign-in-with-wordpress": "Sign in with WordPress",
+ "sign-up-with-wordpress": "Sign up with WordPress"
}
\ No newline at end of file
diff --git a/public/language/ru/admin/advanced/events.json b/public/language/ru/admin/advanced/events.json
index f1d1c69dea..9ec80306ff 100644
--- a/public/language/ru/admin/advanced/events.json
+++ b/public/language/ru/admin/advanced/events.json
@@ -9,9 +9,9 @@
"filter-type": "Тип события",
"filter-start": "Дата начала",
"filter-end": "Дата окончания",
- "filter-user": "Filter by User",
- "filter-user.placeholder": "Type user name to filter...",
- "filter-group": "Filter by Group",
- "filter-group.placeholder": "Type group name to filter...",
+ "filter-user": "Фильтровать по пользователю",
+ "filter-user.placeholder": "Введите имя пользователя для фильтрации…",
+ "filter-group": "Фильтровать по группе",
+ "filter-group.placeholder": "Введите название группы для фильтрации…",
"filter-per-page": "Записей на страницу"
}
\ No newline at end of file
diff --git a/public/language/ru/admin/dashboard.json b/public/language/ru/admin/dashboard.json
index 137241c469..846d395675 100644
--- a/public/language/ru/admin/dashboard.json
+++ b/public/language/ru/admin/dashboard.json
@@ -75,6 +75,7 @@
"graphs.page-views-registered": "Просм. авторизованными",
"graphs.page-views-guest": "Просмотров гостями",
"graphs.page-views-bot": "Просмотров ботами",
+ "graphs.page-views-ap": "ActivityPub Page Views",
"graphs.unique-visitors": "Уникальных посетителей",
"graphs.registered-users": "Авторизованных пользователей",
"graphs.guest-users": "Неавторизированных посетителей",
diff --git a/public/language/ru/admin/manage/categories.json b/public/language/ru/admin/manage/categories.json
index 456149cc90..36843c00a8 100644
--- a/public/language/ru/admin/manage/categories.json
+++ b/public/language/ru/admin/manage/categories.json
@@ -1,11 +1,15 @@
{
"manage-categories": "Manage Categories",
"add-category": "Add category",
+ "add-local-category": "Add Local category",
+ "add-remote-category": "Add Remote category",
+ "remove": "Remove",
"jump-to": "Jump to...",
"settings": "Настройки категории",
"edit-category": "Edit Category",
"privileges": "Права доступа",
"back-to-categories": "Back to categories",
+ "id": "Category ID",
"name": "Название категории",
"handle": "Category Handle",
"handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.",
@@ -103,6 +107,8 @@
"alert.create-success": "Категория успешно создана!",
"alert.none-active": "У вас нет активных категорий.",
"alert.create": "Создать категорию",
+ "alert.add": "Add a Category",
+ "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.Вы точно хотите очистить категорию «%1»?
Очистка категории удаляет все темы и сообщения, а также саму категорию из базы данных. Если вы хотите удалить категорию временно, вместо очистки вам нужно выбрать \"отключить\" .
", "alert.purge-success": "Категория очищена!", "alert.copy-success": "Настройки скопированы!", diff --git a/public/language/ru/admin/settings/activitypub.json b/public/language/ru/admin/settings/activitypub.json index 229272d32e..55eb27a108 100644 --- a/public/language/ru/admin/settings/activitypub.json +++ b/public/language/ru/admin/settings/activitypub.json @@ -18,6 +18,27 @@ "probe-timeout": "Время ожидания поиска (миллисекунды)", "probe-timeout-help": "(По умолчанию: 2000) Если поисковый запрос не получит ответа в установленные сроки, пользователь будет перенаправлен непосредственно по ссылке. Увеличьте это число, если сайты отвечают медленно и вы хотите предоставить дополнительное время.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.one,two,three)",
+ "rules.add": "Add New Rule",
+ "rules.type": "Type",
+ "rules.value": "Value",
+ "rules.cid": "Category",
+
+ "relays": "Relays",
+ "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.",
+ "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.",
+ "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.",
+ "relays.add": "Add New Relay",
+ "relays.relay": "Relay",
+ "relays.state": "State",
+ "relays.state-0": "Pending",
+ "relays.state-1": "Receiving only",
+ "relays.state-2": "Active",
+
"server-filtering": "Фильтрация",
"count": "В настоящее время NodeBB знает о %1 сервере(ах)",
"server.filter-help": "Укажите серверы, для которых вы хотели бы запретить объединение с вашим NodeBB. В качестве альтернативы вы можете выборочно разрешить объединение с определенными серверами. Поддерживаются оба варианта, хотя они и являются взаимоисключающими.",
diff --git a/public/language/ru/category.json b/public/language/ru/category.json
index 4739a2510f..79ef86d73a 100644
--- a/public/language/ru/category.json
+++ b/public/language/ru/category.json
@@ -7,7 +7,7 @@
"new-topic-button": "Создать тему",
"guest-login-post": "Авторизуйтесь, чтобы написать сообщение",
"no-topics": "В этой категории еще нет тем.Do you really want to purge this category \"%1\"?
Purging a category will remove all topics and posts, and delete the category from the database. If you want to remove a category temporarily, you'll want to \"disable\" the category instead.
", "alert.purge-success": "Category purged!", "alert.copy-success": "Settings Copied!", diff --git a/public/language/rw/admin/settings/activitypub.json b/public/language/rw/admin/settings/activitypub.json index 94f9ad7822..1b00672e0f 100644 --- a/public/language/rw/admin/settings/activitypub.json +++ b/public/language/rw/admin/settings/activitypub.json @@ -18,6 +18,27 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.one,two,three)",
+ "rules.add": "Add New Rule",
+ "rules.type": "Type",
+ "rules.value": "Value",
+ "rules.cid": "Category",
+
+ "relays": "Relays",
+ "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.",
+ "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.",
+ "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.",
+ "relays.add": "Add New Relay",
+ "relays.relay": "Relay",
+ "relays.state": "State",
+ "relays.state-0": "Pending",
+ "relays.state-1": "Receiving only",
+ "relays.state-2": "Active",
+
"server-filtering": "Filtering",
"count": "This NodeBB is currently aware of %1 server(s)",
"server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.",
diff --git a/public/language/rw/error.json b/public/language/rw/error.json
index 7c0dd10704..0452b4fc11 100644
--- a/public/language/rw/error.json
+++ b/public/language/rw/error.json
@@ -3,6 +3,7 @@
"invalid-json": "Invalid JSON",
"wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead",
"required-parameters-missing": "Required parameters were missing from this API call: %1",
+ "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.",
"not-logged-in": "Biragaragara ko utinjiyemo.",
"account-locked": "Konte yawe yabaye ifunze",
"search-requires-login": "Gushaka ikintu bisaba kuba ufite konte - Injiramo cyangwa wiyandike.",
@@ -236,6 +237,7 @@
"socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later",
"invalid-plugin-id": "Invalid plugin ID",
"plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP",
+ "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin",
"plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled",
"plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.",
"theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP",
diff --git a/public/language/rw/modules.json b/public/language/rw/modules.json
index 340d16cac4..8440752ccf 100644
--- a/public/language/rw/modules.json
+++ b/public/language/rw/modules.json
@@ -48,6 +48,7 @@
"chat.add-user": "Add User",
"chat.notification-settings": "Notification Settings",
"chat.default-notification-setting": "Default Notification Setting",
+ "chat.join-leave-messages": "Join/Leave Messages",
"chat.notification-setting-room-default": "Room Default",
"chat.notification-setting-none": "No notifications",
"chat.notification-setting-at-mention-only": "@mention only",
diff --git a/public/language/rw/social.json b/public/language/rw/social.json
index 2ba690a187..5b8dd99a46 100644
--- a/public/language/rw/social.json
+++ b/public/language/rw/social.json
@@ -8,5 +8,7 @@
"log-in-with-facebook": "Log in with Facebook",
"continue-with-facebook": "Continue with Facebook",
"sign-in-with-linkedin": "Sign in with LinkedIn",
- "sign-up-with-linkedin": "Sign up with LinkedIn"
+ "sign-up-with-linkedin": "Sign up with LinkedIn",
+ "sign-in-with-wordpress": "Sign in with WordPress",
+ "sign-up-with-wordpress": "Sign up with WordPress"
}
\ No newline at end of file
diff --git a/public/language/sc/admin/dashboard.json b/public/language/sc/admin/dashboard.json
index 6ad973f5f3..0be6d5866c 100644
--- a/public/language/sc/admin/dashboard.json
+++ b/public/language/sc/admin/dashboard.json
@@ -75,6 +75,7 @@
"graphs.page-views-registered": "Page Views Registered",
"graphs.page-views-guest": "Page Views Guest",
"graphs.page-views-bot": "Page Views Bot",
+ "graphs.page-views-ap": "ActivityPub Page Views",
"graphs.unique-visitors": "Unique Visitors",
"graphs.registered-users": "Registered Users",
"graphs.guest-users": "Guest Users",
diff --git a/public/language/sc/admin/manage/categories.json b/public/language/sc/admin/manage/categories.json
index f51152f22d..d66dd814a1 100644
--- a/public/language/sc/admin/manage/categories.json
+++ b/public/language/sc/admin/manage/categories.json
@@ -1,11 +1,15 @@
{
"manage-categories": "Manage Categories",
"add-category": "Add category",
+ "add-local-category": "Add Local category",
+ "add-remote-category": "Add Remote category",
+ "remove": "Remove",
"jump-to": "Jump to...",
"settings": "Category Settings",
"edit-category": "Edit Category",
"privileges": "Privileges",
"back-to-categories": "Back to categories",
+ "id": "Category ID",
"name": "Category Name",
"handle": "Category Handle",
"handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.",
@@ -103,6 +107,8 @@
"alert.create-success": "Category successfully created!",
"alert.none-active": "You have no active categories.",
"alert.create": "Create a Category",
+ "alert.add": "Add a Category",
+ "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.Do you really want to purge this category \"%1\"?
Purging a category will remove all topics and posts, and delete the category from the database. If you want to remove a category temporarily, you'll want to \"disable\" the category instead.
", "alert.purge-success": "Category purged!", "alert.copy-success": "Settings Copied!", diff --git a/public/language/sc/admin/settings/activitypub.json b/public/language/sc/admin/settings/activitypub.json index 94f9ad7822..1b00672e0f 100644 --- a/public/language/sc/admin/settings/activitypub.json +++ b/public/language/sc/admin/settings/activitypub.json @@ -18,6 +18,27 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.one,two,three)",
+ "rules.add": "Add New Rule",
+ "rules.type": "Type",
+ "rules.value": "Value",
+ "rules.cid": "Category",
+
+ "relays": "Relays",
+ "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.",
+ "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.",
+ "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.",
+ "relays.add": "Add New Relay",
+ "relays.relay": "Relay",
+ "relays.state": "State",
+ "relays.state-0": "Pending",
+ "relays.state-1": "Receiving only",
+ "relays.state-2": "Active",
+
"server-filtering": "Filtering",
"count": "This NodeBB is currently aware of %1 server(s)",
"server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.",
diff --git a/public/language/sc/error.json b/public/language/sc/error.json
index 535a568d1e..c3bb2dc892 100644
--- a/public/language/sc/error.json
+++ b/public/language/sc/error.json
@@ -3,6 +3,7 @@
"invalid-json": "Invalid JSON",
"wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead",
"required-parameters-missing": "Required parameters were missing from this API call: %1",
+ "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.",
"not-logged-in": "You don't seem to be logged in.",
"account-locked": "Your account has been locked temporarily",
"search-requires-login": "Searching requires an account - please login or register.",
@@ -236,6 +237,7 @@
"socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later",
"invalid-plugin-id": "Invalid plugin ID",
"plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP",
+ "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin",
"plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled",
"plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.",
"theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP",
diff --git a/public/language/sc/modules.json b/public/language/sc/modules.json
index 7145e11029..981e4a6bc4 100644
--- a/public/language/sc/modules.json
+++ b/public/language/sc/modules.json
@@ -48,6 +48,7 @@
"chat.add-user": "Add User",
"chat.notification-settings": "Notification Settings",
"chat.default-notification-setting": "Default Notification Setting",
+ "chat.join-leave-messages": "Join/Leave Messages",
"chat.notification-setting-room-default": "Room Default",
"chat.notification-setting-none": "No notifications",
"chat.notification-setting-at-mention-only": "@mention only",
diff --git a/public/language/sc/social.json b/public/language/sc/social.json
index 2ba690a187..5b8dd99a46 100644
--- a/public/language/sc/social.json
+++ b/public/language/sc/social.json
@@ -8,5 +8,7 @@
"log-in-with-facebook": "Log in with Facebook",
"continue-with-facebook": "Continue with Facebook",
"sign-in-with-linkedin": "Sign in with LinkedIn",
- "sign-up-with-linkedin": "Sign up with LinkedIn"
+ "sign-up-with-linkedin": "Sign up with LinkedIn",
+ "sign-in-with-wordpress": "Sign in with WordPress",
+ "sign-up-with-wordpress": "Sign up with WordPress"
}
\ No newline at end of file
diff --git a/public/language/sk/admin/dashboard.json b/public/language/sk/admin/dashboard.json
index e979c81301..55c1d314cc 100644
--- a/public/language/sk/admin/dashboard.json
+++ b/public/language/sk/admin/dashboard.json
@@ -75,6 +75,7 @@
"graphs.page-views-registered": "Page Views Registered",
"graphs.page-views-guest": "Page Views Guest",
"graphs.page-views-bot": "Page Views Bot",
+ "graphs.page-views-ap": "ActivityPub Page Views",
"graphs.unique-visitors": "Unikátny navštevníci",
"graphs.registered-users": "Zarestrovaný užívatelia",
"graphs.guest-users": "Guest Users",
diff --git a/public/language/sk/admin/manage/categories.json b/public/language/sk/admin/manage/categories.json
index f6768fe299..5cd7b32ebe 100644
--- a/public/language/sk/admin/manage/categories.json
+++ b/public/language/sk/admin/manage/categories.json
@@ -1,11 +1,15 @@
{
"manage-categories": "Manage Categories",
"add-category": "Add category",
+ "add-local-category": "Add Local category",
+ "add-remote-category": "Add Remote category",
+ "remove": "Remove",
"jump-to": "Jump to...",
"settings": "Nastavenia kategórie",
"edit-category": "Edit Category",
"privileges": "Oprávnenia",
"back-to-categories": "Back to categories",
+ "id": "Category ID",
"name": "Názov kategórie",
"handle": "Category Handle",
"handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.",
@@ -103,6 +107,8 @@
"alert.create-success": "Kategória bola úspešne vytvorená.",
"alert.none-active": "Nemáte žiadne aktívne kategórie.",
"alert.create": "Vytvoriť kategóriu",
+ "alert.add": "Add a Category",
+ "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.Naozaj chcete vyčistiť túto kategóriu „%1“?
Vyčistenie kategórií odstráni všetky témy a príspevky a odstráni kategórie z databázy. Pokiaľ chcete vyčistiť kategórie dočasne. radšej namiesto toho kategóriu „zakážte“.
", "alert.purge-success": "Kategória bola vyčistená!", "alert.copy-success": "Nastavenia boli skopírované!", diff --git a/public/language/sk/admin/settings/activitypub.json b/public/language/sk/admin/settings/activitypub.json index 94f9ad7822..1b00672e0f 100644 --- a/public/language/sk/admin/settings/activitypub.json +++ b/public/language/sk/admin/settings/activitypub.json @@ -18,6 +18,27 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.one,two,three)",
+ "rules.add": "Add New Rule",
+ "rules.type": "Type",
+ "rules.value": "Value",
+ "rules.cid": "Category",
+
+ "relays": "Relays",
+ "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.",
+ "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.",
+ "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.",
+ "relays.add": "Add New Relay",
+ "relays.relay": "Relay",
+ "relays.state": "State",
+ "relays.state-0": "Pending",
+ "relays.state-1": "Receiving only",
+ "relays.state-2": "Active",
+
"server-filtering": "Filtering",
"count": "This NodeBB is currently aware of %1 server(s)",
"server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.",
diff --git a/public/language/sk/error.json b/public/language/sk/error.json
index 8015c6c2b6..cd21ec05a3 100644
--- a/public/language/sk/error.json
+++ b/public/language/sk/error.json
@@ -3,6 +3,7 @@
"invalid-json": "Neplatné JSON",
"wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead",
"required-parameters-missing": "Required parameters were missing from this API call: %1",
+ "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.",
"not-logged-in": "Zdá sa že nie ste prihlásený/á.",
"account-locked": "Váš účet bol dočasne uzamknutý",
"search-requires-login": "K vyhľadávaniu je vyžadovaný účet - prosím prihláste sa alebo zaregistrujte.",
@@ -236,6 +237,7 @@
"socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later",
"invalid-plugin-id": "Invalid plugin ID",
"plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP",
+ "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin",
"plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled",
"plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.",
"theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP",
diff --git a/public/language/sk/modules.json b/public/language/sk/modules.json
index 51c3df2339..1335f8c9c5 100644
--- a/public/language/sk/modules.json
+++ b/public/language/sk/modules.json
@@ -48,6 +48,7 @@
"chat.add-user": "Add User",
"chat.notification-settings": "Notification Settings",
"chat.default-notification-setting": "Default Notification Setting",
+ "chat.join-leave-messages": "Join/Leave Messages",
"chat.notification-setting-room-default": "Room Default",
"chat.notification-setting-none": "No notifications",
"chat.notification-setting-at-mention-only": "@mention only",
diff --git a/public/language/sk/social.json b/public/language/sk/social.json
index 2ba690a187..5b8dd99a46 100644
--- a/public/language/sk/social.json
+++ b/public/language/sk/social.json
@@ -8,5 +8,7 @@
"log-in-with-facebook": "Log in with Facebook",
"continue-with-facebook": "Continue with Facebook",
"sign-in-with-linkedin": "Sign in with LinkedIn",
- "sign-up-with-linkedin": "Sign up with LinkedIn"
+ "sign-up-with-linkedin": "Sign up with LinkedIn",
+ "sign-in-with-wordpress": "Sign in with WordPress",
+ "sign-up-with-wordpress": "Sign up with WordPress"
}
\ No newline at end of file
diff --git a/public/language/sl/admin/dashboard.json b/public/language/sl/admin/dashboard.json
index 7d36506d8f..5504608a86 100644
--- a/public/language/sl/admin/dashboard.json
+++ b/public/language/sl/admin/dashboard.json
@@ -75,6 +75,7 @@
"graphs.page-views-registered": "Ogledov strani-registrirani",
"graphs.page-views-guest": "Ogledov strani-gosti",
"graphs.page-views-bot": "Ogledov strani-robot",
+ "graphs.page-views-ap": "ActivityPub Page Views",
"graphs.unique-visitors": "Edinstveni obiskovalci",
"graphs.registered-users": "Registrirani uporabniki",
"graphs.guest-users": "Gostujoči uporabniki",
diff --git a/public/language/sl/admin/manage/categories.json b/public/language/sl/admin/manage/categories.json
index acd1f9a506..129cce0663 100644
--- a/public/language/sl/admin/manage/categories.json
+++ b/public/language/sl/admin/manage/categories.json
@@ -1,11 +1,15 @@
{
"manage-categories": "Manage Categories",
"add-category": "Add category",
+ "add-local-category": "Add Local category",
+ "add-remote-category": "Add Remote category",
+ "remove": "Remove",
"jump-to": "Jump to...",
"settings": "Nastavitve kategorije",
"edit-category": "Edit Category",
"privileges": "Privilegiji",
"back-to-categories": "Back to categories",
+ "id": "Category ID",
"name": "Ime kategorije",
"handle": "Category Handle",
"handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.",
@@ -103,6 +107,8 @@
"alert.create-success": "Kategorija je uspešno ustvarjena!",
"alert.none-active": "Nimate aktivnih kategorij.",
"alert.create": "Ustvari kategorijo",
+ "alert.add": "Add a Category",
+ "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.Do you really want to purge this category \"%1\"?
Purging a category will remove all topics and posts, and delete the category from the database. If you want to remove a category temporarily, you'll want to \"disable\" the category instead.
", "alert.purge-success": "Kategorija je počiščena!", "alert.copy-success": "Nastavitve so kopirane!", diff --git a/public/language/sl/admin/settings/activitypub.json b/public/language/sl/admin/settings/activitypub.json index 94f9ad7822..1b00672e0f 100644 --- a/public/language/sl/admin/settings/activitypub.json +++ b/public/language/sl/admin/settings/activitypub.json @@ -18,6 +18,27 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.one,two,three)",
+ "rules.add": "Add New Rule",
+ "rules.type": "Type",
+ "rules.value": "Value",
+ "rules.cid": "Category",
+
+ "relays": "Relays",
+ "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.",
+ "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.",
+ "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.",
+ "relays.add": "Add New Relay",
+ "relays.relay": "Relay",
+ "relays.state": "State",
+ "relays.state-0": "Pending",
+ "relays.state-1": "Receiving only",
+ "relays.state-2": "Active",
+
"server-filtering": "Filtering",
"count": "This NodeBB is currently aware of %1 server(s)",
"server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.",
diff --git a/public/language/sl/error.json b/public/language/sl/error.json
index d87f7af441..ac9775d30b 100644
--- a/public/language/sl/error.json
+++ b/public/language/sl/error.json
@@ -3,6 +3,7 @@
"invalid-json": "Invalid JSON",
"wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead",
"required-parameters-missing": "Required parameters were missing from this API call: %1",
+ "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.",
"not-logged-in": "Niste prijavljeni.",
"account-locked": "Vaš račun je bil začasno zaklenjen.",
"search-requires-login": "Iskanje zahteva uporabniški račun - prosimo, da se prijavite ali registrirate.",
@@ -236,6 +237,7 @@
"socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later",
"invalid-plugin-id": "Invalid plugin ID",
"plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP",
+ "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin",
"plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled",
"plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.",
"theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP",
diff --git a/public/language/sl/modules.json b/public/language/sl/modules.json
index 5571ad4c35..9768079262 100644
--- a/public/language/sl/modules.json
+++ b/public/language/sl/modules.json
@@ -48,6 +48,7 @@
"chat.add-user": "Add User",
"chat.notification-settings": "Notification Settings",
"chat.default-notification-setting": "Default Notification Setting",
+ "chat.join-leave-messages": "Join/Leave Messages",
"chat.notification-setting-room-default": "Room Default",
"chat.notification-setting-none": "No notifications",
"chat.notification-setting-at-mention-only": "@mention only",
diff --git a/public/language/sl/social.json b/public/language/sl/social.json
index 2ba690a187..5b8dd99a46 100644
--- a/public/language/sl/social.json
+++ b/public/language/sl/social.json
@@ -8,5 +8,7 @@
"log-in-with-facebook": "Log in with Facebook",
"continue-with-facebook": "Continue with Facebook",
"sign-in-with-linkedin": "Sign in with LinkedIn",
- "sign-up-with-linkedin": "Sign up with LinkedIn"
+ "sign-up-with-linkedin": "Sign up with LinkedIn",
+ "sign-in-with-wordpress": "Sign in with WordPress",
+ "sign-up-with-wordpress": "Sign up with WordPress"
}
\ No newline at end of file
diff --git a/public/language/sq-AL/admin/dashboard.json b/public/language/sq-AL/admin/dashboard.json
index 6ad973f5f3..0be6d5866c 100644
--- a/public/language/sq-AL/admin/dashboard.json
+++ b/public/language/sq-AL/admin/dashboard.json
@@ -75,6 +75,7 @@
"graphs.page-views-registered": "Page Views Registered",
"graphs.page-views-guest": "Page Views Guest",
"graphs.page-views-bot": "Page Views Bot",
+ "graphs.page-views-ap": "ActivityPub Page Views",
"graphs.unique-visitors": "Unique Visitors",
"graphs.registered-users": "Registered Users",
"graphs.guest-users": "Guest Users",
diff --git a/public/language/sq-AL/admin/manage/categories.json b/public/language/sq-AL/admin/manage/categories.json
index f51152f22d..d66dd814a1 100644
--- a/public/language/sq-AL/admin/manage/categories.json
+++ b/public/language/sq-AL/admin/manage/categories.json
@@ -1,11 +1,15 @@
{
"manage-categories": "Manage Categories",
"add-category": "Add category",
+ "add-local-category": "Add Local category",
+ "add-remote-category": "Add Remote category",
+ "remove": "Remove",
"jump-to": "Jump to...",
"settings": "Category Settings",
"edit-category": "Edit Category",
"privileges": "Privileges",
"back-to-categories": "Back to categories",
+ "id": "Category ID",
"name": "Category Name",
"handle": "Category Handle",
"handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.",
@@ -103,6 +107,8 @@
"alert.create-success": "Category successfully created!",
"alert.none-active": "You have no active categories.",
"alert.create": "Create a Category",
+ "alert.add": "Add a Category",
+ "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.Do you really want to purge this category \"%1\"?
Purging a category will remove all topics and posts, and delete the category from the database. If you want to remove a category temporarily, you'll want to \"disable\" the category instead.
", "alert.purge-success": "Category purged!", "alert.copy-success": "Settings Copied!", diff --git a/public/language/sq-AL/admin/settings/activitypub.json b/public/language/sq-AL/admin/settings/activitypub.json index 94f9ad7822..1b00672e0f 100644 --- a/public/language/sq-AL/admin/settings/activitypub.json +++ b/public/language/sq-AL/admin/settings/activitypub.json @@ -18,6 +18,27 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.one,two,three)",
+ "rules.add": "Add New Rule",
+ "rules.type": "Type",
+ "rules.value": "Value",
+ "rules.cid": "Category",
+
+ "relays": "Relays",
+ "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.",
+ "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.",
+ "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.",
+ "relays.add": "Add New Relay",
+ "relays.relay": "Relay",
+ "relays.state": "State",
+ "relays.state-0": "Pending",
+ "relays.state-1": "Receiving only",
+ "relays.state-2": "Active",
+
"server-filtering": "Filtering",
"count": "This NodeBB is currently aware of %1 server(s)",
"server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.",
diff --git a/public/language/sq-AL/error.json b/public/language/sq-AL/error.json
index 28c75032ae..b1aab456a0 100644
--- a/public/language/sq-AL/error.json
+++ b/public/language/sq-AL/error.json
@@ -3,6 +3,7 @@
"invalid-json": "JSON i pavlefshëm",
"wrong-parameter-type": "Pritej një vlerë e tipit %3 për vetinë '%1', por në vend të saj u mor %2",
"required-parameters-missing": "Parametrat e kërkuar mungonin në këtë API: %1",
+ "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.",
"not-logged-in": "Mesa duket nuk jeni identifikuar.",
"account-locked": "Llogaria juaj është bllokuar përkohësisht",
"search-requires-login": "Për të kërkuar ju duhet të keni një llogari - ju lutemi identifikohuni ose regjistrohuni.",
@@ -236,6 +237,7 @@
"socket-reconnect-failed": "Nuk mund të arrihet serveri në këtë moment. Kliko këtu për të provuar përsëri, ose provo më vonë",
"invalid-plugin-id": "Invalid plugin ID",
"plugin-not-whitelisted": "Nuk mund të instalohet plugin – vetëm shtojcat e listuara në listën e bardhë nga Menaxheri i Paketave të NodeBB mund të instalohen nëpërmjet ACP",
+ "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin",
"plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled",
"plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.",
"theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP",
diff --git a/public/language/sq-AL/modules.json b/public/language/sq-AL/modules.json
index e715ed133c..dfeb88b7f5 100644
--- a/public/language/sq-AL/modules.json
+++ b/public/language/sq-AL/modules.json
@@ -48,6 +48,7 @@
"chat.add-user": "Add User",
"chat.notification-settings": "Notification Settings",
"chat.default-notification-setting": "Default Notification Setting",
+ "chat.join-leave-messages": "Join/Leave Messages",
"chat.notification-setting-room-default": "Room Default",
"chat.notification-setting-none": "No notifications",
"chat.notification-setting-at-mention-only": "@mention only",
diff --git a/public/language/sq-AL/social.json b/public/language/sq-AL/social.json
index 2ba690a187..5b8dd99a46 100644
--- a/public/language/sq-AL/social.json
+++ b/public/language/sq-AL/social.json
@@ -8,5 +8,7 @@
"log-in-with-facebook": "Log in with Facebook",
"continue-with-facebook": "Continue with Facebook",
"sign-in-with-linkedin": "Sign in with LinkedIn",
- "sign-up-with-linkedin": "Sign up with LinkedIn"
+ "sign-up-with-linkedin": "Sign up with LinkedIn",
+ "sign-in-with-wordpress": "Sign in with WordPress",
+ "sign-up-with-wordpress": "Sign up with WordPress"
}
\ No newline at end of file
diff --git a/public/language/sr/admin/dashboard.json b/public/language/sr/admin/dashboard.json
index 6ad973f5f3..0be6d5866c 100644
--- a/public/language/sr/admin/dashboard.json
+++ b/public/language/sr/admin/dashboard.json
@@ -75,6 +75,7 @@
"graphs.page-views-registered": "Page Views Registered",
"graphs.page-views-guest": "Page Views Guest",
"graphs.page-views-bot": "Page Views Bot",
+ "graphs.page-views-ap": "ActivityPub Page Views",
"graphs.unique-visitors": "Unique Visitors",
"graphs.registered-users": "Registered Users",
"graphs.guest-users": "Guest Users",
diff --git a/public/language/sr/admin/manage/categories.json b/public/language/sr/admin/manage/categories.json
index f51152f22d..d66dd814a1 100644
--- a/public/language/sr/admin/manage/categories.json
+++ b/public/language/sr/admin/manage/categories.json
@@ -1,11 +1,15 @@
{
"manage-categories": "Manage Categories",
"add-category": "Add category",
+ "add-local-category": "Add Local category",
+ "add-remote-category": "Add Remote category",
+ "remove": "Remove",
"jump-to": "Jump to...",
"settings": "Category Settings",
"edit-category": "Edit Category",
"privileges": "Privileges",
"back-to-categories": "Back to categories",
+ "id": "Category ID",
"name": "Category Name",
"handle": "Category Handle",
"handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.",
@@ -103,6 +107,8 @@
"alert.create-success": "Category successfully created!",
"alert.none-active": "You have no active categories.",
"alert.create": "Create a Category",
+ "alert.add": "Add a Category",
+ "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.Do you really want to purge this category \"%1\"?
Purging a category will remove all topics and posts, and delete the category from the database. If you want to remove a category temporarily, you'll want to \"disable\" the category instead.
", "alert.purge-success": "Category purged!", "alert.copy-success": "Settings Copied!", diff --git a/public/language/sr/admin/settings/activitypub.json b/public/language/sr/admin/settings/activitypub.json index 94f9ad7822..1b00672e0f 100644 --- a/public/language/sr/admin/settings/activitypub.json +++ b/public/language/sr/admin/settings/activitypub.json @@ -18,6 +18,27 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.one,two,three)",
+ "rules.add": "Add New Rule",
+ "rules.type": "Type",
+ "rules.value": "Value",
+ "rules.cid": "Category",
+
+ "relays": "Relays",
+ "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.",
+ "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.",
+ "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.",
+ "relays.add": "Add New Relay",
+ "relays.relay": "Relay",
+ "relays.state": "State",
+ "relays.state-0": "Pending",
+ "relays.state-1": "Receiving only",
+ "relays.state-2": "Active",
+
"server-filtering": "Filtering",
"count": "This NodeBB is currently aware of %1 server(s)",
"server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.",
diff --git a/public/language/sr/error.json b/public/language/sr/error.json
index 8200be23b1..ec8ef67b20 100644
--- a/public/language/sr/error.json
+++ b/public/language/sr/error.json
@@ -3,6 +3,7 @@
"invalid-json": "Неважећи JSON",
"wrong-parameter-type": "Очекивана је вредност типа %3 за својство %1, али је уместо тога примљен %2",
"required-parameters-missing": "Недостајали су обавезни параметри у овом API позиву: %1",
+ "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.",
"not-logged-in": "Изгледа да нисте пријављени.",
"account-locked": "Ваш налог је привремено закључан",
"search-requires-login": "Претраживање захтева налог — пријавите се или се региструјте.",
@@ -236,6 +237,7 @@
"socket-reconnect-failed": "Тренутно није могуће приступити серверу. Кликните овде да бисте покушали поново или покушајте поново касније",
"invalid-plugin-id": "Invalid plugin ID",
"plugin-not-whitelisted": "Инсталација додатне компоненте &ndash није могућа; преко ACP-а могу се инсталирати само додатне компоненте које је на белој листи ставио NodeBB Package Manager",
+ "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin",
"plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled",
"plugins-set-in-configuration": "Није вам дозвољено да мењате стање додатне компоненте онако како је дефинисано у време извршавања (config.json, променљиве окружења или аргументи терминала), уместо тога измените конфигурацију.",
"theme-not-set-in-configuration": "Приликом дефинисања активних додатних компоненти у конфигурацији, промена тема захтева додавање нове теме на листу активних додатних компоненти пре ажурирања у ACP",
diff --git a/public/language/sr/modules.json b/public/language/sr/modules.json
index f10f3e5d83..62ac69ee64 100644
--- a/public/language/sr/modules.json
+++ b/public/language/sr/modules.json
@@ -48,6 +48,7 @@
"chat.add-user": "Додај корисника",
"chat.notification-settings": "Подешавања обавештења",
"chat.default-notification-setting": "Подразумевано подешавање обавештења",
+ "chat.join-leave-messages": "Join/Leave Messages",
"chat.notification-setting-room-default": "Подразумевана соба",
"chat.notification-setting-none": "Без обавештења",
"chat.notification-setting-at-mention-only": "@помињање само",
diff --git a/public/language/sr/social.json b/public/language/sr/social.json
index 5ceb7d44d5..2e6ee44612 100644
--- a/public/language/sr/social.json
+++ b/public/language/sr/social.json
@@ -8,5 +8,7 @@
"log-in-with-facebook": "Пријавите се преко Facebook-а",
"continue-with-facebook": "Наставите се преко Facebook-а",
"sign-in-with-linkedin": "Sign in with LinkedIn",
- "sign-up-with-linkedin": "Sign up with LinkedIn"
+ "sign-up-with-linkedin": "Sign up with LinkedIn",
+ "sign-in-with-wordpress": "Sign in with WordPress",
+ "sign-up-with-wordpress": "Sign up with WordPress"
}
\ No newline at end of file
diff --git a/public/language/sv/admin/dashboard.json b/public/language/sv/admin/dashboard.json
index 6ad973f5f3..0be6d5866c 100644
--- a/public/language/sv/admin/dashboard.json
+++ b/public/language/sv/admin/dashboard.json
@@ -75,6 +75,7 @@
"graphs.page-views-registered": "Page Views Registered",
"graphs.page-views-guest": "Page Views Guest",
"graphs.page-views-bot": "Page Views Bot",
+ "graphs.page-views-ap": "ActivityPub Page Views",
"graphs.unique-visitors": "Unique Visitors",
"graphs.registered-users": "Registered Users",
"graphs.guest-users": "Guest Users",
diff --git a/public/language/sv/admin/manage/categories.json b/public/language/sv/admin/manage/categories.json
index 3e10ad60d8..e43fb441af 100644
--- a/public/language/sv/admin/manage/categories.json
+++ b/public/language/sv/admin/manage/categories.json
@@ -1,11 +1,15 @@
{
"manage-categories": "Manage Categories",
"add-category": "Add category",
+ "add-local-category": "Add Local category",
+ "add-remote-category": "Add Remote category",
+ "remove": "Remove",
"jump-to": "Jump to...",
"settings": "Category Settings",
"edit-category": "Edit Category",
"privileges": "Privileges",
"back-to-categories": "Back to categories",
+ "id": "Category ID",
"name": "Category Name",
"handle": "Category Handle",
"handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.",
@@ -103,6 +107,8 @@
"alert.create-success": "Category successfully created!",
"alert.none-active": "You have no active categories.",
"alert.create": "Create a Category",
+ "alert.add": "Add a Category",
+ "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.Do you really want to purge this category \"%1\"?
Purging a category will remove all topics and posts, and delete the category from the database. If you want to remove a category temporarily, you'll want to \"disable\" the category instead.
", "alert.purge-success": "Category purged!", "alert.copy-success": "Settings Copied!", diff --git a/public/language/sv/admin/settings/activitypub.json b/public/language/sv/admin/settings/activitypub.json index 94f9ad7822..1b00672e0f 100644 --- a/public/language/sv/admin/settings/activitypub.json +++ b/public/language/sv/admin/settings/activitypub.json @@ -18,6 +18,27 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.one,two,three)",
+ "rules.add": "Add New Rule",
+ "rules.type": "Type",
+ "rules.value": "Value",
+ "rules.cid": "Category",
+
+ "relays": "Relays",
+ "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.",
+ "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.",
+ "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.",
+ "relays.add": "Add New Relay",
+ "relays.relay": "Relay",
+ "relays.state": "State",
+ "relays.state-0": "Pending",
+ "relays.state-1": "Receiving only",
+ "relays.state-2": "Active",
+
"server-filtering": "Filtering",
"count": "This NodeBB is currently aware of %1 server(s)",
"server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.",
diff --git a/public/language/sv/error.json b/public/language/sv/error.json
index 6aad430f33..19a3580440 100644
--- a/public/language/sv/error.json
+++ b/public/language/sv/error.json
@@ -3,6 +3,7 @@
"invalid-json": "Ogiltig JSON",
"wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead",
"required-parameters-missing": "Required parameters were missing from this API call: %1",
+ "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.",
"not-logged-in": "Du verkar inte vara inloggad.",
"account-locked": "Ditt konto har tillfälligt blivit låst",
"search-requires-login": "Sökning kräver ett konto, var god logga in eller registrera dig.",
@@ -236,6 +237,7 @@
"socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later",
"invalid-plugin-id": "Invalid plugin ID",
"plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP",
+ "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin",
"plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled",
"plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.",
"theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP",
diff --git a/public/language/sv/modules.json b/public/language/sv/modules.json
index 14b07ee511..14d3713957 100644
--- a/public/language/sv/modules.json
+++ b/public/language/sv/modules.json
@@ -48,6 +48,7 @@
"chat.add-user": "Add User",
"chat.notification-settings": "Notification Settings",
"chat.default-notification-setting": "Default Notification Setting",
+ "chat.join-leave-messages": "Join/Leave Messages",
"chat.notification-setting-room-default": "Room Default",
"chat.notification-setting-none": "No notifications",
"chat.notification-setting-at-mention-only": "@mention only",
diff --git a/public/language/sv/social.json b/public/language/sv/social.json
index 2ba690a187..5b8dd99a46 100644
--- a/public/language/sv/social.json
+++ b/public/language/sv/social.json
@@ -8,5 +8,7 @@
"log-in-with-facebook": "Log in with Facebook",
"continue-with-facebook": "Continue with Facebook",
"sign-in-with-linkedin": "Sign in with LinkedIn",
- "sign-up-with-linkedin": "Sign up with LinkedIn"
+ "sign-up-with-linkedin": "Sign up with LinkedIn",
+ "sign-in-with-wordpress": "Sign in with WordPress",
+ "sign-up-with-wordpress": "Sign up with WordPress"
}
\ No newline at end of file
diff --git a/public/language/th/admin/dashboard.json b/public/language/th/admin/dashboard.json
index 5049bb87e1..fb36057e85 100644
--- a/public/language/th/admin/dashboard.json
+++ b/public/language/th/admin/dashboard.json
@@ -75,6 +75,7 @@
"graphs.page-views-registered": "ยอดวิวจากผู้ลงทะเบียนแล้ว",
"graphs.page-views-guest": "ยอดวิวจากผู้มาเยือน",
"graphs.page-views-bot": "ยอดวิวจากบอต",
+ "graphs.page-views-ap": "ActivityPub Page Views",
"graphs.unique-visitors": "จำนวนผู้ใช้ที่ไม่ซ้ำกัน",
"graphs.registered-users": "ผู้ใช้ที่ลงทะเบียนแล้ว",
"graphs.guest-users": "ผู้ใช้ที่เป็นผู้มาเยือน",
diff --git a/public/language/th/admin/manage/categories.json b/public/language/th/admin/manage/categories.json
index f64e658a6b..fd61be437c 100644
--- a/public/language/th/admin/manage/categories.json
+++ b/public/language/th/admin/manage/categories.json
@@ -1,11 +1,15 @@
{
"manage-categories": "จัดการหมวดหมู่",
"add-category": "เพิ่มหมวดหมู่",
+ "add-local-category": "Add Local category",
+ "add-remote-category": "Add Remote category",
+ "remove": "Remove",
"jump-to": "ไปที่...",
"settings": "การตั้งค่าหมวดหมู่",
"edit-category": "แก้ไขหมวดหมู่",
"privileges": "สิทธิ์",
"back-to-categories": "กลับไปที่หมวดหมู่ทั้งหมด",
+ "id": "Category ID",
"name": "ชื่อหมวดหมู่",
"handle": "Category Handle",
"handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.",
@@ -103,6 +107,8 @@
"alert.create-success": "Category successfully created!",
"alert.none-active": "You have no active categories.",
"alert.create": "Create a Category",
+ "alert.add": "Add a Category",
+ "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.Do you really want to purge this category \"%1\"?
Purging a category will remove all topics and posts, and delete the category from the database. If you want to remove a category temporarily, you'll want to \"disable\" the category instead.
", "alert.purge-success": "Category purged!", "alert.copy-success": "Settings Copied!", diff --git a/public/language/th/admin/settings/activitypub.json b/public/language/th/admin/settings/activitypub.json index 94f9ad7822..1b00672e0f 100644 --- a/public/language/th/admin/settings/activitypub.json +++ b/public/language/th/admin/settings/activitypub.json @@ -18,6 +18,27 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.one,two,three)",
+ "rules.add": "Add New Rule",
+ "rules.type": "Type",
+ "rules.value": "Value",
+ "rules.cid": "Category",
+
+ "relays": "Relays",
+ "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.",
+ "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.",
+ "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.",
+ "relays.add": "Add New Relay",
+ "relays.relay": "Relay",
+ "relays.state": "State",
+ "relays.state-0": "Pending",
+ "relays.state-1": "Receiving only",
+ "relays.state-2": "Active",
+
"server-filtering": "Filtering",
"count": "This NodeBB is currently aware of %1 server(s)",
"server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.",
diff --git a/public/language/th/error.json b/public/language/th/error.json
index 16672afaef..529a74d404 100644
--- a/public/language/th/error.json
+++ b/public/language/th/error.json
@@ -3,6 +3,7 @@
"invalid-json": "รูปแบบ JSON ไม่ถูกต้อง",
"wrong-parameter-type": "ต้องการข้อมูลประเภท %3 สำหรับค่า `%1` แต่ได้รับค่า %2 แทน",
"required-parameters-missing": "ขาดพารามิเตอร์ที่จำเป็นต่อการเรียก API นี้: %1",
+ "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.",
"not-logged-in": "คุณยังไม่ได้เข้าสู่ระบบ",
"account-locked": "บัญชีของคุณถูกระงับการใช้งานชั่วคราว",
"search-requires-login": "\"ฟังก์ชั่นการค้นหา\" ต้องการบัญชีผู้ใช้ กรุณาเข้าสู่ระบบหรือสมัครสมาชิก",
@@ -236,6 +237,7 @@
"socket-reconnect-failed": "ไม่สามารถติดต่อกับเซิร์ฟเวอร์ในขณะนี้ คลิกที่นี่เพื่อลองใหม่ หรือลองอีกครั้งภายหลัง",
"invalid-plugin-id": "รหัสปลั๊กอินไม่ถูกต้อง",
"plugin-not-whitelisted": "ไม่สามารถติดตั้งปลั๊กอิน – เฉพาะปลั๊กอินที่ได้รับอนุญาตจาก NodeBB Package Manager ถึงจะติดตั้งผ่านแผงควบคุมผู้ดูแลระบบได้",
+ "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin",
"plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled",
"plugins-set-in-configuration": "คุณไม่สามารถเปลี่ยนสถานะของปลั๊กอินเนื่องจากถูกกำหนดตอนรัน (ไฟล์ config.json, ตัวแปร environmental หรือระบุตอนสั่งในบรรทัดคำสั่ง) โปรดปรับที่การตั้งค่าแทน",
"theme-not-set-in-configuration": "เมื่อกำหนดปลั๊กอันที่กำลังทำงานในส่วนตั้งค่า การเปลี่ยนธีมต้องเพิ่มทีมในรายการปลั๊กอินที่กำลังใช้งานก่อนที่จะเปลี่ยนในแผงควบคุมผู้ดูแล",
diff --git a/public/language/th/modules.json b/public/language/th/modules.json
index c8198f1d99..f2a1a40e5e 100644
--- a/public/language/th/modules.json
+++ b/public/language/th/modules.json
@@ -48,6 +48,7 @@
"chat.add-user": "เพิ่มผู้ใช้งาน",
"chat.notification-settings": "การตั้งค่าการแจ้งเตือน",
"chat.default-notification-setting": "ค่าเริ่มต้นการแจ้งเตือน",
+ "chat.join-leave-messages": "Join/Leave Messages",
"chat.notification-setting-room-default": "ค่าเริ่มต้นของห้อง",
"chat.notification-setting-none": "ไม่มีการแจ้งเตือน",
"chat.notification-setting-at-mention-only": "เฉพาะเมื่อ @ถูกพูดถึง",
diff --git a/public/language/th/social.json b/public/language/th/social.json
index 7930476093..062af524bc 100644
--- a/public/language/th/social.json
+++ b/public/language/th/social.json
@@ -8,5 +8,7 @@
"log-in-with-facebook": "เข้าสู่ระบบด้วยบัญชี Facebook",
"continue-with-facebook": "ไปต่อโดยใช้บัญชี Facebook",
"sign-in-with-linkedin": "เข้าสู่ระบบด้วยบัญชี LinkedIn",
- "sign-up-with-linkedin": "สร้างบัญชีใหม่ด้วยบัญชี LinkedIn"
+ "sign-up-with-linkedin": "สร้างบัญชีใหม่ด้วยบัญชี LinkedIn",
+ "sign-in-with-wordpress": "Sign in with WordPress",
+ "sign-up-with-wordpress": "Sign up with WordPress"
}
\ No newline at end of file
diff --git a/public/language/tr/admin/dashboard.json b/public/language/tr/admin/dashboard.json
index a6ef8394c2..1c40d281c9 100644
--- a/public/language/tr/admin/dashboard.json
+++ b/public/language/tr/admin/dashboard.json
@@ -75,6 +75,7 @@
"graphs.page-views-registered": "Kayıtlı Kullanıcıların Sayfa Gösterimi",
"graphs.page-views-guest": "Ziyaretçilerin Sayfa Gösterimi",
"graphs.page-views-bot": "Bot Sayfa Gösterimi",
+ "graphs.page-views-ap": "ActivityPub Page Views",
"graphs.unique-visitors": "Benzersiz Ziyaretçiler",
"graphs.registered-users": "Kayıtlı Kullanıcılar",
"graphs.guest-users": "Misafir Kullanıcılar",
diff --git a/public/language/tr/admin/manage/categories.json b/public/language/tr/admin/manage/categories.json
index 1cad49d52c..af50cc0700 100644
--- a/public/language/tr/admin/manage/categories.json
+++ b/public/language/tr/admin/manage/categories.json
@@ -1,11 +1,15 @@
{
"manage-categories": "Manage Categories",
"add-category": "Add category",
+ "add-local-category": "Add Local category",
+ "add-remote-category": "Add Remote category",
+ "remove": "Remove",
"jump-to": "Jump to...",
"settings": "Kategori Ayarları",
"edit-category": "Edit Category",
"privileges": "İzinler",
"back-to-categories": "Back to categories",
+ "id": "Category ID",
"name": "Kategori Adı",
"handle": "Category Handle",
"handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.",
@@ -103,6 +107,8 @@
"alert.create-success": "Kategori başarıyla yaratıldı!",
"alert.none-active": "Aktif kategoriniz mevcut değil.",
"alert.create": "Bir Kategori Yarat",
+ "alert.add": "Add a Category",
+ "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.\"% 1\" kategorisini gerçekten temizlemek istiyor musunuz?
Bir kategoriyi temizlemek, tüm başlıkları ve iletileri kaldıracak ve kategoriyi veritabanından silecektir. Bir kategoriyi geçici olarak kaldırmak isterseniz, kategoriyi \"devre dışı\" bırakmanız yeterlidir.
", "alert.purge-success": "Kategori temizlendi!", "alert.copy-success": "Ayarlar Kopyalandı!", diff --git a/public/language/tr/admin/settings/activitypub.json b/public/language/tr/admin/settings/activitypub.json index 94f9ad7822..1b00672e0f 100644 --- a/public/language/tr/admin/settings/activitypub.json +++ b/public/language/tr/admin/settings/activitypub.json @@ -18,6 +18,27 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.one,two,three)",
+ "rules.add": "Add New Rule",
+ "rules.type": "Type",
+ "rules.value": "Value",
+ "rules.cid": "Category",
+
+ "relays": "Relays",
+ "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.",
+ "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.",
+ "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.",
+ "relays.add": "Add New Relay",
+ "relays.relay": "Relay",
+ "relays.state": "State",
+ "relays.state-0": "Pending",
+ "relays.state-1": "Receiving only",
+ "relays.state-2": "Active",
+
"server-filtering": "Filtering",
"count": "This NodeBB is currently aware of %1 server(s)",
"server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.",
diff --git a/public/language/tr/error.json b/public/language/tr/error.json
index a31b16828a..ea93e9abe6 100644
--- a/public/language/tr/error.json
+++ b/public/language/tr/error.json
@@ -3,6 +3,7 @@
"invalid-json": "Geçersiz JSON",
"wrong-parameter-type": "\"%1\" özelliği için %3 türünde bir değer bekleniyordu, ancak bunun yerine %2 alındı",
"required-parameters-missing": "Bu API çağrısında gerekli parametreler eksikti: %1",
+ "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.",
"not-logged-in": "Giriş yapmamış görünüyorsunuz.",
"account-locked": "Hesabınız geçici olarak kilitlendi",
"search-requires-login": "Arama yapmak için üyelik hesabı gerekiyor. Lütfen giriş yapın ya da kaydolun.",
@@ -236,6 +237,7 @@
"socket-reconnect-failed": "Şu anda sunucuya ulaşılamıyor. Tekrar denemek için buraya tıklayın, veya daha sonra tekrar deneyin.",
"invalid-plugin-id": "Geçersiz Eklenti ID",
"plugin-not-whitelisted": "– eklentisi yüklenemedi, sadece NodeBB Paket Yöneticisi tarafından onaylanan eklentiler kontrol panelinden kurulabilir",
+ "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin",
"plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled",
"plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.",
"theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP",
diff --git a/public/language/tr/modules.json b/public/language/tr/modules.json
index d85dbd2821..929c0cf962 100644
--- a/public/language/tr/modules.json
+++ b/public/language/tr/modules.json
@@ -48,6 +48,7 @@
"chat.add-user": "Kullanıcı Ekle",
"chat.notification-settings": "Notification Settings",
"chat.default-notification-setting": "Default Notification Setting",
+ "chat.join-leave-messages": "Join/Leave Messages",
"chat.notification-setting-room-default": "Room Default",
"chat.notification-setting-none": "No notifications",
"chat.notification-setting-at-mention-only": "@mention only",
diff --git a/public/language/tr/social.json b/public/language/tr/social.json
index 835124b5f5..174f1091ca 100644
--- a/public/language/tr/social.json
+++ b/public/language/tr/social.json
@@ -8,5 +8,7 @@
"log-in-with-facebook": "Facebook ile Giriş Yap",
"continue-with-facebook": "Facebook ile devam et",
"sign-in-with-linkedin": "LinkedIn ile Giriş Yap",
- "sign-up-with-linkedin": "LinkedIn ile Kaydol"
+ "sign-up-with-linkedin": "LinkedIn ile Kaydol",
+ "sign-in-with-wordpress": "Sign in with WordPress",
+ "sign-up-with-wordpress": "Sign up with WordPress"
}
\ No newline at end of file
diff --git a/public/language/uk/admin/dashboard.json b/public/language/uk/admin/dashboard.json
index 6f8ce9fa8a..ab440180bb 100644
--- a/public/language/uk/admin/dashboard.json
+++ b/public/language/uk/admin/dashboard.json
@@ -75,6 +75,7 @@
"graphs.page-views-registered": "Page Views Registered",
"graphs.page-views-guest": "Page Views Guest",
"graphs.page-views-bot": "Page Views Bot",
+ "graphs.page-views-ap": "ActivityPub Page Views",
"graphs.unique-visitors": "Унікальні відвідувачі",
"graphs.registered-users": "Зареєстровані користувачі",
"graphs.guest-users": "Guest Users",
diff --git a/public/language/uk/admin/manage/categories.json b/public/language/uk/admin/manage/categories.json
index dfae6ebe3d..0d2dc60d0a 100644
--- a/public/language/uk/admin/manage/categories.json
+++ b/public/language/uk/admin/manage/categories.json
@@ -1,11 +1,15 @@
{
"manage-categories": "Manage Categories",
"add-category": "Add category",
+ "add-local-category": "Add Local category",
+ "add-remote-category": "Add Remote category",
+ "remove": "Remove",
"jump-to": "Jump to...",
"settings": "Налаштування категорій",
"edit-category": "Edit Category",
"privileges": "Права",
"back-to-categories": "Back to categories",
+ "id": "Category ID",
"name": "Назва категорії",
"handle": "Category Handle",
"handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.",
@@ -103,6 +107,8 @@
"alert.create-success": "Категорія успішно створена!",
"alert.none-active": "У вас немає активних категорій.",
"alert.create": "Створити категорію",
+ "alert.add": "Add a Category",
+ "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.Ви впевнені, що бажаєте стерти категорію \"%1\"?
Стирання категорії видалить всі теми та пости і видалить категорію з бази данних. Якщо ви хотіли тимчасово видалити категорію, вам, натомість, варто її просто \"вимкнути\".
", "alert.purge-success": "Категорію стерто!", "alert.copy-success": "Налаштування скопійовано!", diff --git a/public/language/uk/admin/settings/activitypub.json b/public/language/uk/admin/settings/activitypub.json index 94f9ad7822..1b00672e0f 100644 --- a/public/language/uk/admin/settings/activitypub.json +++ b/public/language/uk/admin/settings/activitypub.json @@ -18,6 +18,27 @@ "probe-timeout": "Lookup Timeout (milliseconds)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.one,two,three)",
+ "rules.add": "Add New Rule",
+ "rules.type": "Type",
+ "rules.value": "Value",
+ "rules.cid": "Category",
+
+ "relays": "Relays",
+ "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.",
+ "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.",
+ "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.",
+ "relays.add": "Add New Relay",
+ "relays.relay": "Relay",
+ "relays.state": "State",
+ "relays.state-0": "Pending",
+ "relays.state-1": "Receiving only",
+ "relays.state-2": "Active",
+
"server-filtering": "Filtering",
"count": "This NodeBB is currently aware of %1 server(s)",
"server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.",
diff --git a/public/language/uk/error.json b/public/language/uk/error.json
index f5ead51c58..002b6a8471 100644
--- a/public/language/uk/error.json
+++ b/public/language/uk/error.json
@@ -3,6 +3,7 @@
"invalid-json": "Некоректний формат JSON",
"wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead",
"required-parameters-missing": "Required parameters were missing from this API call: %1",
+ "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.",
"not-logged-in": "Не схоже, що ви увійшли в систему.",
"account-locked": "Ваш акаунт тимчасово заблоковано",
"search-requires-login": "Для пошуку потрібен акаунт — будь ласка, увійдіть чи зареєструйтесь.",
@@ -236,6 +237,7 @@
"socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later",
"invalid-plugin-id": "Invalid plugin ID",
"plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP",
+ "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin",
"plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled",
"plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.",
"theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP",
diff --git a/public/language/uk/modules.json b/public/language/uk/modules.json
index 476a18f3f0..dd7a740981 100644
--- a/public/language/uk/modules.json
+++ b/public/language/uk/modules.json
@@ -48,6 +48,7 @@
"chat.add-user": "Add User",
"chat.notification-settings": "Notification Settings",
"chat.default-notification-setting": "Default Notification Setting",
+ "chat.join-leave-messages": "Join/Leave Messages",
"chat.notification-setting-room-default": "Room Default",
"chat.notification-setting-none": "No notifications",
"chat.notification-setting-at-mention-only": "@mention only",
diff --git a/public/language/uk/social.json b/public/language/uk/social.json
index 2ba690a187..5b8dd99a46 100644
--- a/public/language/uk/social.json
+++ b/public/language/uk/social.json
@@ -8,5 +8,7 @@
"log-in-with-facebook": "Log in with Facebook",
"continue-with-facebook": "Continue with Facebook",
"sign-in-with-linkedin": "Sign in with LinkedIn",
- "sign-up-with-linkedin": "Sign up with LinkedIn"
+ "sign-up-with-linkedin": "Sign up with LinkedIn",
+ "sign-in-with-wordpress": "Sign in with WordPress",
+ "sign-up-with-wordpress": "Sign up with WordPress"
}
\ No newline at end of file
diff --git a/public/language/ur/_DO_NOT_EDIT_FILES_HERE.md b/public/language/ur/_DO_NOT_EDIT_FILES_HERE.md
new file mode 100644
index 0000000000..1faf87ad65
--- /dev/null
+++ b/public/language/ur/_DO_NOT_EDIT_FILES_HERE.md
@@ -0,0 +1,3 @@
+# The files here are not meant to be edited directly
+
+Please see the → [Internalization README](../README.md).
\ No newline at end of file
diff --git a/public/language/ur/admin/admin.json b/public/language/ur/admin/admin.json
new file mode 100644
index 0000000000..09f3b37699
--- /dev/null
+++ b/public/language/ur/admin/admin.json
@@ -0,0 +1,18 @@
+{
+ "alert.confirm-rebuild-and-restart": "کیا آپ واقعی نوڈ بی بی کو دوبارہ بنانا اور ری اسٹارٹ کرنا چاہتے ہیں؟",
+ "alert.confirm-restart": "کیا آپ واقعی نوڈ بی بی کو ری اسٹارٹ کرنا چاہتے ہیں؟",
+
+ "acp-title": "%1 | نوڈ بی بی ایڈمنسٹریٹر کنٹرول پینل",
+ "settings-header-contents": "مواد",
+ "changes-saved": "تبدیلیاں محفوظ ہو گئیں",
+ "changes-saved-message": "آپ کی نوڈ بی بی کی ترتیبات میں تبدیلیاں محفوظ ہو گئیں۔",
+ "changes-not-saved": "تبدیلیاں محفوظ نہیں ہوئیں",
+ "changes-not-saved-message": "نوڈ بی بی میں آپ کی تبدیلیاں محفوظ کرنے میں ایک مسئلہ پیش آیا۔ (%1)",
+ "save-changes": "تبدیلیاں محفوظ کریں",
+ "min": "کم سے کم:",
+ "max": "زیادہ سے زیادہ:",
+ "view": "دیکھیں",
+ "edit": "ترمیم",
+ "add": "شامل کریں",
+ "select-icon": "آئیکن منتخب کریں"
+}
\ No newline at end of file
diff --git a/public/language/ur/admin/advanced/cache.json b/public/language/ur/admin/advanced/cache.json
new file mode 100644
index 0000000000..c8889cc414
--- /dev/null
+++ b/public/language/ur/admin/advanced/cache.json
@@ -0,0 +1,10 @@
+{
+ "cache": "کیش",
+ "post-cache": "پوسٹ کیش",
+ "group-cache": "گروپ کیش",
+ "local-cache": "لوکل کیش",
+ "object-cache": "آبجیکٹ کیش",
+ "percent-full": "بھرائی: %1%",
+ "post-cache-size": "پوسٹ کیش کا سائز",
+ "items-in-cache": "کیش میں موجود آئٹمز"
+}
\ No newline at end of file
diff --git a/public/language/ur/admin/advanced/database.json b/public/language/ur/admin/advanced/database.json
new file mode 100644
index 0000000000..c4bfddd979
--- /dev/null
+++ b/public/language/ur/admin/advanced/database.json
@@ -0,0 +1,52 @@
+{
+ "x-b": "%1 بی",
+ "x-mb": "%1 ایم بی",
+ "x-gb": "%1 جی بی",
+ "uptime-seconds": "فعال وقت سیکنڈز میں",
+ "uptime-days": "فعال وقت دنوں میں",
+
+ "mongo": "مونگو ڈی بی",
+ "mongo.version": "مونگو ڈی بی ورژن",
+ "mongo.storage-engine": "سٹوریج سسٹم",
+ "mongo.collections": "کلیکشنز",
+ "mongo.objects": "آبجیکٹس",
+ "mongo.avg-object-size": "اوسط آبجیکٹ سائز",
+ "mongo.data-size": "ڈیٹا سائز",
+ "mongo.storage-size": "سٹوریج سائز",
+ "mongo.index-size": "انڈیکس سائز",
+ "mongo.file-size": "فائل سائز",
+ "mongo.resident-memory": "موجودہ فعال میموری",
+ "mongo.virtual-memory": "ورچوئل میموری",
+ "mongo.mapped-memory": "معیاری میموری",
+ "mongo.bytes-in": "بائٹس ان",
+ "mongo.bytes-out": "بائٹس آؤٹ",
+ "mongo.num-requests": "درخواستوں کی تعداد",
+ "mongo.raw-info": "مونگو ڈی بی سے خام ڈیٹا",
+ "mongo.unauthorized": "نوڈ بی بی مونگو ڈی بی سے مطلوبہ اعدادوشمار حاصل کرنے میں ناکام رہا۔ براہ کرم یقینی بنائیں کہ نوڈ بی بی کی طرف سے استعمال ہونے والا صارف 'ایڈمن' ڈیٹا بیس کے لیے 'کلسٹر مانیٹر' کردار شامل کرتا ہے۔",
+
+ "redis": "ریڈس",
+ "redis.version": "ریڈس ورژن",
+ "redis.keys": "چابیاں",
+ "redis.expires": "میعاد ختم",
+ "redis.avg-ttl": "اوسط وقت حیات (ٹی ٹی ایل)",
+ "redis.connected-clients": "جڑے ہوئے کلائنٹس",
+ "redis.connected-slaves": "جڑے ہوئے ثانوی سرورز",
+ "redis.blocked-clients": "بلاک شدہ کلائنٹس",
+ "redis.used-memory": "استعمال شدہ میموری",
+ "redis.memory-frag-ratio": "میموری فریگمنٹیشن تناسب",
+ "redis.total-connections-recieved": "کل وصول شدہ کنکشنز",
+ "redis.total-commands-processed": "کل پروسیس شدہ کمانڈز",
+ "redis.iops": "سیکنڈ میں بیک وقت آپریشنز",
+ "redis.iinput": "سیکنڈ میں بیک وقت ان پٹ",
+ "redis.ioutput": "سیکنڈ میں بیک وقت آؤٹ پٹ",
+ "redis.total-input": "کل ان پٹ",
+ "redis.total-output": "کل آؤٹ پٹ",
+
+ "redis.keyspace-hits": "کامیاب چابی تلاشیں",
+ "redis.keyspace-misses": "ناکام چابی تلاشیں",
+ "redis.raw-info": "ریڈس سے خام ڈیٹا",
+
+ "postgres": "پوسٹ گریس",
+ "postgres.version": "پوسٹ گریس کیو ایل ورژن",
+ "postgres.raw-info": "پوسٹ گریس سے خام ڈیٹا"
+}
diff --git a/public/language/ur/admin/advanced/errors.json b/public/language/ur/admin/advanced/errors.json
new file mode 100644
index 0000000000..90576eda48
--- /dev/null
+++ b/public/language/ur/admin/advanced/errors.json
@@ -0,0 +1,15 @@
+{
+ "errors": "غلطیاں",
+ "figure-x": "شکل %1",
+ "error-events-per-day": "%1 واقعات فی دن",
+ "error.404": "صفحہ نہیں ملا (غلطی 404)",
+ "error.503": "سروس دستیاب نہیں (غلطی 503)",
+ "manage-error-log": "غلطیوں کے لاگ کا انتظام",
+ "export-error-log": "غلطیوں کے لاگ کو برآمد کریں (سی ایس وی)",
+ "clear-error-log": "غلطیوں کے لاگ کو صاف کریں",
+ "route": "راستہ",
+ "count": "تعداد",
+ "no-routes-not-found": "ہورے! کوئی 404 غلطیاں نہیں!",
+ "clear404-confirm": "کیا آپ واقعی 404 غلطیوں کے لاگ کو صاف کرنا چاہتے ہیں؟",
+ "clear404-success": "صفحہ نہیں ملا (غلطی 404) کی غلطیاں صاف کر دی گئیں۔"
+}
\ No newline at end of file
diff --git a/public/language/ur/admin/advanced/events.json b/public/language/ur/admin/advanced/events.json
new file mode 100644
index 0000000000..51678462e3
--- /dev/null
+++ b/public/language/ur/admin/advanced/events.json
@@ -0,0 +1,17 @@
+{
+ "events": "واقعات",
+ "no-events": "کوئی واقعات نہیں",
+ "control-panel": "واقعات کا کنٹرول پینل",
+ "delete-events": "واقعات حذف کریں",
+ "confirm-delete-all-events": "کیا آپ واقعی لاگ میں موجود تمام واقعات کو حذف کرنا چاہتے ہیں؟",
+ "filters": "فلٹرز",
+ "filters-apply": "فلٹرز لگائیں",
+ "filter-type": "واقعہ کی قسم",
+ "filter-start": "شروع کی تاریخ",
+ "filter-end": "ختم کی تاریخ",
+ "filter-user": "صارف کے مطابق فلٹر",
+ "filter-user.placeholder": "فلٹر کرنے کے لیے صارف کا نام درج کریں…",
+ "filter-group": "گروپ کے مطابق فلٹر",
+ "filter-group.placeholder": "فلٹر کرنے کے لیے گروپ کا نام درج کریں…",
+ "filter-per-page": "فی صفحہ"
+}
\ No newline at end of file
diff --git a/public/language/ur/admin/advanced/logs.json b/public/language/ur/admin/advanced/logs.json
new file mode 100644
index 0000000000..02dd594828
--- /dev/null
+++ b/public/language/ur/admin/advanced/logs.json
@@ -0,0 +1,7 @@
+{
+ "logs": "لاگز",
+ "control-panel": "لاگز کا کنٹرول پینل",
+ "reload": "لاگز دوبارہ لوڈ کریں",
+ "clear": "لاگز صاف کریں",
+ "clear-success": "لاگز صاف ہو گئے!"
+}
\ No newline at end of file
diff --git a/public/language/ur/admin/appearance/customise.json b/public/language/ur/admin/appearance/customise.json
new file mode 100644
index 0000000000..0e016c3555
--- /dev/null
+++ b/public/language/ur/admin/appearance/customise.json
@@ -0,0 +1,20 @@
+{
+ "customise": "مرضی کے مطابق بنائیں",
+ "custom-css": "مرضی کا سی ایس ایس/ساس",
+ "custom-css.description": "اپنی مرضی کی سی ایس ایس/ساس ڈیکلریشنز یہاں درج کریں۔ یہ تمام دیگر سٹائلز کے بعد لگائی جائیں گی۔",
+ "custom-css.enable": "مرضی کا سی ایس ایس/ساس فعال کریں",
+
+ "custom-js": "مرضی کا جاوا اسکرپٹ کوڈ",
+ "custom-js.description": "اپنا مرضی کا جاوا اسکرپٹ کوڈ یہاں درج کریں۔ یہ صفحہ مکمل لوڈ ہونے کے بعد چلایا جائے گا۔",
+ "custom-js.enable": "مرضی کا جاوا اسکرپٹ کوڈ فعال کریں",
+
+ "custom-header": "مرضی کی ہیڈر",
+ "custom-header.description": "اپنا مرضی کا ایچ ٹی ایم ایل کوڈ یہاں درج کریں (جیسے کہ میٹا ایلیمنٹس وغیرہ)، یہ آپ کے فورم کے کوڈ میں <head> سیکشن میں شامل ہوں گے۔ اسکرپٹ ایلیمنٹس کی اجازت ہے، لیکن یہ غیر مشورہ ہے، کیونکہ اس کے لیے آپ مرضی کا جاوا اسکرپٹ کوڈ سیکشن استعمال کر سکتے ہیں۔",
+ "custom-header.enable": "مرضی کی ہیڈر فعال کریں",
+
+ "custom-css.livereload": "فوری ری لوڈ فعال کریں",
+ "custom-css.livereload.description": "اگر آپ اسے فعال کرتے ہیں، تو آپ کے اکاؤنٹ استعمال کرنے والے ہر ڈیوائس پر تمام سیشنز ری لوڈ ہوں گے جب آپ 'محفوظ کریں' دبائیں گے۔",
+ "bsvariables": "_variables.scss",
+ "bsvariables.description": "یہاں آپ بوٹسٹریپ متغیرات کو تبدیل کر سکتے ہیں۔ آپ bootstrap.build جیسا ٹول بھی استعمال کر سکتے ہیں اور اس کا نتیجہ یہاں کاپی کر سکتے ہیں۔YYYY-MM-DD میں درج کر سکتے ہیں۔",
+ "page-views-custom-error": "براہ کرم درست تاریخوں کا وقفہ فارمیٹ YYYY-MM-DD میں درج کریں۔",
+
+ "stats.yesterday": "کل",
+ "stats.today": "آج",
+ "stats.last-week": "پچھلا ہفتہ",
+ "stats.this-week": "یہ ہفتہ",
+ "stats.last-month": "پچھلا مہینہ",
+ "stats.this-month": "یہ مہینہ",
+ "stats.all": "شروع سے",
+
+ "updates": "اپ ڈیٹس",
+ "running-version": "آپ نوڈ بی بی ورژن %1 استعمال کر رہے ہیں۔",
+ "keep-updated": "ہمیشہ نوڈ بی بی کا تازہ ترین ورژن استعمال کرنے کی کوشش کریں تاکہ تازہ ترین سیکیورٹی بہتری اور مسائل کے حل سے فائدہ اٹھائیں۔",
+ "up-to-date": "آپ تازہ ترین ورژن استعمال کر رہے ہیں ",
+ "upgrade-available": "ایک نیا ورژن (%1) دستیاب ہے۔ اگر ممکن ہو تو، نوڈ بی بی اپ ڈیٹ کریں۔",
+ "prerelease-upgrade-available": "یہ نوڈ بی بی کا ایک پرانا پری ریلیز ورژن ہے۔ ایک نیا ورژن (%1) دستیاب ہے۔ اگر ممکن ہو تو، نوڈ بی بی اپ ڈیٹ کریں۔",
+ "prerelease-warning": "یہ نوڈ بی بی کا پری ریلیز ورژن ہے۔ غیر متوقع خرابیاں ہو سکتی ہیں۔ ",
+ "fallback-emailer-not-found": "بیک اپ ای میلر نہیں ملا",
+ "running-in-development": "فورم ڈیولپمنٹ موڈ میں چل رہا ہے، اس لیے یہ کمزور ہو سکتا ہے۔ براہ کرم اپنے سسٹم ایڈمنسٹریٹر سے رابطہ کریں۔",
+ "latest-lookup-failed": "نوڈ بی بی کے تازہ ترین دستیاب ورژن کی جانچ نہیں کی جا سکی",
+
+ "notices": "نوٹسز",
+ "restart-not-required": "ری اسٹارٹ کی ضرورت نہیں",
+ "restart-required": "ری اسٹارٹ کی ضرورت ہے",
+ "search-plugin-installed": "تلاش پلگ ان نصب ہے",
+ "search-plugin-not-installed": "تلاش پلگ ان نصب نہیں ہے",
+ "search-plugin-tooltip": "تلاش کی فعالیت کو فعال کرنے کے لیے پلگ انز صفحہ سے تلاش پلگ ان نصب کریں",
+
+ "control-panel": "سسٹم کنٹرول",
+ "rebuild-and-restart": "دوبارہ بنائیں اور ری اسٹارٹ کریں",
+ "restart": "ری اسٹارٹ",
+ "restart-warning": "نوڈ بی بی کو دوبارہ بنانے اور ری اسٹارٹ کرنے سے چند سیکنڈ کے لیے تمام کنکشنز منقطع ہو جائیں گے۔",
+ "restart-disabled": "نوڈ بی بی کے دوبارہ بنانے اور ری اسٹارٹ کی سہولیات غیر فعال ہیں، کیونکہ لگتا ہے کہ نوڈ بی بی مناسب ڈیمن کے ذریعے نہیں چل رہا۔",
+ "maintenance-mode": "مینٹیننس موڈ",
+ "maintenance-mode-title": "نوڈ بی بی کو مینٹیننس موڈ سیٹ کرنے کے لیے یہاں کلک کریں",
+ "dark-mode": "ڈارک موڈ",
+ "realtime-chart-updates": "ریئل ٹائم چارٹ اپ ڈیٹس",
+
+ "active-users": "فعال صارفین",
+ "active-users.users": "صارفین",
+ "active-users.guests": "مہمان",
+ "active-users.total": "کل",
+ "active-users.connections": "کنکشنز",
+
+ "guest-registered-users": "مہمان بمقابلہ رجسٹرڈ صارفین",
+ "guest": "مہمان",
+ "registered": "رجسٹرڈ",
+
+ "user-presence": "صارفین کی موجودگی",
+ "on-categories": "زمرہ جات کی فہرست میں",
+ "reading-posts": "پوسٹس پڑھ رہے ہیں",
+ "browsing-topics": "موضوعات براؤز کر رہے ہیں",
+ "recent": "حالیہ",
+ "unread": "غیر پڑھا",
+
+ "high-presence-topics": "زیادہ موجودگی والے موضوعات",
+ "popular-searches": "مقبول تلاشیں",
+
+ "graphs.page-views": "صفحہ مناظر",
+ "graphs.page-views-registered": "رجسٹرڈ صارفین کے صفحہ مناظر",
+ "graphs.page-views-guest": "مہمانوں کے صفحہ مناظر",
+ "graphs.page-views-bot": "بوٹس کے صفحہ مناظر",
+ "graphs.page-views-ap": "ایکٹیویٹی پب سے صفحہ مناظر",
+ "graphs.unique-visitors": "منفرد زائرین",
+ "graphs.registered-users": "رجسٹرڈ صارفین",
+ "graphs.guest-users": "مہمان",
+ "last-restarted-by": "آخری بار ری اسٹارٹ کیا گیا",
+ "no-users-browsing": "کوئی براؤز کرنے والے صارفین نہیں",
+
+ "back-to-dashboard": "ڈیش بورڈ پر واپس",
+ "details.no-users": "منتخب کردہ مدت میں کوئی نئے صارفین رجسٹر نہیں ہوئے",
+ "details.no-topics": "منتخب کردہ مدت میں کوئی نئے موضوعات شائع نہیں ہوئے",
+ "details.no-searches": "منتخب کردہ مدت میں کوئی تلاشیں نہیں کی گئیں",
+ "details.no-logins": "منتخب کردہ مدت میں کوئی لاگ انز رجسٹر نہیں ہوئے",
+ "details.logins-static": "نوڈ بی بی %1 دنوں تک سیشن ڈیٹا محفوظ رکھتا ہے، اس لیے درج ذیل جدول میں صرف آخری فعال سیشنز دیکھے جا سکتے ہیں",
+ "details.logins-login-time": "لاگ ان کا وقت",
+ "start": "شروع",
+ "end": "ختم",
+ "filter": "فلٹر",
+ "view-as-json": "جیسن کے طور پر دیکھیں",
+ "expand-analytics": "تجزیات پھیلائیں",
+ "clear-search-history": "تلاش کی تاریخ صاف کریں",
+ "clear-search-history-confirm": "کیا آپ واقعی تلاش کی تاریخ صاف کرنا چاہتے ہیں؟",
+ "search-term": "تلاش کی اصطلاح",
+ "search-count": "تعداد",
+ "view-all": "سب دیکھیں"
+}
diff --git a/public/language/ur/admin/development/info.json b/public/language/ur/admin/development/info.json
new file mode 100644
index 0000000000..89ece3e795
--- /dev/null
+++ b/public/language/ur/admin/development/info.json
@@ -0,0 +1,26 @@
+{
+ "you-are-on": "آپ %1:%2 پر ہیں",
+ "ip": "آئی پی %1",
+ "nodes-responded": "%1 نوڈز نے %2 ملی سیکنڈز میں جواب دیا!",
+ "host": "سرور",
+ "primary": "بنیادی / ٹاسکس",
+ "pid": "پروسیس آئی ڈی",
+ "nodejs": "نوڈ جے ایس",
+ "online": "آن لائن",
+ "git": "گٹ",
+ "process-memory": "پروسیس میموری",
+ "system-memory": "سسٹم میموری",
+ "used-memory-process": "پروسیس کی استعمال شدہ میموری",
+ "used-memory-os": "سسٹم کی استعمال شدہ میموری",
+ "total-memory-os": "کل سسٹم میموری",
+ "load": "سسٹم کا بوجھ",
+ "cpu-usage": "سی پی یو کا استعمال",
+ "uptime": "فعال وقت",
+
+ "registered": "رجسٹرڈ",
+ "sockets": "ساکٹس",
+ "connection-count": "کنکشنز کی تعداد",
+ "guests": "مہمان",
+
+ "info": "معلومات"
+}
\ No newline at end of file
diff --git a/public/language/ur/admin/development/logger.json b/public/language/ur/admin/development/logger.json
new file mode 100644
index 0000000000..6bc918549d
--- /dev/null
+++ b/public/language/ur/admin/development/logger.json
@@ -0,0 +1,13 @@
+{
+ "logger": "لاگ",
+ "logger-settings": "لاگ کی ترتیبات",
+ "description": "اگر آپ یہاں چیک مارک لگائیں گے، تو آپ اپنے ٹرمنل میں لاگ دیکھیں گے۔ اگر آپ کوئی پاتھ بتائیں گے، تو اس کے بجائے لاگز فائل میں محفوظ ہوں گے۔ ایچ ٹی ٹی پی کے ذریعے لاگنگ آپ کے فورم کو کب، کون، اور کس طرح کے لوگ وزٹ کر رہے ہیں اس کے اعدادوشمار حاصل کرنے کے لیے مفید ہے۔ ایچ ٹی ٹی پی درخواستوں کو ٹریک کرنے کے علاوہ، ہم socket.io کے واقعات کو بھی ٹریک کر سکتے ہیں۔ Socket.io لاگنگ، redis-cli کے ساتھ مل کر، نوڈ بی بی کے کام کرنے کے طریقے کو سمجھنے کے لیے بہت مفید ہو سکتی ہے۔",
+ "explanation": "ریئل ٹائم میں لاگز کو فعال یا غیر فعال کرنے کے لیے، بس لاگ کی ترتیبات میں چیک مارک لگائیں یا ہٹائیں۔ ری اسٹارٹ کی ضرورت نہیں ہے۔",
+ "enable-http": "ایچ ٹی ٹی پی لاگنگ فعال کریں",
+ "enable-socket": "socket.io واقعات کے لاگز فعال کریں",
+ "file-path": "لاگ فائل کا پاتھ",
+ "file-path-placeholder": "/پاتھ/ٹو/لاگ/فائل.log ::: اگر خالی ہو، تو لاگ ٹرمنل میں آؤٹ پٹ ہوگا",
+
+ "control-panel": "لاگ کا کنٹرول پینل",
+ "update-settings": "لاگ کی ترتیبات اپ ڈیٹ کریں"
+}
\ No newline at end of file
diff --git a/public/language/ur/admin/extend/plugins.json b/public/language/ur/admin/extend/plugins.json
new file mode 100644
index 0000000000..9a183c1a80
--- /dev/null
+++ b/public/language/ur/admin/extend/plugins.json
@@ -0,0 +1,58 @@
+{
+ "plugins": "پلگ انز",
+ "trending": "رجحانات",
+ "installed": "نصب شدہ",
+ "active": "فعال",
+ "inactive": "غیر فعال",
+ "out-of-date": "پرانا",
+ "none-found": "کوئی پلگ انز نہیں ملے۔",
+ "none-active": "کوئی فعال پلگ انز نہیں۔",
+ "find-plugins": "پلگ انز تلاش کریں",
+
+ "plugin-search": "پلگ ان تلاش",
+ "plugin-search-placeholder": "پلگ ان تلاش کریں…",
+ "submit-anonymous-usage": "پلگ ان کے استعمال کے گمنام ڈیٹا بھیجیں",
+ "reorder-plugins": "پلگ انز کو دوبارہ ترتیب دیں",
+ "order-active": "فعال پلگ انز کو ترتیب دیں",
+ "dev-interested": "کیا آپ نوڈ بی بی کے لیے پلگ انز لکھنے میں دلچسپی رکھتے ہیں؟",
+ "docs-info": "پلگ ان بنانے کے بارے میں مکمل دستاویزات نوڈ بی بی دستاویزات پورٹل پر مل سکتی ہیں۔",
+
+ "order.description": "کچھ پلگ انز بہترین طریقے سے کام کرتے ہیں اگر انہیں دوسرے پلگ انز سے پہلے یا بعد میں نصب کیا جائے۔",
+ "order.explanation": "پلگ انز اس ترتیب سے لوڈ ہوتے ہیں جو یہاں بتائی گئی ہے، اوپر سے نیچے تک۔",
+
+ "plugin-item.themes": "تھیمز",
+ "plugin-item.deactivate": "غیر فعال کریں",
+ "plugin-item.activate": "فعال کریں",
+ "plugin-item.install": "نصب کریں",
+ "plugin-item.uninstall": "ہٹائیں",
+ "plugin-item.settings": "ترتیبات",
+ "plugin-item.installed": "نصب شدہ",
+ "plugin-item.latest": "تازہ ترین",
+ "plugin-item.upgrade": "اپ گریڈ",
+ "plugin-item.more-info": "مزید معلومات کے لیے",
+ "plugin-item.unknown": "نامعلوم",
+ "plugin-item.unknown-explanation": "اس پلگ ان کی حالت کا تعین نہیں کیا جا سکتا، شاید ترتیب میں خرابی کی وجہ سے۔",
+ "plugin-item.compatible": "یہ پلگ ان نوڈ بی بی %1 کے ساتھ کام کرتا ہے",
+ "plugin-item.not-compatible": "اس پلگ ان کے پاس مطابقت کی معلومات نہیں ہیں۔ براہ کرم یقینی بنائیں کہ یہ آپ کے اصلی سرور پر نصب کرنے سے پہلے کام کرتا ہے۔",
+
+ "alert.enabled": "پلگ ان فعال ہو گیا",
+ "alert.disabled": "پلگ ان غیر فعال ہو گیا",
+ "alert.upgraded": "پلگ ان اپ گریڈ ہو گیا",
+ "alert.installed": "پلگ ان نصب ہو گیا",
+ "alert.uninstalled": "پلگ ان ہٹایا گیا",
+ "alert.activate-success": "براہ کرم نوڈ بی بی کو دوبارہ بنائیں اور ری لوڈ کریں تاکہ یہ پلگ ان مکمل طور پر فعال ہو جائے۔",
+ "alert.deactivate-success": "پلگ ان کامیابی سے غیر فعال ہو گیا۔",
+ "alert.upgrade-success": "براہ کرم نوڈ بی بی کو دوبارہ بنائیں اور ری لوڈ کریں تاکہ یہ پلگ ان مکمل طور پر اپ ڈیٹ ہو جائے۔",
+ "alert.install-success": "پلگ ان کامیابی سے نصب ہو گیا، براہ کرم اسے فعال کریں",
+ "alert.uninstall-success": "پلگ ان کامیابی سے غیر فعال اور ہٹایا گیا۔",
+ "alert.suggest-error": "نوڈ بی بی پیکیج مینیجر سے رابطہ نہیں کر سکا۔ کیا آپ تازہ ترین ورژن کی تنصیب کے ساتھ جاری رکھنا چاہتے ہیں؟
نوڈ بی بی پیکیج مینیجر سے رابطہ نہیں کر سکا۔ فی الحال اپ گریڈ کی سفارش نہیں کی جاتی۔
", + "alert.incompatible": "آپ کا نوڈ بی بی ورژن (ورژن %1) اس پلگ ان کا زیادہ سے زیادہ ورژن %2 استعمال کر سکتا ہے۔ براہ کرم نوڈ بی بی کو اپ ڈیٹ کریں اگر آپ اس پلگ ان کا نیا ورژن نصب کرنا چاہتے ہیں۔
", + "alert.possibly-incompatible": "مطابقت کی معلومات نہیں
اس پلگ ان نے آپ کے نوڈ بی بی ورژن کے ساتھ مطابقت کے لیے کوئی مخصوص ورژن نہیں بتایا۔ ہم مکمل مطابقت کی ضمانت نہیں دے سکتے اور ہو سکتا ہے کہ آپ کا نوڈ بی بی صحیح طریقے سے شروع نہ ہو۔
اگر نوڈ بی بی شروع نہیں ہوتا، تو درج ذیل کمانڈ استعمال کریں:
$ ./nodebb reset plugin=\"%1\"کیا آپ اس پلگ ان کے تازہ ترین ورژن کی تنصیب کے ساتھ جاری رکھنا چاہتے ہیں؟
", + "alert.reorder": "پلگ انز کو دوبارہ ترتیب دیا گیا", + "alert.reorder-success": "براہ کرم نوڈ بی بی کو دوبارہ بنائیں اور ری اسٹارٹ کریں تاکہ یہ عمل مکمل ہو جائے۔", + + "license.title": "پلگ ان کی лиценس معلومات", + "license.intro": "پلگ ان '%1' '%2' лиценس استعمال کرتا ہے۔ براہ کرم лиценس کے شرائط کو پڑھیں اور یقینی بنائیں کہ آپ انہیں سمجھتے ہیں، اس سے پہلے کہ آپ پلگ ان کو فعال کریں۔", + "license.cta": "کیا آپ اس پلگ ان کو فعال کرنے کے ساتھ جاری رکھنا چاہتے ہیں؟" +} diff --git a/public/language/ur/admin/extend/rewards.json b/public/language/ur/admin/extend/rewards.json new file mode 100644 index 0000000000..1346827606 --- /dev/null +++ b/public/language/ur/admin/extend/rewards.json @@ -0,0 +1,17 @@ +{ + "rewards": "انعامات", + "add-reward": "انعام شامل کریں", + "condition-if-users": "اگر صارف کا", + "condition-is": "ہے:", + "condition-then": "تو:", + "max-claims": "انعام کتنی بار حاصل کیا جا سکتا ہے", + "zero-infinite": "0 = لامحدود بار", + "select-reward": "انعام منتخب کریں", + "delete": "حذف", + "enable": "فعال کریں", + "disable": "غیر فعال کریں", + + "alert.delete-success": "انعام کامیابی سے حذف ہو گیا", + "alert.no-inputs-found": "غلط انعام — کچھ بھی درج نہیں کیا گیا!", + "alert.save-success": "انعامات کامیابی سے محفوظ ہو گئے" +} \ No newline at end of file diff --git a/public/language/ur/admin/extend/widgets.json b/public/language/ur/admin/extend/widgets.json new file mode 100644 index 0000000000..9a7e9ff69d --- /dev/null +++ b/public/language/ur/admin/extend/widgets.json @@ -0,0 +1,37 @@ +{ + "widgets": "ویجٹس", + "available": "دستیاب ویجٹس", + "explanation": "ڈراپ ڈاؤن مینو سے ایک ویجٹ منتخب کریں، پھر اسے بائیں جانب موجود ٹیمپلیٹس میں سے کسی ویجٹ ایریا میں گھسیٹ کر چھوڑیں۔", + "none-installed": "کوئی ویجٹس نہیں ملے! پلگ انز کنٹرول پینل میں بنیادی ویجٹس پلگ ان کو فعال کریں۔", + "clone-from": "ویجٹس کو کلون کریں از", + "containers.available": "دستیاب کنٹینرز", + "containers.explanation": "کسی ویجٹ پر گھسیٹیں اور چھوڑیں", + "containers.none": "کوئی نہیں", + "container.well": "ویب", + "container.jumbotron": "جمبوٹران", + "container.card": "کارڈ", + "container.card-header": "کارڈ ہیڈر", + "container.card-body": "کارڈ باڈی", + "container.title": "عنوان", + "container.body": "مواد", + "container.alert": "انتباہ", + + "alert.confirm-delete": "کیا آپ واقعی اس ویجٹ کو حذف کرنا چاہتے ہیں؟", + "alert.updated": "ویجٹس اپ ڈیٹ ہو گئے", + "alert.update-success": "ویجٹس کامیابی سے اپ ڈیٹ ہو گئے", + "alert.clone-success": "ویجٹس کامیابی سے کلون ہو گئے", + + "error.select-clone": "کلون کرنے کے لیے ایک صفحہ منتخب کریں", + + "title": "عنوان", + "title.placeholder": "عنوان (صرف کچھ کنٹینرز میں دکھایا جاتا ہے)", + "container": "کنٹینر", + "container.placeholder": "کنٹینر گھسیٹیں اور چھوڑیں یا یہاں ایچ ٹی ایم ایل درج کریں۔", + "show-to-groups": "گروپس کو دکھائیں", + "hide-from-groups": "گروپس سے چھپائیں", + "start-date": "شروع کی تاریخ", + "end-date": "ختم کی تاریخ", + "hide-on-mobile": "موبائل ڈیوائسز پر چھپائیں", + "hide-drafts": "ڈرافٹس چھپائیں", + "show-drafts": "ڈرافٹس دکھائیں" +} \ No newline at end of file diff --git a/public/language/ur/admin/manage/admins-mods.json b/public/language/ur/admin/manage/admins-mods.json new file mode 100644 index 0000000000..3e68321b97 --- /dev/null +++ b/public/language/ur/admin/manage/admins-mods.json @@ -0,0 +1,13 @@ +{ + "manage-admins-and-mods": "ایڈمنسٹریٹرز اور ماڈریٹرز کا انتظام", + "administrators": "ایڈمنسٹریٹرز", + "global-moderators": "عالمی ماڈریٹرز", + "moderators": "ماڈریٹرز", + "no-global-moderators": "کوئی عالمی ماڈریٹرز نہیں", + "no-sub-categories": "کوئی ذیلی زمرہ جات نہیں", + "view-children": "ذیلی زمرہ جات دیکھیں (%1)", + "no-moderators": "کوئی ماڈریٹرز نہیں", + "add-administrator": "ایڈمنسٹریٹر شامل کریں", + "add-global-moderator": "عالمی ماڈریٹر شامل کریں", + "add-moderator": "ماڈریٹر شامل کریں" +} \ No newline at end of file diff --git a/public/language/ur/admin/manage/categories.json b/public/language/ur/admin/manage/categories.json new file mode 100644 index 0000000000..d050877aa2 --- /dev/null +++ b/public/language/ur/admin/manage/categories.json @@ -0,0 +1,128 @@ +{ + "manage-categories": "زمرہ جات کا انتظام", + "add-category": "زمرہ شامل کریں", + "add-local-category": "Add Local category", + "add-remote-category": "Add Remote category", + "remove": "Remove", + "jump-to": "پر جائیں…", + "settings": "زمرہ کی ترتیبات", + "edit-category": "زمرہ ترمیم کریں", + "privileges": "اختیارات", + "back-to-categories": "زمرہ جات پر واپس", + "id": "Category ID", + "name": "زمرہ کا نام", + "handle": "زمرہ کا شناخت کنندہ", + "handle.help": "زمرہ کا شناخت کنندہ اس زمرہ کو دیگر نیٹ ورکس میں پیش کرنے کے لیے استعمال ہوتا ہے، جیسے کہ صارف نام۔ اس شناخت کنندہ کا موجودہ صارف نام یا صارف گروپ سے مماثل نہیں ہونا چاہیے۔", + "description": "زمرہ کی تفصیل", + "federatedDescription": "فیڈریٹڈ تفصیل", + "federatedDescription.help": "یہ متن زمرہ کی تفصیل میں شامل کیا جائے گا جب دیگر ویب سائٹس اور ایپلی کیشنز اس کے بارے میں معلومات طلب کریں گی۔", + "federatedDescription.default": "یہ فورم میں ایک زمرہ ہے جس میں موضوعاتی بحثیں شامل ہیں۔ آپ اس فورم کا ذکر کرکے ایک نئی بحث شروع کر سکتے ہیں۔", + "bg-color": "پس منظر کا رنگ", + "text-color": "متن کا رنگ", + "bg-image-size": "پس منظر تصویر کا سائز", + "custom-class": "مرضی کی کلاس", + "num-recent-replies": "حالیہ جوابات کی تعداد", + "ext-link": "خارجی لنک", + "subcategories-per-page": "فی صفحہ ذیلی زمرہ جات کی تعداد", + "is-section": "اس زمرہ کو سیکشن کے طور پر استعمال کریں", + "post-queue": "پوسٹ قطار", + "tag-whitelist": "اجازت شدہ ٹیگز کی فہرست", + "upload-image": "تصویر اپ لوڈ کریں", + "upload": "اپ لوڈ", + "delete-image": "حذف", + "category-image": "زمرہ کی تصویر", + "image-and-icon": "تصویر اور آئیکن", + "parent-category": "بنیادی زمرہ", + "optional-parent-category": "(اختیاری) بنیادی زمرہ", + "top-level": "اعلیٰ سطح", + "parent-category-none": "(کوئی نہیں)", + "copy-parent": "بنیادی سے نقل کریں", + "copy-settings": "سے ترتیبات نقل کریں", + "optional-clone-settings": "(اختیاری) زمرہ سے ترتیبات نقل کریں", + "clone-children": "ذیلی زمرہ جات اور ترتیبات کلون کریں", + "purge": "زمرہ حذف کریں", + + "enable": "فعال کریں", + "disable": "غیر فعال کریں", + "edit": "ترمیم", + "analytics": "تجزیات", + "federation": "فیڈریشن", + + "view-category": "زمرہ دیکھیں", + "set-order": "ترتیب محفوظ کریں", + "set-order-help": "زمرہ کے لیے پوزیشن سیٹ کرنے سے وہ مطلوبہ جگہ پر منتقل ہو جائے گا اور اگر ضروری ہو تو دیگر زمرہ جات کی جگہوں کو تبدیل کر دے گا۔ سب سے چھوٹا ممکنہ نمبر 1 ہے، جو زمرہ کو سب سے اوپر رکھے گا۔", + + "select-category": "زمرہ منتخب کریں", + "set-parent-category": "بنیادی زمرہ سیٹ کریں", + + "privileges.description": "اس سیکشن میں آپ ویب سائٹ کے مختلف حصوں تک رسائی کے اختیارات ترتیب دے سکتے ہیں۔ اختیارات انفرادی صارفین یا پوری گروہوں کو دیے جا سکتے ہیں۔ نیچے دیے گئے ڈراپ ڈاؤن مینو سے درخواست کا دائرہ کار منتخب کریں۔", + "privileges.category-selector": "کے لیے اختیارات ترتیب دیں", + "privileges.warning": "نوٹ: اختیارات کی ترتیبات فوری طور پر اثر انداز ہوتی ہیں۔ ان ترتیبات کو تبدیل کرنے کے بعد زمرہ کو محفوظ کرنے کی ضرورت نہیں ہے۔", + "privileges.section-viewing": "دیکھنے کے اختیارات", + "privileges.section-posting": "پوسٹنگ کے اختیارات", + "privileges.section-moderation": "ماڈریشن کے اختیارات", + "privileges.section-other": "دیگر", + "privileges.section-user": "صارف", + "privileges.search-user": "صارف شامل کریں", + "privileges.no-users": "اس زمرہ میں انفرادی صارفین کے لیے کوئی اختیارات نہیں ہیں۔", + "privileges.section-group": "گروپ", + "privileges.group-private": "یہ گروپ نجی ہے", + "privileges.inheritance-exception": "یہ گروپ رجسٹرڈ صارفین کے گروپ سے اختیارات وراثت میں نہیں لیتا", + "privileges.banned-user-inheritance": "پابندی شدہ صارفین پابندی شدہ صارفین کے گروپ سے اختیارات وراثت میں لیتے ہیں", + "privileges.search-group": "گروپ شامل کریں", + "privileges.copy-to-children": "ذیلی زمرہ جات میں نقل کریں", + "privileges.copy-from-category": "زمرہ سے نقل کریں", + "privileges.copy-privileges-to-all-categories": "تمام زمرہ جات میں نقل کریں", + "privileges.copy-group-privileges-to-children": "اس گروپ کے اختیارات اس زمرہ کے ذیلی عناصر میں نقل کریں۔", + "privileges.copy-group-privileges-to-all-categories": "اس گروپ کے اختیارات تمام زمرہ جات میں نقل کریں۔", + "privileges.copy-group-privileges-from": "اس گروپ کے اختیارات دوسرے زمرہ سے نقل کریں۔", + "privileges.inherit": "اگررجسٹرڈ صارفین گروپ کو کوئی اختیار دیا جاتا ہے، تو باقی تمام گروپس اسے طے شدہ اختیار کے طور پر حاصل کرتے ہیں، چاہے وہ انہیں خاص طور پر نہ دیا گیا ہو۔ آپ یہ طے شدہ اختیار دیکھ رہے ہیں کیونکہ تمام صارفین رجسٹرڈ صارفین گروپ کے رکن ہیں، اس لیے ایک ہی اختیارات کو مزید گروہوں کو دینے کی ضرورت نہیں ہے۔",
+ "privileges.copy-success": "اختیارات نقل ہو گئے!",
+
+ "analytics.back": "زمرہ جات کی فہرست پر واپس",
+ "analytics.title": "زمرہ '%1' کے لیے تجزیاتی ڈیٹا",
+ "analytics.pageviews-hourly": "شکل 1 – اس زمرہ کے لیے گھنٹہ وار صفحہ مناظر",
+ "analytics.pageviews-daily": "شکل 2 – اس زمرہ کے لیے روزانہ صفحہ مناظر",
+ "analytics.topics-daily": "شکل 3 – اس زمرہ میں روزانہ موضوعات کی تعداد",
+ "analytics.posts-daily": "شکل 4 – اس زمرہ میں روزانہ پوسٹس کی تعداد",
+
+ "federation.title": "زمرہ '%1' کے لیے فیڈریشن ترتیبات",
+ "federation.disabled": "ویب سائٹ کے لیے فیڈریشن غیر فعال ہے، اس لیے زمرہ کی فیڈریشن ترتیبات ناقابل رسائی ہیں۔",
+ "federation.disabled-cta": "فیڈریشن ترتیبات →",
+ "federation.syncing-header": "ہم آہنگی",
+ "federation.syncing-intro": "ایک زمرہ ایکٹیویٹی پب پروٹوکول کے ذریعے 'ذرائع کے گروپ' کو فالو کر سکتا ہے۔ اگر نیچے دیے گئے کسی بھی ذریعہ سے مواد موصول ہوتا ہے، تو وہ خود بخود اس زمرہ میں شامل ہو جائے گا۔",
+ "federation.syncing-caveat": "نوٹ: یہاں ہم آہنگی کی ترتیبات یک طرفہ ہم آہنگی قائم کرتی ہیں۔ نوڈ بی بی ذریعہ کو سبسکرائب/فالو کرنے کی کوشش کرے گا، لیکن اس کے برعکس کوئی مفروضہ نہیں ہونا چاہیے۔",
+ "federation.syncing-none": "یہ زمرہ فی الحال کسی کو فالو نہیں کر رہا۔",
+ "federation.syncing-add": "کے ساتھ ہم آہنگی کریں…",
+ "federation.syncing-actorUri": "ذریعہ",
+ "federation.syncing-follow": "فالو کریں",
+ "federation.syncing-unfollow": "فالو بند کریں",
+ "federation.followers": "اس زمرہ کو فالو کرنے والے ریموٹ صارفین",
+ "federation.followers-handle": "شناخت کنندہ",
+ "federation.followers-id": "آئی ڈی",
+ "federation.followers-none": "کوئی فالوورز نہیں۔",
+ "federation.followers-autofill": "خودکار بھریں",
+
+ "alert.created": "بنایا گیا",
+ "alert.create-success": "زمرہ کامیابی سے بنایا گیا!",
+ "alert.none-active": "آپ کے کوئی فعال زمرہ جات نہیں ہیں۔",
+ "alert.create": "زمرہ بنائیں",
+ "alert.add": "Add a Category",
+ "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.کیا آپ واقعی زمرہ '%1' کو حذف کرنا چاہتے ہیں؟
زمرہ حذف کرنے سے تمام موضوعات اور پوسٹس ہٹ جائیں گی، اور زمرہ ڈیٹا بیس سے حذف ہو جائے گا۔ اگر آپ زمرہ کو عارضی طور پر ہٹانا چاہتے ہیں، تو آپ اسے صرف 'غیر فعال' کر سکتے ہیں۔
", + "alert.purge-success": "زمرہ حذف ہو گیا!", + "alert.copy-success": "ترتیبات نقل ہو گئیں!", + "alert.set-parent-category": "بنیادی زمرہ سیٹ کریں", + "alert.updated": "زمرہ جات اپ ڈیٹ ہو گئے", + "alert.updated-success": "شناخت کنندہ %1 کے ساتھ زمرہ جات کامیابی سے اپ ڈیٹ ہو گئے۔", + "alert.upload-image": "زمرہ کے لیے تصویر اپ لوڈ کریں", + "alert.find-user": "صارف تلاش کریں", + "alert.user-search": "یہاں صارف تلاش کریں…", + "alert.find-group": "گروپ تلاش کریں", + "alert.group-search": "یہاں گروپ تلاش کریں…", + "alert.not-enough-whitelisted-tags": "اجازت شدہ ٹیگز کم ہیں۔ آپ کو مزید اجازت شدہ ٹیگز بنانے کی ضرورت ہے!", + "collapse-all": "سب سمیٹیں", + "expand-all": "سب پھیلائیں", + "disable-on-create": "بنانے پر غیر فعال کریں", + "no-matches": "کوئی مماثلت نہیں" +} \ No newline at end of file diff --git a/public/language/ur/admin/manage/digest.json b/public/language/ur/admin/manage/digest.json new file mode 100644 index 0000000000..9724bf4451 --- /dev/null +++ b/public/language/ur/admin/manage/digest.json @@ -0,0 +1,22 @@ +{ + "lead": "نیچے ڈائجسٹ بھیجنے کے اعدادوشمار اور اوقات دکھائے گئے ہیں۔", + "disclaimer": "براہ کرم نوٹ کریں کہ ای میل کی ترسیل کی کوئی ضمانت نہیں ہے، کیونکہ ای میل ٹیکنالوجی کی نوعیت ایسی ہے۔ بہت سی چیزیں اس بات پر اثر انداز ہوتی ہیں کہ آیا بھیجا گیا ای میل واقعی وصول کنندہ تک پہنچتا ہے، جیسے کہ سرور کی ساکھ، بلاک شدہ آئی پی ایڈریسز، یا DKIM/SPF/DMARC کی ترتیب۔", + "disclaimer-continued": "کامیاب ترسیل کا مطلب ہے کہ پیغام نوڈ بی بی سے کامیابی سے بھیجا گیا اور وصول کنندہ کے سرور نے اس کی تصدیق کی۔ اس کا مطلب یہ نہیں کہ ای میل وصول کنندہ کے ان باکس میں پہنچ گیا۔ بہتر نتائج کے لیے، میں ایک خصوصی ای میل بھیجنے کی سروس، جیسے کہ SendGrid، استعمال کرنے کی سفارش کرتا ہوں۔", + + "user": "صارف", + "subscription": "سبسکرپشن کی قسم", + "last-delivery": "آخری کامیاب ترسیل", + "default": "سسٹم کے لیے طے شدہ", + "default-help": "سسٹم کے لیے طے شدہ کا مطلب ہے کہ صارف نے ڈائجسٹ کے لیے عالمی فورم کی ترتیب کے طور پر کوئی دوسری ترتیبات دستی طور پر منتخب نہیں کی، جو فی الحال '%1' ہے۔", + "resend": "ڈائجسٹ دوبارہ بھیجیں", + "resend-all-confirm": "کیا آپ واقعی ڈائجسٹ کو دستی طور پر بھیجنے کا عمل شروع کرنا چاہتے ہیں؟", + "resent-single": "ڈائجسٹ کا دستی طور پر دوبارہ بھیجنا مکمل ہو گیا", + "resent-day": "روزانہ ڈائجسٹ دوبارہ بھیجا گیا", + "resent-week": "ہفتہ وار ڈائجسٹ دوبارہ بھیجا گیا", + "resent-biweek": "دو ہفتہ وار ڈائجسٹ دوبارہ بھیجا گیا", + "resent-month": "ماہانہ ڈائجسٹ دوبارہ بھیجا گیا", + "null": "کبھی نہیں", + "manual-run": "ڈائجسٹ کا دستی طور پر بھیجنا:", + + "no-delivery-data": "ترسیل کے کوئی ڈیٹا نہیں" +} diff --git a/public/language/ur/admin/manage/groups.json b/public/language/ur/admin/manage/groups.json new file mode 100644 index 0000000000..d74d14de3a --- /dev/null +++ b/public/language/ur/admin/manage/groups.json @@ -0,0 +1,49 @@ +{ + "manage-groups": "گروپس کا انتظام", + "add-group": "گروپ شامل کریں", + "edit-group": "گروپ ترمیم کریں", + "back-to-groups": "گروپس پر واپس", + "view-group": "گروپ دیکھیں", + "icon-and-title": "آئیکن اور عنوان", + "name": "گروپ کا نام", + "badge": "بیج", + "properties": "خصوصیات", + "description": "گروپ کی تفصیل", + "member-count": "اراکین کی تعداد", + "system": "سسٹم", + "hidden": "چھپا ہوا", + "private": "نجی", + "edit": "ترمیم", + "delete": "حذف", + "privileges": "اختیارات", + "members-csv": "اراکین (سی ایس وی)", + "search-placeholder": "تلاش", + "create": "گروپ بنائیں", + "description-placeholder": "گروپ کی مختصر تفصیل", + "create-button": "بنائیں", + + "alerts.create-failure": "اوہ!گروپ بنانے میں ایک مسئلہ پیش آیا۔ براہ کرم بعد میں دوبارہ کوشش کریں!
", + "alerts.confirm-delete": "کیا آپ واقعی اس گروپ کو حذف کرنا چاہتے ہیں؟", + + "edit.name": "نام", + "edit.description": "تفصیل", + "edit.user-title": "اراکین کا عہدہ", + "edit.icon": "گروپ کا آئیکن", + "edit.label-color": "گروپ لیبل کا رنگ", + "edit.text-color": "گروپ ٹیکسٹ کا رنگ", + "edit.show-badge": "بیج دکھائیں", + "edit.private-details": "اگر فعال ہو، تو گروپ میں شامل ہونے کے لیے گروپ کے مالک کی منظوری درکار ہوگی۔", + "edit.private-override": "انتباہ: نجی گروپس سسٹم لیول پر غیر فعال ہیں، یہ ترتیب اسے نظر انداز کرتی ہے۔", + "edit.disable-join": "شامل ہونے کی درخواستوں کو غیر فعال کریں", + "edit.disable-leave": "صارفین کو گروپ چھوڑنے سے روکیں", + "edit.hidden": "چھپا ہوا", + "edit.hidden-details": "اگر فعال ہو، تو گروپ گروپس کی فہرست میں نظر نہیں آئے گا اور صارفین کو خصوصی طور پر مدعو کرنا ہوگا۔", + "edit.add-user": "گروپ میں صارف شامل کریں", + "edit.add-user-search": "صارفین تلاش کریں", + "edit.members": "اراکین کی فہرست", + "control-panel": "گروپس کا کنٹرول پینل", + "revert": "واپس کریں", + + "edit.no-users-found": "کوئی صارفین نہیں ملے", + "edit.confirm-remove-user": "کیا آپ واقعی اس صارف کو ہٹانا چاہتے ہیں؟" +} \ No newline at end of file diff --git a/public/language/ur/admin/manage/privileges.json b/public/language/ur/admin/manage/privileges.json new file mode 100644 index 0000000000..cfd5d95854 --- /dev/null +++ b/public/language/ur/admin/manage/privileges.json @@ -0,0 +1,66 @@ +{ + "manage-privileges": "اختیارات کا انتظام", + "discard-changes": "تبدیلیاں مسترد کریں", + "global": "عالمی", + "admin": "ایڈمن", + "group-privileges": "گروپس کے لیے اختیارات", + "user-privileges": "صارفین کے لیے اختیارات", + "edit-privileges": "اختیارات ترمیم کریں", + "select-clear-all": "سب منتخب کریں/سب صاف کریں", + "chat": "گفتگو", + "chat-with-privileged": "اعلیٰ اختیارات والے سے گفتگو", + "upload-images": "تصاویر اپ لوڈ کریں", + "upload-files": "فائلیں اپ لوڈ کریں", + "signature": "دستخط", + "ban": "پابندی", + "mute": "خاموش", + "invite": "دعوت بھیجیں", + "search-content": "مواد تلاش کریں", + "search-users": "صارفین تلاش کریں", + "search-tags": "ٹیگز تلاش کریں", + "view-users": "صارفین دیکھیں", + "view-tags": "ٹیگز دیکھیں", + "view-groups": "گروپس دیکھیں", + "allow-local-login": "مقامی لاگ ان کی اجازت", + "allow-group-creation": "گروپ بنانے کی اجازت", + "view-users-info": "صارفین کی معلومات دیکھیں", + "find-category": "زمرہ تلاش کریں", + "access-category": "زمرہ تک رسائی", + "access-topics": "موضوعات تک رسائی", + "create-topics": "موضوعات بنائیں", + "reply-to-topics": "موضوعات میں جواب دیں", + "schedule-topics": "موضوعات شیڈول کریں", + "tag-topics": "موضوعات پر ٹیگز لگائیں", + "edit-posts": "پوسٹس ترمیم کریں", + "view-edit-history": "ترمیمی تاریخ دیکھیں", + "delete-posts": "پوسٹس حذف کریں", + "view-deleted": "حذف شدہ پوسٹس دیکھیں", + "upvote-posts": "پوسٹس کے لیے مثبت ووٹ", + "downvote-posts": "پوسٹس کے لیے منفی ووٹ", + "delete-topics": "موضوعات حذف کریں", + "purge": "صاف کریں", + "moderate": "ماڈریٹ", + "admin-dashboard": "ڈیش بورڈ", + "admin-categories": "زمرہ جات", + "admin-privileges": "اختیارات", + "admin-users": "صارفین", + "admin-admins-mods": "ایڈمنسٹریٹرز اور ماڈریٹرز", + "admin-groups": "گروپس", + "admin-tags": "ٹیگز", + "admin-settings": "ترتیبات", + + "alert.confirm-moderate": "کیا آپ واقعی اس صارف گروپ کو ماڈریشن کا اختیار دینا چاہتے ہیں؟ یہ گروپ عوامی ہے اور کوئی بھی اس میں آزادانہ طور پر شامل ہو سکتا ہے۔", + "alert.confirm-admins-mods": "کیا آپ واقعی اس صارف/گروپ کو 'ایڈمنسٹریٹرز اور ماڈریٹرز' کا اختیار دینا چاہتے ہیں؟ اس اختیار کے حامل صارفین دوسرے گروپس کے اختیارات تبدیل کر سکتے ہیں، بشمول سپر ایڈمنسٹریٹر کا اختیار دینا۔", + "alert.confirm-save": "براہ کرم ان اختیارات کو محفوظ کرنے کی اپنی خواہش کی تصدیق کریں", + "alert.confirm-discard": "کیا آپ واقعی اختیارات کی تبدیلیوں کو مسترد کرنا چاہتے ہیں؟", + "alert.discarded": "اختیارات کی تبدیلیاں مسترد کر دی گئیں", + "alert.confirm-copyToAll": "کیا آپ واقعی اس %1 سیٹ کو تمام زمرہ جات پر لگانا چاہتے ہیں؟", + "alert.confirm-copyToAllGroup": "کیا آپ واقعی اس گروپ کے %1 سیٹ کو تمام زمرہ جات پر لگانا چاہتے ہیں؟", + "alert.confirm-copyToChildren": "کیا آپ واقعی اس %1 سیٹ کو تمام ذیلی زمرہ جات پر لگانا چاہتے ہیں؟", + "alert.confirm-copyToChildrenGroup": "کیا آپ واقعی اس گروپ کے %1 سیٹ کو تمام ذیلی زمرہ جات پر لگانا چاہتے ہیں؟", + "alert.no-undo": "یہ عمل ناقابل واپسی ہے۔", + "alert.admin-warning": "ایڈمنسٹریٹرز کے پاس طے شدہ طور پر تمام اختیارات ہوتے ہیں", + "alert.copyPrivilegesFrom-title": "نقل کرنے کے لیے زمرہ منتخب کریں", + "alert.copyPrivilegesFrom-warning": "یہ منتخب کردہ زمرہ سے %1 نقل کرے گا۔", + "alert.copyPrivilegesFromGroup-warning": "یہ منتخب کردہ زمرہ سے اس گروپ کے %1 سیٹ کو نقل کرے گا۔" +} \ No newline at end of file diff --git a/public/language/ur/admin/manage/registration.json b/public/language/ur/admin/manage/registration.json new file mode 100644 index 0000000000..0f98371113 --- /dev/null +++ b/public/language/ur/admin/manage/registration.json @@ -0,0 +1,20 @@ +{ + "queue": "قطار", + "description": "رجسٹریشن کی قطار میں کوئی صارفین نہیں ہیں۔CTRL استعمال کریں۔",
+ "create": "ٹیگ بنائیں",
+ "add-tag": "ٹیگ شامل کریں",
+ "modify": "ٹیگز ترمیم کریں",
+ "rename": "ٹیگز کا نام تبدیل کریں",
+ "delete": "منتخب ٹیگز حذف کریں",
+ "search": "ٹیگز تلاش کریں…",
+ "settings": "ٹیگز کی ترتیبات",
+ "name": "ٹیگ کا نام",
+
+ "alerts.editing": "ٹیگ(s) ترمیم ہو رہا ہے",
+ "alerts.confirm-delete": "کیا آپ واقعی منتخب ٹیگز کو حذف کرنا چاہتے ہیں؟",
+ "alerts.update-success": "ٹیگ تبدیل ہو گیا!",
+ "reset-colors": "طے شدہ رنگ بحال کریں"
+}
\ No newline at end of file
diff --git a/public/language/ur/admin/manage/uploads.json b/public/language/ur/admin/manage/uploads.json
new file mode 100644
index 0000000000..2e93264f61
--- /dev/null
+++ b/public/language/ur/admin/manage/uploads.json
@@ -0,0 +1,12 @@
+{
+ "manage-uploads": "اپ لوڈز کا انتظام",
+ "upload-file": "فائل اپ لوڈ کریں",
+ "filename": "فائل کا نام",
+ "usage": "پوسٹس میں استعمال",
+ "orphaned": "غیر استعمال شدہ",
+ "size/filecount": "سائز / فائلوں کی تعداد",
+ "confirm-delete": "کیا آپ واقعی اس فائل کو حذف کرنا چاہتے ہیں؟",
+ "filecount": "%1 فائلیں",
+ "new-folder": "نیا فولڈر",
+ "name-new-folder": "نئے فولڈر کا نام درج کریں"
+}
\ No newline at end of file
diff --git a/public/language/ur/admin/manage/user-custom-fields.json b/public/language/ur/admin/manage/user-custom-fields.json
new file mode 100644
index 0000000000..852009baaa
--- /dev/null
+++ b/public/language/ur/admin/manage/user-custom-fields.json
@@ -0,0 +1,28 @@
+{
+ "title": "صارف کے حسب ضرورت فیلڈز کا انتظام",
+ "create-field": "فیلڈ بنائیں",
+ "edit-field": "فیلڈ میں ترمیم کریں",
+ "manage-custom-fields": "حسب ضرورت فیلڈز کا انتظام",
+ "type-of-input": "ان پٹ کی قسم",
+ "key": "کلید",
+ "name": "نام",
+ "icon": "آئیکن",
+ "type": "قسم",
+ "min-rep": "کم از کم ساکھ",
+ "input-type-text": "ان پٹ (متن)",
+ "input-type-link": "ان پٹ (لنک)",
+ "input-type-number": "ان پٹ (نمبر)",
+ "input-type-date": "ان پٹ (تاریخ)",
+ "input-type-select": "انتخاب",
+ "input-type-select-multi": "متعدد انتخاب",
+ "select-options": "اختیارات",
+ "select-options-help": "منتخب عنصر کے لیے ہر سطر پر ایک آپشن شامل کریں",
+ "minimum-reputation": "کم از کم ساکھ",
+ "minimum-reputation-help": "اگر صارف کی ساکھ مقررہ مقدار سے کم ہو تو وہ اس فیلڈ کو استعمال نہیں کر سکے گا",
+ "delete-field-confirm-x": "کیا آپ واقعی حسب ضرورت فیلڈ '%1' کو حذف کرنا چاہتے ہیں؟",
+ "custom-fields-saved": "حسب ضرورت فیلڈز محفوظ کر دیے گئے ہیں",
+ "visibility": "مرئیت",
+ "visibility-all": "ہر کوئی فیلڈ دیکھ سکتا ہے",
+ "visibility-loggedin": "صرف لاگ ان صارفین فیلڈ دیکھ سکتے ہیں",
+ "visibility-privileged": "صرف اعلیٰ اختیارات والے صارفین (جیسے ایڈمنسٹریٹرز اور ماڈریٹرز) فیلڈ دیکھ سکتے ہیں"
+}
\ No newline at end of file
diff --git a/public/language/ur/admin/manage/users.json b/public/language/ur/admin/manage/users.json
new file mode 100644
index 0000000000..ff733a3fc7
--- /dev/null
+++ b/public/language/ur/admin/manage/users.json
@@ -0,0 +1,152 @@
+{
+ "manage-users": "صارفین کا انتظام",
+ "users": "صارفین",
+ "edit": "عمل",
+ "make-admin": "ایڈمنسٹریٹر کے حقوق دینا",
+ "remove-admin": "ایڈمنسٹریٹر کے حقوق ہٹانا",
+ "change-email": "ای میل تبدیل کریں",
+ "new-email": "نیا ای میل",
+ "validate-email": "ای میل کی تصدیق",
+ "send-validation-email": "تصدیقی ای میل بھیجیں",
+ "change-password": "پاس ورڈ تبدیل کریں",
+ "password-reset-email": "پاس ورڈ بحالی کا ای میل بھیجیں",
+ "force-password-reset": "پاس ورڈ کو زبردستی بحال کریں اور صارف کو سائن آؤٹ کریں",
+ "ban": "پابندی",
+ "ban-users": "صارف/صارفین پر پابندی لگائیں",
+ "temp-ban": "صارف/صارفین پر عارضی پابندی لگائیں",
+ "unban": "صارف/صارفین کی پابندی ہٹائیں",
+ "reset-lockout": "لاک آؤٹ ری سیٹ کریں",
+ "reset-flags": "رپورٹس منسوخ کریں",
+ "delete": "حذف",
+ "delete-users": "صارف/صارفین کو حذف کریں",
+ "delete-content": "صارف/صارفین کے مواد کو حذف کریں",
+ "purge": "صارف/صارفین اور مواد کو حذف کریں",
+ "download-csv": "CSV فارمیٹ میں ڈاؤن لوڈ کریں",
+ "custom-user-fields": "مرضی کے صارف فیلڈز",
+ "manage-groups": "گروپس کا انتظام",
+ "set-reputation": "ساکھ سیٹ کریں",
+ "add-group": "گروپ شامل کریں",
+ "create": "صارف بنائیں",
+ "invite": "ای میل کے ذریعے دعوت دیں",
+ "new": "نیا صارف",
+ "filter-by": "فلٹر بمطابق",
+ "pills.unvalidated": "ای میل تصدیق شدہ نہیں",
+ "pills.validated": "تصدیق شدہ",
+ "pills.banned": "پابندی شدہ",
+
+ "50-per-page": "50 فی صفحہ",
+ "100-per-page": "100 فی صفحہ",
+ "250-per-page": "250 فی صفحہ",
+ "500-per-page": "500 فی صفحہ",
+
+ "search.uid": "صارف شناخت کنندہ کے ذریعے",
+ "search.uid-placeholder": "تلاش کرنے کے لیے صارف شناخت کنندہ درج کریں",
+ "search.username": "صارف نام کے ذریعے",
+ "search.username-placeholder": "تلاش کرنے کے لیے صارف نام درج کریں",
+ "search.email": "ای میل کے ذریعے",
+ "search.email-placeholder": "تلاش کرنے کے لیے ای میل درج کریں",
+ "search.ip": "آئی پی ایڈریس کے ذریعے",
+ "search.ip-placeholder": "تلاش کرنے کے لیے آئی پی ایڈریس درج کریں",
+ "search.not-found": "صارف نہیں ملا!",
+
+ "inactive.3-months": "3 ماہ",
+ "inactive.6-months": "6 ماہ",
+ "inactive.12-months": "12 ماہ",
+
+ "users.uid": "صارف آئی ڈی",
+ "users.username": "صارف نام",
+ "users.email": "ای میل",
+ "users.no-email": "(کوئی ای میل نہیں)",
+ "users.validated": "تصدیق شدہ",
+ "users.not-validated": "غیر تصدیق شدہ",
+ "users.validation-pending": "تصدیق زیر التوا",
+ "users.validation-expired": "تصدیق کی میعاد ختم",
+ "users.ip": "آئی پی ایڈریس",
+ "users.postcount": "پوسٹس کی تعداد",
+ "users.reputation": "ساکھ",
+ "users.flags": "رپورٹس",
+ "users.joined": "شامل ہوا",
+ "users.last-online": "آخری بار آن لائن",
+ "users.banned": "پابندی شدہ",
+
+ "create.username": "صارف نام",
+ "create.email": "ای میل",
+ "create.email-placeholder": "اس صارف کا ای میل",
+ "create.password": "پاس ورڈ",
+ "create.password-confirm": "پاس ورڈ کی تصدیق کریں",
+
+ "temp-ban.length": "دورانیہ",
+ "temp-ban.reason": "وجہ (اختیاری)",
+ "temp-ban.hours": "گھنٹے",
+ "temp-ban.days": "دن",
+ "temp-ban.explanation": "پابندی کا دورانیہ درج کریں۔ 0 کی قیمت پابندی کو مستقل بنائے گی۔",
+
+ "alerts.confirm-ban": "کیا آپ واقعی اس صارف پر مستقل پابندی لگانا چاہتے ہیں؟",
+ "alerts.confirm-ban-multi": "کیا آپ واقعی ان صارفین پر مستقل پابندی لگانا چاہتے ہیں؟",
+ "alerts.ban-success": "صارف/صارفین پر پابندی لگائی گئی!",
+ "alerts.button-ban-x": "%1 صارف/صارفین پر پابندی لگائیں",
+ "alerts.unban-success": "صارف/صارفین کی پابندی ہٹائی گئی!",
+ "alerts.lockout-reset-success": "لاک آؤٹ/لاک آؤٹس ری سیٹ ہو گئے!",
+ "alerts.password-change-success": "پاس ورڈ/پاس ورڈز تبدیل ہو گئے!",
+ "alerts.flag-reset-success": "رپورٹ/رپورٹس منسوخ ہو گئیں!",
+ "alerts.no-remove-yourself-admin": "آپ اپنے ایڈمنسٹریٹر کے حقوق نہیں ہٹا سکتے!",
+ "alerts.make-admin-success": "صارف اب ایڈمنسٹریٹر ہوگا۔",
+ "alerts.confirm-remove-admin": "کیا آپ واقعی اس ایڈمنسٹریٹر کو ہٹانا چاہتے ہیں؟",
+ "alerts.remove-admin-success": "صارف اب ایڈمنسٹریٹر نہیں رہے گا۔",
+ "alerts.make-global-mod-success": "صارف اب عالمی ماڈریٹر ہوگا۔",
+ "alerts.confirm-remove-global-mod": "کیا آپ واقعی اس عالمی ماڈریٹر کو ہٹانا چاہتے ہیں؟",
+ "alerts.remove-global-mod-success": "صارف اب عالمی ماڈریٹر نہیں رہے گا۔",
+ "alerts.make-moderator-success": "صارف اب ماڈریٹر ہوگا۔",
+ "alerts.confirm-remove-moderator": "کیا آپ واقعی اس ماڈریٹر کو ہٹانا چاہتے ہیں؟",
+ "alerts.remove-moderator-success": "صارف اب ماڈریٹر نہیں رہے گا۔",
+ "alerts.confirm-validate-email": "کیا آپ اس صارف/صارفین کے ای میل کی تصدیق کرنا چاہتے ہیں؟",
+ "alerts.confirm-force-password-reset": "کیا آپ واقعی صارف یا صارفین کے پاس ورڈ کو زبردستی بحال کرنا اور انہیں سائن آؤٹ کرنا چاہتے ہیں؟",
+ "alerts.validate-email-success": "ای میلز کی تصدیق ہو گئی",
+ "alerts.validate-force-password-reset-success": "صارف (یا صارفین) کا پاس ورڈ بحال ہو گیا اور ان کی سیشن ختم کر دی گئی۔",
+ "alerts.password-reset-confirm": "کیا آپ اس صارف/صارفین کو پاس ورڈ بحالی کا ای میل بھیجنا چاہتے ہیں؟",
+ "alerts.password-reset-email-sent": "پاس ورڈ بحالی کا ای میل بھیج دیا گیا۔",
+ "alerts.confirm-delete": "انتباہ!کیا آپ واقعی صارف/صارفین کو حذف کرنا چاہتے ہیں؟
یہ عمل ناقابل واپسی ہے! صرف صارف/صارفین کا پروفائل حذف ہوگا، ان کی پوسٹس اور موضوعات باقی رہیں گے۔
", + "alerts.delete-success": "صارف/صارفین حذف ہو گئے!", + "alerts.confirm-delete-content": "انتباہ!کیا آپ واقعی اس صارف یا ان صارفین کے مواد کو حذف کرنا چاہتے ہیں؟
یہ عمل ناقابل واپسی ہے! صارفین کے پروفائلز باقی رہیں گے، لیکن ان کی تمام پوسٹس اور موضوعات حذف ہو جائیں گے۔
", + "alerts.delete-content-success": "صارف/صارفین کا مواد حذف ہو گیا!", + "alerts.confirm-purge": "انتباہ!کیا آپ واقعی صارف/صارفین اور ان کے مواد کو حذف کرنا چاہتے ہیں؟
یہ عمل ناقابل واپسی ہے! تمام صارفی ڈیٹا اور مواد ختم ہو جائے گا!
", + "alerts.create": "صارف بنائیں", + "alerts.button-create": "بنائیں", + "alerts.button-cancel": "منسوخ", + "alerts.button-change": "تبدیل", + "alerts.error-passwords-different": "پاس ورڈز مختلف ہیں!", + "alerts.error-x": "خرابی%1
", + "alerts.create-success": "صارف بنایا گیا!", + + "alerts.prompt-email": "ای میلز: ", + "alerts.email-sent-to": "%1 کو تصدیقی ای میل بھیجا گیا", + "alerts.x-users-found": "صارفین ملے: %1 (%2 سیکنڈ)", + "alerts.select-a-single-user-to-change-email": "ای میل تبدیل کرنے کے لیے ایک صارف منتخب کریں", + "export": "برآمد", + "export-users-fields-title": "CSV کے لیے فیلڈز منتخب کریں", + "export-field-email": "ای میل", + "export-field-username": "صارف نام", + "export-field-uid": "صارف شناخت کنندہ", + "export-field-ip": "آئی پی ایڈریس", + "export-field-joindate": "شامل ہونے کی تاریخ", + "export-field-lastonline": "آخری بار آن لائن", + "export-field-lastposttime": "آخری جواب کا وقت", + "export-field-reputation": "ساکھ", + "export-field-postcount": "پوسٹس کی تعداد", + "export-field-topiccount": "موضوعات کی تعداد", + "export-field-profileviews": "پروفائل مناظر", + "export-field-followercount": "فالوورز کی تعداد", + "export-field-followingcount": "فالو کیے جانے والوں کی تعداد", + "export-field-fullname": "مکمل نام", + "export-field-website": "ویب سائٹ", + "export-field-location": "مقام", + "export-field-birthday": "سالگرہ", + "export-field-signature": "دستخط", + "export-field-aboutme": "صارف کے بارے میں", + + "export-users-started": "صارفین کو CSV فارمیٹ میں برآمد کیا جا رہا ہے… اس میں کچھ وقت لگ سکتا ہے۔ جب یہ مکمل ہو جائے گا تو آپ کو اطلاع دی جائے گی۔", + "export-users-completed": "صارفین CSV فارمیٹ میں برآمد ہو گئے، ڈاؤن لوڈ کے لیے کلک کریں۔", + "email": "ای میل", + "password": "پاس ورڈ", + "manage": "انتظام" +} \ No newline at end of file diff --git a/public/language/ur/admin/menu.json b/public/language/ur/admin/menu.json new file mode 100644 index 0000000000..cd15806540 --- /dev/null +++ b/public/language/ur/admin/menu.json @@ -0,0 +1,93 @@ +{ + "section-dashboard": "ڈیش بورڈ", + "dashboard/overview": "جائزہ", + "dashboard/logins": "لاگ انز", + "dashboard/users": "صارفین", + "dashboard/topics": "موضوعات", + "dashboard/searches": "تلاشیں", + "section-general": "عمومی", + + "section-manage": "انتظام", + "manage/categories": "زمرہ جات", + "manage/privileges": "اختیارات", + "manage/tags": "ٹیگز", + "manage/users": "صارفین", + "manage/admins-mods": "ایڈمنسٹریٹرز اور ماڈریٹرز", + "manage/registration": "رجسٹریشن کی قطار", + "manage/flagged-content": "رپورٹ کیا گیا مواد", + "manage/post-queue": "پوسٹس کی قطار", + "manage/groups": "گروپس", + "manage/ip-blacklist": "IP ایڈریسز کا بلیک لسٹ", + "manage/uploads": "اپ لوڈز", + "manage/digest": "خلاصے", + + "section-settings": "ترتیبات", + "settings/general": "عمومی", + "settings/homepage": "ہوم پیج", + "settings/navigation": "نیویگیشن", + "settings/reputation": "ساکھ اور رپورٹس", + "settings/email": "ای میل", + "settings/user": "صارفین", + "settings/group": "گروپس", + "settings/guest": "مہمان", + "settings/uploads": "اپ لوڈز", + "settings/languages": "زبانیں", + "settings/post": "پوسٹس", + "settings/chat": "چیتس", + "settings/pagination": "صفحہ بندی", + "settings/tags": "ٹیگز", + "settings/notifications": "نوٹیفکیشنز", + "settings/api": "API کے ذریعے رسائی", + "settings/activitypub": "فیڈریشن (ActivityPub)", + "settings/sounds": "آوازیں", + "settings/social": "سوشل", + "settings/cookies": "کوکیز", + "settings/web-crawler": "ویب کرالر", + "settings/sockets": "ساکٹس", + "settings/advanced": "اعلیٰ", + + "settings.page-title": "%1 کی ترتیبات", + + "section-appearance": "ظاہری شکل", + "appearance/themes": "تھیمز", + "appearance/skins": "جلدیں", + "appearance/customise": "اپنی مرضی کا مواد (HTML/JS/CSS)", + + "section-extend": "توسیع", + "extend/plugins": "پلگ انز", + "extend/widgets": "ویجٹس", + "extend/rewards": "انعامات", + + "section-social-auth": "سوشل تصدیق", + + "section-plugins": "پلگ انز", + "extend/plugins.install": "پلگ انز انسٹال کریں", + + "section-advanced": "اعلیٰ", + "advanced/database": "ڈیٹا بیس", + "advanced/events": "ایونٹس", + "advanced/hooks": "ہکس", + "advanced/logs": "لاگس", + "advanced/errors": "غلطیاں", + "advanced/cache": "کیش", + "development/logger": "لاگ", + "development/info": "معلومات", + + "rebuild-and-restart-forum": "فورم کو دوبارہ بنائیں اور دوبارہ شروع کریں", + "rebuild-and-restart": "دوبارہ بنائیں اور دوبارہ شروع کریں", + "restart-forum": "فورم دوبارہ شروع کریں", + "restart": "دوبارہ شروع کریں", + "logout": "لاگ آؤٹ", + "view-forum": "فورم دیکھیں", + + "search.placeholder": "ترتیبات تلاش کریں", + "search.no-results": "کوئی نتائج نہیں…", + "search.search-forum": "فورم میں تلاش کریں ", + "search.keep-typing": "مزید نتائج دیکھنے کے لیے لکھتے رہیں…", + "search.start-typing": "نتائج حاصل کرنے کے لیے لکھنا شروع کریں…", + + "connection-lost": "%1 سے رابطہ منقطع ہو گیا۔ ہم آپ کو دوبارہ جوڑنے کی کوشش کر رہے ہیں…", + + "alerts.version": "NodeBB ورژن %1 استعمال ہو رہا ہے", + "alerts.upgrade": "v%1 پر اپ گریڈ کریں" +} \ No newline at end of file diff --git a/public/language/ur/admin/settings/activitypub.json b/public/language/ur/admin/settings/activitypub.json new file mode 100644 index 0000000000..537f7354eb --- /dev/null +++ b/public/language/ur/admin/settings/activitypub.json @@ -0,0 +1,47 @@ +{ + "intro-lead": "فیڈریشن کیا ہے؟", + "intro-body": "نوڈ بی بی دیگر نوڈ بی بی تنصیبات کے ساتھ رابطہ قائم کر سکتا ہے جو اس کی حمایت کرتی ہیں۔ یہ ایک پروٹوکول کے ذریعے حاصل کیا جاتا ہے جسے ایکٹیویٹی پب کہا جاتا ہے۔ اگر فعال ہو، تو نوڈ بی بی دیگر ایپلی کیشنز اور ویب سائٹس کے ساتھ بھی رابطہ قائم کر سکے گا جو ایکٹیویٹی پب استعمال کرتی ہیں (جیسے کہ ماسٹوڈن، پیئر ٹیوب وغیرہ)۔", + "general": "عام", + "pruning": "مواد ہٹانا", + "content-pruning": "ریموٹ مواد کو محفوظ رکھنے کے دنوں کی تعداد", + "content-pruning-help": "نوٹ کریں کہ ریموٹ مواد جو کسی سرگرمی (جوابات یا مثبت/منفی ووٹ) کا حصہ رہا ہو، محفوظ رہے گا۔ (0 = غیر فعال)", + "user-pruning": "ریموٹ صارف اکاؤنٹس کو کیش کرنے کے دنوں کی تعداد", + "user-pruning-help": "ریموٹ صارف اکاؤنٹس صرف اس صورت میں ہٹائے جائیں گے اگر انہوں نے کچھ پوسٹ نہ کیا ہو۔ بصورت دیگر، وہ دوبارہ حاصل کیے جائیں گے۔ (0 = غیر فعال)", + "enabled": "فیڈریشن فعال کریں", + "enabled-help": "اگر فعال ہو، تو یہ نوڈ بی بی پوری فیڈیورس میں ایکٹیویٹی پب استعمال کرنے والے تمام کلائنٹس کے ساتھ رابطہ قائم کر سکے گا۔", + "allowLoopback": "لوکل لوپ بیک پروسیسنگ کی اجازت دیں", + "allowLoopback-help": "صرف ڈیبگنگ کے لیے مفید۔ اسے غیر فعال رکھنا بہتر ہے۔", + + "probe": "ایپلی کیشن میں کھولیں", + "probe-enabled": "کیا ایکٹیویٹی پب کی حمایت کرنے والی چیزوں کو نوڈ بی بی میں کھولنے کی کوشش کی جائے", + "probe-enabled-help": "اگر فعال ہو، تو نوڈ بی بی ہر خارجی لنک کو چیک کرے گا کہ آیا وہ ایکٹیویٹی پب کی حمایت کرتا ہے اور اگر ہاں، تو اسے براہ راست نوڈ بی بی میں لوڈ کرے گا۔", + "probe-timeout": "پروب ٹائم آؤٹ (ملی سیکنڈز)", + "probe-timeout-help": "(طے شدہ: 2000) اگر پروب کو مقررہ وقت کے اندر جواب نہ ملے، تو صارف کو براہ راست لنک کے ایڈریس پر بھیجا جائے گا۔ اگر ویب سائٹس سست جواب دیتی ہیں اور آپ انہیں زیادہ وقت دینا چاہتے ہیں تو بڑا نمبر سیٹ کریں۔", + + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.one,two,three)",
+ "rules.add": "Add New Rule",
+ "rules.type": "Type",
+ "rules.value": "Value",
+ "rules.cid": "Category",
+
+ "relays": "Relays",
+ "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.",
+ "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.",
+ "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.",
+ "relays.add": "Add New Relay",
+ "relays.relay": "Relay",
+ "relays.state": "State",
+ "relays.state-0": "Pending",
+ "relays.state-1": "Receiving only",
+ "relays.state-2": "Active",
+
+ "server-filtering": "فلٹرنگ",
+ "count": "یہ نوڈ بی بی فی الحال %1 سرور(ز) کے بارے میں جانتا ہے",
+ "server.filter-help": "ان سرورز کی نشاندہی کریں جن کے ساتھ آپ نہیں چاہتے کہ آپ کا نوڈ بی بی رابطہ قائم کرے۔ یا آپ اس کے بجائے مخصوص سرورز کی نشاندہی کر سکتے ہیں جن کے ساتھ رابطہ کی اجازت ہے۔ دونوں اختیارات دستیاب ہیں، لیکن آپ صرف ایک کا انتخاب کر سکتے ہیں۔",
+ "server.filter-help-hostname": "نیچے صرف سرورز کے نام درج کریں (جیسے example.org)، ہر سرور ایک الگ لائن پر ہونا چاہیے۔",
+ "server.filter-allow-list": "اسے اجازت یافتہ سرورز کی فہرست کے طور پر استعمال کریں"
+}
\ No newline at end of file
diff --git a/public/language/ur/admin/settings/advanced.json b/public/language/ur/admin/settings/advanced.json
new file mode 100644
index 0000000000..59c3dec7fe
--- /dev/null
+++ b/public/language/ur/admin/settings/advanced.json
@@ -0,0 +1,47 @@
+{
+ "maintenance-mode": "بحالی کا موڈ",
+ "maintenance-mode.help": "جب فورم بحالی کے موڈ میں ہوتا ہے، تو تمام درخواستیں ایک سٹیٹک ویٹنگ پیج پر ری ڈائریکٹ ہو جاتی ہیں، سوائے ایڈمنسٹریٹرز کے، جو ویب سائٹ کو معمول کے مطابق استعمال کر سکتے ہیں۔",
+ "maintenance-mode.status": "بحالی کے موڈ کے لیے سٹیٹس کوڈ",
+ "maintenance-mode.message": "بحالی کا پیغام",
+ "maintenance-mode.groups-exempt-from-maintenance-mode": "بحالی کے موڈ سے مستثنیٰ گروپس منتخب کریں",
+ "headers": "ہیڈرز",
+ "headers.allow-from": "NodeBB کو iFrame میں رکھنے کے لیے 'ALLOW-FROM' سیٹ کریں",
+ "headers.csp-frame-ancestors": "NodeBB کو iFrame میں رکھنے کے لیے 'Content-Security-Policy frame-ancestors' ہیڈر سیٹ کریں",
+ "headers.csp-frame-ancestors-help": "'none' (کچھ نہیں)، 'self' (خود - طے شدہ) یا اجازت شدہ ایڈریسز کی فہرست۔",
+ "headers.powered-by": "NodeBB سے بھیجے جانے والے 'Powered by' ہیڈر کو حسب ضرورت بنائیں",
+ "headers.acao": "Access-Control-Allow-Origin",
+ "headers.acao-regex": "Access-Control-Allow-Origin کے لیے ریگولر ایکسپریشن",
+ "headers.acao-help": "تمام ویب سائٹس تک رسائی منع کرنے کے لیے خالی چھوڑ دیں",
+ "headers.acao-regex-help": "متحرک اصل کے ساتھ مماثلت کے لیے ریگولر ایکسپریشن درج کریں۔ تمام ویب سائٹس تک رسائی منع کرنے کے لیے اسے خالی چھوڑ دیں۔",
+ "headers.acac": "Access-Control-Allow-Credentials",
+ "headers.acam": "Access-Control-Allow-Methods",
+ "headers.acah": "Access-Control-Allow-Headers",
+ "headers.coep": "Cross-Origin-Embedder-Policy",
+ "headers.coep-help": "جب فعال ہو (طے شدہ)، ہیڈر کی قیمت require-corp ہوگی",
+ "headers.coop": "Cross-Origin-Opener-Policy",
+ "headers.corp": "Cross-Origin-Resource-Policy",
+ "headers.permissions-policy": "Permissions-Policy",
+ "headers.permissions-policy-help": "'permissions-policy' ہیڈر میں قیمت سیٹ کرنے کی اجازت دیتا ہے، جیسے کہ 'geolocation=*, camera=()'۔ مزید معلومات کے لیے یہاں دیکھیں۔",
+ "hsts": "سخت ٹرانسپورٹ سیکیورٹی",
+ "hsts.enabled": "HSTS فعال کریں (تجویز کردہ)",
+ "hsts.maxAge": "HSTS کی زیادہ سے زیادہ عمر",
+ "hsts.subdomains": "HSTS ہیڈر میں ذیلی ڈومینز شامل کریں",
+ "hsts.preload": "HSTS ہیڈر کے لیے پری لوڈنگ کی اجازت دیں",
+ "hsts.help": "اگر یہ فعال ہو، تو اس ویب کے لیے HSTS ہیڈر سیٹ کیا جائے گا۔ آپ منتخب کر سکتے ہیں کہ ذیلی ڈومینز شامل کریں یا ہیڈر میں پری لوڈ فلیگز رکھیں۔ اگر آپ کو یقین نہیں کہ کیا کرنا ہے، تو بہتر ہے کہ کچھ نہ منتخب کریں۔ مزید معلومات",
+ "traffic-management": "ٹریفک کا انتظام",
+ "traffic.help": "NodeBB ایک ماڈیول استعمال کرتا ہے جو مصروف اوقات میں درخواستیں خود بخود مسترد کر دیتا ہے۔ آپ یہاں رویہ ترتیب دے سکتے ہیں، حالانکہ طے شدہ قیمتیں ایک اچھا نقطہ آغاز ہیں۔",
+ "traffic.enable": "ٹریفک مینجمنٹ فعال کریں",
+ "traffic.event-lag": "ایونٹ لوپ میں تاخیر کی حد (ملی سیکنڈز میں)",
+ "traffic.event-lag-help": "اس قیمت کو کم کرنے سے صفحات لوڈ ہونے کا انتظار کم ہوگا، لیکن اس سے زیادہ صارفین کو 'زیادہ بوجھ' کا پیغام زیادہ کثرت سے دکھائی دے گا۔ (ری اسٹارٹ درکار ہے۔)",
+ "traffic.lag-check-interval": "چیک انٹرویل (ملی سیکنڈز میں)",
+ "traffic.lag-check-interval-help": "اس قیمت کو کم کرنے سے NodeBB بوجھ کے اضافے کے لیے زیادہ حساس ہوگا، لیکن چیک کو بہت زیادہ حساس بھی بنا سکتا ہے۔ (ری اسٹارٹ درکار ہے۔)",
+
+ "sockets.settings": "ویب ساکٹ کی ترتیبات",
+ "sockets.max-attempts": "دوبارہ رابطہ کرنے کی زیادہ سے زیادہ کوششیں",
+ "sockets.default-placeholder": "طے شدہ: %1",
+ "sockets.delay": "دوبارہ رابطہ کرنے میں تاخیر",
+
+ "compression.settings": "کمپریشن کی ترتیبات",
+ "compression.enable": "کمپریشن فعال کریں",
+ "compression.help": "یہ ترتیب 'gzip' کے ذریعے کمپریشن کو فعال کرتی ہے۔ مصروف ویب سائٹس کے لیے کمپریشن کا بہترین طریقہ ریورس پراکسی لیول پر ہوتا ہے۔ لیکن ٹیسٹنگ کے مقاصد کے لیے، آپ اسے یہاں بھی فعال کر سکتے ہیں۔"
+}
\ No newline at end of file
diff --git a/public/language/ur/admin/settings/api.json b/public/language/ur/admin/settings/api.json
new file mode 100644
index 0000000000..2272599c1c
--- /dev/null
+++ b/public/language/ur/admin/settings/api.json
@@ -0,0 +1,29 @@
+{
+ "tokens": "شناختی نشان",
+ "settings": "ترتیبات",
+ "lead-text": "اس صفحہ پر آپ نوڈ بی بی میں لکھنے کے لیے API تک رسائی کو ترتیب دے سکتے ہیں۔",
+ "intro": "طے شدہ طور پر، لکھنے کا API صارفین کو ان کے سیشن کوکی کے ذریعے تصدیق کرتا ہے، لیکن نوڈ بی بی اس صفحہ کے ٹوکنز کا استعمال کرتے ہوئے 'Bearer' طریقہ سے تصدیق کی بھی حمایت کرتا ہے۔",
+ "warning": "احتیاط کریں – ٹوکنز کو پاس ورڈز کی طرح سمجھیں۔ اگر کوئی ان تک رسائی حاصل کر لیتا ہے، تو وہ آپ کے اکاؤنٹ تک رسائی حاصل کر سکتا ہے۔",
+ "docs": "API کی مکمل دستاویزات تک رسائی کے لیے یہاں کلک کریں",
+
+ "require-https": "API کا استعمال صرف HTTPS کے ذریعے کریں",
+ "require-https-caveat": "نوٹ: کچھ معاملات میں، جب لوڈ بیلنسرز استعمال کیے جاتے ہیں، تو نوڈ بی بی کو درخواستیں HTTP کے ذریعے بھیجی جا سکتی ہیں – اس صورت میں یہ ترتیب غیر فعال رہنی چاہیے۔",
+
+ "uid": "صارف آئی ڈی",
+ "token": "شناختی نشان",
+ "uid-help-text": "اس کوڈ کے ساتھ منسلک کرنے کے لیے صارف آئی ڈی بتائیں۔ اگر آئی ڈی 0 ہو، تو اسے ماسٹر کوڈ سمجھا جائے گا، جو _uid پیرامیٹر کے ذریعے دیگر صارفین کی شناخت اختیار کر سکتا ہے۔",
+ "description": "تفصیل",
+ "last-seen": "آخری بار دیکھا گیا",
+ "created": "بنایا گیا",
+ "create-token": "شناختی نشان بنائیں",
+ "update-token": "شناختی نشان اپ ڈیٹ کریں",
+ "master-token": "ماسٹر شناخت نشان",
+ "last-seen-never": "یہ کلید کبھی استعمال نہیں ہوئی۔",
+ "no-description": "کوئی تفصیل نہیں۔",
+ "actions": "عمل",
+ "edit": "ترمیم",
+ "roll": "دوبارہ بنائیں",
+
+ "delete-confirm": "کیا آپ واقعی اس شناخت نشان کو حذف کرنا چاہتے ہیں؟ اس کے بعد اسے بحال نہیں کیا جا سکے گا۔",
+ "roll-confirm": "کیا آپ واقعی اس شناخت نشان کو دوبارہ بنانا چاہتے ہیں؟ پرانا فوراً ہٹ جائے گا اور اسے بحال نہیں کیا جا سکے گا۔"
+}
\ No newline at end of file
diff --git a/public/language/ur/admin/settings/chat.json b/public/language/ur/admin/settings/chat.json
new file mode 100644
index 0000000000..332beeca5b
--- /dev/null
+++ b/public/language/ur/admin/settings/chat.json
@@ -0,0 +1,17 @@
+{
+ "zero-is-disabled": "اس پابندی کو غیر فعال کرنے کے لیے 0 درج کریں",
+ "chat-settings": "گفتگو کی ترتیبات",
+ "disable": "گفتگو کو غیر فعال کریں",
+ "disable-editing": "گفتگو کے پیغامات کی ترمیم اور حذف کو غیر فعال کریں",
+ "disable-editing-help": "یہ پابندی ایڈمنسٹریٹرز اور عالمی ماڈریٹرز پر اثر نہیں کرتی",
+ "max-length": "گفتگو کے پیغامات کی زیادہ سے زیادہ لمبائی",
+ "max-length-remote": "ریموٹ گفتگو کے پیغامات کی زیادہ سے زیادہ لمبائی",
+ "max-length-remote-help": "یہ قیمت عام طور پر مقامی صارفین کے لیے پابندی سے زیادہ ہونی چاہیے، کیونکہ ریموٹ پیغامات عام طور پر ناگزیر طور پر لمبے ہوتے ہیں (@مینشنز وغیرہ کی وجہ سے)۔",
+ "max-chat-room-name-length": "گفتگو کے کمروں کے ناموں کی زیادہ سے زیادہ لمبائی",
+ "max-room-size": "گفتگو کے کمرے میں صارفین کی زیادہ سے زیادہ تعداد",
+ "delay": "گفتگو کے پیغامات کے درمیان وقت (ملی سیکنڈز)",
+ "notification-delay": "گفتگو کے پیغامات کے لیے اطلاع سے پہلے تاخیر",
+ "notification-delay-help": "اس وقت کے دوران بھیجے گئے اضافی پیغامات کو یکجا کیا جاتا ہے، اور صارف کو ہر تاخیری مدت کے لیے ایک اطلاع ملتی ہے۔ تاخیر کو غیر فعال کرنے کے لیے 0 سیٹ کریں۔",
+ "restrictions.seconds-edit-after": "گفتگو کے پیغامات کے ترمیم کے لیے سیکنڈز کی تعداد",
+ "restrictions.seconds-delete-after": "گفتگو کے پیغامات کے حذف کے لیے سیکنڈز کی تعداد"
+}
\ No newline at end of file
diff --git a/public/language/ur/admin/settings/cookies.json b/public/language/ur/admin/settings/cookies.json
new file mode 100644
index 0000000000..443dbee344
--- /dev/null
+++ b/public/language/ur/admin/settings/cookies.json
@@ -0,0 +1,13 @@
+{
+ "eu-consent": "ای یو رضامندی",
+ "consent.enabled": "فعال",
+ "consent.message": "اطلاعی پیغام",
+ "consent.acceptance": "قبولیت کا پیغام",
+ "consent.link-text": "پالیسی ٹیکسٹ کا لنک",
+ "consent.link-url": "پالیسی ایڈریس کا لنک",
+ "consent.blank-localised-default": "NodeBB کے طے شدہ ترجمہ شدہ ڈیٹا استعمال کرنے کے لیے اسے خالی چھوڑ دیں",
+ "settings": "ترتیبات",
+ "cookie-domain": "سیشن کوکی کا ڈومین",
+ "max-user-sessions": "صارف کے لیے زیادہ سے زیادہ فعال سیشنز",
+ "blank-default": "طے شدہ قیمت استعمال کرنے کے لیے خالی چھوڑ دیں"
+}
\ No newline at end of file
diff --git a/public/language/ur/admin/settings/email.json b/public/language/ur/admin/settings/email.json
new file mode 100644
index 0000000000..9acc6b43e2
--- /dev/null
+++ b/public/language/ur/admin/settings/email.json
@@ -0,0 +1,54 @@
+{
+ "email-settings": "ای میل کی ترتیبات",
+ "address": "ای میل ایڈریس",
+ "address-help": "مندرجہ ذیل ای میل ایڈریس وہی ہے جو وصول کنندہ 'From' اور 'Reply-To' فیلڈز میں دیکھے گا۔",
+ "from": "'From' فیلڈ کے لیے نام",
+ "from-help": "ای میل میں دکھایا جانے والا بھیجنے والے کا نام۔",
+
+ "confirmation-settings": "تصدیق",
+ "confirmation.expiry": "تصدیقی لنک کی میعاد، گھنٹوں میں",
+
+ "smtp-transport": "SMTP ٹرانسپورٹ",
+ "smtp-transport.enabled": "SMTP ٹرانسپورٹ فعال کریں",
+ "smtp-transport-help": "آپ معروف خدمات کی فہرست سے انتخاب کر سکتے ہیں، یا دستی طور پر ایک درج کر سکتے ہیں۔",
+ "smtp-transport.service": "سروس منتخب کریں",
+ "smtp-transport.service-custom": "مرضی کی سروس",
+ "smtp-transport.service-help": "اوپر دیے گئے سروس کے نام کو منتخب کریں تاکہ اس کے معروف ڈیٹا کو استعمال کیا جا سکے۔ یا 'مرضی کی سروس' منتخب کریں اور نیچے اس کے تفصیلات درج کریں۔",
+ "smtp-transport.gmail-warning1": "اگر آپ Gmail استعمال کر رہے ہیں، تو آپ کو NodeBB کے تصدیقی ڈیٹا استعمال کرنے کے لیے 'ایپلیکیشن پاس ورڈ' بنانا ہوگا۔ آپ اسے ایپلیکیشن پاس ورڈز صفحہ پر بنا سکتے ہیں۔",
+ "smtp-transport.gmail-warning2": "اس حل کے بارے میں مزید معلومات کے لیے، براہ کرم NodeMailer کے اس مسئلے کے بارے میں یہ مضمون دیکھیں۔ ایک اور حل یہ ہوگا کہ تیسری پارٹی کی ای میل پلگ ان استعمال کی جائے، جیسے کہ 'SendGrid'، 'Mailgun' وغیرہ۔ یہاں دستیاب پلگ انز دیکھیں۔",
+ "smtp-transport.auto-enable-toast": "ایسا لگتا ہے کہ آپ ایسی فعالیت ترتیب دے رہے ہیں جس کے لیے SMTP ٹرانسپورٹ کی ضرورت ہے۔ ہم نے آپ کے لیے 'SMTP ٹرانسپورٹ' ترتیب کو فعال کر دیا ہے۔",
+ "smtp-transport.host": "SMTP سرور",
+ "smtp-transport.port": "SMTP پورٹ",
+ "smtp-transport.security": "رابطے کی سیکیورٹی",
+ "smtp-transport.security-encrypted": "خفیہ کردہ",
+ "smtp-transport.security-starttls": "StartTLS",
+ "smtp-transport.security-none": "کوئی نہیں",
+ "smtp-transport.username": "صارف نام",
+ "smtp-transport.username-help": "Gmail سروس کے لیے، یہاں مکمل ای میل ایڈریس درج کریں، خاص طور پر اگر آپ Google Apps کے زیر انتظام ڈومین استعمال کر رہے ہیں۔",
+ "smtp-transport.password": "پاس ورڈ",
+ "smtp-transport.pool": "پولڈ کنکشنز فعال کریں",
+ "smtp-transport.pool-help": "پولڈ کنکشنز ہر ای میل کے لیے نیا کنکشن بنانے سے روکتے ہیں۔ یہ ترتیب صرف اس وقت اثر کرتی ہے جب 'SMTP ٹرانسپورٹ' فعال ہو۔",
+ "smtp-transport.allow-self-signed": "خود دستخط شدہ سرٹیفکیٹس کی اجازت دیں",
+ "smtp-transport.allow-self-signed-help": "اس ترتیب کو فعال کرنے سے خود دستخط شدہ اور غیر درست TLS سرٹیفکیٹس استعمال کرنے کی اجازت ملے گی۔",
+
+ "template": "ای میل ٹیمپلیٹ میں ترمیم کریں",
+ "template.select": "ای میل ٹیمپلیٹ منتخب کریں",
+ "template.revert": "اصل پر واپس جائیں",
+ "testing": "ای میل ٹیسٹنگ",
+ "testing.select": "ای میل ٹیمپلیٹ منتخب کریں",
+ "testing.send": "ٹیسٹ ای میل بھیجیں",
+ "testing.send-help": "ٹیسٹ ای میل موجودہ لاگ ان صارف کے ای میل پر بھیجا جائے گا۔",
+ "subscriptions": "ای میل ڈائجسٹ",
+ "subscriptions.disable": "ای میل ڈائجسٹ غیر فعال کریں",
+ "subscriptions.hour": "بھیجنے کا وقت",
+ "subscriptions.hour-help": "براہ کرم ایک عدد درج کریں جو ڈائجسٹ ای میلز بھیجنے کے وقت کو ظاہر کرے (مثال کے طور پر 0 آدھی رات کے لیے، 17 شام 5 بجے کے لیے)۔ نوٹ کریں کہ یہ وقت سرور کے ٹائم زون کے مطابق ہے اور ہو سکتا ہے کہ آپ کے سسٹم کے کلاک سے مطابقت نہ رکھتا ہو۔url پراپرٹی سے سیٹ کیا جاتا ہے۔",
+ "title.name": "آپ کی کمیونٹی کا نام",
+ "title.show-in-header": "ویب سائٹ کا عنوان ہیڈر میں دکھائیں",
+ "browser-title": "براؤزر کا عنوان",
+ "browser-title-help": "اگر براؤزر کا عنوان بیان نہیں کیا گیا تو ویب سائٹ کا عنوان استعمال کیا جائے گا",
+ "title-layout": "عنوان کا ترتیب",
+ "title-layout-help": "براؤزر کے عنوان کی ساخت کیسے ہوگی، جیسے کہ: {pageTitle} | {browserTitle}",
+ "description.placeholder": "آپ کی کمیونٹی کا مختصر تفصیل",
+ "description": "ویب سائٹ کی تفصیل",
+ "keywords": "ویب سائٹ کے کلیدی الفاظ",
+ "keywords-placeholder": "آپ کی کمیونٹی کی وضاحت کرنے والے کلیدی الفاظ، کوموں سے الگ کیے گئے۔",
+ "logo-and-icons": "ویب سائٹ کا لوگو اور آئیکنز",
+ "logo.image": "تصویر",
+ "logo.image-placeholder": "فورم کے ہیڈر میں دکھائے جانے والے لوگو کا پاتھ",
+ "logo.upload": "اپ لوڈ",
+ "logo.url": "لوگو کا ایڈریس",
+ "logo.url-placeholder": "ویب سائٹ کے لوگو کا ایڈریس",
+ "logo.url-help": "جب صارف لوگو پر کلک کرتا ہے، تو وہ اس ایڈریس پر منتقل ہو جائے گا۔ اگر خالی ہو، تو صارف فورم کے ہوم پیج پر بھیجا جائے گا۔ url پراپرٹی سے سیٹ کیا جاتا ہے۔",
+ "logo.alt-text": "متبادل متن",
+ "log.alt-text-placeholder": "رسائی کے لیے متبادل متن",
+ "favicon": "ویب سائٹ کا فیویکن",
+ "favicon.upload": "اپ لوڈ",
+ "pwa": "پروگریسو ویب ایپلیکیشن",
+ "touch-icon": "ٹچ آئیکن",
+ "touch-icon.upload": "اپ لوڈ",
+ "touch-icon.help": "تجویز کردہ سائز اور فارمیٹ: 512x512، صرف PNG فارمیٹ میں۔ اگر ٹچ آئیکن بیان نہیں کیا گیا تو NodeBB ویب سائٹ کے فیویکن کا استعمال کرے گا۔",
+ "maskable-icon": "ماسک ایبل آئیکن (ہوم اسکرین کے لیے)",
+ "maskable-icon.help": "تجویز کردہ سائز اور فارمیٹ: 512x512، صرف PNG فارمیٹ میں۔ اگر ماسک ایبل آئیکن بیان نہیں کیا گیا تو NodeBB ٹچ آئیکن کا استعمال کرے گا۔",
+ "outgoing-links": "باہر جانے والے لنکس",
+ "outgoing-links.warning-page": "باہری لنکس پر کلک کرنے پر تنبیہی صفحہ دکھائیں",
+ "search": "تلاش",
+ "search-default-in": "میں تلاش کریں",
+ "search-default-in-quick": "فوری تلاش میں",
+ "search-default-sort-by": "ترتیب دیں بمطابق",
+ "outgoing-links.whitelist": "وہ ڈومینز جن کے لیے تنبیہی صفحہ نہ دکھایا جائے",
+ "site-colors": "ویب سائٹ کے رنگوں کے میٹا ڈیٹا",
+ "theme-color": "تھیم کا رنگ",
+ "background-color": "پس منظر کا رنگ",
+ "background-color-help": "وہ رنگ جو ہوم اسکرین کے پس منظر کے طور پر استعمال کیا جائے گا جب ویب سائٹ ایپلیکیشن کے طور پر انسٹال کی جاتی ہے",
+ "undo-timeout": "واپسی کا وقت",
+ "undo-timeout-help": "کچھ عمل، جیسے کہ موضوعات منتقل کرنا، ماڈریٹر اس مخصوص وقت کے اندر واپس کر سکتے ہیں۔ واپسی کو مکمل طور پر غیر فعال کرنے کے لیے 0 سیٹ کریں۔",
+ "topic-tools": "موضوعات کے اوزار",
+ "home-page": "ہوم پیج",
+ "home-page-route": "ہوم پیج کا راستہ",
+ "home-page-description": "منتخب کریں کہ جب صارفین فورم کے مرکزی ایڈریس پر جائیں تو کون سا صفحہ دکھایا جائے۔",
+ "custom-route": "مرضی کا راستہ",
+ "allow-user-home-pages": "صارفین کے ہوم پیجز کی اجازت دیں",
+ "home-page-title": "ہوم پیج کا عنوان (طے شدہ: 'ہوم')",
+ "default-language": "طے شدہ زبان",
+ "auto-detect": "مہمانوں کے لیے زبان کا خودکار پتہ لگانا",
+ "default-language-help": "طے شدہ زبان آپ کے فورم کو دیکھنے والے تمام صارفین کے لیے زبان کی ترتیبات کا تعین کرتی ہے۔ 30، یعنی ایک ماہ)۔ اگر 0 سیٹ کیا گیا تو ہمیشہ تاریخیں دکھائی جائیں گی، اور اگر فیلڈ خالی چھوڑا گیا تو وقت ہمیشہ نسبتاً ہوگا۔",
+ "timestamp.necro-threshold": "مردہ حد (دنوں میں)",
+ "timestamp.necro-threshold-help": "اگر پوسٹس کے درمیان وقت مردہ حد سے زیادہ ہو تو پوسٹس کے درمیان ایک پیغام دکھایا جائے گا۔ (طے شدہ: 7، یعنی ایک ہفتہ)۔ غیر فعال کرنے کے لیے 0 سیٹ کریں۔",
+ "timestamp.topic-views-interval": "موضوعات کے مناظر کی تعداد بڑھانے کا وقفہ (منٹوں میں)",
+ "timestamp.topic-views-interval-help": "موضوعات کے مناظر کی تعداد اس ترتیب کے مطابق ہر X منٹ میں ایک بار بڑھے گی۔",
+ "teaser": "نمائشی پوسٹ",
+ "teaser.last-post": "آخری – آخری پوسٹ دکھائیں، یا اگر کوئی جوابات نہ ہوں تو ابتدائی پوسٹ۔",
+ "teaser.last-reply": "آخری – آخری جواب دکھائیں، یا اگر کوئی جوابات نہ ہوں تو 'کوئی جوابات نہیں'۔",
+ "teaser.first": "پہلی",
+ "showPostPreviewsOnHover": "ماؤس ہاور کرنے پر پوسٹس کا مختصر پیش نظارہ دکھائیں",
+ "unread-and-recent": "حال ہی کے اور غیر پڑھے ہوئے کے لیے ترتیبات",
+ "unread.cutoff": "پوسٹس کی عمر جس کے بعد وہ غیر پڑھے ہوئے میں نہیں دکھائی جاتیں (دنوں میں)",
+ "unread.min-track-last": "موضوع میں پوسٹس کی کم سے کم تعداد جس کے بعد آخری پڑھی ہوئی کو ٹریک کرنا شروع کیا جائے",
+ "recent.max-topics": "حال ہی کے موضوعات کی زیادہ سے زیادہ تعداد",
+ "recent.categoryFilter.disable": "/recent صفحہ پر نظرانداز کیے گئے زمرہ جات کی موضوعات کی فلٹرنگ غیر فعال کریں",
+ "signature": "دستخطوں کی ترتیبات",
+ "signature.disable": "دستخط غیر فعال کریں",
+ "signature.no-links": "دستخطوں میں لنکس کی اجازت نہ دیں",
+ "signature.no-images": "دستخطوں میں تصاویر کی اجازت نہ دیں",
+ "signature.hide-duplicates": "موضوعات میں دہرائے گئے دستخط چھپائیں",
+ "signature.max-length": "دستخطوں کی زیادہ سے زیادہ لمبائی",
+ "composer": "کمپوزر کی ترتیبات",
+ "composer-help": "مندرجہ ذیل ترتیبات نئی موضوعات بنانے یا موجودہ موضوعات میں جواب دینے کے لیے صارفین کے ذریعے استعمال ہونے والے پوسٹ کمپوزر کے عنصر کی فعالیت اور/یا ظاہری شکل کا تعین کرتی ہیں۔",
+ "composer.show-help": "مدد کا ٹیب دکھائیں",
+ "composer.enable-plugin-help": "پلگ انز کو مدد کے ٹیب میں مواد شامل کرنے کی اجازت دیں",
+ "composer.custom-help": "مرضی کا مدد کا متن",
+ "backlinks": "بیک لنکس",
+ "backlinks.enabled": "موضوعات میں بیک لنکس فعال کریں",
+ "backlinks.help": "اگر پوسٹ میں کسی دوسرے موضوع کی طرف حوالہ ہو تو اس پوسٹ کی طرف ایک لنک اس مخصوص وقت کے ساتھ رکھا جائے گا۔",
+ "ip-tracking": "آئی پی ایڈریس ریکارڈنگ",
+ "ip-tracking.each-post": "ہر پوسٹ کے لیے آئی پی ایڈریس ریکارڈ کریں",
+ "enable-post-history": "پوسٹس کی تاریخ فعال کریں"
+}
\ No newline at end of file
diff --git a/public/language/ur/admin/settings/reputation.json b/public/language/ur/admin/settings/reputation.json
new file mode 100644
index 0000000000..ad7cd77e43
--- /dev/null
+++ b/public/language/ur/admin/settings/reputation.json
@@ -0,0 +1,43 @@
+{
+ "reputation": "ساکھ کی ترتیبات",
+ "disable": "ساکھ کا نظام غیر فعال کریں",
+ "disable-down-voting": "منفی ووٹنگ کی ممانعت",
+ "upvote-visibility": "مثبت ووٹس کی نمائش",
+ "upvote-visibility-all": "ہر کوئی مثبت ووٹس دیکھ سکتا ہے",
+ "upvote-visibility-loggedin": "صرف لاگ ان صارفین مثبت ووٹس دیکھ سکتے ہیں",
+ "upvote-visibility-privileged": "صرف اعلیٰ اختیارات والے صارفین (جیسے ایڈمنسٹریٹرز اور ماڈریٹرز) مثبت ووٹس دیکھ سکتے ہیں",
+ "downvote-visibility": "منفی ووٹس کی نمائش",
+ "downvote-visibility-all": "ہر کوئی منفی ووٹس دیکھ سکتا ہے",
+ "downvote-visibility-loggedin": "صرف لاگ ان صارفین منفی ووٹس دیکھ سکتے ہیں",
+ "downvote-visibility-privileged": "صرف اعلیٰ اختیارات والے صارفین (جیسے ایڈمنسٹریٹرز اور ماڈریٹرز) منفی ووٹس دیکھ سکتے ہیں",
+ "thresholds": "سرگرمی کی حدیں",
+ "min-rep-upvote": "پوسٹس کے لیے مثبت ووٹنگ کے لیے کم سے کم ساکھ درکار",
+ "upvotes-per-day": "ایک دن میں مثبت ووٹس (لامحدود کے لیے 0 سیٹ کریں)",
+ "upvotes-per-user-per-day": "ایک صارف کے لیے ایک دن میں مثبت ووٹس (لامحدود کے لیے 0 سیٹ کریں)",
+ "min-rep-downvote": "پوسٹس کے لیے منفی ووٹنگ کے لیے کم سے کم ساکھ درکار",
+ "downvotes-per-day": "ایک دن میں منفی ووٹس (لامحدود کے لیے 0 سیٹ کریں)",
+ "downvotes-per-user-per-day": "ایک صارف کے لیے ایک دن میں منفی ووٹس (لامحدود کے لیے 0 سیٹ کریں)",
+ "min-rep-chat": "گفتگو میں پیغامات بھیجنے کے لیے کم سے کم ساکھ درکار",
+ "min-rep-post-links": "لنکس پوسٹ کرنے کے لیے کم سے کم ساکھ درکار",
+ "min-rep-flag": "پوسٹس کی رپورٹنگ کے لیے کم سے کم ساکھ درکار",
+ "min-rep-aboutme": "صارف کے پروفائل میں 'میرے بارے میں' فیلڈ شامل کرنے کے لیے کم سے کم ساکھ درکار",
+ "min-rep-signature": "صارف کے پروفائل میں 'دستخط' فیلڈ شامل کرنے کے لیے کم سے کم ساکھ درکار",
+ "min-rep-profile-picture": "صارف کے پروفائل میں پروفائل تصویر شامل کرنے کے لیے کم سے کم ساکھ درکار",
+ "min-rep-cover-picture": "صارف کے پروفائل میں کور تصویر شامل کرنے کے لیے کم سے کم ساکھ درکار",
+
+ "flags": "رپورٹس کی ترتیبات",
+ "flags.limit-per-target": "ایک ہی چیز کی رپورٹنگ کی زیادہ سے زیادہ تعداد",
+ "flags.limit-per-target-placeholder": "طے شدہ: 0",
+ "flags.limit-per-target-help": "جب کوئی پوسٹ یا صارف کئی بار رپورٹ کیا جاتا ہے، تو یہ ایک مشترکہ رپورٹ میں شامل ہو جاتا ہے۔ اس ترتیب کو صفر سے زیادہ قیمت پر سیٹ کریں تاکہ ایک پوسٹ یا صارف کے لیے جمع ہونے والی رپورٹس کی تعداد کو محدود کیا جا سکے۔",
+ "flags.limit-post-flags-per-day": "ایک دن میں صارف کے ذریعے رپورٹ کی جا سکنے والی پوسٹس کی زیادہ سے زیادہ تعداد",
+ "flags.limit-post-flags-per-day-help": "غیر فعال کرنے کے لیے 0 سیٹ کریں (طے شدہ: 10)",
+ "flags.limit-user-flags-per-day": "ایک دن میں صارف کے ذریعے رپورٹ کیے جا سکنے والے صارفین کی زیادہ سے زیادہ تعداد",
+ "flags.limit-user-flags-per-day-help": "غیر فعال کرنے کے لیے 0 سیٹ کریں (طے شدہ: 10)",
+ "flags.auto-flag-on-downvote-threshold": "پوسٹس کی خودکار رپورٹنگ کے لیے منفی ووٹس کی تعداد",
+ "flags.auto-flag-on-downvote-threshold-help": "غیر فعال کرنے کے لیے 0 سیٹ کریں (طے شدہ: 0)",
+ "flags.auto-resolve-on-ban": "جب صارف پر پابندی لگائی جاتی ہے تو اس کی تمام رپورٹس خودکار طور پر ہٹائیں",
+ "flags.action-on-resolve": "جب رپورٹنگ حل کی جاتی ہے تو درج ذیل کریں",
+ "flags.action-on-reject": "جب رپورٹنگ مسترد کی جاتی ہے تو درج ذیل کریں",
+ "flags.action.nothing": "کچھ نہ کریں",
+ "flags.action.rescind": "ماڈریٹرز/ایڈمنسٹریٹرز کو بھیجی گئی اطلاع منسوخ کریں"
+}
\ No newline at end of file
diff --git a/public/language/ur/admin/settings/sockets.json b/public/language/ur/admin/settings/sockets.json
new file mode 100644
index 0000000000..a8bfe7c497
--- /dev/null
+++ b/public/language/ur/admin/settings/sockets.json
@@ -0,0 +1,6 @@
+{
+ "reconnection": "دوبارہ رابطہ کی ترتیبات",
+ "max-attempts": "دوبارہ رابطہ کرنے کی زیادہ سے زیادہ کوششیں",
+ "default-placeholder": "طے شدہ: %1",
+ "delay": "دوبارہ رابطہ کرنے میں تاخیر"
+}
\ No newline at end of file
diff --git a/public/language/ur/admin/settings/sounds.json b/public/language/ur/admin/settings/sounds.json
new file mode 100644
index 0000000000..2de9782640
--- /dev/null
+++ b/public/language/ur/admin/settings/sounds.json
@@ -0,0 +1,9 @@
+{
+ "notifications": "اطلاعات",
+ "chat-messages": "گفتگو کے پیغامات",
+ "play-sound": "چلائیں",
+ "incoming-message": "آنے والا پیغام",
+ "outgoing-message": "جانے والا پیغام",
+ "upload-new-sound": "نئی آواز اپ لوڈ کریں",
+ "saved": "ترتیبات محفوظ ہو گئیں"
+}
\ No newline at end of file
diff --git a/public/language/ur/admin/settings/tags.json b/public/language/ur/admin/settings/tags.json
new file mode 100644
index 0000000000..e7c2af1694
--- /dev/null
+++ b/public/language/ur/admin/settings/tags.json
@@ -0,0 +1,13 @@
+{
+ "tag": "ٹیگز کی ترتیبات",
+ "link-to-manage": "ٹیگز کا انتظام",
+ "system-tags": "سسٹم ٹیگز",
+ "system-tags-help": "صرف اعلیٰ اختیارات والے صارفین ہی ان ٹیگز کو استعمال کر سکیں گے۔",
+ "tags-per-topic": "موضوع کے لیے ٹیگز کی تعداد",
+ "min-per-topic": "موضوع کے لیے کم سے کم ٹیگز",
+ "max-per-topic": "موضوع کے لیے زیادہ سے زیادہ ٹیگز",
+ "min-length": "ٹیگز کی کم سے کم لمبائی",
+ "max-length": "ٹیگز کی زیادہ سے زیادہ لمبائی",
+ "related-topics": "متعلقہ موضوعات",
+ "max-related-topics": "زیادہ سے زیادہ متعلقہ موضوعات جو دکھائے جائیں (اگر تھیم اس کی حمایت کرتی ہو)"
+}
\ No newline at end of file
diff --git a/public/language/ur/admin/settings/uploads.json b/public/language/ur/admin/settings/uploads.json
new file mode 100644
index 0000000000..f44da0e354
--- /dev/null
+++ b/public/language/ur/admin/settings/uploads.json
@@ -0,0 +1,46 @@
+{
+ "posts": "پوسٹس",
+ "orphans": "غیر استعمال شدہ فائلیں",
+ "private": "اپ لوڈ کی گئی فائلیں نجی ہوں",
+ "strip-exif-data": "EXIF ڈیٹا ہٹائیں",
+ "preserve-orphaned-uploads": "پوسٹ حذف ہونے کے بعد بھی اپ لوڈ کی گئی فائلوں کو ڈسک پر رکھیں",
+ "orphanExpiryDays": "غیر استعمال شدہ فائلوں کو رکھنے کے دنوں کی تعداد",
+ "orphanExpiryDays-help": "اس تعداد کے دنوں کے بعد غیر استعمال شدہ اپ لوڈ کی گئی فائلیں حذف کر دی جائیں گی۔pdf,xls,doc)۔ اگر یہ فیلڈ خالی چھوڑا جائے تو تمام فائلیں نجی ہوں گی۔",
+ "resize-image-width-threshold": "تصاویر کو اگر وہ مخصوص چوڑائی سے زیادہ ہوں تو ری سائز کریں",
+ "resize-image-width-threshold-help": "(پکسلز میں؛ طے شدہ: 2000 پکسلز۔ 0 = غیر فعال)",
+ "resize-image-width": "تصاویر کا سائز مخصوص چوڑائی تک کم کریں",
+ "resize-image-width-help": "(پکسلز میں؛ طے شدہ: 760 پکسلز۔ 0 = غیر فعال)",
+ "resize-image-keep-original": "ری سائزنگ کے بعد اصل تصویر رکھیں",
+ "resize-image-quality": "تصاویر کی ری سائزنگ کا معیار",
+ "resize-image-quality-help": "ری سائز کی گئی تصاویر کے فائل سائز کو کم کرنے کے لیے کم معیار استعمال کریں۔",
+ "max-file-size": "فائلوں کا زیادہ سے زیادہ سائز (KiB میں)",
+ "max-file-size-help": "(کیبی بائٹس میں؛ طے شدہ: 2048 KiB)",
+ "reject-image-width": "تصاویر کی زیادہ سے زیادہ چوڑائی (پکسلز میں)",
+ "reject-image-width-help": "جن تصاویر کی چوڑائی اس قیمت سے زیادہ ہوگی وہ مسترد کر دی جائیں گی۔",
+ "reject-image-height": "تصاویر کی زیادہ سے زیادہ اونچائی (پکسلز میں)",
+ "reject-image-height-help": "جن تصاویر کی اونچائی اس قیمت سے زیادہ ہوگی وہ مسترد کر دی جائیں گی۔",
+ "allow-topic-thumbnails": "صارفین کو موضوعات کے لیے تھمب نیلز اپ لوڈ کرنے کی اجازت دیں",
+ "topic-thumb-size": "موضوعات کے تھمب نیلز کا سائز",
+ "allowed-file-extensions": "اجازت شدہ فائل ایکسٹینشنز",
+ "allowed-file-extensions-help": "فائل ایکسٹینشنز کوموں سے الگ کرکے درج کریں (مثال: pdf,xls,doc)۔ اگر فہرست خالی ہو تو تمام فائل ایکسٹینشنز کی اجازت ہوگی۔",
+ "upload-limit-threshold": "صارفین کے اپ لوڈز کو محدود کریں:",
+ "upload-limit-threshold-per-minute": "%1 منٹ کے لیے",
+ "upload-limit-threshold-per-minutes": "%1 منٹوں کے لیے",
+ "profile-avatars": "پروفائل تصاویر",
+ "allow-profile-image-uploads": "صارفین کو پروفائل تصاویر اپ لوڈ کرنے کی اجازت دیں",
+ "convert-profile-image-png": "اپ لوڈ کی گئی پروفائل تصاویر کو PNG فارمیٹ میں تبدیل کریں",
+ "default-avatar": "طے شدہ حسب ضرورت تصویر",
+ "upload": "اپ لوڈ",
+ "profile-image-dimension": "پروفائل تصویر کا سائز",
+ "profile-image-dimension-help": "(پکسلز میں؛ طے شدہ: 128 پکسلز)",
+ "max-profile-image-size": "پروفائل تصویر کا زیادہ سے زیادہ فائل سائز",
+ "max-profile-image-size-help": "(کیبی بائٹس میں؛ طے شدہ: 256 KiB)",
+ "max-cover-image-size": "کور تصویر کا زیادہ سے زیادہ فائل سائز",
+ "max-cover-image-size-help": "(کیبی بائٹس میں؛ طے شدہ: 2048 KiB)",
+ "keep-all-user-images": "پروفائل تصاویر اور کور تصاویر کے پرانے ورژن سرور پر رکھیں",
+ "profile-covers": "پروفائل کورز",
+ "default-covers": "طے شدہ کور تصاویر",
+ "default-covers-help": "ان اکاؤنٹس کے لیے طے شدہ کور تصاویر (کوموں سے الگ کیے گئے) شامل کریں جنہوں نے کوئی کور تصویر اپ لوڈ نہیں کی۔"
+}
diff --git a/public/language/ur/admin/settings/user.json b/public/language/ur/admin/settings/user.json
new file mode 100644
index 0000000000..a33c3201f3
--- /dev/null
+++ b/public/language/ur/admin/settings/user.json
@@ -0,0 +1,98 @@
+{
+ "authentication": "تصدیق",
+ "email-confirm-interval": "صارف دوبارہ تصدیقی ای میل نہیں بھیج سکتا جب تک کہ",
+ "email-confirm-interval2": "منٹ گزر نہ جائیں",
+ "allow-login-with": "لاگ ان کی اجازت دیں بذریعہ",
+ "allow-login-with.username-email": "صارف نام یا ای میل",
+ "allow-login-with.username": "صرف صارف نام",
+ "account-settings": "اکاؤنٹ کی ترتیبات",
+ "gdpr-enabled": "GDPR رضامندی کی درخواست کو فعال کریں",
+ "gdpr-enabled-help": "اگر یہ فعال ہو، تو تمام نئے رجسٹرڈ صارفین کو جنرل ڈیٹا پروٹیکشن ریگولیشن (GDPR) کے مطابق ڈیٹا جمع کرنے اور استعمال کے اعدادوشمار کے لیے اپنی رضامندی واضح طور پر دینی ہوگی۔ نوٹ: GDPR کو فعال کرنے سے موجودہ صارفین کو رضامندی دینے کی ضرورت نہیں ہوتی۔ اگر آپ یہ چاہتے ہیں، تو آپ کو GDPR پلگ ان انسٹال کرنا ہوگا۔",
+ "disable-username-changes": "صارف نام کی تبدیلی کو غیر فعال کریں",
+ "disable-email-changes": "ای میل کی تبدیلی کو غیر فعال کریں",
+ "disable-password-changes": "پاس ورڈ کی تبدیلی کو غیر فعال کریں",
+ "allow-account-deletion": "اکاؤنٹ حذف کرنے کی اجازت دیں",
+ "hide-fullname": "صارفین سے مکمل نام چھپائیں",
+ "hide-email": "صارفین سے ای میل چھپائیں",
+ "show-fullname-as-displayname": "اگر موجود ہو تو صارف کا مکمل نام دکھائیں",
+ "themes": "تھیمز",
+ "disable-user-skins": "صارفین کو اپنی سکنز منتخب کرنے سے روکیں",
+ "account-protection": "اکاؤنٹ کی حفاظت",
+ "admin-relogin-duration": "ایڈمنسٹریٹر کا دوبارہ لاگ ان (منٹوں میں)",
+ "admin-relogin-duration-help": "ایک مخصوص وقت کے بعد ایڈمنسٹریٹو سیکشن تک رسائی کے لیے دوبارہ لاگ ان کی ضرورت ہوگی۔ اسے غیر فعال کرنے کے لیے 0 سیٹ کریں۔",
+ "login-attempts": "ایک گھنٹے میں لاگ ان کی کوششوں کی تعداد",
+ "login-attempts-help": "اگر صارف کی لاگ ان کوششیں اس حد سے تجاوز کر جاتی ہیں، تو اکاؤنٹ ایک مخصوص وقت کے لیے مقفل ہو جائے گا۔",
+ "lockout-duration": "اکاؤنٹ کے مقفل ہونے کی مدت (منٹوں میں)",
+ "login-days": "صارف کے لاگ ان سیشن کو یاد رکھنے کے دنوں کی تعداد",
+ "password-expiry-days": "ایک مخصوص مدت کے دنوں میں پاس ورڈ کی تبدیلی کی ضرورت",
+ "session-time": "سیشن کی مدت",
+ "session-time-days": "دن",
+ "session-time-seconds": "سیکنڈز",
+ "session-time-help": "یہ قیمتیں صارفین کے لاگ ان رہنے کی مدت کا تعین کرنے کے لیے استعمال ہوتی ہیں اگر وہ لاگ ان کے وقت 'مجھے یاد رکھیں' پر نشان لگاتے ہیں۔ نوٹ کریں کہ ان میں سے صرف ایک قیمت استعمال ہوگی۔ اگر سیکنڈز کی کوئی قیمت نہیں ہے، تو دنوں کی قیمت استعمال ہوگی۔ اگر دنوں کی بھی کوئی قیمت نہیں ہے، تو طے شدہ قیمت 14 دن استعمال ہوگی۔",
+ "session-duration": "سیشن کی مدت اگر 'مجھے یاد رکھیں' پر نشان نہ لگایا گیا ہو (سیکنڈز میں)",
+ "session-duration-help": "طے شدہ طور پر (یا اگر قیمت 0 ہو) صارف اس وقت تک لاگ ان رہے گا جب تک کہ اس کا سیشن ختم نہ ہو جائے (عام طور پر جب براؤزر یا ٹیب بند ہو جائے)۔ اگر آپ بالکل درست وقت (سیکنڈز میں) متعین کرنا چاہتے ہیں جس کے بعد صارف کا سیشن ختم ہو جائے تو اس ترتیب کا استعمال کریں۔",
+ "online-cutoff": "منٹوں کی تعداد جس کے بعد صارف غیر فعال سمجھا جائے گا",
+ "online-cutoff-help": "اگر صارف اس مدت میں کوئی سرگرمی نہیں کرتا، تو اسے غیر فعال سمجھا جائے گا اور اسے ریئل ٹائم اطلاعات نہیں ملیں گی۔",
+ "registration": "صارفین کی رجسٹریشن",
+ "registration-type": "رجسٹریشن کی قسم",
+ "registration-approval-type": "رجسٹریشن کی منظوری کی قسم",
+ "registration-type.normal": "عام",
+ "registration-type.admin-approval": "ایڈمنسٹریٹر کی منظوری",
+ "registration-type.admin-approval-ip": "IP ایڈریس کے لحاظ سے ایڈمنسٹریٹر کی منظوری",
+ "registration-type.invite-only": "صرف دعوت نامہ",
+ "registration-type.admin-invite-only": "صرف ایڈمنسٹریٹر کی دعوت",
+ "registration-type.disabled": "کوئی رجسٹریشن نہیں",
+ "registration-type.help": "عام — صارفین /register صفحہ سے رجسٹر کر سکتے ہیں۔%1)",
+ "banned.subject": "آپ کو %1 سے بلاک کر دیا گیا ہے",
+ "banned.text1": "صارف %1 کو %2 سے بلاک کر دیا گیا ہے۔",
+ "banned.text2": "یہ پابندی %1 تک نافذ رہے گی۔",
+ "banned.text3": "آپ پر پابندی لگنے کی وجہ یہ ہے:",
+ "closing": "شکریہ!"
+}
\ No newline at end of file
diff --git a/public/language/ur/error.json b/public/language/ur/error.json
new file mode 100644
index 0000000000..21e92491e6
--- /dev/null
+++ b/public/language/ur/error.json
@@ -0,0 +1,266 @@
+{
+ "invalid-data": "غلط ڈیٹا",
+ "invalid-json": "غلط JSON",
+ "wrong-parameter-type": "پراپرٹی '%1' کے لیے %3 قسم کی قدر متوقع تھی، لیکن اس کے بجائے %2 موصول ہوا",
+ "required-parameters-missing": "اس API کال سے ضروری پیرامیٹرز غائب ہیں: %1",
+ "reserved-ip-address": "محفوظ شدہ رینجز سے IP ایڈریسز پر نیٹ ورک کی درخواستوں کی اجازت نہیں ہے۔",
+ "not-logged-in": "ایسا لگتا ہے کہ آپ نے لاگ ان نہیں کیا۔",
+ "account-locked": "آپ کا اکاؤنٹ عارضی طور پر مقفل کر دیا گیا ہے",
+ "search-requires-login": "تلاش کے لیے رجسٹرڈ اکاؤنٹ درکار ہے! براہ کرم لاگ ان کریں یا رجسٹر کریں!",
+ "goback": "پچھلے صفحے پر واپس جانے کے لیے 'بیک' دبائیں",
+ "invalid-cid": "غلط زمرہ شناخت کنندہ",
+ "invalid-tid": "غلط موضوع شناخت کنندہ",
+ "invalid-pid": "غلط پوسٹ شناخت کنندہ",
+ "invalid-uid": "غلط صارف شناخت کنندہ",
+ "invalid-mid": "غلط گفتگو پیغام شناخت کنندہ",
+ "invalid-date": "ایک درست تاریخ متعین کی جانی چاہیے",
+ "invalid-username": "غلط صارف نام",
+ "invalid-email": "غلط ای میل",
+ "invalid-fullname": "غلط مکمل نام",
+ "invalid-location": "غلط مقام",
+ "invalid-birthday": "غلط تاریخ پیدائش",
+ "invalid-title": "غلط عنوان",
+ "invalid-user-data": "غلط صارف ڈیٹا",
+ "invalid-password": "غلط پاس ورڈ",
+ "invalid-login-credentials": "غلط تصدیقی معلومات",
+ "invalid-username-or-password": "براہ کرم صارف نام اور پاس ورڈ درج کریں",
+ "invalid-search-term": "غلط تلاش کا جملہ",
+ "invalid-url": "غلط ایڈریس",
+ "invalid-event": "غلط ایونٹ: %1",
+ "local-login-disabled": "مقامی لاگ ان سسٹم غیر مراعات یافتہ اکاؤنٹس کے لیے غیر فعال ہے۔",
+ "csrf-invalid": "ہم آپ کو لاگ ان نہیں کر سکے، غالباً کیونکہ آپ کا سیشن ختم ہو چکا ہے۔ براہ کرم دوبارہ کوشش کریں",
+ "invalid-path": "غلط راستہ",
+ "folder-exists": "اس نام کا فولڈر پہلے سے موجود ہے",
+ "invalid-pagination-value": "غلط صفحہ بندی کی قدر، یہ %1 اور %2 کے درمیان ہونی چاہیے",
+ "username-taken": "صارف نام پہلے سے لیا جا چکا ہے",
+ "email-taken": "ای میل ایڈریس پہلے سے لیا جا چکا ہے۔",
+ "email-nochange": "درج کردہ ای میل موجودہ ای میل جیسا ہی ہے۔",
+ "email-invited": "اس ای میل پر پہلے سے دعوت بھیجی جا چکی ہے",
+ "email-not-confirmed": "کچھ زمرہ جات اور موضوعات میں پوسٹنگ اس وقت تک ممکن نہیں ہوگی جب تک آپ کا ای میل تصدیق نہ ہو جائے۔ تصدیقی ای میل بھیجنے کے لیے یہاں کلک کریں۔",
+ "email-not-confirmed-chat": "جب تک آپ کا ای میل تصدیق نہ ہو جائے، آپ گفتگو میں لکھ نہیں سکیں گے۔ براہ کرم اپنے ای میل کی تصدیق کے لیے یہاں کلک کریں۔",
+ "email-not-confirmed-email-sent": "آپ کا ای میل ابھی تک تصدیق شدہ نہیں ہے۔ براہ کرم اپنے ان باکس میں تصدیقی ای میل چیک کریں۔ جب تک آپ کا ای میل تصدیق نہ ہو جائے، آپ پیغامات پوسٹ یا گفتگو میں لکھ نہیں سکیں گے۔",
+ "no-email-to-confirm": "آپ نے کوئی ای میل متعین نہیں کیا۔ اکاؤنٹ کی بحالی کے لیے ای میل ضروری ہے، اور کچھ زمرہ جات میں لکھنے کے لیے بھی اس کی ضرورت ہو سکتی ہے۔ ای میل درج کرنے کے لیے یہاں کلک کریں۔",
+ "user-doesnt-have-email": "صارف '%1' نے کوئی ای میل متعین نہیں کیا۔",
+ "email-confirm-failed": "ہم آپ کے ای میل کی تصدیق نہیں کر سکے۔ براہ کرم بعد میں دوبارہ کوشش کریں۔",
+ "confirm-email-already-sent": "تصدیقی ای میل پہلے سے بھیج دیا گیا ہے۔ براہ کرم نئی ای میل بھیجنے سے پہلے %1 منٹ انتظار کریں۔",
+ "confirm-email-expired": "تصدیقی ای میل کی میعاد ختم ہو چکی ہے",
+ "sendmail-not-found": "’sendmail‘ کا قابل عمل فائل نہیں مل سکا۔ براہ کرم یقینی بنائیں کہ یہ انسٹال ہے اور NodeBB کو چلانے والے صارف کے لیے قابل عمل ہے۔",
+ "digest-not-enabled": "اس صارف کے لیے ڈائجسٹ فعال نہیں ہیں، یا سسٹم کی طے شدہ ترتیب یہ ہے کہ ڈائجسٹ نہ بھیجیں",
+ "username-too-short": "صارف نام بہت چھوٹا ہے",
+ "username-too-long": "صارف نام بہت لمبا ہے",
+ "password-too-long": "پاس ورڈ بہت لمبا ہے",
+ "reset-rate-limited": "پاس ورڈ ری سیٹ کی بہت زیادہ درخواستیں (ریٹ کی حد ہے)",
+ "reset-same-password": "براہ کرم موجودہ پاس ورڈ سے مختلف پاس ورڈ استعمال کریں",
+ "user-banned": "صارف پر پابندی لگائی گئی ہے",
+ "user-banned-reason": "معذرت، اس اکاؤنٹ پر پابندی لگائی گئی ہے (وجہ: %1)",
+ "user-banned-reason-until": "معذرت، اس اکاؤنٹ پر %1 تک پابندی لگائی گئی ہے (وجہ: %2)",
+ "user-too-new": "معذرت، لیکن آپ کو اپنی پہلی پوسٹ کرنے سے پہلے کم از کم %1 سیکنڈ انتظار کرنا ہوگا",
+ "blacklisted-ip": "معذرت، لیکن آپ کا IP ایڈریس اس کمیونٹی میں استعمال کے لیے ممنوع ہے۔ اگر آپ کو لگتا ہے کہ یہ غلطی ہے، تو براہ کرم ایڈمنسٹریٹر سے رابطہ کریں۔",
+ "cant-blacklist-self-ip": "آپ اپنا IP ایڈریس بلیک لسٹ میں شامل نہیں کر سکتے",
+ "ban-expiry-missing": "براہ کرم اس پابندی کے لیے اختتامی تاریخ متعین کریں",
+ "no-category": "زمرہ موجود نہیں ہے",
+ "no-topic": "موضوع موجود نہیں ہے",
+ "no-post": "پوسٹ موجود نہیں ہے",
+ "no-group": "گروپ موجود نہیں ہے",
+ "no-user": "صارف موجود نہیں ہے",
+ "no-teaser": "ٹیزر موجود نہیں ہے",
+ "no-flag": "رپورٹ موجود نہیں ہے",
+ "no-chat-room": "گفتگو کا کمرہ موجود نہیں ہے",
+ "no-privileges": "آپ کے پاس اس عمل کے لیے کافی اختیارات نہیں ہیں۔",
+ "category-disabled": "زمرہ غیر فعال ہے",
+ "post-deleted": "پوسٹ حذف کر دی گئی ہے",
+ "topic-locked": "موضوع مقفل ہے",
+ "post-edit-duration-expired": "آپ اپنی پوسٹس کو پوسٹ کرنے کے %1 سیکنڈ تک ترمیم کر سکتے ہیں",
+ "post-edit-duration-expired-minutes": "آپ اپنی پوسٹس کو پوسٹ کرنے کے %1 منٹ تک ترمیم کر سکتے ہیں",
+ "post-edit-duration-expired-minutes-seconds": "آپ اپنی پوسٹس کو پوسٹ کرنے کے %1 منٹ اور %2 سیکنڈ تک ترمیم کر سکتے ہیں",
+ "post-edit-duration-expired-hours": "آپ اپنی پوسٹس کو پوسٹ کرنے کے %1 گھنٹوں تک ترمیم کر سکتے ہیں",
+ "post-edit-duration-expired-hours-minutes": "آپ اپنی پوسٹس کو پوسٹ کرنے کے %1 گھنٹوں اور %2 منٹ تک ترمیم کر سکتے ہیں",
+ "post-edit-duration-expired-days": "آپ اپنی پوسٹس کو پوسٹ کرنے کے %1 دنوں تک ترمیم کر سکتے ہیں",
+ "post-edit-duration-expired-days-hours": "آپ اپنی پوسٹس کو پوسٹ کرنے کے %1 دنوں اور %2 گھنٹوں تک ترمیم کر سکتے ہیں",
+ "post-delete-duration-expired": "آپ اپنی پوسٹس کو پوسٹ کرنے کے %1 سیکنڈ تک حذف کر سکتے ہیں",
+ "post-delete-duration-expired-minutes": "آپ اپنی پوسٹس کو پوسٹ کرنے کے %1 منٹ تک حذف کر سکتے ہیں",
+ "post-delete-duration-expired-minutes-seconds": "آپ اپنی پوسٹس کو پوسٹ کرنے کے %1 منٹ اور %2 سیکنڈ تک حذف کر سکتے ہیں",
+ "post-delete-duration-expired-hours": "آپ اپنی پوسٹس کو پوسٹ کرنے کے %1 گھنٹوں تک حذف کر سکتے ہیں",
+ "post-delete-duration-expired-hours-minutes": "آپ اپنی پوسٹس کو پوسٹ کرنے کے %1 گھنٹوں اور %2 منٹ تک حذف کر سکتے ہیں",
+ "post-delete-duration-expired-days": "آپ اپنی پوسٹس کو پوسٹ کرنے کے %1 دنوں تک حذف کر سکتے ہیں",
+ "post-delete-duration-expired-days-hours": "آپ اپنی پوسٹس کو پوسٹ کرنے کے %1 دنوں اور %2 گھنٹوں تک حذف کر سکتے ہیں",
+ "cant-delete-topic-has-reply": "آپ اپنا موضوع حذف نہیں کر سکتے کیونکہ اس میں پہلے سے ایک جواب موجود ہے",
+ "cant-delete-topic-has-replies": "آپ اپنا موضوع حذف نہیں کر سکتے کیونکہ اس میں پہلے سے %1 جوابات موجود ہیں",
+ "content-too-short": "براہ کرم پوسٹ کا متن لمبا درج کریں۔ پوسٹس میں کم از کم %1 حروف ہونے چاہئیں۔",
+ "content-too-long": "براہ کرم پوسٹ کا متن مختصر درج کریں۔ پوسٹس میں %1 حروف سے زیادہ نہیں ہونے چاہئیں۔",
+ "title-too-short": "براہ کرم عنوان لمبا درج کریں۔ عنوانات میں کم از کم %1 حروف ہونے چاہئیں۔",
+ "title-too-long": "براہ کرم عنوان مختصر درج کریں۔ عنوانات میں %1 حروف سے زیادہ نہیں ہونے چاہئیں۔",
+ "category-not-selected": "کوئی زمرہ منتخب نہیں کیا گیا۔",
+ "too-many-posts": "آپ ہر %1 سیکنڈ میں ایک بار پوسٹ کر سکتے ہیں – براہ کرم دوبارہ پوسٹ کرنے سے پہلے کچھ دیر انتظار کریں",
+ "too-many-posts-newbie": "ایک نئے صارف کے طور پر، آپ ہر %1 سیکنڈ میں ایک بار پوسٹ کر سکتے ہیں جب تک کہ آپ %2 ساکھ حاصل نہ کر لیں – براہ کرم دوبارہ پوسٹ کرنے سے پہلے کچھ دیر انتظار کریں",
+ "too-many-posts-newbie-minutes": "ایک نئے صارف کے طور پر، آپ ہر %1 منٹ میں ایک بار پوسٹ کر سکتے ہیں جب تک کہ آپ %2 ساکھ حاصل نہ کر لیں – براہ کرم دوبارہ پوسٹ کرنے سے پہلے کچھ دیر انتظار کریں",
+ "already-posting": "آپ اس وقت پوسٹ کر رہے ہیں",
+ "tag-too-short": "براہ کرم لمبا ٹیگ درج کریں۔ ٹیگز میں کم از کم %1 حروف ہونے چاہئیں",
+ "tag-too-long": "براہ کرم مختصر ٹیگ درج کریں۔ ٹیگز میں %1 حروف سے زیادہ نہیں ہونے چاہئیں",
+ "tag-not-allowed": "ٹیگ کی اجازت نہیں ہے",
+ "not-enough-tags": "ناکافی ٹیگز۔ موضوعات میں کم از کم %1 ٹیگ ہونا چاہیے",
+ "too-many-tags": "بہت زیادہ ٹیگز۔ موضوعات میں %1 ٹیگز سے زیادہ نہیں ہو سکتے",
+ "cant-use-system-tag": "آپ اس سسٹم ٹیگ کو استعمال نہیں کر سکتے۔",
+ "cant-remove-system-tag": "آپ اس سسٹم ٹیگ کو ہٹا نہیں سکتے۔",
+ "still-uploading": "براہ کرم اپ لوڈ مکمل ہونے کا انتظار کریں۔",
+ "file-too-big": "فائل کا زیادہ سے زیادہ اجازت شدہ سائز %1 KB ہے – براہ کرم چھوٹی فائل اپ لوڈ کریں",
+ "guest-upload-disabled": "مہمانوں کے لیے اپ لوڈ کی اجازت نہیں ہے",
+ "cors-error": "CORS کی غلط ترتیبات کی وجہ سے تصویر اپ لوڈ نہیں کی جا سکی",
+ "upload-ratelimit-reached": "آپ نے ایک ساتھ بہت زیادہ فائلیں اپ لوڈ کی ہیں۔ براہ کرم بعد میں دوبارہ کوشش کریں۔",
+ "upload-error-fallback": "تصویر اپ لوڈ نہیں کی جا سکی – %1",
+ "scheduling-to-past": "مستقبل کی تاریخ منتخب کریں۔",
+ "invalid-schedule-date": "درست تاریخ اور وقت درج کریں۔",
+ "cant-pin-scheduled": "طے شدہ موضوعات کو پن یا ان پن نہیں کیا جا سکتا۔",
+ "cant-merge-scheduled": "طے شدہ موضوعات کو ضم نہیں کیا جا سکتا۔",
+ "cant-move-posts-to-scheduled": "پوسٹس کو طے شدہ موضوع میں منتقل نہیں کیا جا سکتا۔",
+ "cant-move-from-scheduled-to-existing": "طے شدہ موضوع سے پوسٹس کو موجودہ موضوع میں منتقل نہیں کیا جا سکتا۔",
+ "already-bookmarked": "آپ نے اس پوسٹ کو پہلے سے بک مارک کیا ہوا ہے",
+ "already-unbookmarked": "آپ نے اس پوسٹ سے بک مارک پہلے سے ہٹا دیا ہے",
+ "cant-ban-other-admins": "آپ دوسرے ایڈمنسٹریٹرز پر پابندی نہیں لگا سکتے!",
+ "cant-mute-other-admins": "آپ دوسرے ایڈمنسٹریٹرز کو خاموش نہیں کر سکتے!",
+ "user-muted-for-hours": "آپ کو خاموش کر دیا گیا ہے۔ آپ %1 گھنٹوں کے بعد دوبارہ پوسٹ کر سکیں گے",
+ "user-muted-for-minutes": "آپ کو خاموش کر دیا گیا ہے۔ آپ %1 منٹوں کے بعد دوبارہ پوسٹ کر سکیں گے",
+ "cant-make-banned-users-admin": "آپ پابندی والے صارفین کو ایڈمنسٹریٹر کے حقوق نہیں دے سکتے۔",
+ "cant-remove-last-admin": "آپ واحد ایڈمنسٹریٹر ہیں۔ اپنے آپ کو ایڈمنسٹریٹر سے ہٹانے سے پہلے دوسرے صارف کو ایڈمنسٹریٹر بنائیں",
+ "account-deletion-disabled": "اکاؤنٹ حذف کرنا ممنوع ہے",
+ "cant-delete-admin": "اس اکاؤنٹ سے ایڈمنسٹریٹر کے حقوق ہٹائیں اسے حذف کرنے سے پہلے۔",
+ "already-deleting": "پہلے سے حذف ہو رہا ہے",
+ "invalid-image": "غلط تصویر",
+ "invalid-image-type": "غلط تصویر کی قسم۔ اجازت شدہ اقسام ہیں: %1",
+ "invalid-image-extension": "غلط تصویر ایکسٹینشن",
+ "invalid-file-type": "غلط فائل کی قسم۔ اجازت شدہ اقسام ہیں: %1",
+ "invalid-image-dimensions": "تصویر کے طول و عرض بہت بڑے ہیں",
+ "group-name-too-short": "گروپ کا نام بہت چھوٹا ہے",
+ "group-name-too-long": "گروپ کا نام بہت لمبا ہے",
+ "group-already-exists": "اس نام کا گروپ پہلے سے موجود ہے",
+ "group-name-change-not-allowed": "گروپ کے نام کی تبدیلی کی اجازت نہیں ہے",
+ "group-already-member": "صارف پہلے سے اس گروپ کا رکن ہے",
+ "group-not-member": "صارف اس گروپ کا رکن نہیں ہے",
+ "group-needs-owner": "اس گروپ کو کم از کم ایک مالک کی ضرورت ہے",
+ "group-already-invited": "اس صارف کو پہلے سے دعوت دی جا چکی ہے",
+ "group-already-requested": "آپ کی رکنیت کی درخواست پہلے سے بھیجی جا چکی ہے",
+ "group-join-disabled": "آپ اس وقت اس گروپ میں شامل نہیں ہو سکتے",
+ "group-leave-disabled": "آپ اس وقت اس گروپ کو نہیں چھوڑ سکتے",
+ "group-user-not-pending": "صارف کی اس گروپ میں شامل ہونے کی کوئی زیر التواء درخواست نہیں ہے۔",
+ "gorup-user-not-invited": "صارف کو اس گروپ میں شامل ہونے کی دعوت نہیں دی گئی۔",
+ "post-already-deleted": "یہ پوسٹ پہلے سے حذف ہو چکی ہے",
+ "post-already-restored": "یہ پوسٹ پہلے سے بحال ہو چکی ہے",
+ "topic-already-deleted": "یہ موضوع پہلے سے حذف ہو چکا ہے",
+ "topic-already-restored": "یہ موضوع پہلے سے بحال ہو چکا ہے",
+ "cant-purge-main-post": "آپ ابتدائی پوسٹ کو صاف نہیں کر سکتے۔ براہ کرم اس کے بجائے موضوع کو حذف کریں۔",
+ "topic-thumbnails-are-disabled": "موضوعات کے تھمب نیلز غیر فعال ہیں۔",
+ "invalid-file": "غلط فائل",
+ "uploads-are-disabled": "اپ لوڈ کی اجازت نہیں ہے",
+ "signature-too-long": "معذرت، لیکن آپ کے دستخط میں %1 حروف سے زیادہ نہیں ہونے چاہئیں۔",
+ "about-me-too-long": "معذرت، لیکن آپ کے بارے میں معلومات میں %1 حروف سے زیادہ نہیں ہونے چاہئیں۔",
+ "cant-chat-with-yourself": "آپ خود کو پیغام نہیں لکھ سکتے!",
+ "chat-restricted": "اس صارف نے اپنے پیغامات کو محدود کر دیا ہے۔ اس سے پہلے کہ آپ اس کے ساتھ بات چیت کر سکیں، اسے آپ کو فالو کرنا ہوگا۔",
+ "chat-allow-list-user-already-added": "یہ صارف پہلے سے اجازت شدہ فہرست میں ہے",
+ "chat-deny-list-user-already-added": "یہ صارف پہلے سے ممنوعہ فہرست میں ہے",
+ "chat-user-blocked": "آپ کو اس صارف نے بلاک کر دیا ہے۔",
+ "chat-disabled": "گفتگو کا نظام غیر فعال ہے",
+ "too-many-messages": "آپ نے بہت زیادہ پیغامات بھیج دیے ہیں۔ براہ کرم کچھ دیر انتظار کریں۔",
+ "invalid-chat-message": "غلط پیغام",
+ "chat-message-too-long": "گفتگو کے پیغامات %1 حروف سے زیادہ لمبے نہیں ہو سکتے۔",
+ "cant-edit-chat-message": "آپ کو اس پیغام کو ترمیم کرنے کا اختیار نہیں ہے",
+ "cant-delete-chat-message": "آپ کو اس پیغام کو حذف کرنے کا اختیار نہیں ہے",
+ "chat-edit-duration-expired": "آپ اپنے گفتگو کے پیغامات کو پوسٹ کرنے کے %1 سیکنڈ تک ترمیم کر سکتے ہیں",
+ "chat-delete-duration-expired": "آپ اپنے گفتگو کے پیغامات کو پوسٹ کرنے کے %1 سیکنڈ تک حذف کر سکتے ہیں",
+ "chat-deleted-already": "یہ پیغام پہلے سے حذف ہو چکا ہے۔",
+ "chat-restored-already": "یہ پیغام پہلے سے بحال ہو چکا ہے۔",
+ "chat-room-does-not-exist": "گفتگو کا کمرہ موجود نہیں ہے۔",
+ "cant-add-users-to-chat-room": "گفتگو کے کمرے میں صارفین شامل نہیں کیے جا سکتے۔",
+ "cant-remove-users-from-chat-room": "گفتگو کے کمرے سے صارفین ہٹائے نہیں جا سکتے۔",
+ "chat-room-name-too-long": "کمرا کا نام بہت لمبا ہے۔ نام %1 حروف سے زیادہ لمبے نہیں ہو سکتے۔",
+ "remote-chat-received-too-long": "آپ کو %1 سے ایک پیغام موصول ہوا، لیکن یہ بہت لمبا تھا اور اسے مسترد کر دیا گیا۔",
+ "already-voting-for-this-post": "آپ نے اس پوسٹ کے لیے پہلے سے ووٹ دیا ہے۔",
+ "reputation-system-disabled": "ساکھ کا نظام غیر فعال ہے۔",
+ "downvoting-disabled": "منفی ووٹنگ غیر فعال ہے",
+ "not-enough-reputation-to-chat": "گفتگو میں حصہ لینے کے لیے آپ کی ساکھ کم از کم %1 ہونی چاہیے",
+ "not-enough-reputation-to-upvote": "مثبت ووٹ دینے کے لیے آپ کی ساکھ کم از کم %1 ہونی چاہیے",
+ "not-enough-reputation-to-downvote": "منفی ووٹ دینے کے لیے آپ کی ساکھ کم از کم %1 ہونی چاہیے",
+ "not-enough-reputation-to-post-links": "لنکس پوسٹ کرنے کے لیے آپ کی ساکھ کم از کم %1 ہونی چاہیے",
+ "not-enough-reputation-to-flag": "اس پوسٹ کی رپورٹ کرنے کے لیے آپ کی ساکھ کم از کم %1 ہونی چاہیے",
+ "not-enough-reputation-min-rep-website": "ویب سائٹ شامل کرنے کے لیے آپ کی ساکھ کم از کم %1 ہونی چاہیے",
+ "not-enough-reputation-min-rep-aboutme": "اپنے بارے میں معلومات شامل کرنے کے لیے آپ کی ساکھ کم از کم %1 ہونی چاہیے",
+ "not-enough-reputation-min-rep-signature": "دستخط شامل کرنے کے لیے آپ کی ساکھ کم از کم %1 ہونی چاہیے",
+ "not-enough-reputation-min-rep-profile-picture": "پروفائل تصویر شامل کرنے کے لیے آپ کی ساکھ کم از کم %1 ہونی چاہیے",
+ "not-enough-reputation-min-rep-cover-picture": "کور تصویر شامل کرنے کے لیے آپ کی ساکھ کم از کم %1 ہونی چاہیے",
+ "not-enough-reputation-custom-field": "%2 کے لیے آپ کی ساکھ کم از کم %1 ہونی چاہیے",
+ "custom-user-field-value-too-long": "حسب ضرورت فیلڈ کی قدر بہت لمبی ہے، %1",
+ "custom-user-field-select-value-invalid": "حسب ضرورت فیلڈ میں منتخب کردہ آپشن غلط ہے، %1",
+ "custom-user-field-invalid-text": "حسب ضرورت فیلڈ میں متن غلط ہے، %1",
+ "custom-user-field-invalid-link": "حسب ضرورت فیلڈ میں لنک غلط ہے، %1",
+ "custom-user-field-invalid-number": "حسب ضرورت فیلڈ میں نمبر غلط ہے، %1",
+ "custom-user-field-invalid-date": "حسب ضرورت فیلڈ میں تاریخ غلط ہے، %1",
+ "invalid-custom-user-field": "غلط حسب ضرورت فیلڈ۔ '%1' پہلے سے NodeBB کے ذریعے استعمال ہو رہا ہے",
+ "post-already-flagged": "آپ نے اس پوسٹ کی پہلے سے رپورٹ کی ہوئی ہے",
+ "user-already-flagged": "آپ نے اس صارف کی پہلے سے رپورٹ کی ہوئی ہے",
+ "post-flagged-too-many-times": "اس پوسٹ کو پہلے سے دوسرے لوگوں نے رپورٹ کیا ہے",
+ "user-flagged-too-many-times": "اس صارف کو پہلے سے دوسرے لوگوں نے رپورٹ کیا ہے",
+ "too-many-post-flags-per-day": "آپ ایک دن میں زیادہ سے زیادہ %1 پوسٹس رپورٹ کر سکتے ہیں",
+ "too-many-user-flags-per-day": "آپ ایک دن میں زیادہ سے زیادہ %1 صارفین رپورٹ کر سکتے ہیں",
+ "cant-flag-privileged": "آپ اعلیٰ اختیارات والے صارفین (ماڈریٹرز، گلوبل ماڈریٹرز، ایڈمنسٹریٹرز) کے پروفائلز یا مواد کی رپورٹ نہیں کر سکتے",
+ "cant-locate-flag-report": "رپورٹ نہیں مل سکی",
+ "self-vote": "آپ اپنی پوسٹ کے لیے ووٹ نہیں دے سکتے",
+ "too-many-upvotes-today": "آپ ایک دن میں زیادہ سے زیادہ %1 بار مثبت ووٹ دے سکتے ہیں",
+ "too-many-upvotes-today-user": "آپ ایک صارف کے لیے ایک دن میں زیادہ سے زیادہ %1 بار مثبت ووٹ دے سکتے ہیں",
+ "too-many-downvotes-today": "آپ ایک دن میں زیادہ سے زیادہ %1 بار منفی ووٹ دے سکتے ہیں",
+ "too-many-downvotes-today-user": "آپ ایک صارف کے لیے ایک دن میں زیادہ سے زیادہ %1 بار منفی ووٹ دے سکتے ہیں",
+ "reload-failed": "NodeBB کو دوبارہ لوڈ کرتے وقت ایک مسئلہ پیش آیا: '%1'۔ NodeBB موجودہ کلائنٹ وسائل کو برقرار رکھے گا، لیکن آپ کو دوبارہ لوڈ کرنے سے پہلے اپنے آخری اقدامات منسوخ کرنا ہوں گے۔",
+ "registration-error": "رجسٹریشن میں خرابی",
+ "parse-error": "سرور کے جواب کو پارس کرتے وقت کچھ غلط ہو گیا",
+ "wrong-login-type-email": "براہ کرم لاگ ان کے لیے اپنا ای میل استعمال کریں",
+ "wrong-login-type-username": "براہ کرم لاگ ان کے لیے اپنا صارف نام استعمال کریں",
+ "sso-registration-disabled": "%1 سے اکاؤنٹس کی رجسٹریشن ممنوع کر دی گئی ہے، براہ کرم پہلے ای میل کے ساتھ رجسٹر کریں",
+ "sso-multiple-association": "آپ اپنے NodeBB اکاؤنٹ کے ساتھ اس سروس سے ایک سے زیادہ اکاؤنٹس کو جوڑ نہیں سکتے۔ براہ کرم موجودہ اکاؤنٹ سے ربط ہٹائیں اور دوبارہ کوشش کریں۔",
+ "invite-maximum-met": "آپ نے زیادہ سے زیادہ اجازت شدہ افراد کو دعوت دی ہے (%1 میں سے %2)۔",
+ "no-session-found": "کوئی لاگ ان سیشن نہیں ملا!",
+ "not-in-room": "صارف کمرے میں نہیں ہے",
+ "cant-kick-self": "آپ خود کو گروپ سے باہر نہیں نکال سکتے",
+ "no-users-selected": "کوئی صارف منتخب نہیں کیا گیا",
+ "no-groups-selected": "کوئی گروپ منتخب نہیں کیا گیا",
+ "invalid-home-page-route": "غلط ہوم پیج کا راستہ",
+ "invalid-session": "سیشن ختم ہو چکا ہے",
+ "invalid-session-text": "ایسا لگتا ہے کہ آپ کا لاگ ان سیشن ختم ہو چکا ہے۔ براہ کرم صفحہ ریفریش کریں۔",
+ "session-mismatch": "سیشن کا عدم مطابقت",
+ "session-mismatch-text": "ایسا لگتا ہے کہ آپ کا لاگ ان سیشن اب سرور سے مطابقت نہیں رکھتا۔ براہ کرم صفحہ ریفریش کریں۔",
+ "no-topics-selected": "کوئی موضوعات منتخب نہیں کیے گئے!",
+ "cant-move-to-same-topic": "پوسٹ کو اسی موضوع میں منتقل نہیں کیا جا سکتا!",
+ "cant-move-topic-to-same-category": "موضوع کو اسی زمرے میں منتقل نہیں کیا جا سکتا!",
+ "cannot-block-self": "آپ خود کو بلاک نہیں کر سکتے!",
+ "cannot-block-privileged": "آپ ایڈمنسٹریٹرز اور گلوبل ماڈریٹرز کو بلاک نہیں کر سکتے",
+ "cannot-block-guest": "مہمان دوسرے صارفین کو بلاک نہیں کر سکتے",
+ "already-blocked": "یہ صارف پہلے سے بلاک ہے",
+ "already-unblocked": "یہ صارف پہلے سے ان بلاک ہے",
+ "no-connection": "ایسا لگتا ہے کہ آپ کے انٹرنیٹ کنکشن میں کوئی مسئلہ ہے",
+ "socket-reconnect-failed": "اس وقت سرور دستیاب نہیں ہے۔ دوبارہ کوشش کرنے کے لیے یہاں کلک کریں، یا بعد میں دوبارہ کوشش کریں۔",
+ "invalid-plugin-id": "غلط پلگ ان شناخت کنندہ",
+ "plugin-not-whitelisted": "پلگ ان انسٹال نہیں کیا جا سکتا – صرف NodeBB کے پیکیج مینیجر سے منظور شدہ پلگ انز کو ACP کے ذریعے انسٹال کیا جا سکتا ہے",
+ "cannot-toggle-system-plugin": "آپ سسٹم پلگ ان کی حالت کو تبدیل نہیں کر سکتے",
+ "plugin-installation-via-acp-disabled": "ACP کے ذریعے پلگ انز کی تنصیب غیر فعال ہے",
+ "plugins-set-in-configuration": "آپ پلگ ان کی حالت کو تبدیل نہیں کر سکتے کیونکہ یہ اس کے آپریشن کے دوران متعین ہوتی ہے (config.json، ماحولیاتی متغیرات، یا رن ٹائم آرگومنٹس کے ذریعے)۔ اس کے بجائے آپ کنفیگریشن تبدیل کر سکتے ہیں۔",
+ "theme-not-set-in-configuration": "جب کنفیگریشن میں فعال پلگ انز متعین کیے جاتے ہیں، تو تھیمز کو تبدیل کرنے کے لیے نئی تھیم کو فعال پلگ انز میں شامل کرنا ہوتا ہے، اس سے پہلے کہ اسے ACP میں اپ ڈیٹ کیا جائے",
+ "topic-event-unrecognized": "موضوع کا ایونٹ '%1' نامعلوم ہے",
+ "category.handle-taken": "زمرہ کی شناخت پہلے سے لی جا چکی ہے۔ براہ کرم کوئی اور شناخت کنندہ منتخب کریں۔",
+ "cant-set-child-as-parent": "ذیلی زمرہ کو بنیادی زمرہ کے طور پر متعین نہیں کیا جا سکتا",
+ "cant-set-self-as-parent": "زمرہ کو خود کا بنیادی زمرہ نہیں بنایا جا سکتا",
+ "api.master-token-no-uid": "ایک ماسٹر شناخت کنندہ موصول ہوا بغیر درخواست کے جسم میں متعلقہ '_uid' فیلڈ کے",
+ "api.400": "آپ نے جمع کرائے گئے درخواست کے ڈیٹا میں کچھ غلط تھا۔",
+ "api.401": "کوئی سیشن نہیں ملا۔ براہ کرم لاگ ان کریں اور دوبارہ کوشش کریں۔",
+ "api.403": "آپ کو اس کمانڈ کو انجام دینے کی اجازت نہیں ہے",
+ "api.404": "غلط API کمانڈ",
+ "api.426": "لکھنے کی API درخواستوں کے لیے HTTPS درکار ہے۔ براہ کرم اپنی درخواست HTTPS کے ذریعے دوبارہ بھیجیں",
+ "api.429": "آپ نے بہت زیادہ درخواستیں کی ہیں۔ براہ کرم بعد میں دوبارہ کوشش کریں۔",
+ "api.500": "آپ کی درخواست پر عملدرآمد کے دوران ایک غیر متوقع خرابی پیش آئی۔",
+ "api.501": "جس راستے کو آپ کال کرنے کی کوشش کر رہے ہیں وہ ابھی تک موجود نہیں ہے۔ براہ کرم کل دوبارہ کوشش کریں۔",
+ "api.503": "جس راستے کو آپ کال کرنے کی کوشش کر رہے ہیں وہ فی الحال سرور کی ترتیبات کی وجہ سے دستیاب نہیں ہے۔",
+ "api.reauth-required": "جس وسائل تک آپ رسائی حاصل کرنے کی کوشش کر رہے ہیں اس کے لیے (دوبارہ) تصدیق درکار ہے۔",
+ "activitypub.not-enabled": "اس سرور پر فیڈریشن فعال نہیں ہے",
+ "activitypub.invalid-id": "داخل کردہ شناخت کنندہ کو تسلیم نہیں کیا جا سکتا – یہ غلط ہو سکتا ہے۔",
+ "activitypub.get-failed": "مخصوص مواد حاصل نہیں کیا جا سکا۔",
+ "activitypub.pubKey-not-found": "عوامی کلید کو تسلیم نہیں کیا جا سکا، اس لیے ڈیٹا کی تصدیق نہیں کی جا سکی۔",
+ "activitypub.origin-mismatch": "موصول شدہ آبجیکٹ کا اصل بھیجنے والے کے اصل سے مطابقت نہیں رکھتا",
+ "activitypub.actor-mismatch": "موصول شدہ عمل کو متوقع سے مختلف ذریعہ سے انجام دیا جا رہا ہے۔",
+ "activitypub.not-implemented": "درخواست کو مسترد کر دیا گیا کیونکہ اس کا یا اس کے کسی حصے کو اس سرور کے ذریعے سپورٹ نہیں کیا جاتا جس کی طرف یہ ہدایت کی گئی ہے"
+}
\ No newline at end of file
diff --git a/public/language/ur/flags.json b/public/language/ur/flags.json
new file mode 100644
index 0000000000..62a6fb2641
--- /dev/null
+++ b/public/language/ur/flags.json
@@ -0,0 +1,101 @@
+{
+ "state": "حالت",
+ "report": "رپورٹ",
+ "reports": "رپورٹس",
+ "first-reported": "پہلی رپورٹ",
+ "no-flags": "ہورے! کوئی رپورٹس نہیں ملیں۔",
+ "x-flags-found": "رپورٹس ملیں: %1۔",
+ "assignee": "تفویض کردہ",
+ "update": "اپ ڈیٹ",
+ "updated": "اپ ڈیٹ ہوا",
+ "resolved": "حل شدہ",
+ "report-added": "شامل کیا گیا",
+ "report-rescinded": "منسوخ",
+ "target-purged": "اس رپورٹ سے متعلق مواد حذف کر دیا گیا ہے اور اب دستیاب نہیں ہے۔",
+ "target-aboutme-empty": "اس صارف نے اپنے بارے میں سیکشن میں کچھ نہیں بھرا۔",
+
+ "graph-label": "روزانہ ٹیگز",
+ "quick-filters": "فوری فلٹرز",
+ "filter-active": "اس رپورٹس کی فہرست میں ایک یا زیادہ فلٹرز موجود ہیں",
+ "filter-reset": "فلٹرز ہٹائیں",
+ "filters": "فلٹرز کی ترتیبات",
+ "filter-reporterId": "رپورٹ کرنے والا",
+ "filter-targetUid": "رپورٹ کیا گیا",
+ "filter-type": "رپورٹ کی قسم",
+ "filter-type-all": "سب کچھ",
+ "filter-type-post": "پوسٹ",
+ "filter-type-user": "صارف",
+ "filter-state": "حالت",
+ "filter-assignee": "تفویض کردہ",
+ "filter-cid": "زمرہ",
+ "filter-quick-mine": "مجھے تفویض کردہ",
+ "filter-cid-all": "تمام زمرہ جات",
+ "apply-filters": "فلٹرز کا اطلاق کریں",
+ "more-filters": "مزید فلٹرز",
+ "fewer-filters": "کم فلٹرز",
+
+ "quick-actions": "فوری اقدامات",
+ "flagged-user": "رپورٹ کیا گیا صارف",
+ "view-profile": "پروفائل دیکھیں",
+ "start-new-chat": "نئی گفتگو شروع کریں",
+ "go-to-target": "رپورٹ کا ہدف دیکھیں",
+ "assign-to-me": "مجھے تفویض کریں",
+ "delete-post": "پوسٹ حذف کریں",
+ "purge-post": "پوسٹ صاف کریں",
+ "restore-post": "پوسٹ بحال کریں",
+ "delete": "رپورٹ حذف کریں",
+
+ "user-view": "پروفائل دیکھیں",
+ "user-edit": "پروفائل ترمیم کریں",
+
+ "notes": "رپورٹ کے نوٹس",
+ "add-note": "نوٹ شامل کریں",
+ "edit-note": "نوٹ ترمیم کریں",
+ "no-notes": "کوئی اشتراک شدہ نوٹس نہیں ہیں۔",
+ "delete-note-confirm": "کیا آپ واقعی اس رپورٹ کے نوٹ کو حذف کرنا چاہتے ہیں؟",
+ "delete-flag-confirm": "کیا آپ واقعی اس رپورٹ کو حذف کرنا چاہتے ہیں؟",
+ "note-added": "نوٹ شامل کر دیا گیا",
+ "note-deleted": "نوٹ حذف کر دیا گیا",
+ "flag-deleted": "رپورٹ حذف کر دی گئی",
+
+ "history": "اکاؤنٹ اور رپورٹس کی تاریخ",
+ "no-history": "رپورٹ کی کوئی تاریخ نہیں ہے۔",
+
+ "state-all": "تمام حالتیں",
+ "state-open": "نیا/کھلا",
+ "state-wip": "کام جاری ہے",
+ "state-resolved": "حل شدہ",
+ "state-rejected": "مسترد",
+ "no-assignee": "کوئی تفویض نہیں",
+
+ "sort": "ترتیب دیں بذریعہ",
+ "sort-newest": "پہلے نئے",
+ "sort-oldest": "پہلے پرانے",
+ "sort-reports": "پہلے سب سے زیادہ رپورٹس والے",
+ "sort-all": "تمام اقسام کی رپورٹس…",
+ "sort-posts-only": "صرف پوسٹس…",
+ "sort-downvotes": "سب سے زیادہ منفی ووٹس",
+ "sort-upvotes": "سب سے زیادہ مثبت ووٹس",
+ "sort-replies": "سب سے زیادہ جوابات",
+
+ "modal-title": "مواد کی رپورٹنگ",
+ "modal-body": "براہ کرم %1 %2 کی رپورٹنگ کی وجہ بتائیں جائزے کے لیے۔ یا اگر قابل اطلاق ہو تو فوری رپورٹنگ بٹنوں میں سے کوئی استعمال کریں۔",
+ "modal-reason-spam": "سپام",
+ "modal-reason-offensive": "ناگوار",
+ "modal-reason-other": "دیگر (نیچے بیان کریں)",
+ "modal-reason-custom": "اس مواد کی رپورٹنگ کی وجہ…",
+ "modal-notify-remote": "اس رپورٹ کو %1 پر بھیجنا",
+ "modal-submit": "رپورٹ جمع کرائیں",
+ "modal-submit-success": "مواد کو ماڈریٹرز کو رپورٹ کر دیا گیا ہے۔",
+
+ "modal-confirm-rescind": "رپورٹ منسوخ کریں؟",
+
+ "bulk-actions": "اجتماعی اقدامات",
+ "bulk-resolve": "رپورٹ(س) حل کریں",
+ "confirm-purge": "کیا آپ واقعی ان رپورٹس کو مستقل طور پر حذف کرنا چاہتے ہیں؟",
+ "purge-cancelled": "رپورٹ کا حذف منسوخ کر دیا گیا",
+ "bulk-purge": "رپورٹ(س) حذف کریں",
+ "bulk-success": "%1 رپورٹس اپ ڈیٹ ہو گئی ہیں",
+ "flagged-timeago": "رپورٹ کیا گیا ",
+ "auto-flagged": "[خودکار رپورٹ] %1 منفی ووٹس موصول ہوئے۔"
+}
\ No newline at end of file
diff --git a/public/language/ur/global.json b/public/language/ur/global.json
new file mode 100644
index 0000000000..fe2671f98d
--- /dev/null
+++ b/public/language/ur/global.json
@@ -0,0 +1,154 @@
+{
+ "home": "ہوم",
+ "search": "تلاش",
+ "buttons.close": "بند کریں",
+ "403.title": "رسائی مسترد",
+ "403.message": "ایسا لگتا ہے کہ آپ نے ایک ایسے صفحے پر جانے کی کوشش کی ہے جس تک آپ کی رسائی نہیں ہے۔",
+ "403.login": "شاید آپ کو لاگ ان کرنے کی کوشش کرنی چاہیے؟",
+ "404.title": "نہیں ملا",
+ "404.message": "ایسا لگتا ہے کہ آپ نے ایک ایسے صفحے پر جانے کی کوشش کی ہے جو موجود نہیں ہے۔192.168.100.0/22)۔",
+ "hint-2": "آپ لائن کے شروع میں # علامت لگا کر تبصرے شامل کر سکتے ہیں۔",
+
+ "validate.x-valid": "درست قواعد: %1 از %2۔",
+ "validate.x-invalid": "درج ذیل %1 قواعد غلط ہیں:",
+
+ "alerts.applied-success": "بلیک لسٹ کا اطلاق ہو گیا",
+
+ "analytics.blacklist-hourly": "شکل 1 – فی گھنٹہ بلیک لسٹ ہٹس",
+ "analytics.blacklist-daily": "شکل 2 – فی دن بلیک لسٹ ہٹس",
+ "ip-banned": "IP ایڈریس پر پابندی"
+}
\ No newline at end of file
diff --git a/public/language/ur/language.json b/public/language/ur/language.json
new file mode 100644
index 0000000000..4693356e14
--- /dev/null
+++ b/public/language/ur/language.json
@@ -0,0 +1,5 @@
+{
+ "name": "اردو",
+ "code": "ur",
+ "dir": "rtl"
+}
\ No newline at end of file
diff --git a/public/language/ur/login.json b/public/language/ur/login.json
new file mode 100644
index 0000000000..7f2a7d901f
--- /dev/null
+++ b/public/language/ur/login.json
@@ -0,0 +1,12 @@
+{
+ "username-email": "صارف نام / ای میل",
+ "username": "صارف نام",
+ "remember-me": "مجھے یاد رکھیں؟",
+ "forgot-password": "پاس ورڈ بھول گئے؟",
+ "alternative-logins": "دوسرے لاگ ان کے طریقے",
+ "failed-login-attempt": "ناکام لاگ ان کی کوشش",
+ "login-successful": "آپ نے کامیابی سے لاگ ان کیا!",
+ "dont-have-account": "کیا آپ کا اکاؤنٹ نہیں ہے؟",
+ "logged-out-due-to-inactivity": "آپ غیر فعالیت کی وجہ سے ایڈمنسٹریٹو کنٹرول پینل سے خودکار طور پر لاگ آؤٹ ہو گئے ہیں۔",
+ "caps-lock-enabled": "کیپٹل لاک فعال ہے"
+}
\ No newline at end of file
diff --git a/public/language/ur/modules.json b/public/language/ur/modules.json
new file mode 100644
index 0000000000..c3a7a21c7c
--- /dev/null
+++ b/public/language/ur/modules.json
@@ -0,0 +1,135 @@
+{
+ "chat.room-id": "کمرہ %1",
+ "chat.chatting-with": "کے ساتھ گفتگو",
+ "chat.placeholder": "یہاں پیغام درج کریں یا تصاویر ڈریگ اینڈ ڈراپ کریں",
+ "chat.placeholder.mobile": "پیغام درج کریں",
+ "chat.placeholder.message-room": "پیغام #%1",
+ "chat.scroll-up-alert": "تازہ ترین پیغامات کی طرف",
+ "chat.usernames-and-x-others": "%1 اور %2 دیگر",
+ "chat.chat-with-usernames": "%1 کے ساتھ گفتگو",
+ "chat.chat-with-usernames-and-x-others": "%1 اور %2 دیگر کے ساتھ گفتگو",
+ "chat.send": "بھیجیں",
+ "chat.no-active": "آپ کے کوئی جاری گفتگو نہیں ہیں۔",
+ "chat.user-typing-1": "%1 لکھ رہا ہے…",
+ "chat.user-typing-2": "%1 اور %2 لکھ رہے ہیں…",
+ "chat.user-typing-3": "%1، %2 اور %3 لکھ رہے ہیں…",
+ "chat.user-typing-n": "%1، %2 اور %3 دیگر لکھ رہے ہیں…",
+ "chat.user-has-messaged-you": "%1 نے آپ کو پیغام بھیجا۔",
+ "chat.replying-to": "%1 کو جواب",
+ "chat.see-all": "تمام گفتگو",
+ "chat.mark-all-read": "سب کو پڑھا ہوا نشان زد کریں",
+ "chat.no-messages": "براہ کرم پیغامات کی تاریخ دیکھنے کے لیے وصول کنندہ منتخب کریں",
+ "chat.no-users-in-room": "اس کمرے میں کوئی صارفین نہیں ہیں",
+ "chat.recent-chats": "حالیہ گفتگو",
+ "chat.contacts": "رابطے",
+ "chat.message-history": "پیغامات کی تاریخ",
+ "chat.message-deleted": "پیغام حذف کر دیا گیا",
+ "chat.options": "گفتگو کی ترتیبات",
+ "chat.pop-out": "گفتگو کو ونڈو میں کھولیں",
+ "chat.minimize": "چھوٹا کریں",
+ "chat.maximize": "بڑا کریں",
+ "chat.seven-days": "7 دن",
+ "chat.thirty-days": "30 دن",
+ "chat.three-months": "3 ماہ",
+ "chat.delete-message-confirm": "کیا آپ واقعی اس پیغام کو حذف کرنا چاہتے ہیں؟",
+ "chat.retrieving-users": "صارفین حاصل کیے جا رہے ہیں…",
+ "chat.view-users-list": "صارفین کی فہرست دیکھیں",
+ "chat.pinned-messages": "پن کیے گئے پیغامات",
+ "chat.no-pinned-messages": "کوئی پن کیے گئے پیغامات نہیں",
+ "chat.pin-message": "پیغام پن کریں",
+ "chat.unpin-message": "پیغام ان پن کریں",
+ "chat.public-rooms": "عوامی کمرے (%1)",
+ "chat.private-rooms": "نجی کمرے (%1)",
+ "chat.create-room": "گفتگو کا کمرہ بنائیں",
+ "chat.private.option": "نجی (صرف کمرے میں شامل کیے گئے صارفین کے لیے نظر آتا ہے)",
+ "chat.public.option": "عوامی (منتخب گروپس کے تمام صارفین کے لیے نظر آتا ہے)",
+ "chat.public.groups-help": "تمام صارفین کے لیے نظر آنے والا گفتگو کا کمرہ بنانے کے لیے فہرست سے رجسٹرڈ صارفین کا گروپ منتخب کریں۔",
+ "chat.manage-room": "گفتگو کے کمرے کا انتظام",
+ "chat.add-user": "صارف شامل کریں",
+ "chat.notification-settings": "اطلاعات کی ترتیبات",
+ "chat.default-notification-setting": "طے شدہ اطلاعاتی ترتیبات",
+ "chat.join-leave-messages": "شامل ہونے/چھوڑنے کے پیغامات",
+ "chat.notification-setting-room-default": "کمرے کے لیے طے شدہ",
+ "chat.notification-setting-none": "کوئی اطلاعات نہیں",
+ "chat.notification-setting-at-mention-only": "صرف @مینشنز",
+ "chat.notification-setting-all-messages": "تمام پیغامات",
+ "chat.select-groups": "گروپس منتخب کریں",
+ "chat.add-user-help": "یہاں آپ صارفین کو تلاش کر سکتے ہیں۔ جب کوئی صارف منتخب کیا جاتا ہے، اسے گفتگو میں شامل کیا جائے گا۔ نیا صارف اس سے پہلے کے پیغامات نہیں دیکھ سکے گا جو اس کے شامل ہونے سے پہلے لکھے گئے تھے۔ صرف کمرے کے مالکان () صارفین کو کمرے سے ہٹا سکتے ہیں۔",
+ "chat.confirm-chat-with-dnd-user": "یہ صارف 'تنگ نہ کریں' حالت میں ہے۔ کیا آپ واقعی اس سے گفتگو کرنا چاہتے ہیں؟",
+ "chat.room-name-optional": "کمرے کا نام (اختیاری)",
+ "chat.rename-room": "کمرہ دوبارہ نام دیں",
+ "chat.rename-placeholder": "اپنے کمرے کا نام یہاں درج کریں",
+ "chat.rename-help": "یہاں متعین کیا گیا کمرے کا نام اس کے تمام شرکاء کو نظر آئے گا۔",
+ "chat.leave": "چھوڑیں",
+ "chat.leave-room": "کمرہ چھوڑیں",
+ "chat.leave-prompt": "کیا آپ واقعی اس گفتگو کو چھوڑنا چاہتے ہیں؟",
+ "chat.leave-help": "اگر آپ اس گفتگو کو چھوڑتے ہیں تو آپ اس کے بعد کے پیغامات نہیں دیکھ سکیں گے۔ اگر آپ کو دوبارہ شامل کیا جاتا ہے تو آپ اس سے پہلے کی گفتگو کی تاریخ نہیں دیکھ سکیں گے۔",
+ "chat.delete": "حذف کریں",
+ "chat.delete-room": "گفتگو کا کمرہ حذف کریں",
+ "chat.delete-prompt": "کیا آپ واقعی اس گفتگو کے کمرے کو حذف کرنا چاہتے ہیں؟",
+ "chat.in-room": "اس کمرے میں",
+ "chat.kick": "نکالیں",
+ "chat.show-ip": "IP ایڈریس دکھائیں",
+ "chat.copy-text": "متن کاپی کریں",
+ "chat.copy-link": "لنک کاپی کریں",
+ "chat.owner": "کمرے کا مالک",
+ "chat.grant-rescind-ownership": "مالکانہ حقوق دینا/واپس لینا",
+ "chat.system.user-join": "%1 کمرے میں شامل ہوا ",
+ "chat.system.user-leave": "%1 نے کمرہ چھوڑ دیا ",
+ "chat.system.room-rename": "%2 نے اس کمرے کا نام تبدیل کر کے '%1' کر دیا ",
+ "composer.compose": "تحریر کریں",
+ "composer.show-preview": "پیش نظارہ دکھائیں",
+ "composer.hide-preview": "پیش نظارہ چھپائیں",
+ "composer.help": "مدد",
+ "composer.user-said-in": "%1 نے %2 میں کہا:",
+ "composer.user-said": "%1 نے کہا:",
+ "composer.discard": "کیا آپ واقعی اس پوسٹ کو مسترد کرنا چاہتے ہیں؟",
+ "composer.submit-and-lock": "پوسٹ کریں اور لاک کریں",
+ "composer.toggle-dropdown": "ڈراپ ڈاؤن ٹوگل کریں",
+ "composer.uploading": "%1 اپ لوڈ ہو رہا ہے",
+ "composer.formatting.bold": "بولڈ",
+ "composer.formatting.italic": "ایتھیلک",
+ "composer.formatting.heading": "ہیڈنگ",
+ "composer.formatting.heading1": "ہیڈنگ 1",
+ "composer.formatting.heading2": "ہیڈنگ 2",
+ "composer.formatting.heading3": "ہیڈنگ 3",
+ "composer.formatting.heading4": "ہیڈنگ 4",
+ "composer.formatting.heading5": "ہیڈنگ 5",
+ "composer.formatting.heading6": "ہیڈنگ 6",
+ "composer.formatting.list": "فہرست",
+ "composer.formatting.strikethrough": "سٹرائیک تھرو",
+ "composer.formatting.code": "کوڈ",
+ "composer.formatting.link": "لنک",
+ "composer.formatting.picture": "تصویر کا لنک",
+ "composer.upload-picture": "تصویر اپ لوڈ کریں",
+ "composer.upload-file": "فائل اپ لوڈ کریں",
+ "composer.zen-mode": "زین موڈ",
+ "composer.select-category": "زمرہ منتخب کریں",
+ "composer.textarea.placeholder": "اپنی پوسٹ کا مواد یہاں درج کریں۔ آپ تصاویر کو بھی ڈریگ اینڈ ڈراپ کر سکتے ہیں۔",
+ "composer.post-queue-alert": "ہیلو👋!پاس ورڈ کامیابی سے ری سیٹ ہو گیا ہے۔ براہ کرم دوبارہ لاگ ان کریں۔",
+ "wrong-reset-code.title": "غلط ری سیٹ کوڈ",
+ "wrong-reset-code.message": "موصول ہونے والا ری سیٹ کوڈ غلط تھا۔ براہ کرم دوبارہ کوشش کریں یا نیا ری سیٹ کوڈ کی درخواست کریں۔",
+ "new-password": "نیا پاس ورڈ",
+ "repeat-password": "پاس ورڈ کی تصدیق کریں",
+ "changing-password": "پاس ورڈ تبدیل ہو رہا ہے…",
+ "enter-email": "براہ کرم اپنا ای میل ایڈریس درج کریں اور ہم آپ کو آپ کے اکاؤنٹ تک رسائی کے طریقہ کار کے ساتھ ایک ای میل بھیجیں گے۔",
+ "enter-email-address": "ای میل ایڈریس درج کریں",
+ "password-reset-sent": "اگر دیا گیا ایڈریس کسی موجودہ صارف اکاؤنٹ سے مطابقت رکھتا ہے تو پاس ورڈ ری سیٹ کرنے کے لیے ایک ای میل بھیج دیا گیا ہے۔ نوٹ کریں کہ فی منٹ صرف ایک ای میل بھیجا جا سکتا ہے۔",
+ "invalid-email": "غلط ای میل / ای میل موجود نہیں ہے!",
+ "password-too-short": "پاس ورڈ بہت مختصر ہے۔ براہ کرم ایک مختلف پاس ورڈ منتخب کریں۔",
+ "passwords-do-not-match": "آپ کے درج کردہ دونوں پاس ورڈز مختلف ہیں۔",
+ "password-expired": "آپ کا پاس ورڈ ختم ہو گیا ہے۔ براہ کرم ایک نیا پاس ورڈ منتخب کریں۔"
+}
\ No newline at end of file
diff --git a/public/language/ur/rewards.json b/public/language/ur/rewards.json
new file mode 100644
index 0000000000..aed43ef915
--- /dev/null
+++ b/public/language/ur/rewards.json
@@ -0,0 +1,10 @@
+{
+ "awarded-x-reputation": "آپ نے %1 ساکھ کے پوائنٹس حاصل کیے",
+ "awarded-group-membership": "آپ کو گروپ %1 میں شامل کیا گیا ہے",
+
+ "essentials/user.reputation-conditional-value": "(ساکھ %1 %2)",
+ "essentials/user.postcount-conditional-value": "(پوسٹس کی تعداد %1 %2)",
+ "essentials/user.lastonline-conditional-value": "(آخری بار آن لائن %1 %2)",
+ "essentials/user.joindate-conditional-value": "(شامل ہونے کی تاریخ %1 %2)",
+ "essentials/user.daysregistered-conditional-value": "(رجسٹریشن کے دنوں کی تعداد %1 %2)"
+}
\ No newline at end of file
diff --git a/public/language/ur/search.json b/public/language/ur/search.json
new file mode 100644
index 0000000000..85f3afc883
--- /dev/null
+++ b/public/language/ur/search.json
@@ -0,0 +1,110 @@
+{
+ "type-to-search": "یہاں تلاش کے لئے لکھیں",
+ "results-matching": "%1 نتیجہ (نتائج)، „%2“ سے مماثل، (%3 سیکنڈ)",
+ "no-matches": "کوئی مماثلت نہیں",
+ "advanced-search": "اعلیٰ تلاش",
+ "in": "میں",
+ "in-titles": "عنوانات میں",
+ "in-titles-posts": "عنوانات اور پوسٹس میں",
+ "in-posts": "پوسٹس میں",
+ "in-bookmarks": "بک مارکس میں",
+ "in-categories": "زمرہ جات میں",
+ "in-users": "صارفین میں",
+ "in-tags": "ٹیگز میں",
+ "categories": "زمرہ جات",
+ "all-categories": "تمام زمرہ جات",
+ "categories-x": "زمرہ جات: %1",
+ "categories-watched-categories": "زمرہ جات: دیکھے گئے زمرہ جات",
+ "type-a-category": "زمرہ درج کریں",
+ "tags": "ٹیگز",
+ "tags-x": "ٹیگز: %1",
+ "type-a-tag": "ٹیگ درج کریں",
+ "match-words": "الفاظ کی مماثلت",
+ "match-all-words": "تمام الفاظ کی مماثلت",
+ "match-any-word": "کسی ایک لفظ کی مماثلت",
+ "all": "تمام",
+ "any": "کوئی بھی",
+ "posted-by": "کی طرف سے پوسٹ کیا گیا",
+ "posted-by-usernames": "کی طرف سے پوسٹ کیا گیا: %1",
+ "type-a-username": "صارف کا نام درج کریں",
+ "search-child-categories": "ذیلی زمرہ جات کی تلاش",
+ "has-tags": "ٹیگز ہیں",
+ "reply-count": "جوابات کی تعداد",
+ "replies": "جوابات",
+ "replies-atleast-count": "جوابات: کم از کم %1",
+ "replies-atmost-count": "جوابات: زیادہ سے زیادہ %1",
+ "at-least": "کم از کم",
+ "at-most": "زیادہ سے زیادہ",
+ "relevance": "مطابقت",
+ "time": "وقت",
+ "post-time": "پوسٹ کا وقت",
+ "votes": "ووٹ",
+ "newer-than": "اس سے نئے",
+ "older-than": "اس سے پرانے",
+ "any-date": "کوئی بھی تاریخ",
+ "yesterday": "کل",
+ "one-week": "ایک ہفتہ",
+ "two-weeks": "دو ہفتے",
+ "one-month": "ایک ماہ",
+ "three-months": "تین ماہ",
+ "six-months": "چھ ماہ",
+ "one-year": "ایک سال",
+ "time-newer-than-86400": "وقت: کل سے اب تک",
+ "time-older-than-86400": "وقت: کل سے پہلے",
+ "time-newer-than-604800": "وقت: ایک ہفتے سے نئے",
+ "time-older-than-604800": "وقت: ایک ہفتے سے پرانے",
+ "time-newer-than-1209600": "وقت: دو ہفتوں سے نئے",
+ "time-older-than-1209600": "وقت: دو ہفتوں سے پرانے",
+ "time-newer-than-2592000": "وقت: ایک ماہ سے نئے",
+ "time-older-than-2592000": "وقت: ایک ماہ سے پرانے",
+ "time-newer-than-7776000": "وقت: تین ماہ سے نئے",
+ "time-older-than-7776000": "وقت: تین ماہ سے پرانے",
+ "time-newer-than-15552000": "وقت: چھ ماہ سے نئے",
+ "time-older-than-15552000": "وقت: چھ ماہ سے پرانے",
+ "time-newer-than-31104000": "وقت: ایک سال سے نئے",
+ "time-older-than-31104000": "وقت: ایک سال سے پرانے",
+ "sort-by": "ترتیب دیں بمطابق",
+ "sort": "ترتیب",
+ "last-reply-time": "آخری جواب کا وقت",
+ "topic-title": "موضوع کا عنوان",
+ "topic-votes": "موضوع کے لئے ووٹ",
+ "number-of-replies": "جوابات کی تعداد",
+ "number-of-views": "دیکھنے کی تعداد",
+ "topic-start-date": "موضوع کی شروعاتی تاریخ",
+ "username": "صارف کا نام",
+ "category": "زمرہ",
+ "descending": "نازل ترتیب میں",
+ "ascending": "صعودی ترتیب میں",
+ "sort-by-relevance-desc": "ترتیب دیں بمطابق: مطابقت، نازل ترتیب میں",
+ "sort-by-relevance-asc": "ترتیب دیں بمطابق: مطابقت، صعودی ترتیب میں",
+ "sort-by-timestamp-desc": "ترتیب دیں بمطابق: پوسٹ کا وقت، نازل ترتیب میں",
+ "sort-by-timestamp-asc": "ترتیب دیں بمطابق: پوسٹ کا وقت، صعودی ترتیب میں",
+ "sort-by-votes-desc": "ترتیب دیں بمطابق: ووٹوں کی تعداد، نازل ترتیب میں",
+ "sort-by-votes-asc": "ترتیب دیں بمطابق: ووٹوں کی تعداد، صعودی ترتیب میں",
+ "sort-by-topic.lastposttime-desc": "ترتیب دیں بمطابق: آخری جواب کا وقت، نازل ترتیب میں",
+ "sort-by-topic.lastposttime-asc": "ترتیب دیں بمطابق: آخری جواب کا وقت، صعودی ترتیب میں",
+ "sort-by-topic.title-desc": "ترتیب دیں بمطابق: موضوع کا عنوان، نازل ترتیب میں",
+ "sort-by-topic.title-asc": "ترتیب دیں بمطابق: موضوع کا عنوان، صعودی ترتیب میں",
+ "sort-by-topic.postcount-desc": "ترتیب دیں بمطابق: جوابات کی تعداد، نازل ترتیب میں",
+ "sort-by-topic.postcount-asc": "ترتیب دیں بمطابق: جوابات کی تعداد، صعودی ترتیب میں",
+ "sort-by-topic.viewcount-desc": "ترتیب دیں بمطابق: دیکھنے کی تعداد، نازل ترتیب میں",
+ "sort-by-topic.viewcount-asc": "ترتیب دیں بمطابق: دیکھنے کی تعداد، صعودی ترتیب میں",
+ "sort-by-topic.votes-desc": "ترتیب دیں بمطابق: موضوع کے ووٹوں کی تعداد، نازل ترتیب میں",
+ "sort-by-topic.votes-asc": "ترتیب دیں بمطابق: موضوع کے ووٹوں کی تعداد، صعودی ترتیب میں",
+ "sort-by-topic.timestamp-desc": "ترتیب دیں بمطابق: موضوع کی شروعاتی تاریخ، نازل ترتیب میں",
+ "sort-by-topic.timestamp-asc": "ترتیب دیں بمطابق: موضوع کی شروعاتی تاریخ، صعودی ترتیب میں",
+ "sort-by-user.username-desc": "ترتیب دیں بمطابق: صارف کا نام، نازل ترتیب میں",
+ "sort-by-user.username-asc": "ترتیب دیں بمطابق: صارف کا نام، صعودی ترتیب میں",
+ "sort-by-category.name-desc": "ترتیب دیں بمطابق: زمرہ، نازل ترتیب میں",
+ "sort-by-category.name-asc": "ترتیب دیں بمطابق: زمرہ، صعودی ترتیب میں",
+ "save": "محفوظ کریں",
+ "save-preferences": "ترجیحات محفوظ کریں",
+ "clear-preferences": "ترجیحات صاف کریں",
+ "search-preferences-saved": "تلاش کی ترجیحات محفوظ ہو گئیں",
+ "search-preferences-cleared": "تلاش کی ترجیحات صاف ہو گئیں",
+ "show-results-as": "نتائج کو اس طرح دکھائیں",
+ "show-results-as-topics": "نتائج کو موضوعات کے طور پر دکھائیں",
+ "show-results-as-posts": "نتائج کو پوسٹس کے طور پر دکھائیں",
+ "see-more-results": "مزید نتائج دکھائیں (%1)",
+ "search-in-category": "„%1“ میں تلاش کریں"
+}
\ No newline at end of file
diff --git a/public/language/ur/social.json b/public/language/ur/social.json
new file mode 100644
index 0000000000..e457268ea3
--- /dev/null
+++ b/public/language/ur/social.json
@@ -0,0 +1,14 @@
+{
+ "sign-in-with-twitter": "ٹوئٹر کے ساتھ لاگ ان کریں",
+ "sign-up-with-twitter": "ٹوئٹر کے ساتھ رجسٹر کریں",
+ "sign-in-with-github": "گٹ ہب کے ساتھ لاگ ان کریں",
+ "sign-up-with-github": "گٹ ہب کے ساتھ رجسٹر کریں",
+ "sign-in-with-google": "گوگل کے ساتھ لاگ ان کریں",
+ "sign-up-with-google": "گوگل کے ساتھ رجسٹر کریں",
+ "log-in-with-facebook": "فیس بک کے ساتھ لاگ ان کریں",
+ "continue-with-facebook": "فیس بک کے ساتھ جاری رکھیں",
+ "sign-in-with-linkedin": "لنکڈ ان کے ساتھ لاگ ان کریں",
+ "sign-up-with-linkedin": "لنکڈ ان کے ساتھ رجسٹر کریں",
+ "sign-in-with-wordpress": "Sign in with WordPress",
+ "sign-up-with-wordpress": "Sign up with WordPress"
+}
\ No newline at end of file
diff --git a/public/language/ur/success.json b/public/language/ur/success.json
new file mode 100644
index 0000000000..4c6e1a13ac
--- /dev/null
+++ b/public/language/ur/success.json
@@ -0,0 +1,7 @@
+{
+ "success": "ہو گیا",
+ "topic-post": "آپ نے کامیابی سے پوسٹ کیا۔",
+ "post-queued": "آپ کی پوسٹ منظوری کے لیے قطار میں رکھی گئی ہے۔ جب اسے منظور یا مسترد کیا جائے گا تو آپ کو اطلاع موصول ہوگی۔",
+ "authentication-successful": "کامیاب تصدیق",
+ "settings-saved": "ترتیبات محفوظ کر دی گئیں!"
+}
\ No newline at end of file
diff --git a/public/language/ur/tags.json b/public/language/ur/tags.json
new file mode 100644
index 0000000000..e0b0e3ffe7
--- /dev/null
+++ b/public/language/ur/tags.json
@@ -0,0 +1,17 @@
+{
+ "all-tags": "تمام ٹیگز",
+ "no-tag-topics": "اس ٹیگ کے ساتھ کوئی موضوعات نہیں ہیں۔",
+ "no-tags-found": "کوئی ٹیگز نہیں ملے",
+ "tags": "ٹیگز",
+ "enter-tags-here": "%1 – %2 حروف کے ساتھ ٹیگز درج کریں۔",
+ "enter-tags-here-short": "ٹیگز درج کریں...",
+ "no-tags": "ابھی تک کوئی ٹیگز نہیں ہیں۔",
+ "select-tags": "ٹیگز منتخب کریں",
+ "tag-whitelist": "اجازت شدہ ٹیگز کی فہرست",
+ "watching": "دیکھ رہے ہیں",
+ "not-watching": "نہیں دیکھ رہے",
+ "watching.description": "میں نئے موضوعات کے لیے اطلاعات حاصل کرنا چاہتا ہوں۔",
+ "not-watching.description": "میں نئے موضوعات کے لیے اطلاعات حاصل نہیں کرنا چاہتا۔",
+ "following-tag.message": "اب آپ کو اطلاعات موصول ہوں گی جب کوئی اس ٹیگ کے ساتھ موضوع پوسٹ کرے گا۔",
+ "not-following-tag.message": "آپ کو اطلاعات موصول نہیں ہوں گی جب کوئی اس ٹیگ کے ساتھ موضوع پوسٹ کرے گا۔"
+}
\ No newline at end of file
diff --git a/public/language/ur/themes/harmony.json b/public/language/ur/themes/harmony.json
new file mode 100644
index 0000000000..a76466fe61
--- /dev/null
+++ b/public/language/ur/themes/harmony.json
@@ -0,0 +1,23 @@
+{
+ "theme-name": "ہارمنی تھیم",
+ "skins": "سکنز",
+ "collapse": "سمیٹیں",
+ "expand": "پھیلائیں",
+ "sidebar-toggle": "سائڈبار ٹوگل",
+ "login-register-to-search": "تلاش کرنے کے لیے لاگ ان کریں یا رجسٹر کریں۔",
+ "settings.title": "تھیم کی ترتیبات",
+ "settings.enableQuickReply": "فوری جوابات فعال کریں",
+ "settings.enableBreadcrumbs": "زمرہ جات اور موضوعات کے صفحات پر بریڈ کرمبس دکھائیں",
+ "settings.enableBreadcrumbs.why": "بریڈ کرمبس زیادہ تر صفحات پر آسان نیویگیشن کے لیے دکھائی دیتے ہیں۔ زمرہ جات اور موضوعات کے صفحات کا بنیادی ڈیزائن زیادہ عمومی صفحات پر واپس جانے کے دیگر طریقے فراہم کرتا ہے، لیکن اگر آپ چاہیں تو بصری بھرتی کو کم کرنے کے لیے بریڈ کرمبس کا ڈسپلے بند کر سکتے ہیں۔",
+ "settings.centerHeaderElements": "ہیڈر عناصر کو وسط میں رکھیں",
+ "settings.mobileTopicTeasers": "موبائل ڈیوائسز پر موضوعات کے ٹیززر دکھائیں",
+ "settings.stickyToolbar": "سٹیٹک ٹولبار",
+ "settings.stickyToolbar.help": "موضوعات اور زمرہ جات کے صفحات پر ٹولبار ہمیشہ صفحہ کے اوپری حصے میں رہے گی",
+ "settings.topicSidebarTools": "موضوعات کے لیے سائڈبار ٹولز",
+ "settings.topicSidebarTools.help": "یہ ترتیب ویب سائٹ کے ڈیسک ٹاپ ورژن کے استعمال کے دوران موضوعات کے ٹولز کو سائڈبار میں منتقل کر دے گی",
+ "settings.autohideBottombar": "موبائل ڈیوائسز کے لیے نیویگیشن بار کو خودکار طور پر چھپائیں",
+ "settings.autohideBottombar.help": "جب صفحہ نیچے سکرول کیا جائے گا تو موبائل نیویگیشن بار چھپ جائے گی",
+ "settings.topMobilebar": "موبائل نیویگیشن بار کو اوپر منتقل کریں",
+ "settings.openSidebars": "سائڈبارز کھولیں",
+ "settings.chatModals": "گفتگو کے ونڈوز فعال کریں"
+}
\ No newline at end of file
diff --git a/public/language/ur/themes/persona.json b/public/language/ur/themes/persona.json
new file mode 100644
index 0000000000..4233d7dfbc
--- /dev/null
+++ b/public/language/ur/themes/persona.json
@@ -0,0 +1,10 @@
+{
+ "settings.title": "تھیم کی ترتیبات",
+ "settings.intro": "یہاں آپ تھیم کی ترتیبات کو تبدیل کر سکتے ہیں۔ یہ ترتیبات ہر ڈیوائس پر الگ سے محفوظ کی جاتی ہیں، لہذا آپ اپنے مختلف ڈیوائسز (فون، ٹیبلٹ، ڈیسک ٹاپ وغیرہ) پر مختلف ترتیبات رکھ سکتے ہیں۔",
+ "settings.mobile-menu-side": "موبائل ڈیوائس پر مینو کس طرف سے کھلے گا اس کا انتخاب کریں",
+ "settings.autoHidingNavbar": "سکرولنگ کے دوران نیویگیشن بار کو خودکار طور پر چھپائیں",
+ "settings.autoHidingNavbar-xs": "بہت چھوٹی اسکرینز (مثلاً پورٹریٹ موڈ میں فون)",
+ "settings.autoHidingNavbar-sm": "چھوٹی اسکرینز (مثلاً فونز، کچھ ٹیبلٹس)",
+ "settings.autoHidingNavbar-md": "درمیانی سائز کی اسکرینز (مثلاً لینڈسکیپ موڈ میں ٹیبلٹس)",
+ "settings.autoHidingNavbar-lg": "بڑی اسکرینز (مثلاً لیپ ٹاپس اور ڈیسک ٹاپس)"
+}
\ No newline at end of file
diff --git a/public/language/ur/top.json b/public/language/ur/top.json
new file mode 100644
index 0000000000..d98a236c05
--- /dev/null
+++ b/public/language/ur/top.json
@@ -0,0 +1,4 @@
+{
+ "title": "سب سے مقبول",
+ "no-top-topics": "کوئی سب سے مقبول موضوعات نہیں ہیں"
+}
\ No newline at end of file
diff --git a/public/language/ur/topic.json b/public/language/ur/topic.json
new file mode 100644
index 0000000000..bcc6b33aae
--- /dev/null
+++ b/public/language/ur/topic.json
@@ -0,0 +1,228 @@
+{
+ "topic": "موضوع",
+ "title": "عنوان",
+ "no-topics-found": "کوئی موضوعات نہیں ملے!",
+ "no-posts-found": "کوئی پوسٹس نہیں ملیں!",
+ "post-is-deleted": "پوسٹ حذف کر دی گئی!",
+ "topic-is-deleted": "موضوع حذف کر دیا گیا!",
+ "profile": "پروفائل",
+ "posted-by": "%1 کی طرف سے پوسٹ کیا گیا",
+ "posted-by-guest": "مہمان کی طرف سے پوسٹ کیا گیا",
+ "chat": "چیت",
+ "notify-me": "اس موضوع میں نئے جوابات کے لیے نوٹیفکیشنز حاصل کریں",
+ "quote": "اقتباس",
+ "reply": "جواب",
+ "replies-to-this-post": "%1 جوابات",
+ "one-reply-to-this-post": "1 جواب",
+ "last-reply-time": "آخری جواب",
+ "reply-options": "جواب کے اختیارات",
+ "reply-as-topic": "نئے موضوع میں جواب",
+ "guest-login-reply": "جواب دینے کے لیے لاگ ان کریں",
+ "login-to-view": "🔒 اسے دیکھنے کے لیے لاگ ان کریں",
+ "edit": "ترمیم کریں",
+ "delete": "حذف کریں",
+ "delete-event": "ایونٹ حذف کریں",
+ "delete-event-confirm": "کیا آپ واقعی اس ایونٹ کو حذف کرنا چاہتے ہیں؟",
+ "purge": "صاف کریں",
+ "restore": "بحال کریں",
+ "move": "منتقل کریں",
+ "change-owner": "مالک تبدیل کریں",
+ "manage-editors": "ایڈیٹرز کا انتظام کریں",
+ "fork": "تقسیم کریں",
+ "link": "لنک",
+ "share": "شیئر کریں",
+ "tools": "ٹولز",
+ "locked": "مقفل",
+ "pinned": "پن کیا گیا",
+ "pinned-with-expiry": "%1 تک پن کیا گیا",
+ "scheduled": "طے شدہ",
+ "deleted": "حذف شدہ",
+ "moved": "منتقل شدہ",
+ "moved-from": "%1 سے منتقل کیا گیا",
+ "copy-code": "کوڈ کاپی کریں",
+ "copy-ip": "IP ایڈریس کاپی کریں",
+ "ban-ip": "IP ایڈریس بلاک کریں",
+ "view-history": "ترمیمی تاریخ",
+ "wrote-ago": " لکھا",
+ "wrote-on": " پر لکھا",
+ "replied-to-user-ago": "%3 کو جواب دیا",
+ "replied-to-user-on": "%3 کو پر جواب دیا",
+ "user-locked-topic-ago": "%1 نے اس موضوع کو %2 پر مقفل کیا",
+ "user-locked-topic-on": "%1 نے اس موضوع کو %2 پر مقفل کیا",
+ "user-unlocked-topic-ago": "%1 نے اس موضوع کو %2 پر کھول دیا",
+ "user-unlocked-topic-on": "%1 نے اس موضوع کو %2 پر کھول دیا",
+ "user-pinned-topic-ago": "%1 نے اس موضوع کو %2 پر پن کیا",
+ "user-pinned-topic-on": "%1 نے اس موضوع کو %2 پر پن کیا",
+ "user-unpinned-topic-ago": "%1 نے اس موضوع کو %2 پر ان پن کیا",
+ "user-unpinned-topic-on": "%1 نے اس موضوع کو %2 پر ان پن کیا",
+ "user-deleted-topic-ago": "%1 نے اس موضوع کو %2 پر حذف کیا",
+ "user-deleted-topic-on": "%1 نے اس موضوع کو %2 پر حذف کیا",
+ "user-restored-topic-ago": "%1 نے اس موضوع کو %2 پر بحال کیا",
+ "user-restored-topic-on": "%1 نے اس موضوع کو %2 پر بحال کیا",
+ "user-moved-topic-from-ago": "%1 نے اس موضوع کو %2 سے %3 پر منتقل کیا",
+ "user-moved-topic-from-on": "%1 نے اس موضوع کو %2 سے %3 پر منتقل کیا",
+ "user-shared-topic-ago": "%1 نے اس موضوع کو %2 پر شیئر کیا",
+ "user-shared-topic-on": "%1 نے اس موضوع کو %2 پر شیئر کیا",
+ "user-queued-post-ago": "%1 نے اس پوسٹ کو منظوری کے لیے قطار میں %3 پر شامل کیا",
+ "user-queued-post-on": "%1 نے اس پوسٹ کو قطار میں منظوری کے لیے %3 پر شامل کیا",
+ "user-referenced-topic-ago": "%1 نے اس موضوع کی طرف حوالہ دیا %3 پر",
+ "user-referenced-topic-on": "%1 نے اس موضوع کی طرف حوالہ دیا %3 پر",
+ "user-forked-topic-ago": "%1 نے اس موضوع کو تقسیم کیا %3 پر",
+ "user-forked-topic-on": "%1 نے اس موضوع کو تقسیم کیا %3 پر",
+ "bookmark-instructions": "اس موضوع میں آخری پڑھی گئی پوسٹ پر واپس جانے کے لیے یہاں کلک کریں۔",
+ "flag-post": "اس پوسٹ کی رپورٹ کریں",
+ "flag-user": "اس صارف کی رپورٹ کریں",
+ "already-flagged": "پہلے سے رپورٹ کیا جا چکا ہے",
+ "view-flag-report": "رپورٹ دیکھیں",
+ "resolve-flag": "رپورٹ حل کریں",
+ "merged-message": "یہ موضوع %2 میں ضم کر دیا گیا",
+ "forked-message": "یہ موضوع %2 سے الگ کیا گیا",
+ "deleted-message": "موضوع حذف کر دیا گیا۔ صرف موضوعات کے انتظام کے حقوق رکھنے والے صارفین اسے دیکھ سکتے ہیں۔",
+ "following-topic.message": "اب آپ کو اس موضوع میں کسی کے تبصرہ پوسٹ کرنے پر نوٹیفکیشنز موصول ہوں گے۔",
+ "not-following-topic.message": "آپ اس موضوع کو غیر پڑھے ہوئے موضوعات کی فہرست میں دیکھیں گے، لیکن جب لوگ اس میں کچھ پوسٹ کریں گے تو آپ کو نوٹیفکیشنز موصول نہیں ہوں گے۔",
+ "ignoring-topic.message": "اب آپ اس موضوع کو غیر پڑھے ہوئے موضوعات کی فہرست میں نہیں دیکھیں گے۔ جب کوئی آپ کا تذکرہ کرے گا یا آپ کی پوسٹ کے لیے مثبت ووٹ دے گا تو آپ کو نوٹیفکیشن ملے گا۔",
+ "login-to-subscribe": "براہ کرم اس موضوع کے لیے سبسکرائب کرنے کے لیے رجسٹر کریں یا لاگ ان کریں۔",
+ "markAsUnreadForAll.success": "موضوع کو سب کے لیے غیر پڑھا ہوا نشان زد کیا گیا۔",
+ "mark-unread": "غیر پڑھا ہوا نشان زد کریں",
+ "mark-unread.success": "موضوع کو غیر پڑھا ہوا نشان زد کیا گیا۔",
+ "watch": "مشاہدہ کریں",
+ "unwatch": "مشاہدہ بند کریں",
+ "watch.title": "اس موضوع میں نئے جوابات کے لیے نوٹیفکیشنز حاصل کریں",
+ "unwatch.title": "اس موضوع کا مشاہدہ بند کریں",
+ "share-this-post": "اس پوسٹ کو شیئر کریں",
+ "watching": "مشاہدہ کر رہے ہیں",
+ "not-watching": "مشاہدہ نہیں کر رہے",
+ "ignoring": "نظر انداز کر رہے ہیں",
+ "watching.description": "میں نئے جوابات کے لیے نوٹیفکیشنز حاصل کرنا چاہتا ہوں۔
میں چاہتا ہوں کہ موضوع غیر پڑھے ہوئے کی فہرست میں دکھائی دے۔",
+ "not-watching.description": "میں نئے جوابات کے لیے نوٹیفکیشنز نہیں چاہتا۔
موضوع غیر پڑھے ہوئے کی فہرست میں دکھائی دے، صرف اس صورت میں جب زمرہ نظر انداز نہ کیا گیا ہو۔",
+ "ignoring.description": "میں نئے جوابات کے لیے نوٹیفکیشنز نہیں چاہتا۔
میں نہیں چاہتا کہ موضوع غیر پڑھے ہوئے کی فہرست میں دکھائی دے۔",
+ "thread-tools.title": "موضوع کے ٹولز",
+ "thread-tools.markAsUnreadForAll": "سب کے لیے غیر پڑھا ہوا نشان زد کریں",
+ "thread-tools.pin": "موضوع کو پن کریں",
+ "thread-tools.unpin": "موضوع کو ان پن کریں",
+ "thread-tools.lock": "موضوع کو مقفل کریں",
+ "thread-tools.unlock": "موضوع کو کھولیں",
+ "thread-tools.move": "موضوع منتقل کریں",
+ "thread-tools.move-posts": "پوسٹس منتقل کریں",
+ "thread-tools.move-all": "سب منتقل کریں",
+ "thread-tools.change-owner": "مالک تبدیل کریں",
+ "thread-tools.manage-editors": "ایڈیٹرز کا انتظام کریں",
+ "thread-tools.select-category": "زمرہ منتخب کریں",
+ "thread-tools.fork": "موضوع تقسیم کریں",
+ "thread-tools.tag": "موضوع پر ٹیگ لگائیں",
+ "thread-tools.delete": "موضوع حذف کریں",
+ "thread-tools.delete-posts": "پوسٹس حذف کریں",
+ "thread-tools.delete-confirm": "کیا آپ واقعی اس موضوع کو حذف کرنا چاہتے ہیں؟",
+ "thread-tools.restore": "موضوع بحال کریں",
+ "thread-tools.restore-confirm": "کیا آپ واقعی اس موضوع کو بحال کرنا چاہتے ہیں؟",
+ "thread-tools.purge": "موضوع صاف کریں",
+ "thread-tools.purge-confirm": "کیا آپ واقعی اس موضوع کو صاف کرنا چاہتے ہیں؟",
+ "thread-tools.merge-topics": "موضوعات ضم کریں",
+ "thread-tools.merge": "موضوع ضم کریں",
+ "topic-move-success": "موضوع جلد ہی „%1“ میں منتقل ہو جائے گا۔ منتقل کرنے کو منسوخ کرنے کے لیے یہاں کلک کریں۔",
+ "topic-move-multiple-success": "موضوعات جلد ہی „%1“ میں منتقل ہو جائیں گے۔ منتقل کرنے کو منسوخ کرنے کے لیے یہاں کلک کریں۔",
+ "topic-move-all-success": "تمام موضوعات جلد ہی „%1“ میں منتقل ہو جائیں گے۔ منتقل کرنے کو منسوخ کرنے کے لیے یہاں کلک کریں۔",
+ "topic-move-undone": "موضوع کی منتقلی منسوخ کر دی گئی",
+ "topic-move-posts-success": "پوسٹس جلد ہی منتقل ہو جائیں گی۔ منتقل کرنے کو منسوخ کرنے کے لیے یہاں کلک کریں۔",
+ "topic-move-posts-undone": "پوسٹس کی منتقلی منسوخ کر دی گئی",
+ "post-delete-confirm": "کیا آپ واقعی اس پوسٹ کو حذف کرنا چاہتے ہیں؟",
+ "post-restore-confirm": "کیا آپ واقعی اس پوسٹ کو بحال کرنا چاہتے ہیں؟",
+ "post-purge-confirm": "کیا آپ واقعی اس پوسٹ کو صاف کرنا چاہتے ہیں؟",
+ "pin-modal-expiry": "ختم ہونے کی تاریخ",
+ "pin-modal-help": "اگر آپ چاہیں تو یہاں پن کیے گئے موضوعات کے لیے ختم ہونے کی تاریخ بتا سکتے ہیں۔ آپ اس فیلڈ کو خالی بھی چھوڑ سکتے ہیں، اس صورت میں موضوع اس وقت تک پن رہے گا جب تک اسے دستی طور پر ان پن نہ کیا جائے۔",
+ "load-categories": "زمرہ جات لوڈ کریں",
+ "confirm-move": "منتقل کریں",
+ "confirm-fork": "تقسیم کریں",
+ "bookmark": "بک مارک",
+ "bookmarks": "بک مارکس",
+ "bookmarks.has-no-bookmarks": "آپ نے ابھی تک کسی پوسٹ کے لیے بک مارکس محفوظ نہیں کیے۔",
+ "copy-permalink": "مستقل لنک کاپی کریں",
+ "go-to-original": "اصل پوسٹ دیکھیں",
+ "loading-more-posts": "مزید پوسٹس لوڈ ہو رہی ہیں",
+ "move-topic": "موضوع منتقل کریں",
+ "move-topics": "موضوعات منتقل کریں",
+ "move-post": "پوسٹ منتقل کریں",
+ "post-moved": "پوسٹ منتقل کر دی گئی!",
+ "fork-topic": "موضوع تقسیم کریں",
+ "enter-new-topic-title": "نئے موضوع کا عنوان درج کریں",
+ "fork-topic-instruction": "ان پوسٹس پر کلک کریں جنہیں آپ تقسیم کرنا چاہتے ہیں، نئے موضوع کا نام درج کریں، اور „موضوع تقسیم کریں“ پر کلک کریں",
+ "fork-no-pids": "کوئی پوسٹس منتخب نہیں کی گئیں!",
+ "no-posts-selected": "کوئی پوسٹس منتخب نہیں کی گئیں!",
+ "x-posts-selected": "منتخب پوسٹس: %1",
+ "x-posts-will-be-moved-to-y": "%1 پوسٹس „%2“ میں منتقل ہو جائیں گی",
+ "fork-pid-count": "منتخب پوسٹس: %1",
+ "fork-success": "موضوع کامیابی سے تقسیم کر دیا گیا! تقسیم شدہ موضوع پر جانے کے لیے یہاں کلک کریں۔",
+ "delete-posts-instruction": "ان پوسٹس پر کلک کریں جنہیں آپ حذف/صاف کرنا چاہتے ہیں",
+ "merge-topics-instruction": "ان موضوعات پر کلک کریں جنہیں آپ ضم کرنا چاہتے ہیں، یا انہیں تلاش کریں",
+ "merge-topic-list-title": "ضم کیے جانے والے موضوعات کی فہرست",
+ "merge-options": "ضم کرنے کے اختیارات",
+ "merge-select-main-topic": "مرکزی موضوع منتخب کریں",
+ "merge-new-title-for-topic": "موضوع کے لیے نیا عنوان",
+ "topic-id": "موضوع کی شناخت",
+ "move-posts-instruction": "ان پوسٹس پر کلک کریں جنہیں آپ منتقل کرنا چاہتے ہیں، پھر موضوع کی شناخت درج کریں یا ہدف موضوع پر جائیں",
+ "move-topic-instruction": "ہدف زمرہ منتخب کریں اور „منتقل کریں“ پر کلک کریں",
+ "change-owner-instruction": "ان پوسٹس پر کلک کریں جنہیں آپ دوسرے صارف کو منتقل کرنا چاہتے ہیں",
+ "manage-editors-instruction": "نیچے ان صارفین کو نامزد کریں جو اس پوسٹ کو ترمیم کر سکتے ہیں۔",
+ "composer.title-placeholder": "یہاں اپنے موضوع کا عنوان درج کریں...",
+ "composer.handle-placeholder": "یہاں نام درج کریں",
+ "composer.hide": "چھپائیں",
+ "composer.discard": "مسترد کریں",
+ "composer.submit": "شائع کریں",
+ "composer.additional-options": "اضافی اختیارات",
+ "composer.post-later": "بعد میں پوسٹ کریں",
+ "composer.schedule": "طے کریں",
+ "composer.replying-to": "%1 کو جواب",
+ "composer.new-topic": "نیا موضوع",
+ "composer.editing-in": "%1 میں پوسٹ کی ترمیم",
+ "composer.uploading": "اپ لوڈ ہو رہا ہے...",
+ "composer.thumb-url-label": "موضوع کے لیے آئیکن کا ایڈریس پیسٹ کریں",
+ "composer.thumb-title": "اس موضوع میں آئیکن شامل کریں",
+ "composer.thumb-url-placeholder": "http://example.com/thumb.png",
+ "composer.thumb-file-label": "یا فائل اپ لوڈ کریں",
+ "composer.thumb-remove": "فیلڈز صاف کریں",
+ "composer.drag-and-drop-images": "یہاں تصاویر گھسیٹیں",
+ "more-users-and-guests": "مزید %1 صارفین اور %2 مہمان",
+ "more-users": "مزید %1 صارفین",
+ "more-guests": "مزید %1 مہمان",
+ "users-and-others": "%1 اور %2 دیگر",
+ "sort-by": "ترتیب دیں بمطابق",
+ "oldest-to-newest": "پہلے سب سے پرانے",
+ "newest-to-oldest": "پہلے سب سے نئے",
+ "recently-replied": "پہلے تازہ ترین جوابات والے",
+ "recently-created": "پہلے تازہ ترین بنائے گئے",
+ "most-votes": "پہلے سب سے زیادہ ووٹ والے",
+ "most-posts": "پہلے سب سے زیادہ پوسٹس والے",
+ "most-views": "پہلے سب سے زیادہ نظاروں والے",
+ "stale.title": "اس کے بجائے نیا موضوع بنائیں؟",
+ "stale.warning": "جس موضوع میں آپ جواب دے رہے ہیں وہ کافی پرانا ہے۔ کیا آپ اس کے بجائے ایک نیا موضوع بنانا چاہیں گے اور اپنے جواب میں اس کا حوالہ دیں گے؟",
+ "stale.create": "نیا موضوع بنائیں",
+ "stale.reply-anyway": "پھر بھی اس موضوع میں جواب دیں",
+ "link-back": "جواب: [%1](%2)",
+ "diffs.title": "ترمیمی تاریخ",
+ "diffs.description": "اس پوسٹ کی %1 ورژنز ہیں۔ نیچے کسی بھی ورژن پر کلک کریں تاکہ اس وقت کا مواد دیکھیں۔",
+ "diffs.no-revisions-description": "اس پوسٹ کی %1 ورژنز ہیں۔",
+ "diffs.current-revision": "موجودہ ورژن",
+ "diffs.original-revision": "اصل ورژن",
+ "diffs.restore": "اس ورژن کو بحال کریں",
+ "diffs.restore-description": "اس ورژن کی بحالی کے بعد اس پوسٹ کی ترمیمی تاریخ میں ایک نیا ورژن شامل ہو جائے گا۔",
+ "diffs.post-restored": "پوسٹ کو کامیابی سے پچھلے ورژن میں بحال کر دیا گیا",
+ "diffs.delete": "اس ورژن کو حذف کریں",
+ "diffs.deleted": "ورژن حذف کر دیا گیا",
+ "timeago-later": "%1 بعد میں",
+ "timeago-earlier": "%1 پہلے",
+ "first-post": "پہلی پوسٹ",
+ "last-post": "آخری پوسٹ",
+ "go-to-my-next-post": "میری اگلی پوسٹ پر جائیں",
+ "no-more-next-post": "اس موضوع میں آپ کی مزید پوسٹس نہیں ہیں",
+ "open-composer": "ایڈیٹر کھولیں",
+ "post-quick-reply": "فوری جواب",
+ "navigator.index": "پوسٹ %1 از %2",
+ "navigator.unread": "%1 غیر پڑھے ہوئے",
+ "upvote-post": "پوسٹ کے لیے مثبت ووٹ",
+ "downvote-post": "پوسٹ کے لیے منفی ووٹ",
+ "post-tools": "پوسٹس کے ٹولز",
+ "unread-posts-link": "غیر پڑھے ہوئے پوسٹس کا لنک",
+ "thumb-image": "موضوع کی آئیکن",
+ "announcers": "شیئرز",
+ "announcers-x": "شیئرز (%1)"
+}
\ No newline at end of file
diff --git a/public/language/ur/unread.json b/public/language/ur/unread.json
new file mode 100644
index 0000000000..46001ee599
--- /dev/null
+++ b/public/language/ur/unread.json
@@ -0,0 +1,16 @@
+{
+ "title": "غیر پڑھے ہوئے",
+ "no-unread-topics": "کوئی غیر پڑھے ہوئے موضوعات نہیں ہیں۔",
+ "load-more": "مزید لوڈ کریں",
+ "mark-as-read": "پڑھا ہوا نشان زد کریں",
+ "mark-as-unread": "غیر پڑھا ہوا نشان زد کریں",
+ "selected": "منتخب کردہ",
+ "all": "تمام",
+ "all-categories": "تمام زمرہ جات",
+ "topics-marked-as-read.success": "موضوعات کو پڑھا ہوا نشان زد کر دیا گیا!",
+ "all-topics": "تمام موضوعات",
+ "new-topics": "نئے موضوعات",
+ "watched-topics": "دیکھے ہوئے موضوعات",
+ "unreplied-topics": "بغیر جواب والے موضوعات",
+ "multiple-categories-selected": "کئی زمرہ جات منتخب کیے گئے ہیں"
+}
\ No newline at end of file
diff --git a/public/language/ur/uploads.json b/public/language/ur/uploads.json
new file mode 100644
index 0000000000..bccdcb10f9
--- /dev/null
+++ b/public/language/ur/uploads.json
@@ -0,0 +1,9 @@
+{
+ "uploading-file": "فائل اپ لوڈ ہو رہی ہے…",
+ "select-file-to-upload": "اپ لوڈ کرنے کے لیے فائل منتخب کریں!",
+ "upload-success": "فائل کامیابی سے اپ لوڈ ہو گئی!",
+ "maximum-file-size": "زیادہ سے زیادہ %1 KB",
+ "no-uploads-found": "کوئی اپ لوڈز نہیں ملے",
+ "public-uploads-info": "اپ لوڈز عوامی ہیں – تمام زائرین انہیں دیکھ سکتے ہیں۔",
+ "private-uploads-info": "اپ لوڈز نجی ہیں – صرف لاگ ان صارفین انہیں دیکھ سکتے ہیں"
+}
\ No newline at end of file
diff --git a/public/language/ur/user.json b/public/language/ur/user.json
new file mode 100644
index 0000000000..a9dde7f93c
--- /dev/null
+++ b/public/language/ur/user.json
@@ -0,0 +1,234 @@
+{
+ "user-menu": "صارف مینو",
+ "banned": "بلاک کیا گیا",
+ "unbanned": "بلاک ہٹایا گیا",
+ "muted": "خاموش کیا گیا",
+ "unmuted": "خاموشی ہٹائی گئی",
+ "offline": "آف لائن",
+ "deleted": "حذف کیا گیا",
+ "username": "صارف کا نام",
+ "joindate": "شمولیت کی تاریخ",
+ "postcount": "پوسٹس کی تعداد",
+ "email": "ای میل",
+ "confirm-email": "ای میل کی تصدیق کریں",
+ "account-info": "اکاؤنٹ کی معلومات",
+ "admin-actions-label": "انتظامی اقدامات",
+ "ban-account": "اکاؤنٹ بلاک کریں",
+ "ban-account-confirm": "کیا آپ واقعی اس صارف کو بلاک کرنا چاہتے ہیں؟",
+ "unban-account": "اکاؤنٹ کا بلاک ہٹائیں",
+ "mute-account": "اکاؤنٹ کو خاموش کریں",
+ "unmute-account": "اکاؤنٹ کی خاموشی ہٹائیں",
+ "delete-account": "اکاؤنٹ حذف کریں",
+ "delete-account-as-admin": "اکاؤنٹ حذف کریں",
+ "delete-content": "اکاؤنٹ کے مواد کو حذف کریں",
+ "delete-all": "اکاؤنٹ اور مواد کو حذف کریں",
+ "delete-account-confirm": "کیا آپ واقعی اپنی پوسٹس کو گمنام کرنا اور اپنا اکاؤنٹ حذف کرنا چاہتے ہیں؟
یہ عمل ناقابل واپسی ہے اور آپ اپنے ڈیٹا کو دوبارہ بحال نہیں کر سکیں گے۔
اس اکاؤنٹ کو ختم کرنے کی تصدیق کے لیے اپنا پاس ورڈ درج کریں۔",
+ "delete-this-account-confirm": "کیا آپ واقعی اس اکاؤنٹ کو حذف کرنا چاہتے ہیں، لیکن اس کے مواد کو برقرار رکھنا چاہتے ہیں؟
یہ عمل ناقابل واپسی ہے۔ پوسٹس گمنام ہو جائیں گی اور آپ حذف شدہ اکاؤنٹ اور پوسٹس کے درمیان رابطہ دوبارہ بحال نہیں کر سکیں گے
",
+ "delete-account-content-confirm": "کیا آپ واقعی اس اکاؤنٹ کے مواد (پوسٹس/موضوعات/اپ لوڈز) کو حذف کرنا چاہتے ہیں؟
یہ عمل ناقابل واپسی ہے اور آپ کوئی بھی ڈیٹا بحال نہیں کر سکیں گے۔
",
+ "delete-all-confirm": "کیا آپ واقعی اس اکاؤنٹ اور اس کے تمام مواد (پوسٹس/موضوعات/اپ لوڈز) کو حذف کرنا چاہتے ہیں؟
یہ عمل ناقابل واپسی ہے اور آپ کوئی بھی ڈیٹا بحال نہیں کر سکیں گے۔
",
+ "account-deleted": "اکاؤنٹ حذف کر دیا گیا",
+ "account-content-deleted": "اکاؤنٹ کا مواد حذف کر دیا گیا",
+ "fullname": "مکمل نام",
+ "website": "ویب سائٹ",
+ "location": "مقام",
+ "age": "عمر",
+ "joined": "شامل ہوئے",
+ "lastonline": "آخری بار آن لائن",
+ "profile": "پروفائل",
+ "profile-views": "پروفائل کے نظارے",
+ "reputation": "ساکھ",
+ "bookmarks": "بک مارکس",
+ "watched-categories": "دیکھے گئے زمرہ جات",
+ "watched-tags": "دیکھے گئے ٹیگز",
+ "change-all": "سب کچھ تبدیل کریں",
+ "watched": "دیکھے گئے",
+ "ignored": "نظر انداز کیے گئے",
+ "read": "پڑھے گئے",
+ "default-category-watch-state": "زمرہ جات کے مشاہدے کے لیے طے شدہ حالت",
+ "followers": "پیروی کرنے والے",
+ "following": "پیروی کر رہے ہیں",
+ "shares": "شیئرز",
+ "blocks": "بلاکس",
+ "blocked-users": "بلاک کیے گئے صارفین",
+ "block-toggle": "بلاک کو ٹوگل کریں",
+ "block-user": "صارف کو بلاک کریں",
+ "unblock-user": "صارف کا بلاک ہٹائیں",
+ "aboutme": "میرے بارے میں",
+ "signature": "دستخط",
+ "birthday": "سالگرہ",
+ "chat": "چیت",
+ "chat-with": "%1 کے ساتھ بات چیت جاری رکھیں",
+ "new-chat-with": "%1 کے ساتھ نئی بات چیت شروع کریں",
+ "view-remote": "اصل دیکھیں",
+ "flag-profile": "پروفائل کی رپورٹ کریں",
+ "profile-flagged": "پہلے سے رپورٹ کیا جا چکا ہے",
+ "follow": "پیروی کریں",
+ "unfollow": "پیروی بند کریں",
+ "cancel-follow": "پیروی کی درخواست منسوخ کریں",
+ "more": "مزید",
+ "profile-update-success": "پروفائل کامیابی سے اپ ڈیٹ ہو گیا!",
+ "change-picture": "تصویر تبدیل کریں",
+ "change-username": "صارف کا نام تبدیل کریں",
+ "change-email": "ای میل تبدیل کریں",
+ "email-updated": "ای میل تبدیل ہو گئی",
+ "email-same-as-password": "براہ کرم جاری رکھنے کے لیے اپنا موجودہ پاس ورڈ درج کریں – آپ نے اپنی نئی ای میل دوبارہ درج کی",
+ "edit": "ترمیم کریں",
+ "edit-profile": "پروفائل ترمیم کریں",
+ "default-picture": "طے شدہ آئیکن",
+ "uploaded-picture": "اپ لوڈ کی گئی تصویر",
+ "upload-new-picture": "نئی تصویر اپ لوڈ کریں",
+ "upload-new-picture-from-url": "یو آر ایل سے نئی تصویر اپ لوڈ کریں",
+ "current-password": "موجودہ پاس ورڈ",
+ "new-password": "نیا پاس ورڈ",
+ "change-password": "پاس ورڈ تبدیل کریں",
+ "change-password-error": "غلط پاس ورڈ!",
+ "change-password-error-wrong-current": "آپ کا موجودہ پاس ورڈ غلط ہے!",
+ "change-password-error-same-password": "آپ کا نیا پاس ورڈ موجودہ پاس ورڈ کے ساتھ مماثل ہے۔ براہ کرم نیا پاس ورڈ استعمال کریں۔",
+ "change-password-error-match": "پاس ورڈز مختلف ہیں!",
+ "change-password-error-privileges": "آپ کو اس پاس ورڈ کو تبدیل کرنے کے اختیارات نہیں ہیں۔",
+ "change-password-success": "آپ کا پاس ورڈ اپ ڈیٹ ہو گیا!",
+ "confirm-password": "پاس ورڈ کی تصدیق کریں",
+ "password": "پاس ورڈ",
+ "username-taken-workaround": "آپ جو صارف کا نام چاہتے ہیں وہ لیا جا چکا ہے اور اس لیے ہم نے اسے تھوڑا سا تبدیل کیا۔ آپ کا نام %1 ہوگا",
+ "password-same-as-username": "پاس ورڈ آپ کے صارف کے نام جیسا ہے۔ براہ کرم کوئی اور پاس ورڈ منتخب کریں۔",
+ "password-same-as-email": "پاس ورڈ آپ کی ای میل جیسا ہے۔ براہ کرم کوئی اور پاس ورڈ منتخب کریں۔",
+ "weak-password": "سادہ پاس ورڈ۔",
+ "upload-picture": "تصویر اپ لوڈ کریں",
+ "upload-a-picture": "ایک تصویر اپ لوڈ کریں",
+ "remove-uploaded-picture": "اپ لوڈ کی گئی تصویر ہٹائیں",
+ "upload-cover-picture": "کवर تصویر اپ لوڈ کریں",
+ "remove-cover-picture-confirm": "کیا آپ واقعی کور تصویر ہٹانا چاہتے ہیں؟",
+ "crop-picture": "تصویر کاٹ کریں",
+ "upload-cropped-picture": "کاٹ کر اپ لوڈ کریں",
+ "avatar-background-colour": "تصویر کا پس منظر رنگ",
+ "settings": "ترتیبات",
+ "show-email": "میری ای میل دکھائیں",
+ "show-fullname": "میرا مکمل نام دکھائیں",
+ "restrict-chats": "صرف ان صارفین سے پیغامات کی اجازت دیں جن کی میں پیروی کرتا ہوں",
+ "disable-incoming-chats": "آنے والے پیغامات کو غیر فعال کریں ",
+ "chat-allow-list": "درج ذیل صارفین سے پیغامات کی اجازت دیں",
+ "chat-deny-list": "درج ذیل صارفین سے پیغامات منع کریں",
+ "chat-list-add-user": "صارف شامل کریں",
+ "digest-label": "خلاصوں کے لیے سبسکرائب کریں",
+ "digest-description": "اس فورم کے بارے میں ای میل کے ذریعے خبروں (نئے نوٹیفکیشنز اور موضوعات) کے لیے سبسکرائب کریں، منتخب کردہ شیڈول کے مطابق",
+ "digest-off": "بند",
+ "digest-daily": "روزانہ",
+ "digest-weekly": "ہفتہ وار",
+ "digest-biweekly": "ہر دو ہفتے بعد",
+ "digest-monthly": "ماہانہ",
+ "has-no-follower": "اس صارف کے کوئی پیروکار نہیں ہیں :(",
+ "follows-no-one": "یہ صارف کسی کی پیروی نہیں کرتا :(",
+ "has-no-posts": "اس صارف نے ابھی تک کچھ بھی پوسٹ نہیں کیا۔",
+ "has-no-best-posts": "اس صارف کو ابھی تک اپنی پوسٹس کے لیے مثبت ووٹ نہیں ملے۔",
+ "has-no-topics": "اس صارف نے ابھی تک کوئی موضوعات نہیں بنائے۔",
+ "has-no-watched-topics": "اس صارف نے ابھی تک کوئی موضوعات نہیں دیکھے۔",
+ "has-no-ignored-topics": "اس صارف نے ابھی تک کوئی موضوعات کو نظر انداز نہیں کیا۔",
+ "has-no-read-topics": "اس صارف نے ابھی تک کوئی موضوعات نہیں پڑھے۔",
+ "has-no-upvoted-posts": "اس صارف نے ابھی تک مثبت ووٹ نہیں کیا۔",
+ "has-no-downvoted-posts": "اس صارف نے ابھی تک منفی ووٹ نہیں کیا۔",
+ "has-no-controversial-posts": "اس صارف کی ابھی تک منفی ووٹوں والی کوئی پوسٹس نہیں ہیں۔",
+ "has-no-blocks": "آپ نے کسی کو بلاک نہیں کیا۔",
+ "has-no-shares": "اس صارف نے کوئی موضوع شیئر نہیں کیا۔",
+ "email-hidden": "ای میل چھپی ہوئی ہے",
+ "hidden": "چھپا ہوا",
+ "paginate-description": "موضوعات اور پوسٹس کو صفحات پر تقسیم کریں، لامتناہی سکرولنگ کے بجائے",
+ "topics-per-page": "فی صفحہ موضوعات",
+ "posts-per-page": "فی صفحہ پوسٹس",
+ "category-topic-sort": "زمرہ میں موضوعات کی ترتیب",
+ "topic-post-sort": "موضوع میں پوسٹس کی ترتیب",
+ "max-items-per-page": "زیادہ سے زیادہ %1",
+ "acp-language": "ایڈمن پیج کی زبان",
+ "notifications": "نوٹیفکیشنز",
+ "upvote-notif-freq": "مثبت ووٹوں کے نوٹیفکیشنز کی تعدد",
+ "upvote-notif-freq.all": "تمام مثبت ووٹ",
+ "upvote-notif-freq.first": "پوسٹ کے لیے پہلے ووٹ پر",
+ "upvote-notif-freq.everyTen": "ہر دس مثبت ووٹ پر",
+ "upvote-notif-freq.threshold": "1, 5, 10, 25, 50, 100, 150, 200… پر",
+ "upvote-notif-freq.logarithmic": "10, 100, 1000… پر",
+ "upvote-notif-freq.disabled": "غیر فعال",
+ "browsing": "صفحات کی ترتیبات",
+ "open-links-in-new-tab": "بیرونی لنکس کو نئی ونڈو میں کھولیں",
+ "enable-topic-searching": "موضوعات میں تلاش کو فعال کریں",
+ "topic-search-help": "اگر فعال ہو، موضوع کی تلاش براؤزر کے معیاری تلاش کے رویے کو بدل دے گی اور آپ کو پورے موضوع کی تلاش کی اجازت دے گی، نہ کہ صرف اسکرین پر نظر آنے والا مواد",
+ "update-url-with-post-index": "موضوعات دیکھتے وقت ایڈریس بار کو پوسٹ نمبر کے ساتھ اپ ڈیٹ کریں",
+ "scroll-to-my-post": "جواب پوسٹ کرنے کے بعد نئی پوسٹ دکھائیں",
+ "follow-topics-you-reply-to": "جن موضوعات میں آپ جواب دیتے ہیں ان کی پیروی کریں",
+ "follow-topics-you-create": "جن موضوعات کو آپ بناتے ہیں ان کی پیروی کریں",
+ "grouptitle": "گروپ کا عنوان",
+ "group-order-help": "ایک گروپ منتخب کریں اور عنوانات کو دوبارہ ترتیب دینے کے لیے تیر کا استعمال کریں",
+ "show-group-title": "گروپ کا عنوان دکھائیں",
+ "hide-group-title": "گروپ کا عنوان چھپائیں",
+ "order-group-up": "گروپ کو اوپر منتقل کریں",
+ "order-group-down": "گروپ کو نیچے منتقل کریں",
+ "no-group-title": "کوئی گروپ عنوان نہیں",
+ "select-skin": "جلد منتخب کریں",
+ "default": "طے شدہ (%1)",
+ "no-skin": "کوئی جلد نہیں",
+ "select-homepage": "ہوم پیج منتخب کریں",
+ "homepage": "ہوم پیج",
+ "homepage-description": "فورم کے لیے ہوم پیج کے طور پر استعمال کرنے کے لیے ایک صفحہ منتخب کریں، یا „کچھ نہیں“ طے شدہ استعمال کے لیے۔",
+ "custom-route": "اپنی مرضی کے ہوم پیج کا راستہ",
+ "custom-route-help": "یہاں راستے کا نام درج کریں، بغیر آگے کی ترچھی لکیر کے (مثال: „recent“ یا \"category/2/general-discussion\")",
+ "sso.title": "ایک بار لاگ ان خدمات",
+ "sso.associated": "کے ساتھ منسلک",
+ "sso.not-associated": "کے ساتھ منسلک کرنے کے لیے یہاں کلک کریں",
+ "sso.dissociate": "رابطہ منقطع کریں",
+ "sso.dissociate-confirm-title": "منقطع کرنے کی تصدیق",
+ "sso.dissociate-confirm": "کیا آپ واقعی اپنے اکاؤنٹ کو „%1“ سے منقطع کرنا چاہتے ہیں؟",
+ "info.latest-flags": "تازہ ترین رپورٹس",
+ "info.profile": "پروفائل",
+ "info.post": "پوسٹ",
+ "info.view-flag": "رپورٹ دیکھیں",
+ "info.reported-by": "رپورٹ کیا گیا:",
+ "info.no-flags": "کوئی رپورٹ شدہ پوسٹس نہیں ملیں",
+ "info.ban-history": "حالیہ پابندیوں کی تاریخ",
+ "info.no-ban-history": "اس صارف پر کبھی پابندی نہیں لگائی گئی",
+ "info.banned-until": "%1 تک پابندی",
+ "info.banned-expiry": "ختم ہونے کی تاریخ",
+ "info.ban-expired": "پابندی ختم ہو گئی",
+ "info.banned-permanently": "مستقل پابندی",
+ "info.banned-reason-label": "وجہ",
+ "info.banned-no-reason": "کوئی وجہ نہیں بتائی گئی۔",
+ "info.mute-history": "حالیہ خاموشیوں کی تاریخ",
+ "info.no-mute-history": "اس صارف کو کبھی خاموش نہیں کیا گیا",
+ "info.muted-until": "%1 تک خاموش",
+ "info.muted-expiry": "ختم ہونے کی تاریخ",
+ "info.muted-no-reason": "کوئی وجہ نہیں بتائی گئی۔",
+ "info.username-history": "صارف کے ناموں کی تاریخ",
+ "info.email-history": "ای میلوں کی تاریخ",
+ "info.moderation-note": "ماڈریٹر نوٹ",
+ "info.moderation-note.success": "ماڈریٹر نوٹ محفوظ ہو گیا",
+ "info.moderation-note.add": "نوٹ شامل کریں",
+ "sessions.description": "اس صفحے پر آپ اس فورم پر اپنی فعال سیشنز دیکھ سکتے ہیں اور اگر چاہیں تو انہیں منسوخ کر سکتے ہیں۔ آپ اپنے اکاؤنٹ سے لاگ آؤٹ کرکے موجودہ سیشن منسوخ کر سکتے ہیں۔",
+ "revoke-session": "سیشن منسوخ کریں",
+ "browser-version-on-platform": "%1 %2 پر %3",
+ "consent.title": "آپ کے حقوق اور رضامندی",
+ "consent.lead": "یہ عوامی فورم ذاتی معلومات جمع اور پروسیس کرتا ہے۔",
+ "consent.intro": "ہم اس معلومات کو صرف آپ کے فورم کے ساتھ تعامل کو ذاتی بنانے اور آپ کی پوسٹس کو آپ کے صارف اکاؤنٹ سے جوڑنے کے لیے استعمال کرتے ہیں۔ رجسٹریشن کے دوران آپ کو صارف کا نام اور ای میل درج کرنا ہوگا، لیکن اگر آپ چاہیں تو ویب سائٹ پر اپنا صارف پروفائل مکمل کرنے کے لیے اضافی معلومات فراہم کر سکتے ہیں۔
ہم اس معلومات کو اس وقت تک محفوظ رکھتے ہیں جب تک آپ کا صارف اکاؤنٹ موجود ہے۔ آپ کسی بھی وقت اپنا اکاؤنٹ حذف کرکے اس کی رضامندی واپس لے سکتے ہیں۔ آپ کسی بھی وقت „حقوق اور رضامندی“ صفحے کے ذریعے ویب سائٹ پر درج کردہ معلومات کی کاپی مانگ سکتے ہیں۔
اگر آپ کے کوئی سوالات یا خدشات ہیں، تو آپ فورم کے ایڈمن ٹیم سے رابطہ کر سکتے ہیں۔",
+ "consent.email-intro": "ہم کبھی کبھار آپ کے رجسٹرڈ ای میل پر ای میلز بھیج سکتے ہیں تاکہ آپ کو بتائیں کہ کیا ہو رہا ہے، یا آپ کو مطلع کریں کہ کوئی نئی چیز ہے جو آپ سے متعلق ہے۔ آپ صارف کی ترتیبات کے صفحے کے ذریعے خلاصوں کی تعدد کو اپنی مرضی کے مطابق بنا سکتے ہیں (اور انہیں بند بھی کر سکتے ہیں)، اور یہ بھی منتخب کر سکتے ہیں کہ آپ کو ای میل کے ذریعے کون سے نوٹیفکیشنز موصول ہوں۔",
+ "consent.digest-frequency": "جب تک آپ اسے اپنی صارف ترتیبات میں تبدیل نہ کریں، یہ کمیونٹی آپ کو ہر %1 پر ای میل کے ذریعے خلاصے بھیجے گی۔",
+ "consent.digest-off": "جب تک آپ اسے اپنی صارف ترتیبات میں تبدیل نہ کریں، یہ کمیونٹی آپ کو ای میل کے ذریعے خلاصے نہیں بھیجے گی۔",
+ "consent.received": "آپ نے اس ویب سائٹ کو آپ کی ذاتی معلومات جمع کرنے اور پروسیس کرنے کی رضامندی دی ہے۔ کوئی اضافی عمل کی ضرورت نہیں ہے۔",
+ "consent.not-received": "آپ نے اپنی معلومات جمع کرنے اور پروسیس کرنے کی رضامندی نہیں دی۔ ویب سائٹ کی انتظامیہ ڈیٹا تحفظ کے تقاضوں کو پورا کرنے کے لیے کسی بھی وقت آپ کا اکاؤنٹ حذف کر سکتی ہے۔",
+ "consent.give": "رضHامندی دیں",
+ "consent.right-of-access": "آپ کو رسائی کا حق ہے",
+ "consent.right-of-access-description": "آپ کو اس ویب سائٹ کے ذریعے جمع کردہ تمام ڈیٹا تک رسائی کا حق ہے، درخواست پر۔ آپ نیچے دیے گクリック करेंボタン پر کلک کرکے ڈیٹا کی کاپی حاصل کر سکتے ہیں۔",
+ "consent.right-to-rectification": "آپ کو اصلاح کا حق ہے",
+ "consent.right-to-rectification-description": "آپ کو ہمارے دیے گئے کسی بھی غلط ڈیٹا کو تبدیل یا درست کرنے کا حق ہے۔ آپ اپنا پروفائل ترمیم کرکے تبدیل کر سکتے ہیں، اور پوسٹس کا مواد کسی بھی وقت ترمیم کیا جا سکتا ہے۔ اگر آپ کی کوئی مختلف ضرورت ہے، تو براہ کرم ایڈمن ٹیم سے رابطہ کریں۔",
+ "consent.right-to-erasure": "آپ کو حذف کرنے کا حق ہے",
+ "consent.right-to-erasure-description": "آپ کسی بھی وقت اپنا اکاؤنٹ حذف کرکے ڈیٹا جمع کرنے اور/یا پروسیس کرنے کی رضامندی واپس لے سکتے ہیں۔ آپ کا پروفائل حذف کیا جا سکتا ہے، لیکن آپ کا پوسٹ کیا گیا مواد باقی رہے گا۔ اگر آپ اپنا اکاؤنٹ اور آپ کا پوسٹ کیا گیا مواد دونوں حذف کرنا چاہتے ہیں، تو براہ کرم ویب سائٹ کے ایڈمن ٹیم سے رابطہ کریں۔",
+ "consent.right-to-data-portability": "آپ کو ڈیٹا پورٹیبلٹی کا حق ہے",
+ "consent.right-to-data-portability-description": "آپ ہم سے آپ اور آپ کے اکاؤنٹ کے بارے میں جمع کردہ تمام ڈیٹا مشین قابلِ خواندہ فارمیٹ میں مانگ سکتے ہیں۔ آپ نیچے دیے گئے بٹن پر کلک کرکے ایسا کر سکتے ہیں۔",
+ "consent.export-profile": "پروفائل ایکسپورٹ کریں (.json)",
+ "consent.export-profile-success": "پروفائل ایکسپورٹ ہو رہا ہے… تیار ہونے پر آپ کو نوٹیفکیشن موصول ہوگا۔",
+ "consent.export-uploads": "اپ لوڈ کردہ مواد ایکسپورٹ کریں (.zip)",
+ "consent.export-uploads-success": "اپ لوڈ کردہ مواد ایکسپورٹ ہو رہا ہے… تیار ہونے پر آپ کو نوٹیفکیشن موصول ہوگا۔",
+ "consent.export-posts": "پوسٹس ایکسپورٹ کریں (.csv)",
+ "consent.export-posts-success": "پوسٹس ایکسپورٹ ہو رہی ہیں… تیار ہونے پر آپ کو نوٹیفکیشن موصول ہوگا۔",
+ "emailUpdate.intro": "نیچے اپنا ای میل درج کریں۔ یہ فورم شیڈول شدہ خلاصوں اور نوٹیفکیشنز کے لیے ای میل استعمال کرتا ہے، اور بھولے ہوئے پاس ورڈ کی صورت میں اکاؤنٹ کی بحالی کے لیے بھی۔",
+ "emailUpdate.optional": "یہ فیلڈ لازمی نہیں ہے۔ آپ کو ای میل ایڈریس فراہم کرنے کی ضرورت نہیں ہے، لیکن تصدیق شدہ ای میل کے بغیر، آپ اپنا اکاؤنٹ کسی مسئلے کی صورت میں بحال نہیں کر سکیں گے، نہ ہی آپ اپنے ای میل کے ساتھ لاگ ان کر سکیں گے۔",
+ "emailUpdate.required": "یہ فیلڈ لازمی ہے۔",
+ "emailUpdate.change-instructions": "ہم آپ کے دیے گئے ای میل پر ایک تصدیقی ای میل بھیجیں گے، جس میں ایک منفرد لنک ہوگا۔ اس لنک پر عمل کرنے پر آپ کے اس ای میل کے مالک ہونے کی تصدیق ہو جائے گی اور یہ آپ کے اکاؤنٹ سے منسلک ہو جائے گی۔ آپ اپنے اکاؤنٹ کے صفحے سے اس ای میل کو کسی بھی وقت تبدیل کر سکتے ہیں۔",
+ "emailUpdate.password-challenge": "اپنا پاس ورڈ درج کریں تاکہ یہ تصدیق ہو کہ اکاؤنٹ آپ کا ہے۔",
+ "emailUpdate.pending": "آپ کا ای میل ابھی تک تصدیق شدہ نہیں ہے، حالانکہ اس پر ایک تصدیقی ای میل بھیج دیا گیا ہے۔ اگر آپ اسے منسوخ کرنا چاہتے ہیں اور نیا درخواست دینا چاہتے ہیں، تو نیچے دیا گیا فارم پُر کریں۔"
+}
\ No newline at end of file
diff --git a/public/language/ur/users.json b/public/language/ur/users.json
new file mode 100644
index 0000000000..22809a70dd
--- /dev/null
+++ b/public/language/ur/users.json
@@ -0,0 +1,26 @@
+{
+ "all-users": "تمام صارفین",
+ "followed-users": "فالو کیے گئے صارفین",
+ "latest-users": "تازہ ترین صارفین",
+ "top-posters": "سب سے زیادہ پوسٹس والے",
+ "most-reputation": "سب سے زیادہ ساکھ والے",
+ "most-flags": "سب سے زیادہ رپورٹس والے",
+ "search": "تلاش",
+ "enter-username": "تلاش کرنے کے لیے صارف نام درج کریں",
+ "search-user-for-chat": "گفتگو شروع کرنے کے لیے صارف تلاش کریں",
+ "load-more": "مزید لوڈ کریں",
+ "users-found-search-took": "%1 صارفین ملے! تلاش میں %2 سیکنڈ لگے۔",
+ "filter-by": "فلٹر کریں",
+ "online-only": "صرف آن لائن والے",
+ "invite": "دعوت دیں",
+ "prompt-email": "ای میلز:",
+ "groups-to-join": "دعوت قبول کرنے کے بعد شامل ہونے والے گروپس:",
+ "invitation-email-sent": "%1 کو ایک تصدیقی ای میل بھیج دیا گیا ہے",
+ "user-list": "صارفین کی فہرست",
+ "recent-topics": "حالیہ موضوعات",
+ "popular-topics": "مقبول موضوعات",
+ "unread-topics": "غیر پڑھے ہوئے موضوعات",
+ "categories": "زمرہ جات",
+ "tags": "ٹیگز",
+ "no-users-found": "کوئی صارفین نہیں ملے!"
+}
\ No newline at end of file
diff --git a/public/language/ur/world.json b/public/language/ur/world.json
new file mode 100644
index 0000000000..98ab943724
--- /dev/null
+++ b/public/language/ur/world.json
@@ -0,0 +1,21 @@
+{
+ "name": "جهان",
+ "popular": "مقبول موضوعات",
+ "recent": "تمام موضوعات",
+ "help": "مدد",
+
+ "help.title": "یہ صفحہ کیا ہے؟",
+ "help.intro": "فیڈی ورس میں آپ کے اپنے گوشے میں خوش آمدید۔",
+ "help.fediverse": "„فیڈی ورس“ ایک ایسی نیٹ ورک ہے جس میں باہم مربوط ایپلیکیشنز اور ویب سائٹس ایک دوسرے سے بات چیت کرتی ہیں اور جن کے صارفین ایک دوسرے کو دیکھ سکتے ہیں۔ یہ فورم فیڈریٹڈ ہے اور اس سوشل نیٹ ورک (جسے „فیڈی ورس“ کہا جاتا ہے) کے ساتھ تعامل کر سکتا ہے۔ یہ صفحہ فیڈی ورس میں آپ کا اپنا گوشہ ہے۔ اس میں آپ صرف ان موضوعات کو دیکھیں گے جو ان صارفین نے بنائے یا شیئر کیے ہیں جنہیں آپ فالو کرتے ہیں۔",
+ "help.build": "شروع میں یہاں زیادہ موضوعات نہیں ہو سکتے۔ یہ معمول کی بات ہے۔ جب آپ دوسرے صارفین کو فالو کرنا شروع کریں گے تو آپ یہاں زیادہ مواد دیکھنا شروع کریں گے۔",
+ "help.federating": "اسی طرح، اگر اس فورم سے باہر کے صارفین آپ کو فالو کرنا شروع کر دیں، تو آپ کی پوسٹس ان کے ایپلیکیشنز اور ویب سائٹس پر ظاہر ہونا شروع ہو جائیں گی۔",
+ "help.next-generation": "یہ سوشل نیٹ ورک کی نئی نسل ہے۔ آج ہی سے حصہ ڈالنا شروع کریں!",
+
+ "onboard.title": "فیڈی ورس کی طرف آپ کی کھڑکی…",
+ "onboard.what": "یہ آپ کی ذاتی نوعیت کی کیٹیگری ہے جو صرف اس فورم سے باہر کے مواد پر مشتمل ہے۔ یہاں وہ چیزیں ظاہر ہوتی ہیں جو آپ کے فالو کیے ہوئے لوگوں نے بنائیں یا شیئر کیں۔",
+ "onboard.why": "اس فورم سے باہر بہت کچھ ہو رہا ہے، اور ہر چیز آپ کے مفادات سے مطابقت نہیں رکھتی۔ اس لیے مخصوص لوگوں کو فالو کرنا یہ ظاہر کرنے کا بہترین طریقہ ہے کہ آپ ان سے مزید دیکھنا چاہتے ہیں۔",
+ "onboard.how": "اس دوران، آپ اس فورم کے قابل رسائی مواد کو دیکھنے کے لیے اوپر کے بٹن استعمال کر سکتے ہیں۔ اس طرح آپ نئے مواد کی دریافت شروع کر سکتے ہیں!",
+
+ "show-categories": "زمرہ جات دکھائیں",
+ "hide-categories": "زمرہ جات چھپائیں"
+}
\ No newline at end of file
diff --git a/public/language/vi/admin/dashboard.json b/public/language/vi/admin/dashboard.json
index 6d2a736380..d12d7771e2 100644
--- a/public/language/vi/admin/dashboard.json
+++ b/public/language/vi/admin/dashboard.json
@@ -75,6 +75,7 @@
"graphs.page-views-registered": "Đã Đăng Ký Xem Trang",
"graphs.page-views-guest": "Khách Xem Trang",
"graphs.page-views-bot": "Bot Xem Trang",
+ "graphs.page-views-ap": "Lượt Xem Trang ActivityPub",
"graphs.unique-visitors": "Khách Độc Lập",
"graphs.registered-users": "Thành Viên Chính Thức",
"graphs.guest-users": "Người dùng khách",
diff --git a/public/language/vi/admin/manage/categories.json b/public/language/vi/admin/manage/categories.json
index d89112626c..45f3d12d89 100644
--- a/public/language/vi/admin/manage/categories.json
+++ b/public/language/vi/admin/manage/categories.json
@@ -1,12 +1,16 @@
{
"manage-categories": "Quản lý Danh mục",
"add-category": "Thêm Danh Mục",
+ "add-local-category": "Thêm danh mục Cục Bộ",
+ "add-remote-category": "Thêm danh mục Từ Xa",
+ "remove": "Xóa",
"jump-to": "Chuyển tới...",
"settings": "Cài Đặt Chuyên Mục",
"edit-category": "Sửa Danh Mục",
"privileges": "Đặc quyền",
"back-to-categories": "Quay lại danh mục",
- "name": "Tên Chuyên Mục",
+ "id": "ID Danh Mục",
+ "name": "Tên Danh Mục",
"handle": "Xử Lý Danh Mục",
"handle.help": "Xử lý danh mục của bạn được sử dụng làm đại diện cho danh mục này trên các mạng khác, tương tự như tên đăng nhập. Thẻ điều khiển danh mục không được khớp với tên người dùng hoặc nhóm người dùng hiện có.",
"description": "Mô Tả Chuyên Mục",
@@ -103,6 +107,8 @@
"alert.create-success": "Đã tạo chuyên mục thành công!",
"alert.none-active": "Bạn không có chuyên mục hoạt động.",
"alert.create": "Tạo Chuyên Mục",
+ "alert.add": "Thêm Danh Mục",
+ "alert.add-help": "Các danh mục từ xa có thể được thêm vào danh sách danh sách bằng cách chỉ định cách xử lý của chúng.
Ghi chú — Danh mục từ xa có thể không phản ánh tất cả các chủ đề được xuất bản trừ khi có ít nhất một người dùng cục bộ theo dõi/xem nó.",
"alert.confirm-purge": "
Bạn có chắc muốn loại bỏ danh mục \"%1\" này không?
Xóa danh mục sẽ xóa tất cả các chủ đề và bài đăng, đồng thời xóa danh mục khỏi cơ sở dữ liệu. Nếu bạn muốn xóa một danh mụctạm thời, thay vào đó bạn sẽ muốn \"vô hiệu hóa\" danh mục.
", "alert.purge-success": "Đã loại bỏ chuyên mục!", "alert.copy-success": "Đã Sao Chép Cài Đặt!", diff --git a/public/language/vi/admin/manage/users.json b/public/language/vi/admin/manage/users.json index 8a0a2b0806..d3136effd4 100644 --- a/public/language/vi/admin/manage/users.json +++ b/public/language/vi/admin/manage/users.json @@ -119,7 +119,7 @@ "alerts.create-success": "Đã tạo người dùng!", "alerts.prompt-email": "Thư điện tử:", - "alerts.email-sent-to": "Email mời đã được gửi đến %1", + "alerts.email-sent-to": "Một email mời đã được gửi đến %1", "alerts.x-users-found": "Tìm được %1 người, (%2 giây)", "alerts.select-a-single-user-to-change-email": "Chọn một người dùng để thay đổi email", "export": "Xuất", diff --git a/public/language/vi/admin/settings/activitypub.json b/public/language/vi/admin/settings/activitypub.json index a2502f039a..c9e7308bb1 100644 --- a/public/language/vi/admin/settings/activitypub.json +++ b/public/language/vi/admin/settings/activitypub.json @@ -18,6 +18,27 @@ "probe-timeout": "Hết Thời Gian Tra Cứu (mili giây)", "probe-timeout-help": "(Mặc định: 2000) Nếu truy vấn tra cứu không nhận được phản hồi trong khung thời gian đã đặt, thay vào đó sẽ đưa người dùng đến liên kết trực tiếp. Điều chỉnh con số này cao hơn nếu các trang web phản hồi chậm và bạn muốn dành thêm thời gian.", + "rules": "Phân loại", + "rules-intro": "Nội dung được phát hiện thông qua ActivityPub có thể được tự động phân loại dựa trên các quy tắc nhất định (ví dụ: hashtag)", + "rules.modal.title": "Cách nó hoạt động", + "rules.modal.instructions": "Bất kỳ nội dung đến nào cũng được kiểm tra theo các quy tắc phân loại này và nội dung phù hợp được tự động chuyển sang danh mục lựa chọn.một,hai,ba)",
+ "rules.add": "Thêm Quy Tắc Mới",
+ "rules.type": "Loại",
+ "rules.value": "Giá trị",
+ "rules.cid": "Danh mục",
+
+ "relays": "Relays",
+ "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.",
+ "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.",
+ "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.",
+ "relays.add": "Add New Relay",
+ "relays.relay": "Relay",
+ "relays.state": "State",
+ "relays.state-0": "Pending",
+ "relays.state-1": "Receiving only",
+ "relays.state-2": "Active",
+
"server-filtering": "Lọc",
"count": "NodeBB này hiện đã biết về %1 máy chủ",
"server.filter-help": "Chỉ định các máy chủ mà bạn muốn cấm liên kết với NodeBB của mình. Ngoài ra, bạn có thể chọn tham gia có chọn lọc cho phép liên kết có chọn lọc với các máy chủ cụ thể. Cả hai tùy chọn đều được hỗ trợ, mặc dù chúng loại trừ lẫn nhau.",
diff --git a/public/language/vi/category.json b/public/language/vi/category.json
index 4abc703c44..ccddbfda2b 100644
--- a/public/language/vi/category.json
+++ b/public/language/vi/category.json
@@ -1,7 +1,7 @@
{
"category": "Danh mục",
"subcategories": "Danh mục phụ",
- "uncategorized": "Chưa có danh mục",
+ "uncategorized": "Chưa phân loại",
"uncategorized.description": "Các chủ đề không phù hợp với bất kỳ danh mục hiện có nào",
"handle.description": "Có thể theo dõi danh mục này từ mạng xã hội mở thông qua xử lý %1",
"new-topic-button": "Chủ Đề Mới",
diff --git a/public/language/vi/error.json b/public/language/vi/error.json
index c95f985908..ec481020a1 100644
--- a/public/language/vi/error.json
+++ b/public/language/vi/error.json
@@ -3,6 +3,7 @@
"invalid-json": "JSON không hợp lệ",
"wrong-parameter-type": "Giá trị của loại %3 được mong đợi cho thuộc tính `%1`, nhưng thay vào đó, %2 đã được nhận",
"required-parameters-missing": "Các thông số bắt buộc bị thiếu trong lệnh gọi API này: %1",
+ "reserved-ip-address": "Không được phép yêu cầu mạng đến phạm vi IP dành riêng.",
"not-logged-in": "Có vẻ như bạn chưa đăng nhập.",
"account-locked": "Tài khoản của bạn tạm thời bị khóa",
"search-requires-login": "Tìm kiếm yêu cầu một tài khoản - vui lòng đăng nhập hoặc đăng ký.",
@@ -83,8 +84,8 @@
"post-delete-duration-expired-hours-minutes": "Bạn chỉ được phép xóa bài viết sau khi đăng %1 giờ(s) 2 phút(s)",
"post-delete-duration-expired-days": "Bạn chỉ được phép xóa các bài viết sau khi đăng %1 ngày(s)",
"post-delete-duration-expired-days-hours": "Bạn chỉ được phép xóa các bài viết sau khi đăng %1 ngày(s) %2 giờ(s)",
- "cant-delete-topic-has-reply": "Bạn không thể xóa chủ đề vì đã có 1 bình luận",
- "cant-delete-topic-has-replies": "Bạn không thể xóa chủ đề này vì đã có %1 bình luận",
+ "cant-delete-topic-has-reply": "Bạn không thể xóa chủ đề của bạn sau khi nó có câu trả lời",
+ "cant-delete-topic-has-replies": "Bạn không thể xóa chủ đề của bạn sau khi nó có %1 trả lời",
"content-too-short": "Vui lòng nhập một bài viết dài hơn. Bài viết phải chứa ít nhất %1 ký tự.",
"content-too-long": "Hãy nhập một bài đăng ngắn hơn. Bài đăng không thể dài hơn %1 ký tự.",
"title-too-short": "Hãy nhập tiêu đề dài hơn. Tiêu đề nên có ít nhất %1 ký tự.",
@@ -126,7 +127,7 @@
"already-deleting": "Đã sẵn sàng xóa",
"invalid-image": "Hình ảnh không hợp lệ",
"invalid-image-type": "Định dạng ảnh không hợp lệ. Các loại được phép là: %1",
- "invalid-image-extension": "Định dạng ảnh không hợp lệ",
+ "invalid-image-extension": "Phần mở rộng ảnh không hợp lệ",
"invalid-file-type": "Loại tệp không hợp lệ. Loại cho phép là: %1",
"invalid-image-dimensions": "Độ phân giải của ảnh quá lớn",
"group-name-too-short": "Tên nhóm quá ngắn",
@@ -136,7 +137,7 @@
"group-already-member": "Đã là thành viên của nhóm.",
"group-not-member": "Không phải thành viên nhóm này.",
"group-needs-owner": "Yêu cầu phải có ít nhất một chủ nhóm",
- "group-already-invited": "Thành viên này đã được mời",
+ "group-already-invited": "Người dùng này đã được mời",
"group-already-requested": "Yêu cầu tham gia thành viên của bạn đã được gửi.",
"group-join-disabled": "Bạn không thể tham gia nhóm này vào lúc này",
"group-leave-disabled": "Bạn không thể rời khỏi nhóm này vào lúc này",
@@ -158,7 +159,7 @@
"chat-deny-list-user-already-added": "Người dùng này đã có trong danh sách từ chối của bạn",
"chat-user-blocked": "Bạn đã bị chặn bởi người dùng này.",
"chat-disabled": "Hệ thống trò chuyện bị tắt",
- "too-many-messages": "Bạn đã gửi quá nhiều tin nhắn, vui lòng đợi trong giây lát.",
+ "too-many-messages": "Bạn đã gửi quá nhiều tin nhắn, vui lòng đợi một lúc.",
"invalid-chat-message": "Tin nhắn trò chuyện không hợp lệ",
"chat-message-too-long": "Tin nhắn trò chuyện không được dài hơn %1 ký tự.",
"cant-edit-chat-message": "Bạn không được phép sửa tin nhắn này",
@@ -201,7 +202,7 @@
"too-many-user-flags-per-day": "Bạn chỉ được gắn cờ %1 người dùng mỗi ngày",
"cant-flag-privileged": "Bạn không có quyền gắn cờ hồ sơ hay nội dung của người dùng đặc quyền (kiểm duyệt viên/người kiểm duyệt chung/quản trị viên)",
"cant-locate-flag-report": "Không thể định vị báo cáo cờ",
- "self-vote": "Bạn không thể tự bầu cho bài đăng của mình",
+ "self-vote": "Bạn không thể tự bình chọn bài đăng của mình",
"too-many-upvotes-today": "Bạn chỉ có thể ủng hộ %1 lần một ngày",
"too-many-upvotes-today-user": "Bạn chỉ được ủng hộ người dùng %1 lần một ngày",
"too-many-downvotes-today": "Bạn chỉ có thể phản đối %1 lần một ngày",
@@ -224,7 +225,7 @@
"invalid-session-text": "Có vẻ như phiên đăng nhập của bạn không còn hoạt động. Vui lòng làm mới trang này.",
"session-mismatch": "Phiên Không Khớp",
"session-mismatch-text": "Có vẻ như phiên đăng nhập của bạn không còn khớp với máy chủ. Vui lòng làm mới trang này.",
- "no-topics-selected": "Không có chủ đề nào đang được chọn!",
+ "no-topics-selected": "Không chọn chủ đề!",
"cant-move-to-same-topic": "Bạn không thể đưa bài đăng vào cùng chủ đề!",
"cant-move-topic-to-same-category": "Không thể di chuyển chủ đề đến cùng danh mục!",
"cannot-block-self": "Bạn không thể tự khóa bạn!",
@@ -236,6 +237,7 @@
"socket-reconnect-failed": "Không thể truy cập máy chủ vào lúc này. Nhấp vào đây để thử lại hoặc thử lại sau",
"invalid-plugin-id": "ID plugin không hợp lệ",
"plugin-not-whitelisted": "Không thể cài đặt plugin – chỉ có plugin được Quản Lý Gói NodeBB đưa vào danh sách trắng mới có thể được cài đặt qua ACP",
+ "cannot-toggle-system-plugin": "Bạn không thể chuyển đổi trạng thái của một plugin hệ thống",
"plugin-installation-via-acp-disabled": "Cài đặt plugin qua ACP bị tắt",
"plugins-set-in-configuration": "Bạn không được phép thay đổi trạng thái plugin vì chúng được xác định trong thời gian chạy (config.json, biến môi trường hoặc đối số đầu cuối), thay vào đó hãy sửa đổi cấu hình.",
"theme-not-set-in-configuration": "Khi xác định các plugin hoạt động trong cấu hình, thay đổi giao diện buộc phải thêm giao diện mới vào danh sách các plugin hoạt động trước khi cập nhật nó trong ACP",
diff --git a/public/language/vi/global.json b/public/language/vi/global.json
index 3ac87d5132..5a066d9b0e 100644
--- a/public/language/vi/global.json
+++ b/public/language/vi/global.json
@@ -134,11 +134,11 @@
"upload": "Tải lên",
"uploads": "Tải lên",
"allowed-file-types": "Loại cho phép là %1",
- "unsaved-changes": "Có một vài thay đổi chưa được lưu. Bạn muốn rời đi ngay?",
- "reconnecting-message": "Có vẻ như bạn đã mất kết nối tới %1, vui lòng đợi một lúc để chúng tôi thử kết nối lại.",
- "play": "Chơi",
+ "unsaved-changes": "Bạn có những thay đổi chưa lưu. Bạn có chắc muốn điều hướng đi?",
+ "reconnecting-message": "Có vẻ như bạn đã mất kết nối tới %1, hãy đợi trong khi chúng tôi cố gắng kết nối lại.",
+ "play": "Phát",
"cookies.message": "Trang web này sử dụng cookie để đảm bảo bạn có được trải nghiệm tốt.",
- "cookies.accept": "Đã rõ!",
+ "cookies.accept": "Hiểu rồi!",
"cookies.learn-more": "Tìm Hiểu Thêm",
"edited": "Đã Sửa",
"disabled": "Đã tắt",
@@ -146,7 +146,7 @@
"selected": "Đã chọn",
"copied": "Đã sao chép",
"user-search-prompt": "Nhập để tìm kiếm thành viên",
- "hidden": "Ẩn",
+ "hidden": "Đã ẩn",
"sort": "Xếp",
"actions": "Hành Động",
"rss-feed": "Nguồn RSS",
diff --git a/public/language/vi/groups.json b/public/language/vi/groups.json
index df781930e5..d4c6e34b97 100644
--- a/public/language/vi/groups.json
+++ b/public/language/vi/groups.json
@@ -29,7 +29,7 @@
"details.disableJoinRequests": "Tắt yêu cầu tham gia",
"details.disableLeave": "Không cho phép người dùng rời khỏi nhóm",
"details.grant": "Cấp/Huỷ bỏ quyền sở hữu",
- "details.kick": "Đá ra",
+ "details.kick": "Loại ra",
"details.kick-confirm": "Bạn có chắc chắn muốn xoá thành viên này khỏi nhóm?",
"details.add-member": "Thêm Thành Viên",
"details.owner-options": "Quản Trị Nhóm",
diff --git a/public/language/vi/modules.json b/public/language/vi/modules.json
index 2d5b2e4b92..c238cc8d51 100644
--- a/public/language/vi/modules.json
+++ b/public/language/vi/modules.json
@@ -19,7 +19,7 @@
"chat.see-all": "Tất cả trò chuyện",
"chat.mark-all-read": "Đánh dấu tất cả đã đọc",
"chat.no-messages": "Hãy chọn người nhận để xem lịch sử tin nhắn trò chuyện",
- "chat.no-users-in-room": "Không có người nào trong phòng này.",
+ "chat.no-users-in-room": "Không có ai trong phòng này",
"chat.recent-chats": "Trò Chuyện Gần Đây",
"chat.contacts": "Liên hệ",
"chat.message-history": "Lịch Sử Tin Nhắn",
@@ -48,6 +48,7 @@
"chat.add-user": "Thêm Người",
"chat.notification-settings": "Cài Đặt Thông Báo",
"chat.default-notification-setting": "Cài Đặt Thông Báo Mặc Định",
+ "chat.join-leave-messages": "Tham gia/Rời đi Tin Nhắn",
"chat.notification-setting-room-default": "Phòng Mặc Định",
"chat.notification-setting-none": "Không thông báo",
"chat.notification-setting-at-mention-only": "Chỉ khi @đề cập",
@@ -100,7 +101,7 @@
"composer.formatting.code": "Mã",
"composer.formatting.link": "Liên kết",
"composer.formatting.picture": "Liên Kết Ảnh",
- "composer.upload-picture": "Tải ảnh lên",
+ "composer.upload-picture": "Tải Lên Ảnh",
"composer.upload-file": "Tải Lên Tệp",
"composer.zen-mode": "Chế Độ Zen",
"composer.select-category": "Chọn chuyên mục",
diff --git a/public/language/vi/pages.json b/public/language/vi/pages.json
index 851486de52..f260251b9b 100644
--- a/public/language/vi/pages.json
+++ b/public/language/vi/pages.json
@@ -1,10 +1,10 @@
{
"home": "Trang chủ",
"unread": "Chủ Đề Chưa Đọc",
- "popular-day": "Chủ đề nổi bật hôm nay",
- "popular-week": "Chủ đề nội bật tuần này",
- "popular-month": "Chủ đề nổi bật tháng này",
- "popular-alltime": "Chủ đề nổi bật mọi thời đại",
+ "popular-day": "Chủ đề phổ biến hôm nay",
+ "popular-week": "Chủ đề phổ biến tuần này",
+ "popular-month": "Chủ đề phổ biến tháng này",
+ "popular-alltime": "Chủ đề phổ biến mọi lúc",
"recent": "Chủ Đề Gần Đây",
"top-day": "Chủ đề được bình chọn nhiều hôm nay",
"top-week": "Chủ đề được bình chọn nhiều tuần này",
@@ -24,7 +24,7 @@
"users/search": "Tìm Kiếm Người Dùng",
"notifications": "Thông báo",
"tags": "Thẻ",
- "tag": "Các chủ đề được gắn thẻ bên dưới "%1"",
+ "tag": "Chủ đề được gắn thẻ bên dưới "%1"",
"register": "Đăng ký một tài khoản",
"registration-complete": "Đăng ký hoàn tất",
"login": "Đăng nhập vào tài khoản của bạn",
@@ -42,11 +42,11 @@
"account/edit/username": "Chỉnh sửa tên đăng nhập của \"%1\"",
"account/edit/email": "Chỉnh sửa email của \"%1\"",
"account/info": "Thông Tin Tài Khoản",
- "account/following": "Thành viên %1 đang theo dõi",
- "account/followers": "Thành viên đang theo dõi %1",
- "account/posts": "Bài viết được đăng bởi %1",
+ "account/following": "Người %1 theo dõi",
+ "account/followers": "Những người theo dõi %1",
+ "account/posts": "Bài viết làm bởi %1",
"account/latest-posts": "Bài viết mới nhất do %1",
- "account/topics": "Chủ đề được tạo bởi %1",
+ "account/topics": "Chủ đề tạo bởi %1",
"account/groups": "Nhóm của %1",
"account/watched-categories": "Danh Mục Đã Xem Của %1",
"account/watched-tags": "%1's Thẻ Đã Xem",
@@ -64,7 +64,7 @@
"account/uploads": "Tải lên bởi %1",
"account/sessions": "Phiên Đăng Nhập",
"account/shares": "Chủ đề được chia sẻ bởi %1",
- "confirm": "Đã xác nhận email",
+ "confirm": "Đã Xác Nhận Email",
"maintenance.text": "%1 hiện đang bảo trì.您确定要清除此版块“%1”吗?
清除版块将删除所有主题和帖子,并从数据库中删除版块。 如果您想暂时移除版块,请使用停用版块。
", "alert.purge-success": "版块已删除!", "alert.copy-success": "设置已复制!", diff --git a/public/language/zh-CN/admin/settings/activitypub.json b/public/language/zh-CN/admin/settings/activitypub.json index 748ffbbbbe..39de463e70 100644 --- a/public/language/zh-CN/admin/settings/activitypub.json +++ b/public/language/zh-CN/admin/settings/activitypub.json @@ -18,6 +18,27 @@ "probe-timeout": "查询超时(毫秒)", "probe-timeout-help": "(默认值:2000)如果查询在设定的时间内没有收到回复,将直接把用户发送到链接。如果网站响应速度较慢,您希望给予更多时间,则可将此数字调高。", + "rules": "分类", + "rules-intro": "通过 ActivityPub 发现的内容可根据特定规则(如标签)进行自动分类。", + "rules.modal.title": "它的工作原理", + "rules.modal.instructions": "所有传入的内容都会根据这些分类规则进行检查,符合规则的内容会自动移动到指定的版块目录中。一,二,三)",
+ "rules.add": "添加规则",
+ "rules.type": "类型",
+ "rules.value": "值",
+ "rules.cid": "版块",
+
+ "relays": "中继服务",
+ "relays.intro": "中继服务能提升您NodeBB论坛内容的发现效率。订阅中继服务意味着:中继服务接收的内容将转发至此处,而本论坛发布的内容则由中继服务向外同步传播。",
+ "relays.warning": "注:中继服务可能接收大量流量,并可能增加存储和处理成本。",
+ "relays.litepub": "NodeBB 遵循 LitePub 风格的中继标准。您在此处输入的URL应以 /actor 结尾。",
+ "relays.add": "添加新中继服务",
+ "relays.relay": "中继服务",
+ "relays.state": "状态",
+ "relays.state-0": "待处理",
+ "relays.state-1": "仅接收",
+ "relays.state-2": "已启用",
+
"server-filtering": "过滤",
"count": "该 NodeBB 目前可检测到 %1 台服务器",
"server.filter-help": "指定您希望禁止与 NodeBB 联邦化的服务器。或者,您也可以选择性地 允许 与特定服务器联邦化。两者只能选其一。",
diff --git a/public/language/zh-CN/error.json b/public/language/zh-CN/error.json
index dd760f4ad1..e91848b78d 100644
--- a/public/language/zh-CN/error.json
+++ b/public/language/zh-CN/error.json
@@ -3,6 +3,7 @@
"invalid-json": "无效 JSON",
"wrong-parameter-type": "属性 `%1` 要求是类型 %3 的值,却收到了 %2",
"required-parameters-missing": "此 API 调用必需参数缺少了:%1",
+ "reserved-ip-address": "不允许向保留 IP 地址发送网络请求。",
"not-logged-in": "您还没有登录。",
"account-locked": "您的帐号已被临时锁定",
"search-requires-login": "搜索功能仅限会员使用 - 请先登录或者注册。",
@@ -236,6 +237,7 @@
"socket-reconnect-failed": "目前无法连接到服务器。请点击这里重试,或稍后再试",
"invalid-plugin-id": "无效插件ID",
"plugin-not-whitelisted": "无法安装插件 – 只有被NodeBB包管理器列入白名单的插件才能通过ACP安装。",
+ "cannot-toggle-system-plugin": "您不能切换系统插件的状态",
"plugin-installation-via-acp-disabled": "ACP 安装插件已被禁用",
"plugins-set-in-configuration": "您不能修改插件状态因为它们在运行时中被定义(config.json,环境变量或终端选项),请转而修改配置。",
"theme-not-set-in-configuration": "在配置中定义活跃的插件时,需要先将新主题加入活跃插件的列表,才能在管理员控制面板中修改主题",
diff --git a/public/language/zh-CN/modules.json b/public/language/zh-CN/modules.json
index 184fc4dec0..d9ae48b598 100644
--- a/public/language/zh-CN/modules.json
+++ b/public/language/zh-CN/modules.json
@@ -48,6 +48,7 @@
"chat.add-user": "添加用户",
"chat.notification-settings": "通知设置",
"chat.default-notification-setting": "默认通知设置",
+ "chat.join-leave-messages": "加入/退出 消息",
"chat.notification-setting-room-default": "默认房间",
"chat.notification-setting-none": "无通知",
"chat.notification-setting-at-mention-only": "仅@提及",
diff --git a/public/language/zh-CN/post-queue.json b/public/language/zh-CN/post-queue.json
index a1a5d47414..4beb41d8ff 100644
--- a/public/language/zh-CN/post-queue.json
+++ b/public/language/zh-CN/post-queue.json
@@ -33,7 +33,7 @@
"reject-all-confirm": "您想要拒绝全部帖子吗?",
"reject-selected": "拒绝选中项",
"reject-selected-confirm": "您确定要拒绝%1个选择的帖子吗?",
- "remove-all": "移动全部",
+ "remove-all": "移除全部",
"remove-all-confirm": "你想删除所有的帖子吗?",
"remove-selected": "移除所选内容",
"remove-selected-confirm": "你想删除%1的选定帖子吗?",
diff --git a/public/language/zh-CN/social.json b/public/language/zh-CN/social.json
index ff9388001d..2800dfa892 100644
--- a/public/language/zh-CN/social.json
+++ b/public/language/zh-CN/social.json
@@ -7,6 +7,8 @@
"sign-up-with-google": "通过 Google 注册",
"log-in-with-facebook": "通过 Facebook 登录",
"continue-with-facebook": "继续使用 Facebook 登录",
- "sign-in-with-linkedin": "通过LinkedIn登录",
- "sign-up-with-linkedin": "通过LinkedIn注册"
+ "sign-in-with-linkedin": "通过 LinkedIn 登录",
+ "sign-up-with-linkedin": "通过 LinkedIn 注册",
+ "sign-in-with-wordpress": "通过 WordPress 登录",
+ "sign-up-with-wordpress": "通过 WordPress 注册"
}
\ No newline at end of file
diff --git a/public/language/zh-TW/admin/dashboard.json b/public/language/zh-TW/admin/dashboard.json
index 4cc161c595..acf93cadda 100644
--- a/public/language/zh-TW/admin/dashboard.json
+++ b/public/language/zh-TW/admin/dashboard.json
@@ -75,6 +75,7 @@
"graphs.page-views-registered": "註冊使用者頁面瀏覽量",
"graphs.page-views-guest": "訪客頁面瀏覽量",
"graphs.page-views-bot": "爬蟲頁面瀏覽量",
+ "graphs.page-views-ap": "ActivityPub Page Views",
"graphs.unique-visitors": "不重複訪客",
"graphs.registered-users": "已註冊使用者",
"graphs.guest-users": "Guest Users",
diff --git a/public/language/zh-TW/admin/manage/categories.json b/public/language/zh-TW/admin/manage/categories.json
index 7a857a1d8c..4da07dc01c 100644
--- a/public/language/zh-TW/admin/manage/categories.json
+++ b/public/language/zh-TW/admin/manage/categories.json
@@ -1,11 +1,15 @@
{
"manage-categories": "Manage Categories",
"add-category": "Add category",
+ "add-local-category": "Add Local category",
+ "add-remote-category": "Add Remote category",
+ "remove": "Remove",
"jump-to": "Jump to...",
"settings": "版面設定",
"edit-category": "Edit Category",
"privileges": "權限",
"back-to-categories": "Back to categories",
+ "id": "Category ID",
"name": "版面名稱",
"handle": "Category Handle",
"handle.help": "Your category handle is used as a representation of this category across other networks, similar to a username. A category handle must not match an existing username or user group.",
@@ -103,6 +107,8 @@
"alert.create-success": "版面建立成功!",
"alert.none-active": "您沒有有效的版面。",
"alert.create": "建立一個版面",
+ "alert.add": "Add a Category",
+ "alert.add-help": "Remote categories can be added to the categories listing by specifying their handle.您確定要清除 “%1” 版面嗎?
清除版塊將刪除所有主題和帖子,並從數據庫中刪除版塊。 如果您想暫時移除版塊,請使用停用版塊。
", "alert.purge-success": "版面已刪除!", "alert.copy-success": "設定已複製!", diff --git a/public/language/zh-TW/admin/settings/activitypub.json b/public/language/zh-TW/admin/settings/activitypub.json index 87f654e125..6fdf6c0602 100644 --- a/public/language/zh-TW/admin/settings/activitypub.json +++ b/public/language/zh-TW/admin/settings/activitypub.json @@ -18,6 +18,27 @@ "probe-timeout": "查詢時限 (毫秒)", "probe-timeout-help": "(Default: 2000) If the lookup query does not receive a response within the set timeframe, will send the user to the link directly instead. Adjust this number higher if sites are responding slowly and you wish to give extra time.", + "rules": "Categorization", + "rules-intro": "Content discovered via ActivityPub can be automatically categorized based on certain rules (e.g. hashtag)", + "rules.modal.title": "How it works", + "rules.modal.instructions": "Any incoming content is checked against these categorization rules, and matching content is automatically moved into the category of choice.one,two,three)",
+ "rules.add": "Add New Rule",
+ "rules.type": "Type",
+ "rules.value": "Value",
+ "rules.cid": "Category",
+
+ "relays": "Relays",
+ "relays.intro": "A relay improves discovery of content to and from your NodeBB. Subscribing to a relay means content received by the relay is forwarded here, and content posted here is syndicated outward by the relay.",
+ "relays.warning": "Note: Relays can send larges amounts of traffic in, and may increase storage and processing costs.",
+ "relays.litepub": "NodeBB follows the LitePub-style relay standard. The URL you enter here should end with /actor.",
+ "relays.add": "Add New Relay",
+ "relays.relay": "Relay",
+ "relays.state": "State",
+ "relays.state-0": "Pending",
+ "relays.state-1": "Receiving only",
+ "relays.state-2": "Active",
+
"server-filtering": "過濾...",
"count": "本 NodeBB 已發現 %1 台伺服器。",
"server.filter-help": "Specify servers you would like to bar from federating with your NodeBB. Alternatively, you may opt to selectively allow federation with specific servers, instead. Both options are supported, although they are mutually exclusive.",
diff --git a/public/language/zh-TW/error.json b/public/language/zh-TW/error.json
index f7b64e8001..14a9bcdbbb 100644
--- a/public/language/zh-TW/error.json
+++ b/public/language/zh-TW/error.json
@@ -3,6 +3,7 @@
"invalid-json": "無效 JSON",
"wrong-parameter-type": "A value of type %3 was expected for property `%1`, but %2 was received instead",
"required-parameters-missing": "Required parameters were missing from this API call: %1",
+ "reserved-ip-address": "Network requests to reserved IP ranges are not allowed.",
"not-logged-in": "您還沒有登入。",
"account-locked": "您的帳戶已被暫時鎖定",
"search-requires-login": "搜尋功能僅限成員使用 - 請先登入或者註冊。",
@@ -236,6 +237,7 @@
"socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later",
"invalid-plugin-id": "無效的插件 ID",
"plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP",
+ "cannot-toggle-system-plugin": "You cannot toggle the state of a system plugin",
"plugin-installation-via-acp-disabled": "Plugin installation via ACP is disabled",
"plugins-set-in-configuration": "You are not allowed to change plugin state as they are defined at runtime (config.json, environmental variables or terminal arguments), please modify the configuration instead.",
"theme-not-set-in-configuration": "When defining active plugins in configuration, changing themes requires adding the new theme to the list of active plugins before updating it in the ACP",
diff --git a/public/language/zh-TW/modules.json b/public/language/zh-TW/modules.json
index a0d0f953d3..0262c03fb0 100644
--- a/public/language/zh-TW/modules.json
+++ b/public/language/zh-TW/modules.json
@@ -48,6 +48,7 @@
"chat.add-user": "Add User",
"chat.notification-settings": "Notification Settings",
"chat.default-notification-setting": "Default Notification Setting",
+ "chat.join-leave-messages": "Join/Leave Messages",
"chat.notification-setting-room-default": "Room Default",
"chat.notification-setting-none": "No notifications",
"chat.notification-setting-at-mention-only": "@mention only",
diff --git a/public/language/zh-TW/social.json b/public/language/zh-TW/social.json
index dc1efd7912..0c2974c36c 100644
--- a/public/language/zh-TW/social.json
+++ b/public/language/zh-TW/social.json
@@ -8,5 +8,7 @@
"log-in-with-facebook": "以Facebook登入",
"continue-with-facebook": "以Facebook繼續使用",
"sign-in-with-linkedin": "以 LinkedIn 登入",
- "sign-up-with-linkedin": "以 LinkedIn 註冊"
+ "sign-up-with-linkedin": "以 LinkedIn 註冊",
+ "sign-in-with-wordpress": "Sign in with WordPress",
+ "sign-up-with-wordpress": "Sign up with WordPress"
}
\ No newline at end of file
diff --git a/public/openapi/components/schemas/Chats.yaml b/public/openapi/components/schemas/Chats.yaml
index dc84aca4ef..036b937158 100644
--- a/public/openapi/components/schemas/Chats.yaml
+++ b/public/openapi/components/schemas/Chats.yaml
@@ -27,6 +27,10 @@ RoomObject:
description: Timestamp of when room was created
notificationSetting:
type: number
+ description: The notification setting for the room, 0 = no notifications, 1 = only mentions, 2 = all messages
+ joinLeaveMessages:
+ type: number
+ description: Whether join/leave messages are enabled in the room
MessageObject:
type: object
properties:
diff --git a/public/openapi/components/schemas/PostObject.yaml b/public/openapi/components/schemas/PostObject.yaml
index bcb2f79e53..1904cded51 100644
--- a/public/openapi/components/schemas/PostObject.yaml
+++ b/public/openapi/components/schemas/PostObject.yaml
@@ -300,6 +300,8 @@ PostDataObject:
type: boolean
attachments:
type: array
+ uploads:
+ type: array
replies:
type: object
properties:
diff --git a/public/openapi/components/schemas/TopicObject.yaml b/public/openapi/components/schemas/TopicObject.yaml
index b26623072e..30ba9d3a77 100644
--- a/public/openapi/components/schemas/TopicObject.yaml
+++ b/public/openapi/components/schemas/TopicObject.yaml
@@ -106,6 +106,9 @@ TopicObject:
description: A topic identifier
content:
type: string
+ sourceContent:
+ type: string
+ nullable: true
timestampISO:
type: string
description: An ISO 8601 formatted date string (complementing `timestamp`)
@@ -118,6 +121,10 @@ TopicObject:
username:
type: string
description: A friendly name for a given user account
+ displayname:
+ type: string
+ isLocal:
+ type: boolean
userslug:
type: string
description: An URL-safe variant of the username (i.e. lower-cased, spaces
diff --git a/public/openapi/components/schemas/admin/relays.yaml b/public/openapi/components/schemas/admin/relays.yaml
new file mode 100644
index 0000000000..67bdd09b10
--- /dev/null
+++ b/public/openapi/components/schemas/admin/relays.yaml
@@ -0,0 +1,18 @@
+RelayObject:
+ type: object
+ properties:
+ url:
+ type: string
+ description: The relay actor endpoint
+ example: https://example.org/actor
+ state:
+ type: number
+ description: "The established state of the relay(0: pending; 1: one way receive; 2: bidirectional)"
+ enum: [0, 1, 2]
+ label:
+ type: string
+ description: A language key pertaining to the `state` value
+RelaysArray:
+ type: array
+ items:
+ $ref: '#/RelayObject'
\ No newline at end of file
diff --git a/public/openapi/components/schemas/admin/rules.yaml b/public/openapi/components/schemas/admin/rules.yaml
new file mode 100644
index 0000000000..282d6eff11
--- /dev/null
+++ b/public/openapi/components/schemas/admin/rules.yaml
@@ -0,0 +1,23 @@
+RuleObject:
+ type: object
+ properties:
+ rid:
+ type: string
+ description: a valid rule ID
+ example: 4eb506f8-a173-4693-a41b-e23604bc973a
+ type:
+ type: string
+ description: The auto-categorization rule type
+ example: hashtag
+ value:
+ type: string
+ description: The value that incoming content will be matched against (used alongside `type`)
+ example: 'example'
+ cid:
+ type: number
+ description: The category ID of a local category
+ example: 1
+RulesArray:
+ type: array
+ items:
+ $ref: '#/RuleObject'
\ No newline at end of file
diff --git a/public/openapi/read/admin/analytics.yaml b/public/openapi/read/admin/analytics.yaml
index 508325aace..67f8ed516c 100644
--- a/public/openapi/read/admin/analytics.yaml
+++ b/public/openapi/read/admin/analytics.yaml
@@ -53,6 +53,10 @@ get:
items:
type: number
pageviews:guest:
+ type: array
+ items:
+ type: number
+ pageviews:ap:
type: array
items:
type: number
\ No newline at end of file
diff --git a/public/openapi/read/admin/manage/categories.yaml b/public/openapi/read/admin/manage/categories.yaml
index b4e6102ac1..bbf8efb8fe 100644
--- a/public/openapi/read/admin/manage/categories.yaml
+++ b/public/openapi/read/admin/manage/categories.yaml
@@ -48,6 +48,8 @@ get:
type: string
order:
type: number
+ isLocal:
+ type: boolean
subCategoriesPerPage:
type: number
children:
diff --git a/public/openapi/read/admin/settings/activitypub.yaml b/public/openapi/read/admin/settings/activitypub.yaml
index b0871999b6..48a0415fef 100644
--- a/public/openapi/read/admin/settings/activitypub.yaml
+++ b/public/openapi/read/admin/settings/activitypub.yaml
@@ -16,4 +16,8 @@ get:
instanceCount:
type: number
description: The number of ActivityPub-enabled instances that this forum knows about.
+ rules:
+ $ref: ../../../components/schemas/admin/rules.yaml#/RulesArray
+ relays:
+ $ref: ../../../components/schemas/admin/relays.yaml#/RelaysArray
- $ref: ../../../components/schemas/CommonProps.yaml#/CommonProps
\ No newline at end of file
diff --git a/public/openapi/read/unread.yaml b/public/openapi/read/unread.yaml
index e916231d65..ea69f666dd 100644
--- a/public/openapi/read/unread.yaml
+++ b/public/openapi/read/unread.yaml
@@ -138,6 +138,9 @@ get:
description: A topic identifier
content:
type: string
+ sourceContent:
+ type: string
+ nullable: true
timestampISO:
type: string
description: An ISO 8601 formatted date string (complementing `timestamp`)
@@ -150,6 +153,10 @@ get:
username:
type: string
description: A friendly name for a given user account
+ displayname:
+ type: string
+ isLocal:
+ type: boolean
userslug:
type: string
description: An URL-safe variant of the username (i.e. lower-cased, spaces
diff --git a/public/openapi/read/user/userslug/chats/roomid.yaml b/public/openapi/read/user/userslug/chats/roomid.yaml
index 5c5fd1c296..73c4a62da9 100644
--- a/public/openapi/read/user/userslug/chats/roomid.yaml
+++ b/public/openapi/read/user/userslug/chats/roomid.yaml
@@ -56,6 +56,8 @@ get:
type: array
notificationOptionsIcon:
type: string
+ joinLeaveMessages:
+ type: number
messages:
type: array
items:
@@ -360,6 +362,8 @@ get:
type: string
notificationSetting:
type: number
+ joinLeaveMessages:
+ type: number
publicRooms:
type: array
items:
diff --git a/public/openapi/write.yaml b/public/openapi/write.yaml
index 5f58bdfbf1..7771469725 100644
--- a/public/openapi/write.yaml
+++ b/public/openapi/write.yaml
@@ -270,6 +270,14 @@ paths:
$ref: 'write/admin/chats/roomId.yaml'
/admin/groups:
$ref: 'write/admin/groups.yaml'
+ /admin/activitypub/rules:
+ $ref: 'write/admin/activitypub/rules.yaml'
+ /admin/activitypub/rules/{rid}:
+ $ref: 'write/admin/activitypub/rules/rid.yaml'
+ /admin/activitypub/relays:
+ $ref: 'write/admin/activitypub/relays.yaml'
+ /admin/activitypub/relays/{url}:
+ $ref: 'write/admin/activitypub/relays/url.yaml'
/files/:
$ref: 'write/files.yaml'
/files/folder:
diff --git a/public/openapi/write/admin/activitypub/relays.yaml b/public/openapi/write/admin/activitypub/relays.yaml
new file mode 100644
index 0000000000..0f58c7580d
--- /dev/null
+++ b/public/openapi/write/admin/activitypub/relays.yaml
@@ -0,0 +1,28 @@
+post:
+ tags:
+ - admin
+ summary: add relay
+ description: This operation establishes a connection to a remote relay for content discovery purposes
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ url:
+ type: string
+ description: The relay actor endpoint
+ example: https://example.org/actor
+ responses:
+ '200':
+ description: rule successfully created
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ status:
+ $ref: ../../../components/schemas/Status.yaml#/Status
+ response:
+ $ref: ../../../components/schemas/admin/relays.yaml#/RelaysArray
diff --git a/public/openapi/write/admin/activitypub/relays/url.yaml b/public/openapi/write/admin/activitypub/relays/url.yaml
new file mode 100644
index 0000000000..4f4b182ffb
--- /dev/null
+++ b/public/openapi/write/admin/activitypub/relays/url.yaml
@@ -0,0 +1,25 @@
+delete:
+ tags:
+ - admin
+ summary: remove relay
+ description: This operation removes a pre-established relay connection
+ parameters:
+ - in: path
+ name: url
+ schema:
+ type: string
+ required: true
+ description: The relay actor endpoint, URL encoded.
+ example: https%3A%2F%2Fexample.org%2Factor
+ responses:
+ '200':
+ description: rule successfully deleted
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ status:
+ $ref: ../../../../components/schemas/Status.yaml#/Status
+ response:
+ $ref: ../../../../components/schemas/admin/relays.yaml#/RelaysArray
diff --git a/public/openapi/write/admin/activitypub/rules.yaml b/public/openapi/write/admin/activitypub/rules.yaml
new file mode 100644
index 0000000000..8a74425101
--- /dev/null
+++ b/public/openapi/write/admin/activitypub/rules.yaml
@@ -0,0 +1,36 @@
+post:
+ tags:
+ - admin
+ summary: create auto-categorization rule
+ description: This operation creates a new auto-categorization rule that is applied to new remote content received via ActivityPub.
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ type:
+ type: string
+ description: The auto-categorization rule type
+ example: hashtag
+ value:
+ type: string
+ description: The value that incoming content will be matched against (used alongside `type`)
+ example: 'example'
+ cid:
+ type: number
+ description: The category ID of a local category
+ example: 1
+ responses:
+ '200':
+ description: rule successfully created
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ status:
+ $ref: ../../../components/schemas/Status.yaml#/Status
+ response:
+ $ref: ../../../components/schemas/admin/rules.yaml#/RulesArray
diff --git a/public/openapi/write/admin/activitypub/rules/rid.yaml b/public/openapi/write/admin/activitypub/rules/rid.yaml
new file mode 100644
index 0000000000..08243c16a2
--- /dev/null
+++ b/public/openapi/write/admin/activitypub/rules/rid.yaml
@@ -0,0 +1,25 @@
+delete:
+ tags:
+ - admin
+ summary: delete auto-categorization rule
+ description: This operation deletes a previously set-up auto-categorization rule
+ parameters:
+ - in: path
+ name: rid
+ schema:
+ type: string
+ required: true
+ description: a valid rule ID
+ example: 4eb506f8-a173-4693-a41b-e23604bc973a
+ responses:
+ '200':
+ description: rule successfully deleted
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ status:
+ $ref: ../../../../components/schemas/Status.yaml#/Status
+ response:
+ $ref: ../../../../components/schemas/admin/rules.yaml#/RulesArray
diff --git a/public/openapi/write/chats/roomId/messages/mid.yaml b/public/openapi/write/chats/roomId/messages/mid.yaml
index 5053f1546d..c899627802 100644
--- a/public/openapi/write/chats/roomId/messages/mid.yaml
+++ b/public/openapi/write/chats/roomId/messages/mid.yaml
@@ -49,7 +49,7 @@ put:
type: number
required: true
description: a valid message id
- example: 5
+ example: 2
requestBody:
required: true
content:
@@ -92,7 +92,7 @@ delete:
type: number
required: true
description: a valid message id
- example: 5
+ example: 2
responses:
'200':
description: Message successfully deleted
@@ -125,7 +125,7 @@ post:
type: number
required: true
description: a valid message id
- example: 5
+ example: 2
responses:
'200':
description: message successfully restored
diff --git a/public/openapi/write/chats/roomId/messages/mid/ip.yaml b/public/openapi/write/chats/roomId/messages/mid/ip.yaml
index 0d2a82cba9..2c2af8fb1b 100644
--- a/public/openapi/write/chats/roomId/messages/mid/ip.yaml
+++ b/public/openapi/write/chats/roomId/messages/mid/ip.yaml
@@ -17,7 +17,7 @@ get:
type: string
required: true
description: a valid chat message id
- example: 5
+ example: 2
responses:
'200':
description: Chat message ip address retrieved
diff --git a/public/openapi/write/topics/tid/thumbs.yaml b/public/openapi/write/topics/tid/thumbs.yaml
index 3817d2a5a3..5d28264266 100644
--- a/public/openapi/write/topics/tid/thumbs.yaml
+++ b/public/openapi/write/topics/tid/thumbs.yaml
@@ -83,55 +83,6 @@ post:
type: string
name:
type: string
-put:
- tags:
- - topics
- summary: migrate topic thumbnail
- description: This operation migrates a thumbnails from a topic or draft, to another tid or draft.
- parameters:
- - in: path
- name: tid
- schema:
- type: string
- required: true
- description: a valid topic id or draft uuid
- example: 1
- requestBody:
- required: true
- content:
- application/json:
- schema:
- type: object
- properties:
- tid:
- type: string
- description: a valid topic id or draft uuid
- example: '1'
- responses:
- '200':
- description: Topic thumbnails migrated
- content:
- application/json:
- schema:
- type: object
- properties:
- status:
- $ref: ../../../components/schemas/Status.yaml#/Status
- response:
- type: array
- description: A list of the topic thumbnails in the destination topic
- items:
- type: object
- properties:
- id:
- type: string
- name:
- type: string
- path:
- type: string
- url:
- type: string
- description: Path to a topic thumbnail
delete:
tags:
- topics
diff --git a/public/openapi/write/topics/tid/thumbs/order.yaml b/public/openapi/write/topics/tid/thumbs/order.yaml
index a0f1602bc8..a8acefbf0a 100644
--- a/public/openapi/write/topics/tid/thumbs/order.yaml
+++ b/public/openapi/write/topics/tid/thumbs/order.yaml
@@ -2,14 +2,14 @@ put:
tags:
- topics
summary: reorder topic thumbnail
- description: This operation sets the order for a topic thumbnail. It can handle either topics (if a valid `tid` is passed in), or drafts. A 404 is returned if the topic or draft does not actually contain that thumbnail path. Paths passed in should **not** contain the path to the uploads folder (`config.upload_url` on client side)
+ description: This operation sets the order for a topic thumbnail. A 404 is returned if the topic does not contain path. Paths passed in should **not** contain the path to the uploads folder (`config.upload_url` on client side)
parameters:
- in: path
name: tid
schema:
type: string
required: true
- description: a valid topic id or draft uuid
+ description: a valid topic id
example: 2
requestBody:
required: true
diff --git a/public/scss/admin/fonts.scss b/public/scss/admin/fonts.scss
index ba9988154d..d13da2cc7b 100644
--- a/public/scss/admin/fonts.scss
+++ b/public/scss/admin/fonts.scss
@@ -1,20 +1,24 @@
-@use "@fontsource/inter/scss/mixins" as Inter;
-@use "@fontsource/poppins/scss/mixins" as Poppins;
+@use "pkg:@fontsource-utils/scss" as fontsource;
+@use "pkg:@fontsource/inter/scss" as inter;
+@use "pkg:@fontsource/poppins/scss" as poppins;
$weights: $font-weight-light, $font-weight-normal, $font-weight-semibold, $font-weight-bold;
$subsets: (latin, latin-ext);
-@include Inter.faces(
- $weights: $weights,
- $subsets: $subsets,
- $display: fallback,
- $directory: "./plugins/core/inter"
+@include fontsource.faces(
+ $metadata: inter.$metadata,
+ $subsets: $subsets,
+ $weights: $weights,
+ $styles: all,
+ $directory: "./plugins/core/inter"
);
-@include Poppins.faces(
- $weights: $weights,
- $subsets: $subsets,
- $display: fallback,
- $directory: "./plugins/core/poppins"
+
+@include fontsource.faces(
+ $metadata: poppins.$metadata,
+ $subsets: $subsets,
+ $weights: $weights,
+ $styles: all,
+ $directory: "./plugins/core/poppins"
);
.ff-base { font-family: $font-family-base !important; }
diff --git a/public/src/admin/admin.js b/public/src/admin/admin.js
index e0d4e49b7f..7e68c65b8c 100644
--- a/public/src/admin/admin.js
+++ b/public/src/admin/admin.js
@@ -9,43 +9,12 @@ require('../../scripts-admin');
app.onDomReady();
(function () {
- let logoutTimer = 0;
- let logoutMessage;
- function startLogoutTimer() {
- if (app.config.adminReloginDuration <= 0) {
- return;
- }
- if (logoutTimer) {
- clearTimeout(logoutTimer);
- }
- // pre-translate language string gh#9046
- if (!logoutMessage) {
- require(['translator'], function (translator) {
- translator.translate('[[login:logged-out-due-to-inactivity]]', function (translated) {
- logoutMessage = translated;
- });
- });
- }
-
- logoutTimer = setTimeout(function () {
- require(['bootbox'], function (bootbox) {
- bootbox.alert({
- closeButton: false,
- message: logoutMessage,
- callback: function () {
- window.location.reload();
- },
- });
- });
- }, 3600000);
- }
-
- require(['hooks', 'admin/settings'], (hooks, Settings) => {
+ require(['hooks', 'admin/settings', 'admin/modules/relogin-timer'], (hooks, Settings, reloginTimer) => {
hooks.on('action:ajaxify.end', (data) => {
updatePageTitle(data.url);
setupRestartLinks();
showCorrectNavTab();
- startLogoutTimer();
+ reloginTimer.start(app.config.adminReloginDuration);
$('[data-bs-toggle="tooltip"]').tooltip({
animation: false,
@@ -59,6 +28,7 @@ app.onDomReady();
Settings.populateTOC();
}
});
+
hooks.on('action:ajaxify.start', function () {
require(['bootstrap'], function (boostrap) {
const offcanvas = boostrap.Offcanvas.getInstance('#offcanvas');
diff --git a/public/src/admin/dashboard.js b/public/src/admin/dashboard.js
index a2b624c5a8..5fa55a7e0d 100644
--- a/public/src/admin/dashboard.js
+++ b/public/src/admin/dashboard.js
@@ -165,6 +165,7 @@ function setupGraphs(callback) {
t.translateKey('admin/dashboard:graphs.page-views-registered', []),
t.translateKey('admin/dashboard:graphs.page-views-guest', []),
t.translateKey('admin/dashboard:graphs.page-views-bot', []),
+ t.translateKey('admin/dashboard:graphs.page-views-ap', []),
t.translateKey('admin/dashboard:graphs.unique-visitors', []),
t.translateKey('admin/dashboard:graphs.registered-users', []),
t.translateKey('admin/dashboard:graphs.guest-users', []),
@@ -231,6 +232,18 @@ function setupGraphs(callback) {
fill: 'origin',
tension: tension,
backgroundColor: 'rgba(151,187,205,0.2)',
+ borderColor: 'rgba(110, 187, 132, 1)',
+ pointBackgroundColor: 'rgba(110, 187, 132, 1)',
+ pointHoverBackgroundColor: 'rgba(110, 187, 132, 1)',
+ pointBorderColor: '#fff',
+ pointHoverBorderColor: 'rgba(110, 187, 132, 1)',
+ data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ },
+ {
+ label: translations[5],
+ fill: 'origin',
+ tension: tension,
+ backgroundColor: 'rgba(151,187,205,0.2)',
borderColor: 'rgba(151,187,205,1)',
pointBackgroundColor: 'rgba(151,187,205,1)',
pointHoverBackgroundColor: 'rgba(151,187,205,1)',
@@ -247,7 +260,8 @@ function setupGraphs(callback) {
data.datasets[1].yAxisID = 'left-y-axis';
data.datasets[2].yAxisID = 'left-y-axis';
data.datasets[3].yAxisID = 'left-y-axis';
- data.datasets[4].yAxisID = 'right-y-axis';
+ data.datasets[4].yAxisID = 'left-y-axis';
+ data.datasets[5].yAxisID = 'right-y-axis';
graphs.traffic = new Chart(trafficCtx, {
type: 'line',
@@ -269,7 +283,7 @@ function setupGraphs(callback) {
type: 'linear',
title: {
display: true,
- text: translations[4],
+ text: translations[5],
},
beginAtZero: true,
},
@@ -446,7 +460,8 @@ function updateTrafficGraph(units, until, amount) {
graphs.traffic.data.datasets[1].data = data.pageviewsRegistered;
graphs.traffic.data.datasets[2].data = data.pageviewsGuest;
graphs.traffic.data.datasets[3].data = data.pageviewsBot;
- graphs.traffic.data.datasets[4].data = data.uniqueVisitors;
+ graphs.traffic.data.datasets[4].data = data.appageviews;
+ graphs.traffic.data.datasets[5].data = data.uniqueVisitors;
graphs.traffic.data.labels = graphs.traffic.data.xLabels;
graphs.traffic.update();
diff --git a/public/src/admin/manage/categories.js b/public/src/admin/manage/categories.js
index 7342d0c1de..c50edfc3df 100644
--- a/public/src/admin/manage/categories.js
+++ b/public/src/admin/manage/categories.js
@@ -27,6 +27,7 @@ define('admin/manage/categories', [
Categories.render(ajaxify.data.categoriesTree);
$('button[data-action="create"]').on('click', Categories.throwCreateModal);
+ $('button[data-action="add"]').on('click', Categories.throwAddModal);
// Enable/Disable toggle events
$('.categories').on('click', '.category-tools [data-action="toggle"]', function () {
@@ -68,7 +69,7 @@ define('admin/manage/categories', [
if (val && cid) {
const modified = {};
modified[cid] = { order: Math.max(1, parseInt(val, 10)) };
- api.put('/categories/' + cid, modified[cid]).then(function () {
+ api.put('/categories/' + encodeURIComponent(cid), modified[cid]).then(function () {
ajaxify.refresh();
}).catch(alerts.error);
} else {
@@ -80,6 +81,8 @@ define('admin/manage/categories', [
});
});
+ $('.categories').on('click', 'a[data-action="remove"]', Categories.removeCategory);
+
$('#toggle-collapse-all').on('click', function () {
const $this = $(this);
const isCollapsed = parseInt($this.attr('data-collapsed'), 10) === 1;
@@ -151,6 +154,38 @@ define('admin/manage/categories', [
});
};
+ Categories.throwAddModal = function () {
+ Benchpress.render('admin/partials/categories/add', {}).then(function (html) {
+ const modal = bootbox.dialog({
+ title: '[[admin/manage/categories:alert.add]]',
+ message: html,
+ buttons: {
+ save: {
+ label: '[[global:save]]',
+ className: 'btn-primary',
+ callback: submit,
+ },
+ },
+ });
+
+ function submit() {
+ const formData = modal.find('form').serializeObject();
+ api.post('/api/admin/manage/categories', formData).then(() => {
+ ajaxify.refresh();
+ modal.modal('hide');
+ }).catch(alerts.error);
+ return false;
+ }
+
+ modal.find('form').on('submit', submit);
+ });
+ };
+
+ Categories.removeCategory = function () {
+ const cid = this.getAttribute('data-cid');
+ api.del(`/api/admin/manage/categories/${encodeURIComponent(cid)}`).then(ajaxify.refresh);
+ };
+
Categories.create = function (payload) {
api.post('/categories', payload, function (err, data) {
if (err) {
@@ -187,7 +222,7 @@ define('admin/manage/categories', [
Categories.toggle = function (cids, disabled) {
const listEl = document.querySelector('.categories [data-cid="0"]');
- Promise.all(cids.map(cid => api.put('/categories/' + cid, {
+ Promise.all(cids.map(cid => api.put('/categories/' + encodeURIComponent(cid), {
disabled: disabled ? 1 : 0,
}).then(() => {
const categoryEl = listEl.querySelector(`li[data-cid="${cid}"]`);
@@ -239,7 +274,7 @@ define('admin/manage/categories', [
}
newCategoryId = -1;
- api.put('/categories/' + cid, modified[cid]).catch(alerts.error);
+ api.put('/categories/' + encodeURIComponent(cid), modified[cid]).catch(alerts.error);
}
}
diff --git a/public/src/admin/modules/relogin-timer.js b/public/src/admin/modules/relogin-timer.js
new file mode 100644
index 0000000000..f8504b51a2
--- /dev/null
+++ b/public/src/admin/modules/relogin-timer.js
@@ -0,0 +1,36 @@
+import { translate } from 'translator';
+import { alert as bootboxAlert } from 'bootbox';
+
+let logoutTimer = 0;
+let logoutMessage;
+
+export function start(adminReloginDuration) {
+ clearTimer();
+ if (adminReloginDuration <= 0) {
+ return;
+ }
+
+ // pre-translate language string gh#9046
+ if (!logoutMessage) {
+ translate('[[login:logged-out-due-to-inactivity]]', function (translated) {
+ logoutMessage = translated;
+ });
+ }
+
+ const timeoutMs = adminReloginDuration * 60000;
+ logoutTimer = setTimeout(function () {
+ bootboxAlert({
+ closeButton: false,
+ message: logoutMessage,
+ callback: function () {
+ window.location.reload();
+ },
+ });
+ }, timeoutMs);
+}
+
+function clearTimer() {
+ if (logoutTimer) {
+ clearTimeout(logoutTimer);
+ }
+}
\ No newline at end of file
diff --git a/public/src/admin/settings.js b/public/src/admin/settings.js
index 247f9646b2..f189718426 100644
--- a/public/src/admin/settings.js
+++ b/public/src/admin/settings.js
@@ -2,8 +2,8 @@
define('admin/settings', [
- 'uploader', 'mousetrap', 'hooks', 'alerts', 'settings', 'bootstrap',
-], function (uploader, mousetrap, hooks, alerts, settings, bootstrap) {
+ 'uploader', 'mousetrap', 'hooks', 'alerts', 'settings', 'bootstrap', 'admin/modules/relogin-timer',
+], function (uploader, mousetrap, hooks, alerts, settings, bootstrap, reloginTimer) {
const Settings = {};
Settings.populateTOC = function () {
@@ -217,6 +217,9 @@ define('admin/settings', [
for (const [field, value] of Object.entries(data)) {
app.config[field] = value;
+ if (field === 'adminReloginDuration') {
+ reloginTimer.start(parseInt(value, 10));
+ }
}
callback();
diff --git a/public/src/admin/settings/activitypub.js b/public/src/admin/settings/activitypub.js
new file mode 100644
index 0000000000..8c79d95b4b
--- /dev/null
+++ b/public/src/admin/settings/activitypub.js
@@ -0,0 +1,143 @@
+'use strict';
+
+define('admin/settings/activitypub', [
+ 'benchpress',
+ 'bootbox',
+ 'categorySelector',
+ 'api',
+ 'alerts',
+], function (Benchpress, bootbox, categorySelector, api, alerts) {
+ const Module = {};
+
+ Module.init = function () {
+ const rulesEl = document.getElementById('rules');
+ if (rulesEl) {
+ rulesEl.addEventListener('click', (e) => {
+ const subselector = e.target.closest('[data-action]');
+ if (subselector) {
+ const action = subselector.getAttribute('data-action');
+ switch (action) {
+ case 'rules.add': {
+ Module.throwRulesModal();
+ break;
+ }
+
+ case 'rules.delete': {
+ const rid = subselector.closest('tr').getAttribute('data-rid');
+ api.del(`/admin/activitypub/rules/${rid}`, {}).then(async (data) => {
+ const html = await Benchpress.render('admin/settings/activitypub', { rules: data }, 'rules');
+ const tbodyEl = document.querySelector('#rules tbody');
+ if (tbodyEl) {
+ tbodyEl.innerHTML = html;
+ }
+ }).catch(alerts.error);
+ }
+ }
+ }
+ });
+ }
+
+ const relaysEl = document.getElementById('relays');
+ if (relaysEl) {
+ relaysEl.addEventListener('click', (e) => {
+ const subselector = e.target.closest('[data-action]');
+ if (subselector) {
+ const action = subselector.getAttribute('data-action');
+ switch (action) {
+ case 'relays.add': {
+ Module.throwRelaysModal();
+ break;
+ }
+
+ case 'relays.remove': {
+ const url = subselector.closest('tr').getAttribute('data-url');
+ api.del(`/admin/activitypub/relays/${encodeURIComponent(url)}`, {}).then(async (data) => {
+ const html = await app.parseAndTranslate('admin/settings/activitypub', 'relays', { relays: data });
+ const tbodyEl = document.querySelector('#relays tbody');
+ if (tbodyEl) {
+ $(tbodyEl).html(html);
+ }
+ }).catch(alerts.error);
+ }
+ }
+ }
+ });
+ }
+ };
+
+ Module.throwRulesModal = function () {
+ Benchpress.render('admin/partials/activitypub/rules', {}).then(function (html) {
+ const submit = function () {
+ const formEl = modal.find('form').get(0);
+ const payload = Object.fromEntries(new FormData(formEl));
+
+ api.post('/admin/activitypub/rules', payload).then(async (data) => {
+ const html = await Benchpress.render('admin/settings/activitypub', { rules: data }, 'rules');
+ const tbodyEl = document.querySelector('#rules tbody');
+ if (tbodyEl) {
+ tbodyEl.innerHTML = html;
+ }
+ }).catch(alerts.error);
+ };
+ const modal = bootbox.dialog({
+ title: '[[admin/settings/activitypub:rules.add]]',
+ message: html,
+ buttons: {
+ save: {
+ label: '[[global:save]]',
+ className: 'btn-primary',
+ callback: submit,
+ },
+ },
+ });
+
+ modal.on('shown.bs.modal', function () {
+ modal.find('input').focus();
+ });
+
+ // category switcher
+ categorySelector.init(modal.find('[component="category-selector"]'), {
+ onSelect: function (selectedCategory) {
+ modal.find('[name="cid"]').val(selectedCategory.cid);
+ },
+ cacheList: false,
+ showLinks: true,
+ template: 'admin/partials/category/selector-dropdown-right',
+ });
+ });
+ };
+
+ Module.throwRelaysModal = function () {
+ Benchpress.render('admin/partials/activitypub/relays', {}).then(function (html) {
+ const submit = function () {
+ const formEl = modal.find('form').get(0);
+ const payload = Object.fromEntries(new FormData(formEl));
+
+ api.post('/admin/activitypub/relays', payload).then(async (data) => {
+ const html = await app.parseAndTranslate('admin/settings/activitypub', 'relays', { relays: data });
+ const tbodyEl = document.querySelector('#relays tbody');
+ if (tbodyEl) {
+ $(tbodyEl).html(html);
+ }
+ }).catch(alerts.error);
+ };
+ const modal = bootbox.dialog({
+ title: '[[admin/settings/activitypub:relays.add]]',
+ message: html,
+ buttons: {
+ save: {
+ label: '[[global:save]]',
+ className: 'btn-primary',
+ callback: submit,
+ },
+ },
+ });
+
+ modal.on('shown.bs.modal', function () {
+ modal.find('input').focus();
+ });
+ });
+ };
+
+ return Module;
+});
diff --git a/public/src/client/chats/manage.js b/public/src/client/chats/manage.js
index 2ab92c4295..f2e5cbc04c 100644
--- a/public/src/client/chats/manage.js
+++ b/public/src/client/chats/manage.js
@@ -75,12 +75,16 @@ define('forum/chats/manage', [
modal.find('[component="chat/manage/save"]').on('click', () => {
const notifSettingEl = modal.find('[component="chat/room/notification/setting"]');
+ const joinLeaveMessagesEl = modal.find('[component="chat/room/join-leave-messages"]');
+
api.put(`/chats/${roomId}`, {
groups: modal.find('[component="chat/room/groups"]').val(),
notificationSetting: notifSettingEl.val(),
+ joinLeaveMessages: joinLeaveMessagesEl.is(':checked') ? 1 : 0,
}).then((payload) => {
ajaxify.data.groups = payload.groups;
ajaxify.data.notificationSetting = payload.notificationSetting;
+ ajaxify.data.joinLeaveMessages = payload.joinLeaveMessages;
const roomDefaultOption = payload.notificationOptions[0];
$('[component="chat/notification/setting"] [data-icon]').first().attr(
'data-icon', roomDefaultOption.icon
diff --git a/public/src/client/notifications.js b/public/src/client/notifications.js
index d42b23d51e..2b82174ff4 100644
--- a/public/src/client/notifications.js
+++ b/public/src/client/notifications.js
@@ -13,7 +13,7 @@ define('forum/notifications', ['components', 'notifications'], function (compone
notifications.handleUnreadButton(listEl);
components.get('notifications/mark_all').on('click', function () {
- notifications.markAllRead(function () {
+ notifications.markAllRead(ajaxify.data.selectedFilter.filter, function () {
components.get('notifications/item').removeClass('unread');
});
});
diff --git a/public/src/client/topic.js b/public/src/client/topic.js
index 43a64a9fa3..cce6f1f99a 100644
--- a/public/src/client/topic.js
+++ b/public/src/client/topic.js
@@ -202,7 +202,7 @@ define('forum/topic', [
$('[component="topic/thumb/select"]').removeClass('border-primary');
$(this).addClass('border-primary');
$('[component="topic/thumb/current"]')
- .attr('src', $(this).attr('src'));
+ .attr('src', $(this).find('img').attr('src'));
});
}
});
diff --git a/public/src/client/topic/events.js b/public/src/client/topic/events.js
index 4a8a36a768..10c5d54cf2 100644
--- a/public/src/client/topic/events.js
+++ b/public/src/client/topic/events.js
@@ -162,7 +162,7 @@ define('forum/topic/events', [
translator.unescape(data.post.content)
);
parentEl.find('img:not(.not-responsive)').addClass('img-fluid');
- parentEl.find('[component="post/parent/content]" img:not(.emoji)').each(function () {
+ parentEl.find('[component="post/parent/content"] img:not(.emoji)').each(function () {
images.wrapImageInLink($(this));
});
}
@@ -176,6 +176,12 @@ define('forum/topic/events', [
});
}
+ if (data.topic.thumbsupdated) {
+ require(['topicThumbs'], function (topicThumbs) {
+ topicThumbs.updateTopicThumbs(data.topic.tid);
+ });
+ }
+
postTools.removeMenu(components.get('post', 'pid', data.post.pid));
}
diff --git a/public/src/modules/helpers.common.js b/public/src/modules/helpers.common.js
index 183c1bbb70..896d0b485b 100644
--- a/public/src/modules/helpers.common.js
+++ b/public/src/modules/helpers.common.js
@@ -34,6 +34,7 @@ module.exports = function (utils, Benchpress, relative_path) {
humanReadableNumber,
formattedNumber,
txEscape,
+ uploadBasename,
generatePlaceholderWave,
register,
__escape: identity,
@@ -379,6 +380,12 @@ module.exports = function (utils, Benchpress, relative_path) {
return String(text).replace(/%/g, '%').replace(/,/g, ',');
}
+ function uploadBasename(str, sep = '/') {
+ const hasTimestampPrefix = /^\d+-/;
+ const name = str.substr(str.lastIndexOf(sep) + 1);
+ return hasTimestampPrefix.test(name) ? name.slice(14) : name;
+ }
+
function generatePlaceholderWave(items) {
const html = items.map((i) => {
if (i === 'divider') {
diff --git a/public/src/modules/notifications.js b/public/src/modules/notifications.js
index 891ca17fe1..f4806eafdf 100644
--- a/public/src/modules/notifications.js
+++ b/public/src/modules/notifications.js
@@ -59,7 +59,9 @@ define('notifications', [
const nid = notifEl.attr('data-nid');
markNotification(nid, true);
});
- components.get('notifications').on('click', '.mark-all-read', Notifications.markAllRead);
+ components.get('notifications').on('click', '.mark-all-read', () => {
+ Notifications.markAllRead();
+ });
Notifications.handleUnreadButton(notifList);
@@ -105,7 +107,7 @@ define('notifications', [
});
if (!unreadNotifs[notifData.nid]) {
- unreadNotifs[notifData.nid] = true;
+ unreadNotifs[notifData.nid] = notifData;
}
};
@@ -165,12 +167,21 @@ define('notifications', [
}
};
- Notifications.markAllRead = function () {
- socket.emit('notifications.markAllRead', function (err) {
+ Notifications.markAllRead = function (filter = '') {
+ socket.emit('notifications.markAllRead', { filter }, function (err) {
if (err) {
alerts.error(err);
}
- unreadNotifs = {};
+ if (filter) {
+ Object.keys(unreadNotifs).forEach(nid => {
+ if (unreadNotifs[nid].type === filter) {
+ delete unreadNotifs[nid];
+ }
+ });
+ } else {
+ unreadNotifs = {};
+ }
+
const notifEls = $('[component="notifications/list"] [data-nid]');
notifEls.removeClass('unread');
notifEls.find('.mark-read .unread').addClass('hidden');
diff --git a/public/src/modules/topicThumbs.js b/public/src/modules/topicThumbs.js
index 70c13218d3..340d856ded 100644
--- a/public/src/modules/topicThumbs.js
+++ b/public/src/modules/topicThumbs.js
@@ -7,23 +7,27 @@ define('topicThumbs', [
Thumbs.get = id => api.get(`/topics/${id}/thumbs`, { thumbsOnly: 1 });
- Thumbs.getByPid = pid => api.get(`/posts/${encodeURIComponent(pid)}`, {}).then(post => Thumbs.get(post.tid));
-
Thumbs.delete = (id, path) => api.del(`/topics/${id}/thumbs`, {
path: path,
});
+ Thumbs.updateTopicThumbs = async (tid) => {
+ const thumbs = await Thumbs.get(tid);
+ const html = await app.parseAndTranslate('partials/topic/thumbs', { thumbs });
+ $('[component="topic/thumb/list"]').html(html);
+ };
+
Thumbs.deleteAll = (id) => {
Thumbs.get(id).then((thumbs) => {
Promise.all(thumbs.map(thumb => Thumbs.delete(id, thumb.url)));
});
};
- Thumbs.upload = id => new Promise((resolve) => {
+ Thumbs.upload = () => new Promise((resolve) => {
uploader.show({
title: '[[topic:composer.thumb-title]]',
method: 'put',
- route: config.relative_path + `/api/v3/topics/${id}/thumbs`,
+ route: config.relative_path + `/api/topic/thumb/upload`,
}, function (url) {
resolve(url);
});
@@ -32,24 +36,16 @@ define('topicThumbs', [
Thumbs.modal = {};
Thumbs.modal.open = function (payload) {
- const { id, pid } = payload;
+ const { id, postData } = payload;
let { modal } = payload;
- let numThumbs;
+ const thumbs = postData.thumbs || [];
return new Promise((resolve) => {
- Promise.all([
- Thumbs.get(id),
- pid ? Thumbs.getByPid(pid) : [],
- ]).then(results => new Promise((resolve) => {
- const thumbs = results.reduce((memo, cur) => memo.concat(cur));
- numThumbs = thumbs.length;
-
- resolve(thumbs);
- })).then(thumbs => Benchpress.render('modals/topic-thumbs', { thumbs })).then((html) => {
+ Benchpress.render('modals/topic-thumbs', { thumbs }).then((html) => {
if (modal) {
translator.translate(html, function (translated) {
modal.find('.bootbox-body').html(translated);
- Thumbs.modal.handleSort({ modal, numThumbs });
+ Thumbs.modal.handleSort({ modal, thumbs });
});
} else {
modal = bootbox.dialog({
@@ -62,7 +58,11 @@ define('topicThumbs', [
label: ' [[modules:thumbs.modal.add]]',
className: 'btn-success',
callback: () => {
- Thumbs.upload(id).then(() => {
+ Thumbs.upload().then((thumbUrl) => {
+ postData.thumbs.push(
+ thumbUrl.replace(new RegExp(`^${config.upload_url}`), '')
+ );
+
Thumbs.modal.open({ ...payload, modal });
require(['composer'], (composer) => {
composer.updateThumbCount(id, $(`[component="composer"][data-uuid="${id}"]`));
@@ -79,7 +79,7 @@ define('topicThumbs', [
},
});
Thumbs.modal.handleDelete({ ...payload, modal });
- Thumbs.modal.handleSort({ modal, numThumbs });
+ Thumbs.modal.handleSort({ modal, thumbs });
}
});
});
@@ -94,42 +94,42 @@ define('topicThumbs', [
if (!ok) {
return;
}
-
- const id = ev.target.closest('[data-id]').getAttribute('data-id');
const path = ev.target.closest('[data-path]').getAttribute('data-path');
- api.del(`/topics/${id}/thumbs`, {
- path: path,
- }).then(() => {
+ const postData = payload.postData;
+ if (postData && postData.thumbs && postData.thumbs.includes(path)) {
+ postData.thumbs = postData.thumbs.filter(thumb => thumb !== path);
Thumbs.modal.open(payload);
require(['composer'], (composer) => {
composer.updateThumbCount(uuid, $(`[component="composer"][data-uuid="${uuid}"]`));
});
- }).catch(alerts.error);
+ }
});
}
});
};
- Thumbs.modal.handleSort = ({ modal, numThumbs }) => {
- if (numThumbs > 1) {
+ Thumbs.modal.handleSort = ({ modal, thumbs }) => {
+ if (thumbs.length > 1) {
const selectorEl = modal.find('.topic-thumbs-modal');
selectorEl.sortable({
- items: '[data-id]',
+ items: '[data-path]',
+ });
+ selectorEl.on('sortupdate', function () {
+ if (!thumbs) return;
+ const newOrder = [];
+ selectorEl.find('[data-path]').each(function () {
+ const path = $(this).attr('data-path');
+ const thumb = thumbs.find(t => t === path);
+ if (thumb) {
+ newOrder.push(thumb);
+ }
+ });
+ // Mutate thumbs array in place
+ thumbs.length = 0;
+ Array.prototype.push.apply(thumbs, newOrder);
});
- selectorEl.on('sortupdate', Thumbs.modal.handleSortChange);
}
};
- Thumbs.modal.handleSortChange = (ev, ui) => {
- const items = ui.item.get(0).parentNode.querySelectorAll('[data-id]');
- Array.from(items).forEach((el, order) => {
- const id = el.getAttribute('data-id');
- let path = el.getAttribute('data-path');
- path = path.replace(new RegExp(`^${config.upload_url}`), '');
-
- api.put(`/topics/${id}/thumbs/order`, { path, order }).catch(alerts.error);
- });
- };
-
return Thumbs;
});
diff --git a/public/src/utils.common.js b/public/src/utils.common.js
index 0014b3ae85..4ecf17e4f2 100644
--- a/public/src/utils.common.js
+++ b/public/src/utils.common.js
@@ -570,10 +570,7 @@ const utils = {
params: function (options = {}) {
let url;
if (options.url && !options.url.startsWith('http')) {
- // relative path passed in
- options.url = options.url.replace(new RegExp(`/?${config.relative_path.slice(1)}/`, 'g'), '');
- url = new URL(document.location);
- url.pathname = options.url;
+ url = new URL(options.url, 'http://dummybase');
} else {
url = new URL(options.url || document.location);
}
diff --git a/src/activitypub/actors.js b/src/activitypub/actors.js
index fdaed03c2c..753310d918 100644
--- a/src/activitypub/actors.js
+++ b/src/activitypub/actors.js
@@ -14,6 +14,7 @@ const utils = require('../utils');
const TTLCache = require('../cache/ttl');
const failedWebfingerCache = TTLCache({
+ name: 'ap-failed-webfinger-cache',
max: 5000,
ttl: 1000 * 60 * 10, // 10 minutes
});
@@ -126,7 +127,6 @@ Actors.assert = async (ids, options = {}) => {
try {
activitypub.helpers.log(`[activitypub/actors] Processing ${id}`);
const actor = (typeof id === 'object' && id.hasOwnProperty('id')) ? id : await activitypub.get('uid', 0, id, { cache: process.env.CI === 'true' });
-
// webfinger backreference check
const { hostname: domain } = new URL(id);
const { actorUri: canonicalId } = await activitypub.helpers.query(`${actor.preferredUsername}@${domain}`);
@@ -134,6 +134,7 @@ Actors.assert = async (ids, options = {}) => {
return null;
}
+
let typeOk = false;
if (Array.isArray(actor.type)) {
typeOk = actor.type.some(type => activitypub._constants.acceptableActorTypes.has(type));
@@ -146,7 +147,7 @@ Actors.assert = async (ids, options = {}) => {
categories.add(actor.id);
}
}
-
+
if (
!typeOk ||
!activitypub._constants.requiredActorProps.every(prop => actor.hasOwnProperty(prop))
@@ -166,7 +167,6 @@ Actors.assert = async (ids, options = {}) => {
// no action required
activitypub.helpers.log(`[activitypub/actor.assert] Unable to retrieve follower counts for ${actor.id}`);
}
-
// Save url for backreference
const url = Array.isArray(actor.url) ? actor.url.shift() : actor.url;
if (url && url !== actor.id) {
@@ -608,6 +608,8 @@ Actors.prune = async () => {
let deletionCountNonExisting = 0;
let notDeletedDueToLocalContent = 0;
const preservedIds = [];
+ const cleanupUids = [];
+
await batch.processArray(ids, async (ids) => {
const exists = await Promise.all([
db.exists(ids.map(id => `userRemote:${id}`)),
@@ -654,7 +656,10 @@ Actors.prune = async () => {
await user.deleteAccount(uid);
deletionCount += 1;
} catch (err) {
- winston.error(err.stack);
+ winston.error(`Failed to delete user with uid ${uid}: ${err.stack}`);
+ if (err.message === '[[error:no-user]]') {
+ cleanupUids.push(uid);
+ }
}
} else {
notDeletedDueToLocalContent += 1;
@@ -662,6 +667,14 @@ Actors.prune = async () => {
}
}));
+ if (cleanupUids.length) {
+ await Promise.all([
+ db.sortedSetRemove('usersRemote:lastCrawled', cleanupUids),
+ db.deleteAll(cleanupUids.map(uid => `userRemote:${uid}`)),
+ ]);
+ winston.info(`[actors/prune] Cleaned up ${cleanupUids.length} remote users that were not found in the database.`);
+ }
+
// Remote categories
let counts = await categories.getCategoriesFields(cids, ['topic_count']);
counts = counts.map(count => count.topic_count);
diff --git a/src/activitypub/contexts.js b/src/activitypub/contexts.js
index 0748e42ece..9d6c948a5e 100644
--- a/src/activitypub/contexts.js
+++ b/src/activitypub/contexts.js
@@ -113,19 +113,6 @@ Contexts.getItems = async (uid, id, options) => {
return chain;
}
- // Handle special case where originating object is not actually part of the context collection
- const inputId = activitypub.helpers.isUri(options.input) ? options.input : options.input.id;
- const inCollection = Array.from(chain).map(p => p.pid).includes(inputId);
- if (!inCollection) {
- const item = activitypub.helpers.isUri(options.input) ?
- await parseString(uid, options.input) :
- await parseItem(uid, options.input);
-
- if (item) {
- chain.add(item);
- }
- }
-
return chain;
};
diff --git a/src/activitypub/feps.js b/src/activitypub/feps.js
index 7b7816516d..b8bf765bef 100644
--- a/src/activitypub/feps.js
+++ b/src/activitypub/feps.js
@@ -18,21 +18,29 @@ Feps.announce = async function announce(id, activity) {
return;
}
+ let relays = await activitypub.relays.list();
+ relays = relays.reduce((memo, { state, url }) => {
+ if (state === 2) {
+ memo.push(url);
+ }
+ return memo;
+ }, []);
const followers = await activitypub.notes.getCategoryFollowers(cid);
- if (!followers.length) {
+ const targets = relays.concat(followers);
+ if (!targets.length) {
return;
}
const { actor } = activity;
if (actor && !actor.startsWith(nconf.get('url'))) {
- followers.unshift(actor);
+ targets.unshift(actor);
}
const now = Date.now();
if (activity.type === 'Create') {
const isMain = await posts.isMain(localId || id);
if (isMain) {
- activitypub.helpers.log(`[activitypub/inbox.announce(1b12)] Announcing plain object (${activity.id}) to followers of cid ${cid}`);
- await activitypub.send('cid', cid, followers, {
+ activitypub.helpers.log(`[activitypub/inbox.announce(1b12)] Announcing plain object (${activity.id}) to followers of cid ${cid} and ${relays.length} relays`);
+ await activitypub.send('cid', cid, targets, {
id: `${nconf.get('url')}/post/${encodeURIComponent(id)}#activity/announce/${now}`,
type: 'Announce',
actor: `${nconf.get('url')}/category/${cid}`,
@@ -43,8 +51,8 @@ Feps.announce = async function announce(id, activity) {
}
}
- activitypub.helpers.log(`[activitypub/inbox.announce(1b12)] Announcing ${activity.type} (${activity.id}) to followers of cid ${cid}`);
- await activitypub.send('cid', cid, followers, {
+ activitypub.helpers.log(`[activitypub/inbox.announce(1b12)] Announcing ${activity.type} (${activity.id}) to followers of cid ${cid} and ${relays.length} relays`);
+ await activitypub.send('cid', cid, targets, {
id: `${nconf.get('url')}/post/${encodeURIComponent(id)}#activity/announce/${now + 1}`,
type: 'Announce',
actor: `${nconf.get('url')}/category/${cid}`,
diff --git a/src/activitypub/helpers.js b/src/activitypub/helpers.js
index f7708b9bd9..e6eb2e1c08 100644
--- a/src/activitypub/helpers.js
+++ b/src/activitypub/helpers.js
@@ -21,6 +21,7 @@ const activitypub = require('.');
const webfingerRegex = /^(@|acct:)?[\w-.]+@.+$/;
const webfingerCache = ttl({
+ name: 'ap-webfinger-cache',
max: 5000,
ttl: 1000 * 60 * 60 * 24, // 24 hours
});
@@ -112,6 +113,7 @@ Helpers.query = async (id) => {
headers: {
accept: 'application/jrd+json',
},
+ timeout: 5000,
}));
} catch (e) {
return false;
@@ -128,9 +130,12 @@ Helpers.query = async (id) => {
({ href: actorUri } = actorUri);
}
- const { subject, publicKey } = body;
+ let { subject, publicKey } = body;
+ // Fix missing scheme
+ if (!subject.startsWith('acct:') && !subject.startsWith('did:')) {
+ subject = `acct:${subject}`;
+ }
const payload = { subject, username, hostname, actorUri, publicKey };
-
const claimedId = new URL(subject).pathname;
webfingerCache.set(claimedId, payload);
if (claimedId !== id) {
@@ -192,6 +197,9 @@ Helpers.resolveLocalId = async (input) => {
case 'message':
return { type: 'message', id: value, ...activityData };
+
+ case 'actor':
+ return { type: 'application', id: null };
}
return { type: null, id: null, ...activityData };
diff --git a/src/activitypub/inbox.js b/src/activitypub/inbox.js
index ec2f9d7fb0..754720f208 100644
--- a/src/activitypub/inbox.js
+++ b/src/activitypub/inbox.js
@@ -288,6 +288,9 @@ inbox.announce = async (req) => {
cid = actor;
}
+ // Received via relay?
+ const fromRelay = await activitypub.relays.is(actor);
+
switch(true) {
case object.type === 'Like': {
const id = object.object.id || object.object;
@@ -333,7 +336,7 @@ inbox.announce = async (req) => {
socketHelpers.sendNotificationToPostOwner(pid, actor, 'announce', 'notifications:activitypub.announce');
} else { // Remote object
// Follower check
- if (!cid) {
+ if (!fromRelay && !cid) {
const { followers } = await activitypub.actors.getLocalFollowCounts(actor);
if (!followers) {
winston.verbose(`[activitypub/inbox.announce] Rejecting ${object.id} via ${actor} due to no followers`);
@@ -367,9 +370,12 @@ inbox.announce = async (req) => {
inbox.follow = async (req) => {
const { actor, object, id: followId } = req.body;
+
// Sanity checks
const { type, id } = await helpers.resolveLocalId(object.id);
- if (!['category', 'user'].includes(type)) {
+ if (type === 'application') {
+ return activitypub.relays.handshake(req.body);
+ } else if (!['category', 'user'].includes(type)) {
throw new Error('[[error:activitypub.invalid-id]]');
}
@@ -454,7 +460,9 @@ inbox.accept = async (req) => {
const { type } = object;
const { type: localType, id } = await helpers.resolveLocalId(object.actor);
- if (!['user', 'category'].includes(localType)) {
+ if (object.id === `${nconf.get('url')}/actor`) {
+ return activitypub.relays.handshake(req.body);
+ } else if (!['user', 'category'].includes(localType)) {
throw new Error('[[error:invalid-data]]');
}
@@ -617,6 +625,8 @@ inbox.reject = async (req) => {
const queueId = `${type}:${id}:${hostname}`;
// stop retrying rejected requests
- clearTimeout(activitypub.retryQueue.get(queueId));
- activitypub.retryQueue.delete(queueId);
+ await Promise.all([
+ db.sortedSetRemove('ap:retry:queue', queueId),
+ db.delete(`ap:retry:queue:${queueId}`),
+ ]);
};
diff --git a/src/activitypub/index.js b/src/activitypub/index.js
index 926f8d0754..4c1f3eee69 100644
--- a/src/activitypub/index.js
+++ b/src/activitypub/index.js
@@ -14,20 +14,22 @@ const messaging = require('../messaging');
const user = require('../user');
const utils = require('../utils');
const ttl = require('../cache/ttl');
-const lru = require('../cache/lru');
const batch = require('../batch');
-const pubsub = require('../pubsub');
const analytics = require('../analytics');
+const crypto = require('crypto');
const requestCache = ttl({
+ name: 'ap-request-cache',
max: 5000,
ttl: 1000 * 60 * 5, // 5 minutes
});
const probeCache = ttl({
+ name: 'ap-probe-cache',
max: 500,
ttl: 1000 * 60 * 60, // 1 hour
});
const probeRateLimit = ttl({
+ name: 'ap-probe-rate-limit-cache',
ttl: 1000 * 3, // 3 seconds
});
@@ -45,7 +47,7 @@ ActivityPub._constants = Object.freeze({
],
acceptableActorTypes: new Set(['Application', 'Organization', 'Person', 'Service']),
acceptableGroupTypes: new Set(['Group']),
- requiredActorProps: ['inbox', 'outbox'],
+ requiredActorProps: ['inbox'],
acceptedProtocols: ['https', ...(process.env.CI === 'true' ? ['http'] : [])],
acceptable: {
customFields: new Set(['PropertyValue', 'Link', 'Note']),
@@ -63,31 +65,35 @@ ActivityPub.contexts = require('./contexts');
ActivityPub.actors = require('./actors');
ActivityPub.instances = require('./instances');
ActivityPub.feps = require('./feps');
+ActivityPub.rules = require('./rules');
+ActivityPub.relays = require('./relays');
ActivityPub.startJobs = () => {
ActivityPub.helpers.log('[activitypub/jobs] Registering jobs.');
- new CronJob('0 0 * * *', async () => {
+ async function tryCronJob(method) {
if (!meta.config.activitypubEnabled) {
return;
}
try {
- await ActivityPub.notes.prune();
- await db.sortedSetsRemoveRangeByScore(['activities:datetime'], '-inf', Date.now() - 604800000);
+ await method();
} catch (err) {
winston.error(err.stack);
}
+ }
+ new CronJob('0 0 * * *', async () => {
+ await tryCronJob(async () => {
+ await ActivityPub.notes.prune();
+ await db.sortedSetsRemoveRangeByScore(['activities:datetime'], '-inf', Date.now() - 604800000);
+ });
}, null, true, null, null, false); // change last argument to true for debugging
new CronJob('*/30 * * * *', async () => {
- if (!meta.config.activitypubEnabled) {
- return;
- }
- try {
- await ActivityPub.actors.prune();
- } catch (err) {
- winston.error(err.stack);
- }
+ await tryCronJob(ActivityPub.actors.prune);
}, null, true, null, null, false); // change last argument to true for debugging
+
+ new CronJob('0 * * * * *', async () => {
+ await tryCronJob(retryFailedMessages);
+ }, null, true, null, null, false);
};
ActivityPub.resolveId = async (uid, id) => {
@@ -132,7 +138,6 @@ ActivityPub.resolveInboxes = async (ids) => {
}, [[], []]);
const categoryData = await categories.getCategoriesFields(cids, ['inbox', 'sharedInbox']);
const userData = await user.getUsersFields(uids, ['inbox', 'sharedInbox']);
-
currentIds.forEach((id) => {
if (cids.includes(id)) {
const data = categoryData[cids.indexOf(id)];
@@ -346,29 +351,11 @@ ActivityPub.get = async (type, id, uri, options) => {
}
};
-ActivityPub.retryQueue = lru({
- name: 'activitypub-retry-queue',
- max: 4000,
- ttl: 1000 * 60 * 60 * 24 * 60,
- dispose: (value) => {
- if (value) {
- clearTimeout(value);
- }
- },
-});
-
-// handle clearing retry queue from another member of the cluster
-pubsub.on(`activitypub-retry-queue:lruCache:del`, (keys) => {
- if (Array.isArray(keys)) {
- keys.forEach(key => clearTimeout(ActivityPub.retryQueue.get(key)));
- }
-});
-
-async function sendMessage(uri, id, type, payload, attempts = 1) {
- const keyData = await ActivityPub.getPrivateKey(type, id);
- const headers = await ActivityPub.sign(keyData, uri, payload);
-
+async function sendMessage(uri, id, type, payload) {
try {
+ const keyData = await ActivityPub.getPrivateKey(type, id);
+ const headers = await ActivityPub.sign(keyData, uri, payload);
+
const { response, body } = await request.post(uri, {
headers: {
...headers,
@@ -380,25 +367,15 @@ async function sendMessage(uri, id, type, payload, attempts = 1) {
if (String(response.statusCode).startsWith('2')) {
ActivityPub.helpers.log(`[activitypub/send] Successfully sent ${payload.type} to ${uri}`);
- } else {
- if (typeof body === 'object') {
- throw new Error(JSON.stringify(body));
- }
- throw new Error(String(body));
+ return true;
}
+ if (typeof body === 'object') {
+ throw new Error(JSON.stringify(body));
+ }
+ throw new Error(String(body));
} catch (e) {
ActivityPub.helpers.log(`[activitypub/send] Could not send ${payload.type} to ${uri}; error: ${e.message}`);
- // add to retry queue
- if (attempts < 12) { // stop attempting after ~2 months
- const timeout = (4 ** attempts) * 1000; // exponential backoff
- const queueId = `${payload.type}:${payload.id}:${new URL(uri).hostname}`;
- const timeoutId = setTimeout(() => sendMessage(uri, id, type, payload, attempts + 1), timeout);
- ActivityPub.retryQueue.set(queueId, timeoutId);
-
- ActivityPub.helpers.log(`[activitypub/send] Added ${payload.type} to ${uri} to retry queue for ${timeout}ms`);
- } else {
- winston.warn(`[activitypub/send] Max attempts reached for ${payload.type} to ${uri}; giving up on sending`);
- }
+ return false;
}
}
@@ -427,17 +404,92 @@ ActivityPub.send = async (type, id, targets, payload) => {
...payload,
};
- // Runs in background... potentially a better queue is required... later.
- batch.processArray(
- inboxes,
- async inboxBatch => Promise.all(inboxBatch.map(async uri => sendMessage(uri, id, type, payload))),
- {
- batch: 50,
- interval: 100,
- },
- );
+ const oneMinute = 1000 * 60;
+ batch.processArray(inboxes, async (inboxBatch) => {
+ const retryQueueAdd = [];
+ const retryQueuedSet = [];
+
+ await Promise.all(inboxBatch.map(async (uri) => {
+ const ok = await sendMessage(uri, id, type, payload);
+ if (!ok) {
+ const queueId = crypto.createHash('sha256').update(`${type}:${id}:${uri}`).digest('hex');
+ const nextTryOn = Date.now() + oneMinute;
+ retryQueueAdd.push(['ap:retry:queue', nextTryOn, queueId]);
+ retryQueuedSet.push([`ap:retry:queue:${queueId}`, {
+ queueId,
+ uri,
+ id,
+ type,
+ attempts: 1,
+ timestamp: nextTryOn,
+ payload: JSON.stringify(payload),
+ }]);
+ }
+ }));
+
+ if (retryQueueAdd.length) {
+ await Promise.all([
+ db.sortedSetAddBulk(retryQueueAdd),
+ db.setObjectBulk(retryQueuedSet),
+ ]);
+ }
+ }, {
+ batch: 50,
+ interval: 100,
+ }).catch(err => winston.error(err.stack));
};
+async function retryFailedMessages() {
+ const queueIds = await db.getSortedSetRangeByScore('ap:retry:queue', 0, 50, '-inf', Date.now());
+ const queuedData = (await db.getObjects(queueIds.map(id => `ap:retry:queue:${id}`)));
+
+ const retryQueueAdd = [];
+ const retryQueuedSet = [];
+ const queueIdsToRemove = [];
+
+ const oneMinute = 1000 * 60;
+ await Promise.all(queuedData.map(async (data, index) => {
+ const queueId = queueIds[index];
+ if (!data) {
+ queueIdsToRemove.push(queueId);
+ return;
+ }
+
+ const { uri, id, type, attempts, payload } = data;
+ if (!uri || !id || !type || !payload || attempts > 10) {
+ queueIdsToRemove.push(queueId);
+ return;
+ }
+ let payloadObj;
+ try {
+ payloadObj = JSON.parse(payload);
+ } catch (err) {
+ queueIdsToRemove.push(queueId);
+ return;
+ }
+ const ok = await sendMessage(uri, id, type, payloadObj);
+ if (ok) {
+ queueIdsToRemove.push(queueId);
+ } else {
+ const nextAttempt = (parseInt(attempts, 10) || 0) + 1;
+ const timeout = (2 ** nextAttempt) * oneMinute; // exponential backoff
+ const nextTryOn = Date.now() + timeout;
+ retryQueueAdd.push(['ap:retry:queue', nextTryOn, queueId]);
+ retryQueuedSet.push([`ap:retry:queue:${queueId}`, {
+ attempts: nextAttempt,
+ timestamp: nextTryOn,
+ }]);
+ }
+ }));
+
+ await Promise.all([
+ db.sortedSetAddBulk(retryQueueAdd),
+ db.setObjectBulk(retryQueuedSet),
+ db.sortedSetRemove('ap:retry:queue', queueIdsToRemove),
+ db.deleteAll(queueIdsToRemove.map(id => `ap:retry:queue:${id}`)),
+ ]);
+}
+
ActivityPub.record = async ({ id, type, actor }) => {
const now = Date.now();
const { hostname } = new URL(actor);
@@ -511,6 +563,43 @@ ActivityPub.buildRecipients = async function (object, { pid, uid, cid }) {
};
};
+ActivityPub.checkHeader = async (url, timeout) => {
+ timeout = timeout || meta.config.activitypubProbeTimeout || 2000;
+ const { response } = await request.head(url, {
+ timeout,
+ });
+ const { headers } = response;
+ if (headers && headers.link) {
+ // Multiple link headers could be combined
+ const links = headers.link.split(',');
+ let apLink = false;
+
+ links.forEach((link) => {
+ let parts = link.split(';');
+ const url = parts.shift().match(/<(.+)>/)[1];
+ if (!url || apLink) {
+ return;
+ }
+
+ parts = parts
+ .map(p => p.trim())
+ .reduce((memo, cur) => {
+ cur = cur.split('=');
+ memo[cur[0]] = cur[1].slice(1, -1);
+ return memo;
+ }, {});
+
+ if (parts.rel === 'alternate' && parts.type === 'application/activity+json') {
+ apLink = url;
+ }
+ });
+
+ return apLink;
+ }
+
+ return false;
+};
+
ActivityPub.probe = async ({ uid, url }) => {
/**
* Checks whether a passed-in id or URL is an ActivityPub object and can be mapped to a local representation
@@ -520,8 +609,8 @@ ActivityPub.probe = async ({ uid, url }) => {
// Disable on config setting; restrict lookups to HTTPS-enabled URLs only
const { activitypubProbe } = meta.config;
- const { protocol } = new URL(url);
- if (!activitypubProbe || protocol !== 'https:') {
+ const { protocol, host } = new URL(url);
+ if (!activitypubProbe || protocol !== 'https:' || host === nconf.get('url_parsed').host) {
return false;
}
@@ -577,37 +666,18 @@ ActivityPub.probe = async ({ uid, url }) => {
}
// Opportunistic HEAD
- async function checkHeader(timeout) {
- const { response } = await request.head(url, {
- timeout,
- });
- const { headers } = response;
- if (headers && headers.link) {
- let parts = headers.link.split(';');
- parts.shift();
- parts = parts
- .map(p => p.trim())
- .reduce((memo, cur) => {
- cur = cur.split('=');
- memo[cur[0]] = cur[1].slice(1, -1);
- return memo;
- }, {});
-
- if (parts.rel === 'alternate' && parts.type === 'application/activity+json') {
- probeCache.set(url, true);
- return true;
- }
- }
-
- return false;
- }
try {
probeRateLimit.set(uid, true);
- return await checkHeader(meta.config.activitypubProbeTimeout || 2000);
+ const probe = await ActivityPub.checkHeader(url).then((result) => {
+ probeCache.set(url, result);
+ return !!result;
+ });
+
+ return !!probe;
} catch (e) {
if (e.name === 'TimeoutError') {
// Return early but retry for caching purposes
- checkHeader(1000 * 60).then((result) => {
+ ActivityPub.checkHeader(url, 1000 * 60).then((result) => {
probeCache.set(url, result);
}).catch(err => ActivityPub.helpers.log(err.stack));
return false;
diff --git a/src/activitypub/mocks.js b/src/activitypub/mocks.js
index e5a8e8e363..b3c641e0c3 100644
--- a/src/activitypub/mocks.js
+++ b/src/activitypub/mocks.js
@@ -5,6 +5,7 @@ const mime = require('mime');
const path = require('path');
const validator = require('validator');
const sanitize = require('sanitize-html');
+const tokenizer = require('sbd');
const db = require('../database');
const user = require('../user');
@@ -84,7 +85,7 @@ Mocks._normalize = async (object) => {
content = 'This post did not contain any content.';
}
- switch (true) {
+ switch (true) { // image handling
case image && image.hasOwnProperty('url') && !!image.url: {
image = image.url;
break;
@@ -101,7 +102,8 @@ Mocks._normalize = async (object) => {
}
if (image) {
const parsed = new URL(image);
- if (!mime.getType(parsed.pathname).startsWith('image/')) {
+ const type = mime.getType(parsed.pathname);
+ if (!type || type.startsWith('image/')) {
activitypub.helpers.log(`[activitypub/mocks.post] Received image not identified as image due to MIME type: ${image}`);
image = null;
}
@@ -191,7 +193,7 @@ Mocks.profile = async (actors) => {
const iconBackgrounds = await user.getIconBackgrounds();
let bgColor = Array.prototype.reduce.call(preferredUsername, (cur, next) => cur + next.charCodeAt(), 0);
bgColor = iconBackgrounds[bgColor % iconBackgrounds.length];
-
+ summary = summary || '';
// Replace emoji in summary
if (tag && Array.isArray(tag)) {
tag
@@ -715,8 +717,12 @@ Mocks.notes.public = async (post) => {
// Special handling for main posts (as:Article w/ as:Note preview)
const noteAttachment = isMainPost ? [...attachment] : null;
- const uploads = await posts.uploads.listWithSizes(post.pid);
- const isThumb = await db.isSortedSetMembers(`topic:${post.tid}:thumbs`, uploads.map(u => u.name));
+ const [uploads, thumbs] = await Promise.all([
+ posts.uploads.listWithSizes(post.pid),
+ topics.getTopicField(post.tid, 'thumbs'),
+ ]);
+ const isThumb = uploads.map(u => Array.isArray(thumbs) ? thumbs.includes(u.name) : false);
+
uploads.forEach(({ name, width, height }, idx) => {
const mediaType = mime.getType(name);
const url = `${nconf.get('url') + nconf.get('upload_url')}/${name}`;
@@ -751,7 +757,17 @@ Mocks.notes.public = async (post) => {
attachment: normalizeAttachment(noteAttachment),
};
- summary = post.content;
+ const sentences = tokenizer.sentences(post.content, { sanitize: true });
+ // Append sentences to summary until it contains just under 500 characters of content
+ const limit = 500;
+ summary = sentences.reduce((memo, sentence) => {
+ const remaining = limit - memo.length;
+ if (sentence.length < remaining) {
+ memo += ` ${sentence}`;
+ }
+
+ return memo;
+ }, '');
}
let context = await posts.getPostField(post.pid, 'context');
diff --git a/src/activitypub/notes.js b/src/activitypub/notes.js
index 64c6dcd53f..52f5d373e3 100644
--- a/src/activitypub/notes.js
+++ b/src/activitypub/notes.js
@@ -2,6 +2,7 @@
const winston = require('winston');
const nconf = require('nconf');
+const tokenizer = require('sbd');
const db = require('../database');
const batch = require('../batch');
@@ -55,16 +56,19 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => {
return null;
}
- const id = !activitypub.helpers.isUri(input) ? input.id : input;
- const lockStatus = await lock(id, '[[error:activitypub.already-asserting]]');
+ let id = !activitypub.helpers.isUri(input) ? input.id : input;
+ const lockStatus = await lock(id);
if (!lockStatus) { // unable to achieve lock, stop processing.
+ winston.warn(`[activitypub/notes.assert] Unable to acquire lock, skipping processing of ${id}`);
return null;
}
+ id = await activitypub.checkHeader(id);
+
let chain;
let context = await activitypub.contexts.get(uid, id);
if (context.tid) {
- unlock(id);
+ await unlock(id);
const { tid } = context;
return { tid, count: 0 };
} else if (context.context) {
@@ -85,7 +89,7 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => {
// Can't resolve — give up.
if (!chain.length) {
- unlock(id);
+ await unlock(id);
return null;
}
@@ -108,7 +112,7 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => {
if (tid && members.every(Boolean)) {
// All cached, return early.
activitypub.helpers.log('[notes/assert] No new notes to process.');
- unlock(id);
+ await unlock(id);
return { tid, count: 0 };
}
@@ -137,6 +141,7 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => {
}).shift();
} catch (e) {
// noop
+ winston.error('[activitypub/notes.assert] Could not parse URL of mainPid', e.stack);
}
if (remoteCid || recipientCids.length) {
@@ -144,8 +149,16 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => {
options.cid = remoteCid || recipientCids.shift();
}
+ // Auto-categorization (takes place only if all other categorization efforts fail)
+ if (!options.cid) {
+ options.cid = await assignCategory(mainPost);
+ }
+
// mainPid ok to leave as-is
- title = title || activitypub.helpers.generateTitle(utils.decodeHTMLEntities(content || sourceContent));
+ if (!title) {
+ const sentences = tokenizer.sentences(content || sourceContent, { sanitize: true });
+ title = sentences.shift();
+ }
// Remove any custom emoji from title
if (_activitypub && _activitypub.tag && Array.isArray(_activitypub.tag)) {
@@ -164,6 +177,7 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => {
uid || hasTid ||
options.skipChecks || options.cid ||
await assertRelation(chain[inputIndex !== -1 ? inputIndex : 0]);
+
const privilege = `topics:${tid ? 'reply' : 'create'}`;
const allowed = await privileges.categories.can(privilege, options.cid || cid, activitypub._constants.uid);
if (!hasRelation || !allowed) {
@@ -171,7 +185,7 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => {
activitypub.helpers.log(`[activitypub/notes.assert] Not asserting ${id} as it has no relation to existing tracked content.`);
}
- unlock(id);
+ await unlock(id);
return null;
}
@@ -384,6 +398,29 @@ async function assertRelation(post) {
return followers > 0 || uids.length;
}
+async function assignCategory(post) {
+ let cid = undefined;
+ const rules = await activitypub.rules.list();
+ const tags = await Notes._normalizeTags(post._activitypub.tag || []);
+
+ cid = rules.reduce((cid, { type, value, cid: target }) => {
+ if (!cid) {
+ switch (type) {
+ case 'hashtag': {
+ if (tags.includes(value)) {
+ return target;
+ }
+ break;
+ }
+ }
+ }
+
+ return cid;
+ }, cid);
+
+ return cid;
+}
+
Notes.updateLocalRecipients = async (id, { to, cc }) => {
const recipients = new Set([...(to || []), ...(cc || [])]);
const uids = new Set();
diff --git a/src/activitypub/relays.js b/src/activitypub/relays.js
new file mode 100644
index 0000000000..afaeaaa05f
--- /dev/null
+++ b/src/activitypub/relays.js
@@ -0,0 +1,126 @@
+'use strict';
+
+const nconf = require('nconf');
+
+const db = require('../database');
+
+const activitypub = module.parent.exports;
+const Relays = module.exports;
+
+Relays.is = async (actor) => {
+ return db.isSortedSetMember('relays:createtime', actor);
+};
+
+Relays.list = async () => {
+ let relays = await db.getSortedSetMembersWithScores('relays:state');
+ relays = relays.reduce((memo, { value, score }) => {
+ let label = '[[admin/settings/activitypub:relays.state-0]]';
+ switch(score) {
+ case 1: {
+ label = '[[admin/settings/activitypub:relays.state-1]]';
+ break;
+ }
+
+ case 2: {
+ label = '[[admin/settings/activitypub:relays.state-2]]';
+ break;
+ }
+ }
+
+ memo.push({
+ url: value,
+ state: score,
+ label,
+ });
+
+ return memo;
+ }, []);
+
+ return relays;
+};
+
+Relays.add = async (url) => {
+ const now = Date.now();
+ await activitypub.send('uid', 0, url, {
+ '@context': [
+ 'https://www.w3.org/ns/activitystreams',
+ 'https://pleroma.example/schemas/litepub-0.1.jsonld',
+ ],
+ id: `${nconf.get('url')}/actor#activity/follow/${encodeURIComponent(url)}/${now}`,
+ type: 'Follow',
+ to: [url],
+ object: url,
+ state: 'pending',
+ });
+
+ await Promise.all([
+ db.sortedSetAdd('relays:createtime', now, url),
+ db.sortedSetAdd('relays:state', 0, url),
+ ]);
+};
+
+Relays.remove = async (url) => {
+ const now = new Date();
+ const createtime = await db.sortedSetScore('relays:createtime', url);
+ if (!createtime) {
+ throw new Error('[[error:invalid-data]]');
+ }
+
+ await activitypub.send('uid', 0, url, {
+ '@context': [
+ 'https://www.w3.org/ns/activitystreams',
+ 'https://pleroma.example/schemas/litepub-0.1.jsonld',
+ ],
+ id: `${nconf.get('url')}/actor#activity/undo:follow/${encodeURIComponent(url)}/${now.getTime()}`,
+ type: 'Undo',
+ to: [url],
+ published: now.toISOString(),
+ object: {
+ '@context': [
+ 'https://www.w3.org/ns/activitystreams',
+ 'https://pleroma.example/schemas/litepub-0.1.jsonld',
+ ],
+ id: `${nconf.get('url')}/actor#activity/follow/${encodeURIComponent(url)}/${createtime}`,
+ type: 'Follow',
+ actor: `${nconf.get('url')}/actor`,
+ to: [url],
+ object: url,
+ state: 'cancelled',
+ },
+ });
+
+ await Promise.all([
+ db.sortedSetRemove('relays:createtime', url),
+ db.sortedSetRemove('relays:state', url),
+ ]);
+};
+
+Relays.handshake = async (object) => {
+ const now = new Date();
+ const { type, actor } = object;
+
+ // Confirm relay was added
+ const exists = await db.isSortedSetMember('relays:createtime', actor);
+ if (!exists) {
+ throw new Error('[[error:api.400]]');
+ }
+
+ if (type === 'Follow') {
+ await db.sortedSetIncrBy('relays:state', 1, actor);
+ await activitypub.send('uid', 0, actor, {
+ '@context': [
+ 'https://www.w3.org/ns/activitystreams',
+ 'https://pleroma.example/schemas/litepub-0.1.jsonld',
+ ],
+ id: `${nconf.get('url')}/actor#activity/accept/${encodeURIComponent(actor)}/${now.getTime()}`,
+ type: 'Accept',
+ to: [actor],
+ published: now.toISOString(),
+ object,
+ });
+ } else if (type === 'Accept') {
+ await db.sortedSetIncrBy('relays:state', 1, actor);
+ } else {
+ throw new Error('[[error:api.400]]');
+ }
+};
\ No newline at end of file
diff --git a/src/activitypub/rules.js b/src/activitypub/rules.js
new file mode 100644
index 0000000000..3a9a560552
--- /dev/null
+++ b/src/activitypub/rules.js
@@ -0,0 +1,33 @@
+'use strict';
+
+const db = require('../database');
+const utils = require('../utils');
+
+const Rules = module.exports;
+
+Rules.list = async () => {
+ const rids = await db.getSortedSetMembers('categorization:rid');
+ let rules = await db.getObjects(rids.map(rid => `rid:${rid}`));
+ rules = rules.map((rule, idx) => {
+ rule.rid = rids[idx];
+ return rule;
+ });
+
+ return rules;
+};
+
+Rules.add = async (type, value, cid) => {
+ const uuid = utils.generateUUID();
+
+ await Promise.all([
+ db.setObject(`rid:${uuid}`, { type, value, cid }),
+ db.sortedSetAdd('categorization:rid', Date.now(), uuid),
+ ]);
+};
+
+Rules.delete = async (rid) => {
+ await Promise.all([
+ db.sortedSetRemove('categorization:rid', rid),
+ db.delete(`rid:${rid}`),
+ ]);
+};
\ No newline at end of file
diff --git a/src/analytics.js b/src/analytics.js
index b70aec7e5c..773f7088af 100644
--- a/src/analytics.js
+++ b/src/analytics.js
@@ -21,6 +21,7 @@ let local = {
pageViewsRegistered: 0,
pageViewsGuest: 0,
pageViewsBot: 0,
+ apPageViews: 0,
uniquevisitors: 0,
};
const empty = _.cloneDeep(local);
@@ -101,8 +102,17 @@ Analytics.pageView = async function (payload) {
local.pageViewsGuest += 1;
}
- if (payload.ip) {
- const score = await db.sortedSetScore('ip:recent', payload.ip);
+ await incrementUniqueVisitors(payload.ip);
+};
+
+Analytics.apPageView = async function ({ ip }) {
+ local.apPageViews += 1;
+ await incrementUniqueVisitors(ip);
+};
+
+async function incrementUniqueVisitors(ip) {
+ if (ip) {
+ const score = await db.sortedSetScore('ip:recent', ip);
let record = !score;
if (score) {
const today = new Date();
@@ -112,10 +122,10 @@ Analytics.pageView = async function (payload) {
if (record) {
local.uniquevisitors += 1;
- await db.sortedSetAdd('ip:recent', Date.now(), payload.ip);
+ await db.sortedSetAdd('ip:recent', Date.now(), ip);
}
}
-};
+}
Analytics.writeData = async function () {
const today = new Date();
@@ -162,6 +172,12 @@ Analytics.writeData = async function () {
total.pageViewsBot = 0;
}
+ if (total.apPageViews > 0) {
+ incrByBulk.push(['analytics:pageviews:ap', total.apPageViews, today.getTime()]);
+ incrByBulk.push(['analytics:pageviews:ap:month', total.apPageViews, month.getTime()]);
+ total.apPageViews = 0;
+ }
+
if (total.uniquevisitors > 0) {
incrByBulk.push(['analytics:uniquevisitors', total.uniquevisitors, today.getTime()]);
total.uniquevisitors = 0;
diff --git a/src/api/activitypub.js b/src/api/activitypub.js
index 1f074f6776..7e4ae7da18 100644
--- a/src/api/activitypub.js
+++ b/src/api/activitypub.js
@@ -187,6 +187,11 @@ activitypubApi.create.privateNote = enabledCheck(async (caller, { messageObj })
activitypubApi.update = {};
activitypubApi.update.profile = enabledCheck(async (caller, { uid }) => {
+ // Local users only
+ if (!utils.isNumber(uid)) {
+ return;
+ }
+
const [object, targets] = await Promise.all([
activitypub.mocks.actors.user(uid),
db.getSortedSetMembers(`followersRemote:${caller.uid}`),
@@ -203,6 +208,11 @@ activitypubApi.update.profile = enabledCheck(async (caller, { uid }) => {
});
activitypubApi.update.category = enabledCheck(async (caller, { cid }) => {
+ // Local categories only
+ if (!utils.isNumber(cid)) {
+ return;
+ }
+
const [object, targets] = await Promise.all([
activitypub.mocks.actors.category(cid),
activitypub.notes.getCategoryFollowers(cid),
diff --git a/src/api/chats.js b/src/api/chats.js
index e1538c4426..5099479b51 100644
--- a/src/api/chats.js
+++ b/src/api/chats.js
@@ -162,9 +162,17 @@ chatsAPI.update = async (caller, data) => {
await db.setObjectField(`chat:room:${data.roomId}`, 'groups', JSON.stringify(data.groups));
}
}
- if (data.hasOwnProperty('notificationSetting') && isAdmin) {
- await db.setObjectField(`chat:room:${data.roomId}`, 'notificationSetting', data.notificationSetting);
+ if (isAdmin) {
+ const updateData = {};
+ if (data.hasOwnProperty('notificationSetting')) {
+ updateData.notificationSetting = data.notificationSetting;
+ }
+ if (data.hasOwnProperty('joinLeaveMessages')) {
+ updateData.joinLeaveMessages = data.joinLeaveMessages;
+ }
+ await db.setObject(`chat:room:${data.roomId}`, updateData);
}
+
const loadedRoom = await messaging.loadRoom(caller.uid, {
roomId: data.roomId,
});
diff --git a/src/api/helpers.js b/src/api/helpers.js
index 168e5539b6..7df860a569 100644
--- a/src/api/helpers.js
+++ b/src/api/helpers.js
@@ -35,7 +35,7 @@ exports.buildReqObject = (req, payload) => {
params: req.params,
method: req.method,
body: payload || req.body,
- session: session,
+ session: JSON.parse(JSON.stringify(session)),
ip: req.ip,
host: host,
protocol: encrypted ? 'https' : 'http',
@@ -44,7 +44,7 @@ exports.buildReqObject = (req, payload) => {
path: referer.slice(referer.indexOf(host) + host.length),
baseUrl: req.baseUrl,
originalUrl: req.originalUrl,
- headers: headers,
+ headers: { ...headers },
};
};
diff --git a/src/api/posts.js b/src/api/posts.js
index 7946a1d311..54454e6e52 100644
--- a/src/api/posts.js
+++ b/src/api/posts.js
@@ -120,9 +120,7 @@ postsAPI.edit = async function (caller, data) {
data.timestamp = parseInt(data.timestamp, 10) || Date.now();
const editResult = await posts.edit(data);
- if (editResult.topic.isMainPost) {
- await topics.thumbs.migrate(data.uuid, editResult.topic.tid);
- }
+
const selfPost = parseInt(caller.uid, 10) === parseInt(editResult.post.uid, 10);
if (!selfPost && editResult.post.changed) {
await events.log({
diff --git a/src/api/topics.js b/src/api/topics.js
index 0155429ecc..38e3cefbf8 100644
--- a/src/api/topics.js
+++ b/src/api/topics.js
@@ -1,7 +1,5 @@
'use strict';
-const validator = require('validator');
-
const user = require('../user');
const topics = require('../topics');
const categories = require('../categories');
@@ -23,17 +21,13 @@ const socketHelpers = require('../socket.io/helpers');
const topicsAPI = module.exports;
topicsAPI._checkThumbPrivileges = async function ({ tid, uid }) {
- // req.params.tid could be either a tid (pushing a new thumb to an existing topic)
- // or a post UUID (a new topic being composed)
- const isUUID = validator.isUUID(tid);
-
// Sanity-check the tid if it's strictly not a uuid
- if (!isUUID && (isNaN(parseInt(tid, 10)) || !await topics.exists(tid))) {
+ if ((isNaN(parseInt(tid, 10)) || !await topics.exists(tid))) {
throw new Error('[[error:no-topic]]');
}
// While drafts are not protected, tids are
- if (!isUUID && !await privileges.topics.canEdit(tid, uid)) {
+ if (!await privileges.topics.canEdit(tid, uid)) {
throw new Error('[[error:no-privileges]]');
}
};
@@ -80,7 +74,6 @@ topicsAPI.create = async function (caller, data) {
}
const result = await topics.post(payload);
- await topics.thumbs.migrate(data.uuid, result.topicData.tid);
socketHelpers.emitToUids('event:new_post', { posts: [result.postData] }, [caller.uid]);
socketHelpers.emitToUids('event:new_topic', result.topicData, [caller.uid]);
@@ -233,17 +226,6 @@ topicsAPI.getThumbs = async (caller, { tid, thumbsOnly }) => {
return await topics.thumbs.get(tid, { thumbsOnly });
};
-// topicsAPI.addThumb
-
-topicsAPI.migrateThumbs = async (caller, { from, to }) => {
- await Promise.all([
- topicsAPI._checkThumbPrivileges({ tid: from, uid: caller.uid }),
- topicsAPI._checkThumbPrivileges({ tid: to, uid: caller.uid }),
- ]);
-
- await topics.thumbs.migrate(from, to);
-};
-
topicsAPI.deleteThumb = async (caller, { tid, path }) => {
await topicsAPI._checkThumbPrivileges({ tid: tid, uid: caller.uid });
await topics.thumbs.delete(tid, path);
diff --git a/src/cache/lru.js b/src/cache/lru.js
index 094d3a3e93..a1dbfbd705 100644
--- a/src/cache/lru.js
+++ b/src/cache/lru.js
@@ -31,6 +31,9 @@ module.exports = function (opts) {
});
const lruCache = new LRUCache(opts);
+ if (!opts.name) {
+ winston.warn(`[cache/init] ${chalk.white.bgRed.bold('WARNING')} The cache name is not set. This will be required in the future.\n ${new Error('t').stack} `);
+ }
const cache = {};
cache.name = opts.name;
@@ -92,6 +95,9 @@ module.exports = function (opts) {
};
cache.del = function (keys) {
+ if (!cache.enabled) {
+ return;
+ }
if (!Array.isArray(keys)) {
keys = [keys];
}
diff --git a/src/cache/ttl.js b/src/cache/ttl.js
index 8647d0b9ac..c8ed90af57 100644
--- a/src/cache/ttl.js
+++ b/src/cache/ttl.js
@@ -3,10 +3,15 @@
module.exports = function (opts) {
const TTLCache = require('@isaacs/ttlcache');
const os = require('os');
+ const winston = require('winston');
+ const chalk = require('chalk');
const pubsub = require('../pubsub');
const ttlCache = new TTLCache(opts);
+ if (!opts.name) {
+ winston.warn(`[cache/init] ${chalk.white.bgRed.bold('WARNING')} The cache name is not set. This will be required in the future.\n ${new Error('t').stack} `);
+ }
const cache = {};
cache.name = opts.name;
@@ -65,6 +70,9 @@ module.exports = function (opts) {
};
cache.del = function (keys) {
+ if (!cache.enabled) {
+ return;
+ }
if (!Array.isArray(keys)) {
keys = [keys];
}
diff --git a/src/categories/index.js b/src/categories/index.js
index de7ff6d769..047de142c8 100644
--- a/src/categories/index.js
+++ b/src/categories/index.js
@@ -85,6 +85,9 @@ Categories.getCategoryById = async function (data) {
};
Categories.getCidByHandle = async function (handle) {
+ if (!handle) {
+ return null;
+ }
let cid = await db.sortedSetScore('categoryhandle:cid', handle);
if (!cid) {
// remote cids
@@ -101,7 +104,7 @@ Categories.getAllCidsFromSet = async function (key) {
}
cids = await db.getSortedSetRange(key, 0, -1);
- cids = cids.map(cid => parseInt(cid, 10));
+ cids = cids.map(cid => utils.isNumber(cid) ? parseInt(cid, 10) : cid);
cache.set(key, cids);
return cids.slice();
};
@@ -274,7 +277,7 @@ Categories.getChildrenTree = getChildrenTree;
Categories.getParentCids = async function (currentCid) {
let cid = currentCid;
const parents = [];
- while (parseInt(cid, 10)) {
+ while (utils.isNumber(cid) ? parseInt(cid, 10) : cid) {
// eslint-disable-next-line
cid = await Categories.getCategoryField(cid, 'parentCid');
if (cid) {
@@ -289,12 +292,12 @@ Categories.getChildrenCids = async function (rootCid) {
async function recursive(keys) {
let childrenCids = await db.getSortedSetRange(keys, 0, -1);
- childrenCids = childrenCids.filter(cid => !allCids.includes(parseInt(cid, 10)));
+ childrenCids = childrenCids.filter(cid => !allCids.includes(utils.isNumber(cid) ? parseInt(cid, 10) : cid));
if (!childrenCids.length) {
return;
}
keys = childrenCids.map(cid => `cid:${cid}:children`);
- childrenCids.forEach(cid => allCids.push(parseInt(cid, 10)));
+ childrenCids.forEach(cid => allCids.push(utils.isNumber(cid) ? parseInt(cid, 10) : cid));
await recursive(keys);
}
const key = `cid:${rootCid}:children`;
diff --git a/src/categories/search.js b/src/categories/search.js
index bd6a96435c..50521d882a 100644
--- a/src/categories/search.js
+++ b/src/categories/search.js
@@ -5,7 +5,6 @@ const _ = require('lodash');
const privileges = require('../privileges');
const activitypub = require('../activitypub');
const plugins = require('../plugins');
-const utils = require('../utils');
const db = require('../database');
module.exports = function (Categories) {
@@ -44,7 +43,8 @@ module.exports = function (Categories) {
const childrenCids = await getChildrenCids(cids, uid);
const uniqCids = _.uniq(cids.concat(childrenCids));
- const categoryData = await Categories.getCategories(uniqCids);
+ let categoryData = await Categories.getCategories(uniqCids);
+ categoryData = categoryData.filter(Boolean);
Categories.getTree(categoryData, 0);
await Categories.getRecentTopicReplies(categoryData, uid, data.qs);
@@ -64,7 +64,7 @@ module.exports = function (Categories) {
return c1.order - c2.order;
});
searchResult.timing = (process.elapsedTimeSince(startTime) / 1000).toFixed(2);
- searchResult.categories = categoryData.filter(c => cids.includes(c.cid));
+ searchResult.categories = categoryData.filter(c => cids.includes(String(c.cid)));
return searchResult;
};
@@ -81,7 +81,7 @@ module.exports = function (Categories) {
const split = data.split(':');
split.shift();
const cid = split.join(':');
- return utils.isNumber(cid) ? parseInt(cid, 10) : cid;
+ return cid;
});
}
diff --git a/src/categories/update.js b/src/categories/update.js
index 2f2effd96d..bf32317ae2 100644
--- a/src/categories/update.js
+++ b/src/categories/update.js
@@ -60,7 +60,7 @@ module.exports = function (Categories) {
return await updateOrder(cid, value);
}
- await db.setObjectField(`category:${cid}`, key, value);
+ await db.setObjectField(`${utils.isNumber(cid) ? 'category' : 'categoryRemote'}:${cid}`, key, value);
if (key === 'description') {
await Categories.parseDescription(cid, value);
}
@@ -83,7 +83,7 @@ module.exports = function (Categories) {
await Promise.all([
db.sortedSetRemove(`cid:${oldParent}:children`, cid),
db.sortedSetAdd(`cid:${newParent}:children`, categoryData.order, cid),
- db.setObjectField(`category:${cid}`, 'parentCid', newParent),
+ db.setObjectField(`${utils.isNumber(cid) ? 'category' : 'categoryRemote'}:${cid}`, 'parentCid', newParent),
]);
cache.del([
@@ -104,8 +104,12 @@ module.exports = function (Categories) {
}
async function updateOrder(cid, order) {
- const parentCid = await Categories.getCategoryField(cid, 'parentCid');
- await db.sortedSetsAdd('categories:cid', order, cid);
+ const parentCid = (await Categories.getCategoryField(cid, 'parentCid')) || 0;
+ const isLocal = utils.isNumber(cid);
+
+ if (isLocal) {
+ await db.sortedSetsAdd('categories:cid', order, cid);
+ }
const childrenCids = await db.getSortedSetRange(
`cid:${parentCid}:children`, 0, -1
@@ -128,7 +132,7 @@ module.exports = function (Categories) {
);
await db.setObjectBulk(
- childrenCids.map((cid, index) => [`category:${cid}`, { order: index + 1 }])
+ childrenCids.map((cid, index) => [`${utils.isNumber(cid) ? 'category' : 'categoryRemote'}:${cid}`, { order: index + 1 }])
);
cache.del([
diff --git a/src/cli/index.js b/src/cli/index.js
index a413ec6ef1..e868b702c4 100644
--- a/src/cli/index.js
+++ b/src/cli/index.js
@@ -42,7 +42,7 @@ try {
checkVersion('lru-cache');
} catch (e) {
if (['ENOENT', 'DEP_WRONG_VERSION', 'MODULE_NOT_FOUND'].includes(e.code)) {
- console.warn('Dependencies outdated or not yet installed.');
+ console.warn(`Dependencies outdated or not yet installed. Error Code: ${e.code}\n${e.stack}`);
console.log('Installing them now...\n');
packageInstall.updatePackageFile();
diff --git a/src/controllers/accounts/edit.js b/src/controllers/accounts/edit.js
index 61a13399a0..1192687a5f 100644
--- a/src/controllers/accounts/edit.js
+++ b/src/controllers/accounts/edit.js
@@ -143,7 +143,7 @@ async function renderRoute(name, req, res) {
}
editController.uploadPicture = async function (req, res, next) {
- const userPhoto = req.files.files[0];
+ const userPhoto = req.files[0];
try {
const updateUid = await user.getUidByUserslug(req.params.userslug);
const isAllowed = await privileges.users.canEdit(req.uid, updateUid);
diff --git a/src/controllers/accounts/profile.js b/src/controllers/accounts/profile.js
index b22e6eb73c..fab317fe17 100644
--- a/src/controllers/accounts/profile.js
+++ b/src/controllers/accounts/profile.js
@@ -52,6 +52,11 @@ profileController.get = async function (req, res, next) {
if (meta.config.activitypubEnabled) {
// Include link header for richer parsing
res.set('Link', `<${nconf.get('url')}/uid/${userData.uid}>; rel="alternate"; type="application/activity+json"`);
+
+ if (!utils.isNumber(userData.uid)) {
+ res.set('Link', `<${userData.url || userData.uid}>; rel="canonical"`);
+ res.set('x-robots-tag', 'noindex');
+ }
}
res.render('account/profile', userData);
@@ -163,10 +168,21 @@ function addTags(res, userData) {
res.locals.linkTags = [];
- res.locals.linkTags.push({
- rel: 'canonical',
- href: `${url}/user/${userData.userslug}`,
- });
+ if (utils.isNumber(userData.uid)) {
+ res.locals.linkTags.push({
+ rel: 'canonical',
+ href: `${url}/user/${userData.userslug}`,
+ });
+ } else {
+ res.locals.linkTags.push({
+ rel: 'canonical',
+ href: userData.url || userData.uid,
+ });
+ res.locals.metaTags.push({
+ name: 'robots',
+ content: 'noindex',
+ });
+ }
if (meta.config.activitypubEnabled) {
res.locals.linkTags.push({
diff --git a/src/controllers/activitypub/index.js b/src/controllers/activitypub/index.js
index de478a6021..121c8536aa 100644
--- a/src/controllers/activitypub/index.js
+++ b/src/controllers/activitypub/index.js
@@ -144,15 +144,15 @@ Controller.postInbox = async (req, res) => {
// Note: underlying methods are internal use only, hence no exposure via src/api
const method = String(req.body.type).toLowerCase();
if (!activitypub.inbox.hasOwnProperty(method)) {
- winston.warn(`[activitypub/inbox] Received Activity of type ${method} but unable to handle. Ignoring.`);
+ activitypub.helpers.log(`[activitypub/inbox] Received Activity of type ${method} but unable to handle. Ignoring.`);
return res.sendStatus(200);
}
try {
await activitypub.inbox[method](req);
await activitypub.record(req.body);
- helpers.formatApiResponse(202, res);
+ await helpers.formatApiResponse(202, res);
} catch (e) {
- helpers.formatApiResponse(500, res, e);
+ helpers.formatApiResponse(500, res, e).catch(err => winston.error(err.stack));
}
};
diff --git a/src/controllers/admin/categories.js b/src/controllers/admin/categories.js
index 7d9eb61a18..e6bb7aaa41 100644
--- a/src/controllers/admin/categories.js
+++ b/src/controllers/admin/categories.js
@@ -12,6 +12,8 @@ const meta = require('../../meta');
const activitypub = require('../../activitypub');
const helpers = require('../helpers');
const pagination = require('../../pagination');
+const utils = require('../../utils');
+const cache = require('../../cache');
const categoriesController = module.exports;
@@ -48,14 +50,14 @@ categoriesController.get = async function (req, res, next) {
categoriesController.getAll = async function (req, res) {
const rootCid = parseInt(req.query.cid, 10) || 0;
+ const rootChildren = await categories.getAllCidsFromSet(`cid:${rootCid}:children`);
async function getRootAndChildren() {
- const rootChildren = await categories.getAllCidsFromSet(`cid:${rootCid}:children`);
const childCids = _.flatten(await Promise.all(rootChildren.map(cid => categories.getChildrenCids(cid))));
return [rootCid].concat(rootChildren.concat(childCids));
}
// Categories list will be rendered on client side with recursion, etc.
- const cids = await (rootCid ? getRootAndChildren() : categories.getAllCidsFromSet('categories:cid'));
+ const cids = await getRootAndChildren();
let rootParent = 0;
if (rootCid) {
@@ -67,9 +69,15 @@ categoriesController.getAll = async function (req, res) {
'order', 'color', 'bgColor', 'backgroundImage', 'imageClass',
'subCategoriesPerPage', 'description',
];
- const categoriesData = await categories.getCategoriesFields(cids, fields);
- const result = await plugins.hooks.fire('filter:admin.categories.get', { categories: categoriesData, fields: fields });
- let tree = categories.getTree(result.categories, rootParent);
+ let categoriesData = await categories.getCategoriesFields(cids, fields);
+ ({ categories: categoriesData } = await plugins.hooks.fire('filter:admin.categories.get', { categories: categoriesData, fields: fields }));
+
+ categoriesData = categoriesData.map((category) => {
+ category.isLocal = utils.isNumber(category.cid);
+ return category;
+ });
+
+ let tree = categories.getTree(categoriesData, rootParent);
const cidsCount = rootCid && tree[0] ? tree[0].children.length : tree.length;
const pageCount = Math.max(1, Math.ceil(cidsCount / meta.config.categoriesPerPage));
@@ -176,3 +184,41 @@ categoriesController.getFederation = async function (req, res) {
followers,
});
};
+
+categoriesController.addRemote = async function (req, res) {
+ let { handle, id } = req.body;
+ if (handle && !id) {
+ ({ actorUri: id } = await activitypub.helpers.query(handle));
+ }
+
+ if (!id) {
+ return res.sendStatus(404);
+ }
+
+ await activitypub.actors.assertGroup(id);
+ const exists = await categories.exists(id);
+
+ if (!exists) {
+ return res.sendStatus(404);
+ }
+
+ const score = await db.sortedSetCard('cid:0:children');
+ const order = score + 1; // order is 1-based lol
+ await Promise.all([
+ db.sortedSetAdd('cid:0:children', order, id),
+ categories.setCategoryField(id, 'order', order),
+ ]);
+ cache.del('cid:0:children');
+
+ res.sendStatus(200);
+};
+
+categoriesController.removeRemote = async function (req, res) {
+ if (utils.isNumber(req.params.cid)) {
+ return helpers.formatApiResponse(400, res);
+ }
+
+ await db.sortedSetRemove('cid:0:children', req.params.cid);
+ cache.del('cid:0:children');
+ res.sendStatus(200);
+};
diff --git a/src/controllers/admin/dashboard.js b/src/controllers/admin/dashboard.js
index aa173eca07..20f31c2b67 100644
--- a/src/controllers/admin/dashboard.js
+++ b/src/controllers/admin/dashboard.js
@@ -91,7 +91,7 @@ async function getLatestVersion() {
dashboardController.getAnalytics = async (req, res, next) => {
// Basic validation
const validUnits = ['days', 'hours'];
- const validSets = ['uniquevisitors', 'pageviews', 'pageviews:registered', 'pageviews:bot', 'pageviews:guest'];
+ const validSets = ['uniquevisitors', 'pageviews', 'pageviews:registered', 'pageviews:bot', 'pageviews:guest', 'pageviews:ap'];
const until = req.query.until ? new Date(parseInt(req.query.until, 10)) : Date.now();
const count = req.query.count || (req.query.units === 'hours' ? 24 : 30);
if (isNaN(until) || !validUnits.includes(req.query.units)) {
diff --git a/src/controllers/admin/events.js b/src/controllers/admin/events.js
index 72d9b4c3e1..9b4d493071 100644
--- a/src/controllers/admin/events.js
+++ b/src/controllers/admin/events.js
@@ -60,11 +60,11 @@ eventsController.get = async function (req, res) {
pagination: pagination.create(page, pageCount, req.query),
types: types,
query: {
- start: validator.escape(String(req.query.start)),
- end: validator.escape(String(req.query.end)),
- username: validator.escape(String(req.query.username)),
- group: validator.escape(String(req.query.group)),
- perPage: validator.escape(String(req.query.perPage)),
+ start: validator.escape(String(req.query.start || '')),
+ end: validator.escape(String(req.query.end || '')),
+ username: validator.escape(String(req.query.username || '')),
+ group: validator.escape(String(req.query.group || '')),
+ perPage: validator.escape(String(req.query.perPage || '')),
},
});
};
diff --git a/src/controllers/admin/info.js b/src/controllers/admin/info.js
index 6f63faf8a9..3c0457cd82 100644
--- a/src/controllers/admin/info.js
+++ b/src/controllers/admin/info.js
@@ -117,14 +117,18 @@ function getCpuUsage() {
}
function humanReadableUptime(seconds) {
+ const oneHourInSeconds = 3600;
+ const oneDayInSeconds = oneHourInSeconds * 24;
if (seconds < 60) {
return `${Math.floor(seconds)}s`;
- } else if (seconds < 3600) {
+ } else if (seconds < oneHourInSeconds) {
return `${Math.floor(seconds / 60)}m`;
- } else if (seconds < 3600 * 24) {
+ } else if (seconds < oneDayInSeconds) {
return `${Math.floor(seconds / (60 * 60))}h`;
}
- return `${Math.floor(seconds / (60 * 60 * 24))}d`;
+ const days = Math.floor(seconds / (oneDayInSeconds));
+ const hours = Math.floor((seconds % (oneDayInSeconds)) / oneHourInSeconds);
+ return `${days}d ${hours}h`;
}
async function getGitInfo() {
diff --git a/src/controllers/admin/logs.js b/src/controllers/admin/logs.js
index 51ed116eca..1fe0bb86c9 100644
--- a/src/controllers/admin/logs.js
+++ b/src/controllers/admin/logs.js
@@ -4,6 +4,7 @@ const validator = require('validator');
const winston = require('winston');
const meta = require('../../meta');
+const translator = require('../../translator');
const logsController = module.exports;
@@ -15,6 +16,6 @@ logsController.get = async function (req, res) {
winston.error(err.stack);
}
res.render('admin/advanced/logs', {
- data: validator.escape(logs),
+ data: translator.escape(validator.escape(logs)),
});
};
diff --git a/src/controllers/admin/settings.js b/src/controllers/admin/settings.js
index 69d74ddbae..184c6c0ed6 100644
--- a/src/controllers/admin/settings.js
+++ b/src/controllers/admin/settings.js
@@ -159,11 +159,17 @@ settingsController.api = async (req, res) => {
};
settingsController.activitypub = async (req, res) => {
- const instanceCount = await activitypub.instances.getCount();
+ const [instanceCount, rules, relays] = await Promise.all([
+ activitypub.instances.getCount(),
+ activitypub.rules.list(),
+ activitypub.relays.list(),
+ ]);
res.render('admin/settings/activitypub', {
title: `[[admin/menu:settings/activitypub]]`,
instanceCount,
+ rules,
+ relays,
});
};
@@ -186,7 +192,3 @@ settingsController.advanced = async (req, res) => {
groupsExemptFromMaintenanceMode: groupData,
});
};
-
-
-
-
diff --git a/src/controllers/admin/uploads.js b/src/controllers/admin/uploads.js
index 0f70380695..c8e8075456 100644
--- a/src/controllers/admin/uploads.js
+++ b/src/controllers/admin/uploads.js
@@ -12,7 +12,9 @@ const image = require('../../image');
const plugins = require('../../plugins');
const pagination = require('../../pagination');
-const allowedImageTypes = ['image/png', 'image/jpeg', 'image/pjpeg', 'image/jpg', 'image/gif', 'image/svg+xml'];
+const allowedImageTypes = [
+ 'image/png', 'image/jpeg', 'image/pjpeg', 'image/jpg', 'image/gif', 'image/svg+xml',
+];
const uploadsController = module.exports;
@@ -146,7 +148,7 @@ async function getFileData(currentDir, file) {
}
uploadsController.uploadCategoryPicture = async function (req, res, next) {
- const uploadedFile = req.files.files[0];
+ const uploadedFile = req.files[0];
let params = null;
try {
@@ -162,7 +164,7 @@ uploadsController.uploadCategoryPicture = async function (req, res, next) {
};
uploadsController.uploadFavicon = async function (req, res, next) {
- const uploadedFile = req.files.files[0];
+ const uploadedFile = req.files[0];
const allowedTypes = ['image/x-icon', 'image/vnd.microsoft.icon'];
await validateUpload(uploadedFile, allowedTypes);
@@ -177,7 +179,7 @@ uploadsController.uploadFavicon = async function (req, res, next) {
};
uploadsController.uploadTouchIcon = async function (req, res, next) {
- const uploadedFile = req.files.files[0];
+ const uploadedFile = req.files[0];
const allowedTypes = ['image/png'];
const sizes = [36, 48, 72, 96, 144, 192, 512];
@@ -204,7 +206,7 @@ uploadsController.uploadTouchIcon = async function (req, res, next) {
uploadsController.uploadMaskableIcon = async function (req, res, next) {
- const uploadedFile = req.files.files[0];
+ const uploadedFile = req.files[0];
const allowedTypes = ['image/png'];
await validateUpload(uploadedFile, allowedTypes);
@@ -219,7 +221,7 @@ uploadsController.uploadMaskableIcon = async function (req, res, next) {
};
uploadsController.uploadFile = async function (req, res, next) {
- const uploadedFile = req.files.files[0];
+ const uploadedFile = req.files[0];
let params;
try {
params = JSON.parse(req.body.params);
@@ -254,7 +256,7 @@ uploadsController.uploadOgImage = async function (req, res, next) {
};
async function upload(name, req, res, next) {
- const uploadedFile = req.files.files[0];
+ const uploadedFile = req.files[0];
await validateUpload(uploadedFile, allowedImageTypes);
const filename = name + path.extname(uploadedFile.name);
diff --git a/src/controllers/category.js b/src/controllers/category.js
index fe3809b825..bf72a8cff0 100644
--- a/src/controllers/category.js
+++ b/src/controllers/category.js
@@ -172,7 +172,7 @@ categoryController.get = async function (req, res, next) {
if (meta.config.activitypubEnabled) {
// Include link header for richer parsing
- res.set('Link', `<${nconf.get('url')}/actegory/${cid}>; rel="alternate"; type="application/activity+json"`);
+ res.set('Link', `<${nconf.get('url')}/category/${cid}>; rel="alternate"; type="application/activity+json"`);
// Category accessible
const remoteOk = await privileges.categories.can('read', cid, activitypub._constants.uid);
@@ -222,12 +222,14 @@ function addTags(categoryData, res, currentPage) {
];
if (categoryData.backgroundImage) {
- if (!categoryData.backgroundImage.startsWith('http')) {
- categoryData.backgroundImage = url + categoryData.backgroundImage;
+ let { backgroundImage } = categoryData;
+ backgroundImage = utils.decodeHTMLEntities(backgroundImage);
+ if (!backgroundImage.startsWith('http')) {
+ backgroundImage = url + backgroundImage.replace(new RegExp(`^${nconf.get('relative_path')}`), '');
}
res.locals.metaTags.push({
property: 'og:image',
- content: categoryData.backgroundImage,
+ content: backgroundImage,
noEscape: true,
});
}
@@ -257,7 +259,7 @@ function addTags(categoryData, res, currentPage) {
res.locals.linkTags.push({
rel: 'alternate',
type: 'application/activity+json',
- href: `${nconf.get('url')}/actegory/${categoryData.cid}`,
+ href: `${nconf.get('url')}/category/${categoryData.cid}`,
});
}
}
diff --git a/src/controllers/helpers.js b/src/controllers/helpers.js
index a6ade8c73b..e11692867e 100644
--- a/src/controllers/helpers.js
+++ b/src/controllers/helpers.js
@@ -448,6 +448,10 @@ helpers.getHomePageRoutes = async function (uid) {
};
helpers.formatApiResponse = async (statusCode, res, payload) => {
+ if (!res.hasOwnProperty('req')) {
+ console.log('formatApiResponse', statusCode, payload);
+ }
+
if (res.req.method === 'HEAD') {
return res.sendStatus(statusCode);
}
diff --git a/src/controllers/uploads.js b/src/controllers/uploads.js
index d2e392b5d3..d2cfb48107 100644
--- a/src/controllers/uploads.js
+++ b/src/controllers/uploads.js
@@ -18,7 +18,7 @@ const uploadsController = module.exports;
uploadsController.upload = async function (req, res, filesIterator) {
let files;
try {
- files = req.files.files;
+ files = req.files;
} catch (e) {
return helpers.formatApiResponse(400, res);
}
@@ -27,9 +27,6 @@ uploadsController.upload = async function (req, res, filesIterator) {
if (!Array.isArray(files)) {
return helpers.formatApiResponse(500, res, new Error('[[error:invalid-file]]'));
}
- if (Array.isArray(files[0])) {
- files = files[0];
- }
try {
const images = [];
@@ -126,7 +123,7 @@ async function resizeImage(fileObj) {
uploadsController.uploadThumb = async function (req, res) {
if (!meta.config.allowTopicsThumbnail) {
- deleteTempFiles(req.files.files);
+ deleteTempFiles(req.files);
return helpers.formatApiResponse(503, res, new Error('[[error:topic-thumbnails-are-disabled]]'));
}
@@ -201,7 +198,9 @@ async function saveFileToLocal(uid, folder, uploadedFile) {
}
function deleteTempFiles(files) {
- files.forEach(fileObj => file.delete(fileObj.path));
+ if (Array.isArray(files)) {
+ files.forEach(fileObj => file.delete(fileObj.path));
+ }
}
require('../promisify')(uploadsController, ['upload', 'uploadPost', 'uploadThumb']);
diff --git a/src/controllers/write/admin.js b/src/controllers/write/admin.js
index c4c8e29c8c..8fc7151dc0 100644
--- a/src/controllers/write/admin.js
+++ b/src/controllers/write/admin.js
@@ -1,9 +1,11 @@
'use strict';
+const categories = require('../../categories');
const api = require('../../api');
const helpers = require('../helpers');
const messaging = require('../../messaging');
const events = require('../../events');
+const activitypub = require('../../activitypub');
const Admin = module.exports;
@@ -82,3 +84,36 @@ Admin.chats.deleteRoom = async (req, res) => {
Admin.listGroups = async (req, res) => {
helpers.formatApiResponse(200, res, await api.admin.listGroups());
};
+
+Admin.activitypub = {};
+
+Admin.activitypub.addRule = async (req, res) => {
+ const { type, value, cid } = req.body;
+ const exists = await categories.exists(cid);
+ if (!value || !exists) {
+ return helpers.formatApiResponse(400, res);
+ }
+
+ await activitypub.rules.add(type, value, cid);
+ helpers.formatApiResponse(200, res, await activitypub.rules.list());
+};
+
+Admin.activitypub.deleteRule = async (req, res) => {
+ const { rid } = req.params;
+ await activitypub.rules.delete(rid);
+ helpers.formatApiResponse(200, res, await activitypub.rules.list());
+};
+
+Admin.activitypub.addRelay = async (req, res) => {
+ const { url } = req.body;
+
+ await activitypub.relays.add(url);
+ helpers.formatApiResponse(200, res, await activitypub.relays.list());
+};
+
+Admin.activitypub.removeRelay = async (req, res) => {
+ const { url } = req.params;
+
+ await activitypub.relays.remove(url);
+ helpers.formatApiResponse(200, res, await activitypub.relays.list());
+};
diff --git a/src/controllers/write/topics.js b/src/controllers/write/topics.js
index b46002bb65..06aded5913 100644
--- a/src/controllers/write/topics.js
+++ b/src/controllers/write/topics.js
@@ -138,25 +138,18 @@ Topics.addThumb = async (req, res) => {
const files = await uploadsController.uploadThumb(req, res); // response is handled here
- // Add uploaded files to topic zset
+ // Add uploaded files to topic hash
if (files && files.length) {
- await Promise.all(files.map(async (fileObj) => {
+ for (const fileObj of files) {
+ // eslint-disable-next-line no-await-in-loop
await topics.thumbs.associate({
id: req.params.tid,
path: fileObj.url,
});
- }));
+ }
}
};
-Topics.migrateThumbs = async (req, res) => {
- await api.topics.migrateThumbs(req, {
- from: req.params.tid,
- to: req.body.tid,
- });
-
- helpers.formatApiResponse(200, res, await api.topics.getThumbs(req, { tid: req.body.tid }));
-};
Topics.deleteThumb = async (req, res) => {
if (!req.body.path.startsWith('http')) {
diff --git a/src/database/mongo/hash.js b/src/database/mongo/hash.js
index 958d5384f3..59ea48e1c9 100644
--- a/src/database/mongo/hash.js
+++ b/src/database/mongo/hash.js
@@ -95,7 +95,7 @@ module.exports = function (module) {
};
module.getObjectField = async function (key, field) {
- if (!key) {
+ if (!key || !field) {
return null;
}
const cachedData = {};
@@ -104,7 +104,11 @@ module.exports = function (module) {
return cachedData[key].hasOwnProperty(field) ? cachedData[key][field] : null;
}
field = helpers.fieldToString(field);
- const item = await module.client.collection('objects').findOne({ _key: key }, { projection: { _id: 0, [field]: 1 } });
+ const item = await module.client.collection('objects').findOne({
+ _key: key,
+ }, {
+ projection: { _id: 0, [field]: 1 },
+ });
if (!item) {
return null;
}
diff --git a/src/database/postgres/hash.js b/src/database/postgres/hash.js
index 5e3a842d22..834d46ec3e 100644
--- a/src/database/postgres/hash.js
+++ b/src/database/postgres/hash.js
@@ -153,7 +153,7 @@ SELECT h."data"
};
module.getObjectField = async function (key, field) {
- if (!key) {
+ if (!key || !field) {
return null;
}
diff --git a/src/database/redis.js b/src/database/redis.js
index f73ee79313..d2118aa925 100644
--- a/src/database/redis.js
+++ b/src/database/redis.js
@@ -61,7 +61,7 @@ redisModule.checkCompatibilityVersion = function (version, callback) {
};
redisModule.close = async function () {
- await redisModule.client.quit();
+ await redisModule.client.close();
if (redisModule.objectCache) {
redisModule.objectCache.reset();
}
@@ -104,8 +104,11 @@ redisModule.info = async function (cxn) {
redisModule.socketAdapter = async function () {
const redisAdapter = require('@socket.io/redis-adapter');
- const pub = await connection.connect(nconf.get('redis'));
- const sub = await connection.connect(nconf.get('redis'));
+ const redisConfig = nconf.get('redis');
+ const [pub, sub] = await Promise.all([
+ connection.connect(redisConfig),
+ connection.connect(redisConfig),
+ ]);
return redisAdapter(pub, sub, {
key: `db:${nconf.get('redis:database')}:adapter_key`,
});
diff --git a/src/database/redis/connection.js b/src/database/redis/connection.js
index a4ba757ef6..fb2aceb33b 100644
--- a/src/database/redis/connection.js
+++ b/src/database/redis/connection.js
@@ -1,7 +1,7 @@
'use strict';
const nconf = require('nconf');
-const Redis = require('ioredis');
+const { createClient, createCluster, createSentinel } = require('redis');
const winston = require('winston');
const connection = module.exports;
@@ -13,28 +13,40 @@ connection.connect = async function (options) {
let cxn;
if (options.cluster) {
- cxn = new Redis.Cluster(options.cluster, options.options);
- } else if (options.sentinels) {
- cxn = new Redis({
- sentinels: options.sentinels,
+ const rootNodes = options.cluster.map(node => ({ url : `redis://${node.host}:${node.port}` }));
+ cxn = createCluster({
...options.options,
+ rootNodes: rootNodes,
+ });
+ } else if (options.sentinels) {
+ const sentinelRootNodes = options.sentinels.map(sentinel => ({ host: sentinel.host, port: sentinel.port }));
+ cxn = createSentinel({
+ ...options.options,
+ name: 'sentinel-db',
+ sentinelRootNodes,
});
} else if (redis_socket_or_host && String(redis_socket_or_host).indexOf('/') >= 0) {
// If redis.host contains a path name character, use the unix dom sock connection. ie, /tmp/redis.sock
- cxn = new Redis({
+ cxn = createClient({
...options.options,
- path: redis_socket_or_host,
password: options.password,
- db: options.database,
+ database: options.database,
+ socket: {
+ path: redis_socket_or_host,
+ reconnectStrategy: 3000,
+ },
});
} else {
// Else, connect over tcp/ip
- cxn = new Redis({
+ cxn = createClient({
...options.options,
- host: redis_socket_or_host,
- port: options.port,
password: options.password,
- db: options.database,
+ database: options.database,
+ socket: {
+ host: redis_socket_or_host,
+ port: options.port,
+ reconnectStrategy: 3000,
+ },
});
}
@@ -49,9 +61,14 @@ connection.connect = async function (options) {
});
cxn.on('ready', () => {
// back-compat with node_redis
- cxn.batch = cxn.pipeline;
+ cxn.batch = cxn.multi;
resolve(cxn);
});
+ cxn.connect().then(() => {
+ winston.info('Connected to Redis successfully');
+ }).catch((err) => {
+ winston.error('Error connecting to Redis:', err);
+ });
if (options.password) {
cxn.auth(options.password);
diff --git a/src/database/redis/hash.js b/src/database/redis/hash.js
index 4c6e7b374f..f046e62180 100644
--- a/src/database/redis/hash.js
+++ b/src/database/redis/hash.js
@@ -25,12 +25,13 @@ module.exports = function (module) {
if (!Object.keys(data).length) {
return;
}
+ const strObj = helpers.objectFieldsToString(data);
if (Array.isArray(key)) {
const batch = module.client.batch();
- key.forEach(k => batch.hmset(k, data));
+ key.forEach(k => batch.hSet(k, strObj));
await helpers.execBatch(batch);
} else {
- await module.client.hmset(key, data);
+ await module.client.hSet(key, strObj);
}
cache.del(key);
@@ -49,10 +50,16 @@ module.exports = function (module) {
const batch = module.client.batch();
data.forEach((item) => {
+ Object.keys(item[1]).forEach((key) => {
+ if (item[1][key] === undefined || item[1][key] === null) {
+ delete item[1][key];
+ }
+ });
if (Object.keys(item[1]).length) {
- batch.hmset(item[0], item[1]);
+ batch.hSet(item[0], helpers.objectFieldsToString(item[1]));
}
});
+
await helpers.execBatch(batch);
cache.del(data.map(item => item[0]));
};
@@ -61,12 +68,15 @@ module.exports = function (module) {
if (!field) {
return;
}
+ if (value === null || value === undefined) {
+ return;
+ }
if (Array.isArray(key)) {
const batch = module.client.batch();
- key.forEach(k => batch.hset(k, field, value));
+ key.forEach(k => batch.hSet(k, field, String(value)));
await helpers.execBatch(batch);
} else {
- await module.client.hset(key, field, value);
+ await module.client.hSet(key, field, String(value));
}
cache.del(key);
@@ -86,15 +96,15 @@ module.exports = function (module) {
};
module.getObjectField = async function (key, field) {
- if (!key) {
+ if (!key || !field) {
return null;
}
const cachedData = {};
cache.getUnCachedKeys([key], cachedData);
if (cachedData[key]) {
- return cachedData[key].hasOwnProperty(field) ? cachedData[key][field] : null;
+ return Object.hasOwn(cachedData[key], field) ? cachedData[key][field] : null;
}
- return await module.client.hget(key, String(field));
+ return await module.client.hGet(key, String(field));
};
module.getObjectFields = async function (key, fields) {
@@ -116,10 +126,10 @@ module.exports = function (module) {
let data = [];
if (unCachedKeys.length > 1) {
const batch = module.client.batch();
- unCachedKeys.forEach(k => batch.hgetall(k));
+ unCachedKeys.forEach(k => batch.hGetAll(k));
data = await helpers.execBatch(batch);
} else if (unCachedKeys.length === 1) {
- data = [await module.client.hgetall(unCachedKeys[0])];
+ data = [await module.client.hGetAll(unCachedKeys[0])];
}
// convert empty objects into null for back-compat with node_redis
@@ -149,21 +159,21 @@ module.exports = function (module) {
};
module.getObjectKeys = async function (key) {
- return await module.client.hkeys(key);
+ return await module.client.hKeys(key);
};
module.getObjectValues = async function (key) {
- return await module.client.hvals(key);
+ return await module.client.hVals(key);
};
module.isObjectField = async function (key, field) {
- const exists = await module.client.hexists(key, field);
+ const exists = await module.client.hExists(key, String(field));
return exists === 1;
};
module.isObjectFields = async function (key, fields) {
const batch = module.client.batch();
- fields.forEach(f => batch.hexists(String(key), String(f)));
+ fields.forEach(f => batch.hExists(String(key), String(f)));
const results = await helpers.execBatch(batch);
return Array.isArray(results) ? helpers.resultsToBool(results) : null;
};
@@ -174,7 +184,7 @@ module.exports = function (module) {
}
field = field.toString();
if (field) {
- await module.client.hdel(key, field);
+ await module.client.hDel(key, field);
cache.del(key);
}
};
@@ -189,10 +199,10 @@ module.exports = function (module) {
}
if (Array.isArray(key)) {
const batch = module.client.batch();
- key.forEach(k => batch.hdel(k, fields));
+ key.forEach(k => batch.hDel(k, fields));
await helpers.execBatch(batch);
} else {
- await module.client.hdel(key, fields);
+ await module.client.hDel(key, fields);
}
cache.del(key);
@@ -214,10 +224,10 @@ module.exports = function (module) {
let result;
if (Array.isArray(key)) {
const batch = module.client.batch();
- key.forEach(k => batch.hincrby(k, field, value));
+ key.forEach(k => batch.hIncrBy(k, field, value));
result = await helpers.execBatch(batch);
} else {
- result = await module.client.hincrby(key, field, value);
+ result = await module.client.hIncrBy(key, field, value);
}
cache.del(key);
return Array.isArray(result) ? result.map(value => parseInt(value, 10)) : parseInt(result, 10);
@@ -231,7 +241,7 @@ module.exports = function (module) {
const batch = module.client.batch();
data.forEach((item) => {
for (const [field, value] of Object.entries(item[1])) {
- batch.hincrby(item[0], field, value);
+ batch.hIncrBy(item[0], field, value);
}
});
await helpers.execBatch(batch);
diff --git a/src/database/redis/helpers.js b/src/database/redis/helpers.js
index 8961da8255..39585b1f88 100644
--- a/src/database/redis/helpers.js
+++ b/src/database/redis/helpers.js
@@ -5,13 +5,8 @@ const helpers = module.exports;
helpers.noop = function () {};
helpers.execBatch = async function (batch) {
- const results = await batch.exec();
- return results.map(([err, res]) => {
- if (err) {
- throw err;
- }
- return res;
- });
+ const results = await batch.execAsPipeline();
+ return results;
};
helpers.resultsToBool = function (results) {
@@ -21,10 +16,29 @@ helpers.resultsToBool = function (results) {
return results;
};
-helpers.zsetToObjectArray = function (data) {
- const objects = new Array(data.length / 2);
- for (let i = 0, k = 0; i < objects.length; i += 1, k += 2) {
- objects[i] = { value: data[k], score: parseFloat(data[k + 1]) };
- }
- return objects;
+helpers.objectFieldsToString = function (obj) {
+ const stringified = Object.fromEntries(
+ Object.entries(obj).map(([key, value]) => [key, String(value)])
+ );
+ return stringified;
+};
+
+helpers.normalizeLexRange = function (min, max, reverse) {
+ let minmin;
+ let maxmax;
+ if (reverse) {
+ minmin = '+';
+ maxmax = '-';
+ } else {
+ minmin = '-';
+ maxmax = '+';
+ }
+
+ if (min !== minmin && !min.match(/^[[(]/)) {
+ min = `[${min}`;
+ }
+ if (max !== maxmax && !max.match(/^[[(]/)) {
+ max = `[${max}`;
+ }
+ return { lmin: min, lmax: max };
};
diff --git a/src/database/redis/list.js b/src/database/redis/list.js
index 101ef178e3..229069b6ed 100644
--- a/src/database/redis/list.js
+++ b/src/database/redis/list.js
@@ -1,27 +1,25 @@
'use strict';
module.exports = function (module) {
- const helpers = require('./helpers');
-
module.listPrepend = async function (key, value) {
if (!key) {
return;
}
- await module.client.lpush(key, value);
+ await module.client.lPush(key, Array.isArray(value) ? value.map(String) : String(value));
};
module.listAppend = async function (key, value) {
if (!key) {
return;
}
- await module.client.rpush(key, value);
+ await module.client.rPush(key, Array.isArray(value) ? value.map(String) : String(value));
};
module.listRemoveLast = async function (key) {
if (!key) {
return;
}
- return await module.client.rpop(key);
+ return await module.client.rPop(key);
};
module.listRemoveAll = async function (key, value) {
@@ -29,11 +27,11 @@ module.exports = function (module) {
return;
}
if (Array.isArray(value)) {
- const batch = module.client.batch();
- value.forEach(value => batch.lrem(key, 0, value));
- await helpers.execBatch(batch);
+ const batch = module.client.multi();
+ value.forEach(value => batch.lRem(key, 0, value));
+ await batch.execAsPipeline();
} else {
- await module.client.lrem(key, 0, value);
+ await module.client.lRem(key, 0, value);
}
};
@@ -41,17 +39,17 @@ module.exports = function (module) {
if (!key) {
return;
}
- await module.client.ltrim(key, start, stop);
+ await module.client.lTrim(key, start, stop);
};
module.getListRange = async function (key, start, stop) {
if (!key) {
return;
}
- return await module.client.lrange(key, start, stop);
+ return await module.client.lRange(key, start, stop);
};
module.listLength = async function (key) {
- return await module.client.llen(key);
+ return await module.client.lLen(key);
};
};
diff --git a/src/database/redis/main.js b/src/database/redis/main.js
index b849361a8e..a6506ba701 100644
--- a/src/database/redis/main.js
+++ b/src/database/redis/main.js
@@ -4,7 +4,7 @@ module.exports = function (module) {
const helpers = require('./helpers');
module.flushdb = async function () {
- await module.client.send_command('flushdb', []);
+ await module.client.sendCommand(['FLUSHDB']);
};
module.emptydb = async function () {
@@ -32,9 +32,9 @@ module.exports = function (module) {
const seen = Object.create(null);
do {
/* eslint-disable no-await-in-loop */
- const res = await module.client.scan(cursor, 'MATCH', params.match, 'COUNT', 10000);
- cursor = res[0];
- const values = res[1].filter((value) => {
+ const res = await module.client.scan(cursor, { MATCH: params.match, COUNT: 10000 });
+ cursor = res.cursor;
+ const values = res.keys.filter((value) => {
const isSeen = !!seen[value];
if (!isSeen) {
seen[value] = 1;
@@ -67,7 +67,7 @@ module.exports = function (module) {
if (!keys || !Array.isArray(keys) || !keys.length) {
return [];
}
- return await module.client.mget(keys);
+ return await module.client.mGet(keys);
};
module.set = async function (key, value) {
@@ -96,26 +96,26 @@ module.exports = function (module) {
};
module.expire = async function (key, seconds) {
- await module.client.expire(key, seconds);
+ await module.client.EXPIRE(key, seconds);
};
module.expireAt = async function (key, timestamp) {
- await module.client.expireat(key, timestamp);
+ await module.client.EXPIREAT(key, timestamp);
};
module.pexpire = async function (key, ms) {
- await module.client.pexpire(key, ms);
+ await module.client.PEXPIRE(key, ms);
};
module.pexpireAt = async function (key, timestamp) {
- await module.client.pexpireat(key, timestamp);
+ await module.client.PEXPIREAT(key, timestamp);
};
module.ttl = async function (key) {
- return await module.client.ttl(key);
+ return await module.client.TTL(key);
};
module.pttl = async function (key) {
- return await module.client.pttl(key);
+ return await module.client.PTTL(key);
};
};
diff --git a/src/database/redis/pubsub.js b/src/database/redis/pubsub.js
index a7d220682d..1868ac86ad 100644
--- a/src/database/redis/pubsub.js
+++ b/src/database/redis/pubsub.js
@@ -13,12 +13,7 @@ const PubSub = function () {
self.queue = [];
connection.connect().then((client) => {
self.subClient = client;
- self.subClient.subscribe(channelName);
- self.subClient.on('message', (channel, message) => {
- if (channel !== channelName) {
- return;
- }
-
+ self.subClient.subscribe(channelName, (message) => {
try {
const msg = JSON.parse(message);
self.emit(msg.event, msg.data);
diff --git a/src/database/redis/sets.js b/src/database/redis/sets.js
index b2b390598b..dd7e484325 100644
--- a/src/database/redis/sets.js
+++ b/src/database/redis/sets.js
@@ -10,7 +10,7 @@ module.exports = function (module) {
if (!value.length) {
return;
}
- await module.client.sadd(key, value);
+ await module.client.sAdd(key, value.map(String));
};
module.setsAdd = async function (keys, value) {
@@ -18,7 +18,7 @@ module.exports = function (module) {
return;
}
const batch = module.client.batch();
- keys.forEach(k => batch.sadd(String(k), String(value)));
+ keys.forEach(k => batch.sAdd(String(k), String(value)));
await helpers.execBatch(batch);
};
@@ -34,57 +34,57 @@ module.exports = function (module) {
}
const batch = module.client.batch();
- key.forEach(k => batch.srem(String(k), value));
+ key.forEach(k => batch.sRem(String(k), value.map(String)));
await helpers.execBatch(batch);
};
module.setsRemove = async function (keys, value) {
const batch = module.client.batch();
- keys.forEach(k => batch.srem(String(k), value));
+ keys.forEach(k => batch.sRem(String(k), String(value)));
await helpers.execBatch(batch);
};
module.isSetMember = async function (key, value) {
- const result = await module.client.sismember(key, value);
+ const result = await module.client.sIsMember(key, String(value));
return result === 1;
};
module.isSetMembers = async function (key, values) {
const batch = module.client.batch();
- values.forEach(v => batch.sismember(String(key), String(v)));
+ values.forEach(v => batch.sIsMember(String(key), String(v)));
const results = await helpers.execBatch(batch);
return results ? helpers.resultsToBool(results) : null;
};
module.isMemberOfSets = async function (sets, value) {
const batch = module.client.batch();
- sets.forEach(s => batch.sismember(String(s), String(value)));
+ sets.forEach(s => batch.sIsMember(String(s), String(value)));
const results = await helpers.execBatch(batch);
return results ? helpers.resultsToBool(results) : null;
};
module.getSetMembers = async function (key) {
- return await module.client.smembers(key);
+ return await module.client.sMembers(key);
};
module.getSetsMembers = async function (keys) {
const batch = module.client.batch();
- keys.forEach(k => batch.smembers(String(k)));
+ keys.forEach(k => batch.sMembers(String(k)));
return await helpers.execBatch(batch);
};
module.setCount = async function (key) {
- return await module.client.scard(key);
+ return await module.client.sCard(key);
};
module.setsCount = async function (keys) {
const batch = module.client.batch();
- keys.forEach(k => batch.scard(String(k)));
+ keys.forEach(k => batch.sCard(String(k)));
return await helpers.execBatch(batch);
};
module.setRemoveRandom = async function (key) {
- return await module.client.spop(key);
+ return await module.client.sPop(key);
};
return module;
diff --git a/src/database/redis/sorted.js b/src/database/redis/sorted.js
index 013477da5a..d8613a3a68 100644
--- a/src/database/redis/sorted.js
+++ b/src/database/redis/sorted.js
@@ -11,34 +11,74 @@ module.exports = function (module) {
require('./sorted/intersect')(module);
module.getSortedSetRange = async function (key, start, stop) {
- return await sortedSetRange('zrange', key, start, stop, '-inf', '+inf', false);
+ return await sortedSetRange(key, start, stop, '-inf', '+inf', false, false, false);
};
module.getSortedSetRevRange = async function (key, start, stop) {
- return await sortedSetRange('zrevrange', key, start, stop, '-inf', '+inf', false);
+ return await sortedSetRange(key, start, stop, '-inf', '+inf', false, true, false);
};
module.getSortedSetRangeWithScores = async function (key, start, stop) {
- return await sortedSetRange('zrange', key, start, stop, '-inf', '+inf', true);
+ return await sortedSetRange(key, start, stop, '-inf', '+inf', true, false, false);
};
module.getSortedSetRevRangeWithScores = async function (key, start, stop) {
- return await sortedSetRange('zrevrange', key, start, stop, '-inf', '+inf', true);
+ return await sortedSetRange(key, start, stop, '-inf', '+inf', true, true, false);
};
- async function sortedSetRange(method, key, start, stop, min, max, withScores) {
+ module.getSortedSetRangeByScore = async function (key, start, count, min, max) {
+ return await sortedSetRangeByScore(key, start, count, min, max, false, false);
+ };
+
+ module.getSortedSetRevRangeByScore = async function (key, start, count, max, min) {
+ return await sortedSetRangeByScore(key, start, count, max, min, false, true);
+ };
+
+ module.getSortedSetRangeByScoreWithScores = async function (key, start, count, min, max) {
+ return await sortedSetRangeByScore(key, start, count, min, max, true, false);
+ };
+
+ module.getSortedSetRevRangeByScoreWithScores = async function (key, start, count, max, min) {
+ return await sortedSetRangeByScore(key, start, count, max, min, true, true);
+ };
+
+ async function sortedSetRangeByScore(key, start, count, min, max, withScores, rev) {
+ if (parseInt(count, 10) === 0) {
+ return [];
+ }
+ const stop = (parseInt(count, 10) === -1) ? -1 : (start + count - 1);
+ return await sortedSetRange(key, start, stop, min, max, withScores, rev, true);
+ }
+
+ async function sortedSetRange(key, start, stop, min, max, withScores, rev, byScore) {
+ const opts = {};
+ const cmd = withScores ? 'zRangeWithScores' : 'zRange';
+ if (byScore) {
+ opts.BY = 'SCORE';
+ opts.LIMIT = { offset: start, count: stop !== -1 ? stop + 1 : stop };
+ }
+ if (rev) {
+ opts.REV = true;
+ }
+
if (Array.isArray(key)) {
if (!key.length) {
return [];
}
const batch = module.client.batch();
- key.forEach(key => batch[method](genParams(method, key, 0, stop, min, max, true)));
+
+ if (byScore) {
+ key.forEach(key => batch.zRangeWithScores(key, min, max, {
+ ...opts,
+ LIMIT: { offset: 0, count: stop !== -1 ? stop + 1 : stop },
+ }));
+ } else {
+ key.forEach(key => batch.zRangeWithScores(key, 0, stop, { ...opts }));
+ }
+
const data = await helpers.execBatch(batch);
-
- const batchData = data.map(setData => helpers.zsetToObjectArray(setData));
-
- let objects = dbHelpers.mergeBatch(batchData, 0, stop, method === 'zrange' ? 1 : -1);
-
+ const batchData = data;
+ let objects = dbHelpers.mergeBatch(batchData, 0, stop, rev ? -1 : 1);
if (start > 0) {
objects = objects.slice(start, stop !== -1 ? stop + 1 : undefined);
}
@@ -48,63 +88,25 @@ module.exports = function (module) {
return objects;
}
- const params = genParams(method, key, start, stop, min, max, withScores);
- const data = await module.client[method](params);
+ let data;
+ if (byScore) {
+ data = await module.client[cmd](key, min, max, opts);
+ } else {
+ data = await module.client[cmd](key, start, stop, opts);
+ }
+
if (!withScores) {
return data;
}
- const objects = helpers.zsetToObjectArray(data);
- return objects;
- }
-
- function genParams(method, key, start, stop, min, max, withScores) {
- const params = {
- zrevrange: [key, start, stop],
- zrange: [key, start, stop],
- zrangebyscore: [key, min, max],
- zrevrangebyscore: [key, max, min],
- };
- if (withScores) {
- params[method].push('WITHSCORES');
- }
-
- if (method === 'zrangebyscore' || method === 'zrevrangebyscore') {
- const count = stop !== -1 ? stop - start + 1 : stop;
- params[method].push('LIMIT', start, count);
- }
- return params[method];
- }
-
- module.getSortedSetRangeByScore = async function (key, start, count, min, max) {
- return await sortedSetRangeByScore('zrangebyscore', key, start, count, min, max, false);
- };
-
- module.getSortedSetRevRangeByScore = async function (key, start, count, max, min) {
- return await sortedSetRangeByScore('zrevrangebyscore', key, start, count, min, max, false);
- };
-
- module.getSortedSetRangeByScoreWithScores = async function (key, start, count, min, max) {
- return await sortedSetRangeByScore('zrangebyscore', key, start, count, min, max, true);
- };
-
- module.getSortedSetRevRangeByScoreWithScores = async function (key, start, count, max, min) {
- return await sortedSetRangeByScore('zrevrangebyscore', key, start, count, min, max, true);
- };
-
- async function sortedSetRangeByScore(method, key, start, count, min, max, withScores) {
- if (parseInt(count, 10) === 0) {
- return [];
- }
- const stop = (parseInt(count, 10) === -1) ? -1 : (start + count - 1);
- return await sortedSetRange(method, key, start, stop, min, max, withScores);
+ return data;
}
module.sortedSetCount = async function (key, min, max) {
- return await module.client.zcount(key, min, max);
+ return await module.client.zCount(key, min, max);
};
module.sortedSetCard = async function (key) {
- return await module.client.zcard(key);
+ return await module.client.zCard(key);
};
module.sortedSetsCard = async function (keys) {
@@ -112,7 +114,7 @@ module.exports = function (module) {
return [];
}
const batch = module.client.batch();
- keys.forEach(k => batch.zcard(String(k)));
+ keys.forEach(k => batch.zCard(String(k)));
return await helpers.execBatch(batch);
};
@@ -125,26 +127,26 @@ module.exports = function (module) {
}
const batch = module.client.batch();
if (min !== '-inf' || max !== '+inf') {
- keys.forEach(k => batch.zcount(String(k), min, max));
+ keys.forEach(k => batch.zCount(String(k), min, max));
} else {
- keys.forEach(k => batch.zcard(String(k)));
+ keys.forEach(k => batch.zCard(String(k)));
}
const counts = await helpers.execBatch(batch);
return counts.reduce((acc, val) => acc + val, 0);
};
module.sortedSetRank = async function (key, value) {
- return await module.client.zrank(key, value);
+ return await module.client.zRank(key, String(value));
};
module.sortedSetRevRank = async function (key, value) {
- return await module.client.zrevrank(key, value);
+ return await module.client.zRevRank(key, String(value));
};
module.sortedSetsRanks = async function (keys, values) {
const batch = module.client.batch();
for (let i = 0; i < values.length; i += 1) {
- batch.zrank(keys[i], String(values[i]));
+ batch.zRank(keys[i], String(values[i]));
}
return await helpers.execBatch(batch);
};
@@ -152,7 +154,7 @@ module.exports = function (module) {
module.sortedSetsRevRanks = async function (keys, values) {
const batch = module.client.batch();
for (let i = 0; i < values.length; i += 1) {
- batch.zrevrank(keys[i], String(values[i]));
+ batch.zRevRank(keys[i], String(values[i]));
}
return await helpers.execBatch(batch);
};
@@ -160,7 +162,7 @@ module.exports = function (module) {
module.sortedSetRanks = async function (key, values) {
const batch = module.client.batch();
for (let i = 0; i < values.length; i += 1) {
- batch.zrank(key, String(values[i]));
+ batch.zRank(key, String(values[i]));
}
return await helpers.execBatch(batch);
};
@@ -168,7 +170,7 @@ module.exports = function (module) {
module.sortedSetRevRanks = async function (key, values) {
const batch = module.client.batch();
for (let i = 0; i < values.length; i += 1) {
- batch.zrevrank(key, String(values[i]));
+ batch.zRevRank(key, String(values[i]));
}
return await helpers.execBatch(batch);
};
@@ -177,8 +179,7 @@ module.exports = function (module) {
if (!key || value === undefined) {
return null;
}
-
- const score = await module.client.zscore(key, value);
+ const score = await module.client.zScore(key, String(value));
return score === null ? score : parseFloat(score);
};
@@ -187,7 +188,7 @@ module.exports = function (module) {
return [];
}
const batch = module.client.batch();
- keys.forEach(key => batch.zscore(String(key), String(value)));
+ keys.forEach(key => batch.zScore(String(key), String(value)));
const scores = await helpers.execBatch(batch);
return scores.map(d => (d === null ? d : parseFloat(d)));
};
@@ -197,7 +198,7 @@ module.exports = function (module) {
return [];
}
const batch = module.client.batch();
- values.forEach(value => batch.zscore(String(key), String(value)));
+ values.forEach(value => batch.zScore(String(key), String(value)));
const scores = await helpers.execBatch(batch);
return scores.map(d => (d === null ? d : parseFloat(d)));
};
@@ -211,9 +212,9 @@ module.exports = function (module) {
if (!values.length) {
return [];
}
- const batch = module.client.batch();
- values.forEach(v => batch.zscore(key, String(v)));
- const results = await helpers.execBatch(batch);
+ const batch = module.client.multi();
+ values.forEach(v => batch.zScore(key, String(v)));
+ const results = await batch.execAsPipeline();
return results.map(utils.isNumber);
};
@@ -221,20 +222,18 @@ module.exports = function (module) {
if (!Array.isArray(keys) || !keys.length) {
return [];
}
- const batch = module.client.batch();
- keys.forEach(k => batch.zscore(k, String(value)));
- const results = await helpers.execBatch(batch);
+ const batch = module.client.multi();
+ keys.forEach(k => batch.zScore(k, String(value)));
+ const results = await batch.execAsPipeline();
return results.map(utils.isNumber);
};
module.getSortedSetMembers = async function (key) {
- return await module.client.zrange(key, 0, -1);
+ return await module.client.zRange(key, 0, -1);
};
module.getSortedSetMembersWithScores = async function (key) {
- return helpers.zsetToObjectArray(
- await module.client.zrange(key, 0, -1, 'WITHSCORES')
- );
+ return await module.client.zRangeWithScores(key, 0, -1);
};
module.getSortedSetsMembers = async function (keys) {
@@ -242,7 +241,7 @@ module.exports = function (module) {
return [];
}
const batch = module.client.batch();
- keys.forEach(k => batch.zrange(k, 0, -1));
+ keys.forEach(k => batch.zRange(k, 0, -1));
return await helpers.execBatch(batch);
};
@@ -251,65 +250,52 @@ module.exports = function (module) {
return [];
}
const batch = module.client.batch();
- keys.forEach(k => batch.zrange(k, 0, -1, 'WITHSCORES'));
+ keys.forEach(k => batch.zRangeWithScores(k, 0, -1));
const res = await helpers.execBatch(batch);
- return res.map(helpers.zsetToObjectArray);
+ return res;
};
module.sortedSetIncrBy = async function (key, increment, value) {
- const newValue = await module.client.zincrby(key, increment, value);
+ const newValue = await module.client.zIncrBy(key, increment, String(value));
return parseFloat(newValue);
};
module.sortedSetIncrByBulk = async function (data) {
const multi = module.client.multi();
data.forEach((item) => {
- multi.zincrby(item[0], item[1], item[2]);
+ multi.zIncrBy(item[0], item[1], String(item[2]));
});
const result = await multi.exec();
- return result.map(item => item && parseFloat(item[1]));
+ return result;
};
- module.getSortedSetRangeByLex = async function (key, min, max, start, count) {
- return await sortedSetLex('zrangebylex', false, key, min, max, start, count);
+ module.getSortedSetRangeByLex = async function (key, min, max, start = 0, count = -1) {
+ const { lmin, lmax } = helpers.normalizeLexRange(min, max, false);
+ return await module.client.zRange(key, lmin, lmax, {
+ BY: 'LEX',
+ LIMIT: { offset: start, count: count },
+ });
};
- module.getSortedSetRevRangeByLex = async function (key, max, min, start, count) {
- return await sortedSetLex('zrevrangebylex', true, key, max, min, start, count);
+ module.getSortedSetRevRangeByLex = async function (key, max, min, start = 0, count = -1) {
+ const { lmin, lmax } = helpers.normalizeLexRange(max, min, true);
+ return await module.client.zRange(key, lmin, lmax, {
+ REV: true,
+ BY: 'LEX',
+ LIMIT: { offset: start, count: count },
+ });
};
module.sortedSetRemoveRangeByLex = async function (key, min, max) {
- await sortedSetLex('zremrangebylex', false, key, min, max);
+ const { lmin, lmax } = helpers.normalizeLexRange(min, max, false);
+ await module.client.zRemRangeByLex(key, lmin, lmax);
};
module.sortedSetLexCount = async function (key, min, max) {
- return await sortedSetLex('zlexcount', false, key, min, max);
+ const { lmin, lmax } = helpers.normalizeLexRange(min, max, false);
+ return await module.client.zLexCount(key, lmin, lmax);
};
- async function sortedSetLex(method, reverse, key, min, max, start, count) {
- let minmin;
- let maxmax;
- if (reverse) {
- minmin = '+';
- maxmax = '-';
- } else {
- minmin = '-';
- maxmax = '+';
- }
-
- if (min !== minmin && !min.match(/^[[(]/)) {
- min = `[${min}`;
- }
- if (max !== maxmax && !max.match(/^[[(]/)) {
- max = `[${max}`;
- }
- const args = [key, min, max];
- if (count) {
- args.push('LIMIT', start, count);
- }
- return await module.client[method](args);
- }
-
module.getSortedSetScan = async function (params) {
let cursor = '0';
@@ -318,20 +304,19 @@ module.exports = function (module) {
const seen = Object.create(null);
do {
/* eslint-disable no-await-in-loop */
- const res = await module.client.zscan(params.key, cursor, 'MATCH', params.match, 'COUNT', 5000);
- cursor = res[0];
+ const res = await module.client.zScan(params.key, cursor, { MATCH: params.match, COUNT: 5000 });
+ cursor = res.cursor;
done = cursor === '0';
- const data = res[1];
- for (let i = 0; i < data.length; i += 2) {
- const value = data[i];
- if (!seen[value]) {
- seen[value] = 1;
+ for (let i = 0; i < res.members.length; i ++) {
+ const item = res.members[i];
+ if (!seen[item.value]) {
+ seen[item.value] = 1;
if (params.withScores) {
- returnData.push({ value: value, score: parseFloat(data[i + 1]) });
+ returnData.push({ value: item.value, score: parseFloat(item.score) });
} else {
- returnData.push(value);
+ returnData.push(item.value);
}
if (params.limit && returnData.length >= params.limit) {
done = true;
diff --git a/src/database/redis/sorted/add.js b/src/database/redis/sorted/add.js
index 660618b8a4..264c877845 100644
--- a/src/database/redis/sorted/add.js
+++ b/src/database/redis/sorted/add.js
@@ -1,7 +1,6 @@
'use strict';
module.exports = function (module) {
- const helpers = require('../helpers');
const utils = require('../../../utils');
module.sortedSetAdd = async function (key, score, value) {
@@ -14,7 +13,8 @@ module.exports = function (module) {
if (!utils.isNumber(score)) {
throw new Error(`[[error:invalid-score, ${score}]]`);
}
- await module.client.zadd(key, score, String(value));
+
+ await module.client.zAdd(key, { score, value: String(value) });
};
async function sortedSetAddMulti(key, scores, values) {
@@ -30,11 +30,8 @@ module.exports = function (module) {
throw new Error(`[[error:invalid-score, ${scores[i]}]]`);
}
}
- const args = [key];
- for (let i = 0; i < scores.length; i += 1) {
- args.push(scores[i], String(values[i]));
- }
- await module.client.zadd(args);
+ const members = scores.map((score, i) => ({ score, value: String(values[i])}));
+ await module.client.zAdd(key, members);
}
module.sortedSetsAdd = async function (keys, scores, value) {
@@ -51,13 +48,16 @@ module.exports = function (module) {
throw new Error('[[error:invalid-data]]');
}
- const batch = module.client.batch();
+ const batch = module.client.multi();
for (let i = 0; i < keys.length; i += 1) {
if (keys[i]) {
- batch.zadd(keys[i], isArrayOfScores ? scores[i] : scores, String(value));
+ batch.zAdd(keys[i], {
+ score: isArrayOfScores ? scores[i] : scores,
+ value: String(value),
+ });
}
}
- await helpers.execBatch(batch);
+ await batch.execAsPipeline();
};
module.sortedSetAddBulk = async function (data) {
@@ -69,8 +69,8 @@ module.exports = function (module) {
if (!utils.isNumber(item[1])) {
throw new Error(`[[error:invalid-score, ${item[1]}]]`);
}
- batch.zadd(item[0], item[1], item[2]);
+ batch.zAdd(item[0], { score: item[1], value: String(item[2]) });
});
- await helpers.execBatch(batch);
+ await batch.execAsPipeline();
};
};
diff --git a/src/database/redis/sorted/intersect.js b/src/database/redis/sorted/intersect.js
index 2b2ed1fe90..983c11abc4 100644
--- a/src/database/redis/sorted/intersect.js
+++ b/src/database/redis/sorted/intersect.js
@@ -8,52 +8,46 @@ module.exports = function (module) {
return 0;
}
const tempSetName = `temp_${Date.now()}`;
-
- const interParams = [tempSetName, keys.length].concat(keys);
-
const multi = module.client.multi();
- multi.zinterstore(interParams);
- multi.zcard(tempSetName);
+ multi.zInterStore(tempSetName, keys);
+ multi.zCard(tempSetName);
multi.del(tempSetName);
const results = await helpers.execBatch(multi);
return results[1] || 0;
};
module.getSortedSetIntersect = async function (params) {
- params.method = 'zrange';
+ params.reverse = false;
return await getSortedSetRevIntersect(params);
};
module.getSortedSetRevIntersect = async function (params) {
- params.method = 'zrevrange';
+ params.reverse = true;
return await getSortedSetRevIntersect(params);
};
async function getSortedSetRevIntersect(params) {
- const { sets } = params;
+ let { sets } = params;
const start = params.hasOwnProperty('start') ? params.start : 0;
const stop = params.hasOwnProperty('stop') ? params.stop : -1;
const weights = params.weights || [];
const tempSetName = `temp_${Date.now()}`;
- let interParams = [tempSetName, sets.length].concat(sets);
+ const interParams = {};
if (weights.length) {
- interParams = interParams.concat(['WEIGHTS'].concat(weights));
+ sets = sets.map((set, index) => ({ key: set, weight: weights[index] }));
}
if (params.aggregate) {
- interParams = interParams.concat(['AGGREGATE', params.aggregate]);
+ interParams['AGGREGATE'] = params.aggregate.toUpperCase();
}
- const rangeParams = [tempSetName, start, stop];
- if (params.withScores) {
- rangeParams.push('WITHSCORES');
- }
+ const rangeCmd = params.withScores ? 'zRangeWithScores' : 'zRange';
const multi = module.client.multi();
- multi.zinterstore(interParams);
- multi[params.method](rangeParams);
+ multi.zInterStore(tempSetName, sets, interParams);
+ multi[rangeCmd](tempSetName, start, stop, { REV: params.reverse});
multi.del(tempSetName);
let results = await helpers.execBatch(multi);
@@ -61,6 +55,6 @@ module.exports = function (module) {
return results ? results[1] : null;
}
results = results[1] || [];
- return helpers.zsetToObjectArray(results);
+ return results;
}
};
diff --git a/src/database/redis/sorted/remove.js b/src/database/redis/sorted/remove.js
index 0c2b0164b0..df4c980b11 100644
--- a/src/database/redis/sorted/remove.js
+++ b/src/database/redis/sorted/remove.js
@@ -18,10 +18,10 @@ module.exports = function (module) {
if (Array.isArray(key)) {
const batch = module.client.batch();
- key.forEach(k => batch.zrem(k, value));
+ key.forEach(k => batch.zRem(k, value.map(String)));
await helpers.execBatch(batch);
} else {
- await module.client.zrem(key, value);
+ await module.client.zRem(key, value.map(String));
}
};
@@ -31,7 +31,7 @@ module.exports = function (module) {
module.sortedSetsRemoveRangeByScore = async function (keys, min, max) {
const batch = module.client.batch();
- keys.forEach(k => batch.zremrangebyscore(k, min, max));
+ keys.forEach(k => batch.zRemRangeByScore(k, min, max));
await helpers.execBatch(batch);
};
@@ -40,7 +40,7 @@ module.exports = function (module) {
return;
}
const batch = module.client.batch();
- data.forEach(item => batch.zrem(item[0], item[1]));
+ data.forEach(item => batch.zRem(item[0], String(item[1])));
await helpers.execBatch(batch);
};
};
diff --git a/src/database/redis/sorted/union.js b/src/database/redis/sorted/union.js
index acd57c2db0..329c5f125f 100644
--- a/src/database/redis/sorted/union.js
+++ b/src/database/redis/sorted/union.js
@@ -4,25 +4,20 @@
module.exports = function (module) {
const helpers = require('../helpers');
module.sortedSetUnionCard = async function (keys) {
- const tempSetName = `temp_${Date.now()}`;
if (!keys.length) {
return 0;
}
- const multi = module.client.multi();
- multi.zunionstore([tempSetName, keys.length].concat(keys));
- multi.zcard(tempSetName);
- multi.del(tempSetName);
- const results = await helpers.execBatch(multi);
- return Array.isArray(results) && results.length ? results[1] : 0;
+ const results = await module.client.zUnion(keys);
+ return results ? results.length : 0;
};
module.getSortedSetUnion = async function (params) {
- params.method = 'zrange';
+ params.reverse = false;
return await module.sortedSetUnion(params);
};
module.getSortedSetRevUnion = async function (params) {
- params.method = 'zrevrange';
+ params.reverse = true;
return await module.sortedSetUnion(params);
};
@@ -32,21 +27,16 @@ module.exports = function (module) {
}
const tempSetName = `temp_${Date.now()}`;
-
- const rangeParams = [tempSetName, params.start, params.stop];
- if (params.withScores) {
- rangeParams.push('WITHSCORES');
- }
-
+ const rangeCmd = params.withScores ? 'zRangeWithScores' : 'zRange';
const multi = module.client.multi();
- multi.zunionstore([tempSetName, params.sets.length].concat(params.sets));
- multi[params.method](rangeParams);
+ multi.zUnionStore(tempSetName, params.sets);
+ multi[rangeCmd](tempSetName, params.start, params.stop, { REV: params.reverse });
multi.del(tempSetName);
let results = await helpers.execBatch(multi);
if (!params.withScores) {
return results ? results[1] : null;
}
results = results[1] || [];
- return helpers.zsetToObjectArray(results);
+ return results;
};
};
diff --git a/src/install.js b/src/install.js
index f0903d3a16..064d5d77ff 100644
--- a/src/install.js
+++ b/src/install.js
@@ -526,6 +526,7 @@ async function enableDefaultPlugins() {
let defaultEnabled = [
'nodebb-plugin-composer-default',
+ 'nodebb-plugin-dbsearch',
'nodebb-plugin-markdown',
'nodebb-plugin-mentions',
'nodebb-plugin-web-push',
diff --git a/src/messaging/rooms.js b/src/messaging/rooms.js
index 8b57b81da7..934bf4e40d 100644
--- a/src/messaging/rooms.js
+++ b/src/messaging/rooms.js
@@ -22,7 +22,7 @@ const roomUidCache = cacheCreate({
});
const intFields = [
- 'roomId', 'timestamp', 'userCount', 'messageCount',
+ 'roomId', 'timestamp', 'userCount', 'messageCount', 'joinLeaveMessages',
];
module.exports = function (Messaging) {
@@ -88,6 +88,7 @@ module.exports = function (Messaging) {
timestamp: now,
notificationSetting: data.notificationSetting,
messageCount: 0,
+ joinLeaveMessages: data.joinLeaveMessages || 0,
};
if (data.hasOwnProperty('roomName') && data.roomName) {
@@ -126,7 +127,7 @@ module.exports = function (Messaging) {
'chat:rooms:public:order:all',
]);
- if (!isPublic) {
+ if (!isPublic && parseInt(room.joinLeaveMessages, 10) === 1) {
// chat owner should also get the user-join system message
await Messaging.addSystemMessage('user-join', uid, roomId);
}
@@ -280,12 +281,22 @@ module.exports = function (Messaging) {
async function addUidsToRoom(uids, roomId) {
const now = Date.now();
const timestamps = uids.map(() => now);
+
await Promise.all([
db.sortedSetAdd(`chat:room:${roomId}:uids`, timestamps, uids),
db.sortedSetAdd(`chat:room:${roomId}:uids:online`, timestamps, uids),
]);
await updateUserCount([roomId]);
- await Promise.all(uids.map(uid => Messaging.addSystemMessage('user-join', uid, roomId)));
+ if (await joinLeaveMessagesEnabled(roomId)) {
+ await Promise.all(
+ uids.map(uid => Messaging.addSystemMessage('user-join', uid, roomId))
+ );
+ }
+ }
+
+ async function joinLeaveMessagesEnabled(roomId) {
+ const roomData = await Messaging.getRoomData(roomId, ['joinLeaveMessages']);
+ return roomData && roomData.joinLeaveMessages === 1;
}
Messaging.removeUsersFromRoom = async (uid, uids, roomId) => {
@@ -319,7 +330,9 @@ module.exports = function (Messaging) {
}
Messaging.leaveRoom = async (uids, roomId) => {
- const isInRoom = await Promise.all(uids.map(uid => Messaging.isUserInRoom(uid, roomId)));
+ const isInRoom = await Promise.all(
+ uids.map(uid => Messaging.isUserInRoom(uid, roomId))
+ );
uids = uids.filter((uid, index) => isInRoom[index]);
const keys = uids
@@ -334,8 +347,11 @@ module.exports = function (Messaging) {
], uids),
db.sortedSetsRemove(keys, roomId),
]);
-
- await Promise.all(uids.map(uid => Messaging.addSystemMessage('user-leave', uid, roomId)));
+ if (await joinLeaveMessagesEnabled(roomId)) {
+ await Promise.all(
+ uids.map(uid => Messaging.addSystemMessage('user-leave', uid, roomId))
+ );
+ }
await updateOwner(roomId);
await updateUserCount([roomId]);
};
@@ -357,10 +373,13 @@ module.exports = function (Messaging) {
], roomIds),
]);
- await Promise.all(
- roomIds.map(roomId => updateOwner(roomId))
- .concat(roomIds.map(roomId => Messaging.addSystemMessage('user-leave', uid, roomId)))
- );
+ await Promise.all(roomIds.map(async (roomId) => {
+ await updateOwner(roomId);
+ if (await joinLeaveMessagesEnabled(roomId)) {
+ await Messaging.addSystemMessage('user-leave', uid, roomId);
+ }
+ }));
+
await updateUserCount(roomIds);
};
diff --git a/src/meta/minifier.js b/src/meta/minifier.js
index 6a617cc45d..0ab268dc4f 100644
--- a/src/meta/minifier.js
+++ b/src/meta/minifier.js
@@ -162,6 +162,7 @@ actions.buildCSS = async function buildCSS(data) {
try {
const opts = {
loadPaths: data.paths,
+ importers: [new sass.NodePackageImporter()],
};
if (data.minify) {
opts.silenceDeprecations = [
diff --git a/src/middleware/activitypub.js b/src/middleware/activitypub.js
index b38caa552a..4504f83d44 100644
--- a/src/middleware/activitypub.js
+++ b/src/middleware/activitypub.js
@@ -3,11 +3,18 @@
const db = require('../database');
const meta = require('../meta');
const activitypub = require('../activitypub');
+const analytics = require('../analytics');
+const helpers = require('./helpers');
const middleware = module.exports;
middleware.enabled = async (req, res, next) => next(!meta.config.activitypubEnabled ? 'route' : undefined);
+middleware.pageview = async (req, res, next) => {
+ await analytics.apPageView({ ip: req.ip });
+ next();
+};
+
middleware.assertS2S = async function (req, res, next) {
// For whatever reason, express accepts does not recognize "profile" as a valid differentiator
// Therefore, manual header parsing is used here.
@@ -53,7 +60,7 @@ middleware.verify = async function (req, res, next) {
next();
};
-middleware.assertPayload = async function (req, res, next) {
+middleware.assertPayload = helpers.try(async function (req, res, next) {
// Checks the validity of the incoming payload against the sender and rejects on failure
activitypub.helpers.log('[middleware/activitypub] Validating incoming payload...');
@@ -125,7 +132,7 @@ middleware.assertPayload = async function (req, res, next) {
activitypub.helpers.log('[middleware/activitypub] Key ownership cross-check passed.');
next();
-};
+});
middleware.resolveObjects = async function (req, res, next) {
const { type, object } = req.body;
diff --git a/src/middleware/index.js b/src/middleware/index.js
index 5a0e69842f..67d8e2faa0 100644
--- a/src/middleware/index.js
+++ b/src/middleware/index.js
@@ -5,7 +5,6 @@ const validator = require('validator');
const nconf = require('nconf');
const toobusy = require('toobusy-js');
const util = require('util');
-const multipart = require('connect-multiparty');
const { csrfSynchronisedProtection } = require('./csrf');
const plugins = require('../plugins');
@@ -24,10 +23,11 @@ const controllers = {
};
const delayCache = cacheCreate({
+ name: 'delay-middleware',
ttl: 1000 * 60,
max: 200,
});
-const multipartMiddleware = multipart();
+
const middleware = module.exports;
@@ -101,17 +101,30 @@ middleware.pluginHooks = helpers.try(async (req, res, next) => {
});
middleware.validateFiles = function validateFiles(req, res, next) {
- if (!req.files.files) {
+ if (!req.files) {
return next(new Error(['[[error:invalid-files]]']));
}
-
- if (Array.isArray(req.files.files) && req.files.files.length) {
- return next();
+ function makeFilesCompatible(files) {
+ if (Array.isArray(files)) {
+ // multer uses originalname and mimetype, but we use name and type
+ files.forEach((file) => {
+ if (file.originalname) {
+ file.name = file.originalname;
+ }
+ if (file.mimetype) {
+ file.type = file.mimetype;
+ }
+ });
+ }
+ next();
+ }
+ if (Array.isArray(req.files) && req.files.length) {
+ return makeFilesCompatible(req.files);
}
- if (typeof req.files.files === 'object') {
- req.files.files = [req.files.files];
- return next();
+ if (typeof req.files === 'object') {
+ req.files = [req.files];
+ return makeFilesCompatible(req.files);
}
return next(new Error(['[[error:invalid-files]]']));
@@ -291,14 +304,3 @@ middleware.checkRequired = function (fields, req, res, next) {
controllers.helpers.formatApiResponse(400, res, new Error(`[[error:required-parameters-missing, ${missing.join(' ')}]]`));
};
-
-middleware.handleMultipart = (req, res, next) => {
- // Applies multipart handler on applicable content-type
- const { 'content-type': contentType } = req.headers;
-
- if (contentType && !contentType.startsWith('multipart/form-data')) {
- return next();
- }
-
- multipartMiddleware(req, res, next);
-};
diff --git a/src/middleware/uploads.js b/src/middleware/uploads.js
index d1ce5b09b2..fdbfc3dbda 100644
--- a/src/middleware/uploads.js
+++ b/src/middleware/uploads.js
@@ -20,10 +20,12 @@ exports.ratelimit = helpers.try(async (req, res, next) => {
}
if (!cache) {
cache = cacheCreate({
+ name: 'upload-rate-limit-cache',
+ max: 100,
ttl: meta.config.uploadRateLimitCooldown * 1000,
});
}
- const count = (cache.get(`${req.ip}:uploaded_file_count`) || 0) + req.files.files.length;
+ const count = (cache.get(`${req.ip}:uploaded_file_count`) || 0) + req.files.length;
if (count > meta.config.uploadRateLimitThreshold) {
return next(new Error(['[[error:upload-ratelimit-reached]]']));
}
diff --git a/src/notifications.js b/src/notifications.js
index df7cf51fb4..bcf27c7d66 100644
--- a/src/notifications.js
+++ b/src/notifications.js
@@ -22,6 +22,7 @@ const Notifications = module.exports;
// ttlcache for email-only chat notifications
const notificationCache = ttlCache({
+ name: 'notification-email-cache',
max: 1000,
ttl: (meta.config.notificationSendDelay || 60) * 1000,
noDisposeOnSet: true,
@@ -382,10 +383,20 @@ Notifications.markReadMultiple = async function (nids, uid) {
]);
};
-Notifications.markAllRead = async function (uid) {
+Notifications.markAllRead = async function (uid, filter = '') {
await batch.processSortedSet(`uid:${uid}:notifications:unread`, async (unreadNotifs) => {
- const nids = unreadNotifs.map(n => n && n.value);
- const datetimes = unreadNotifs.map(n => n && n.score);
+ let nids = unreadNotifs.map(n => n && n.value);
+ let datetimes = unreadNotifs.map(n => n && n.score);
+ if (filter !== '') {
+ const notificationKeys = nids.map(nid => `notifications:${nid}`);
+ let notificationData = await db.getObjectsFields(notificationKeys, ['nid', 'type', 'datetime']);
+ notificationData = notificationData.filter(n => n && n.nid && n.type === filter);
+ if (!notificationData.length) {
+ return;
+ }
+ nids = notificationData.map(n => n.nid);
+ datetimes = notificationData.map(n => n.datetime || Date.now());
+ }
await Promise.all([
db.sortedSetRemove(`uid:${uid}:notifications:unread`, nids),
db.sortedSetAdd(`uid:${uid}:notifications:read`, datetimes, nids),
diff --git a/src/posts/data.js b/src/posts/data.js
index d74a22e69d..28e6c24aaa 100644
--- a/src/posts/data.js
+++ b/src/posts/data.js
@@ -70,5 +70,13 @@ function modifyPost(post, fields) {
if (!fields.length || fields.includes('attachments')) {
post.attachments = (post.attachments || '').split(',').filter(Boolean);
}
+
+ if (!fields.length || fields.includes('uploads')) {
+ try {
+ post.uploads = post.uploads ? JSON.parse(post.uploads) : [];
+ } catch (err) {
+ post.uploads = [];
+ }
+ }
}
}
diff --git a/src/posts/edit.js b/src/posts/edit.js
index 077616b29e..0b8b6ff009 100644
--- a/src/posts/edit.js
+++ b/src/posts/edit.js
@@ -29,7 +29,7 @@ module.exports = function (Posts) {
}
const topicData = await topics.getTopicFields(postData.tid, [
- 'cid', 'mainPid', 'title', 'timestamp', 'scheduled', 'slug', 'tags',
+ 'cid', 'mainPid', 'title', 'timestamp', 'scheduled', 'slug', 'tags', 'thumbs',
]);
await scheduledTopicCheck(data, topicData);
@@ -49,12 +49,14 @@ module.exports = function (Posts) {
uid: data.uid,
});
+ // needs to be before editMainPost, otherwise scheduled topics use wrong timestamp
+ await Posts.setPostFields(data.pid, result.post);
+
const [editor, topic] = await Promise.all([
user.getUserFields(data.uid, ['username', 'userslug']),
editMainPost(data, postData, topicData),
]);
- await Posts.setPostFields(data.pid, result.post);
const contentChanged = ((data.sourceContent || data.content) !== oldContent) ||
topic.renamed ||
topic.tagsupdated;
@@ -142,6 +144,15 @@ module.exports = function (Posts) {
await topics.validateTags(data.tags, topicData.cid, data.uid, tid);
}
+ const thumbs = topics.thumbs.filterThumbs(data.thumbs);
+ const thumbsupdated = Array.isArray(data.thumbs) &&
+ !_.isEqual(data.thumbs, topicData.thumbs);
+
+ if (thumbsupdated) {
+ newTopicData.thumbs = JSON.stringify(thumbs);
+ newTopicData.numThumbs = thumbs.length;
+ }
+
const results = await plugins.hooks.fire('filter:topic.edit', {
req: data.req,
topic: newTopicData,
@@ -172,6 +183,7 @@ module.exports = function (Posts) {
renamed: renamed,
tagsupdated: tagsupdated,
tags: tags,
+ thumbsupdated: thumbsupdated,
oldTags: topicData.tags,
rescheduled: rescheduling(data, topicData),
};
diff --git a/src/posts/parse.js b/src/posts/parse.js
index 8d0c902e46..b67c14526e 100644
--- a/src/posts/parse.js
+++ b/src/posts/parse.js
@@ -1,7 +1,6 @@
'use strict';
const nconf = require('nconf');
-const url = require('url');
const winston = require('winston');
const sanitize = require('sanitize-html');
const _ = require('lodash');
@@ -88,16 +87,9 @@ module.exports = function (Posts) {
while (current !== null) {
if (current[1]) {
try {
- parsed = url.parse(current[1]);
- if (!parsed.protocol) {
- if (current[1].startsWith('/')) {
- // Internal link
- absolute = nconf.get('base_url') + current[1];
- } else {
- // External link
- absolute = `//${current[1]}`;
- }
-
+ parsed = new URL(current[1], nconf.get('url'));
+ absolute = parsed.toString();
+ if (absolute !== current[1]) {
const offset = current[0].indexOf(current[1]);
content = content.slice(0, current.index + offset) +
absolute +
diff --git a/src/posts/queue.js b/src/posts/queue.js
index 8c1bbf90d0..a6aca50cca 100644
--- a/src/posts/queue.js
+++ b/src/posts/queue.js
@@ -24,7 +24,8 @@ module.exports = function (Posts) {
if (!postData) {
const ids = await db.getSortedSetRange('post:queue', 0, -1);
const keys = ids.map(id => `post:queue:${id}`);
- postData = await db.getObjects(keys);
+ postData = (await db.getObjects(keys)).filter(Boolean);
+
postData.forEach((data) => {
if (data) {
data.data = JSON.parse(data.data);
@@ -50,7 +51,7 @@ module.exports = function (Posts) {
cache.set('post-queue', _.cloneDeep(postData));
}
if (filter.id) {
- postData = postData.filter(p => p.id === filter.id);
+ postData = postData.filter(p => p && p.id === filter.id);
}
if (options.metadata) {
await Promise.all(postData.map(addMetaData));
@@ -59,11 +60,11 @@ module.exports = function (Posts) {
// Filter by tid if present
if (filter.tid) {
const tid = String(filter.tid);
- postData = postData.filter(item => item.data.tid && String(item.data.tid) === tid);
+ postData = postData.filter(item => item && item.data.tid && String(item.data.tid) === tid);
} else if (Array.isArray(filter.tid)) {
const tids = filter.tid.map(String);
postData = postData.filter(
- item => item.data.tid && tids.includes(String(item.data.tid))
+ item => item && item.data.tid && tids.includes(String(item.data.tid))
);
}
diff --git a/src/posts/uploads.js b/src/posts/uploads.js
index 17e82250ba..372c30ca1e 100644
--- a/src/posts/uploads.js
+++ b/src/posts/uploads.js
@@ -46,12 +46,14 @@ module.exports = function (Posts) {
Posts.uploads.sync = async function (pid) {
// Scans a post's content and updates sorted set of uploads
- const [content, currentUploads, isMainPost] = await Promise.all([
- Posts.getPostField(pid, 'content'),
- Posts.uploads.list(pid),
+ const [postData, isMainPost] = await Promise.all([
+ Posts.getPostFields(pid, ['content', 'uploads']),
Posts.isMain(pid),
]);
+ const content = postData.content || '';
+ const currentUploads = postData.uploads || [];
+
// Extract upload file paths from post content
let match = searchRegex.exec(content);
let uploads = new Set();
@@ -75,14 +77,19 @@ module.exports = function (Posts) {
// Create add/remove sets
const add = uploads.filter(path => !currentUploads.includes(path));
const remove = currentUploads.filter(path => !uploads.includes(path));
- await Promise.all([
- Posts.uploads.associate(pid, add),
- Posts.uploads.dissociate(pid, remove),
- ]);
+ await Posts.uploads.associate(pid, add);
+ await Posts.uploads.dissociate(pid, remove);
};
- Posts.uploads.list = async function (pid) {
- return await db.getSortedSetMembers(`post:${pid}:uploads`);
+ Posts.uploads.list = async function (pids) {
+ const isArray = Array.isArray(pids);
+ if (isArray) {
+ const uploads = await Posts.getPostsFields(pids, ['uploads']);
+ return uploads.map(p => p.uploads || []);
+ }
+
+ const uploads = await Posts.getPostField(pids, 'uploads');
+ return uploads;
};
Posts.uploads.listWithSizes = async function (pid) {
@@ -157,33 +164,38 @@ module.exports = function (Posts) {
};
Posts.uploads.associate = async function (pid, filePaths) {
- // Adds an upload to a post's sorted set of uploads
filePaths = !Array.isArray(filePaths) ? [filePaths] : filePaths;
if (!filePaths.length) {
return;
}
filePaths = await _filterValidPaths(filePaths); // Only process files that exist and are within uploads directory
+ const currentUploads = await Posts.uploads.list(pid);
+ filePaths.forEach((path) => {
+ if (!currentUploads.includes(path)) {
+ currentUploads.push(path);
+ }
+ });
const now = Date.now();
- const scores = filePaths.map((p, i) => now + i);
const bulkAdd = filePaths.map(path => [`upload:${md5(path)}:pids`, now, pid]);
+
await Promise.all([
- db.sortedSetAdd(`post:${pid}:uploads`, scores, filePaths),
+ db.setObjectField(`post:${pid}`, 'uploads', JSON.stringify(currentUploads)),
db.sortedSetAddBulk(bulkAdd),
Posts.uploads.saveSize(filePaths),
]);
};
Posts.uploads.dissociate = async function (pid, filePaths) {
- // Removes an upload from a post's sorted set of uploads
filePaths = !Array.isArray(filePaths) ? [filePaths] : filePaths;
if (!filePaths.length) {
return;
}
-
+ let currentUploads = await Posts.uploads.list(pid);
+ currentUploads = currentUploads.filter(upload => !filePaths.includes(upload));
const bulkRemove = filePaths.map(path => [`upload:${md5(path)}:pids`, pid]);
const promises = [
- db.sortedSetRemove(`post:${pid}:uploads`, filePaths),
+ db.setObjectField(`post:${pid}`, 'uploads', JSON.stringify(currentUploads)),
db.sortedSetRemoveBulk(bulkRemove),
];
diff --git a/src/request.js b/src/request.js
index b84b198914..3ffde3916e 100644
--- a/src/request.js
+++ b/src/request.js
@@ -1,18 +1,84 @@
'use strict';
+const dns = require('dns').promises;
+require('undici'); // keep this here, needed for SSRF (see `lookup()`)
+
const nconf = require('nconf');
+const ipaddr = require('ipaddr.js');
const { CookieJar } = require('tough-cookie');
const fetchCookie = require('fetch-cookie').default;
const { version } = require('../package.json');
+const plugins = require('./plugins');
+const ttl = require('./cache/ttl');
+const checkCache = ttl({
+ name: 'request-check',
+ max: 1000,
+ ttl: 1000 * 60 * 60, // 1 hour
+});
+let allowList = new Set();
+let initialized = false;
+
exports.jar = function () {
return new CookieJar();
};
const userAgent = `NodeBB/${version.split('.').shift()}.x (${nconf.get('url')})`;
+async function init() {
+ if (initialized) {
+ return;
+ }
+
+ allowList.add(nconf.get('url_parsed').host);
+ const { allowed } = await plugins.hooks.fire('filter:request.init', { allowed: allowList });
+ if (allowed instanceof Set) {
+ allowList = allowed;
+ }
+ initialized = true;
+}
+
+/**
+ * This method (alongside `check()`) guards against SSRF via DNS rebinding.
+ *
+ * - `check()` does a DNS lookup and ensures that all returned IPs do not belong to a reserved IP address space
+ * - `lookup()` provides additional logic that uses the cached DNS result from `check()`
+ * instead of doing another lookup (which is where DNS rebinding comes into play.)
+ * - For whatever reason `undici` needs to be required so that lookup can be overwritten properly.
+ */
+function lookup(hostname, options, callback) {
+ let { ok, lookup } = checkCache.get(hostname);
+ lookup = lookup && [...lookup];
+ if (!ok) {
+ throw new Error('lookup-failed');
+ }
+
+ if (!lookup) {
+ // trusted, do regular lookup
+ dns.lookup(hostname, options).then((addresses) => {
+ callback(null, addresses);
+ });
+ return;
+ }
+
+ // Lookup needs to behave asynchronously — https://github.com/nodejs/node/issues/28664
+ process.nextTick(() => {
+ if (options.all === true) {
+ callback(null, lookup);
+ } else {
+ const { address, family } = lookup.shift();
+ callback(null, address, family);
+ }
+ });
+}
+
// Initialize fetch - somewhat hacky, but it's required for globalDispatcher to be available
async function call(url, method, { body, timeout, jar, ...config } = {}) {
+ const { ok } = await check(url);
+ if (!ok) {
+ throw new Error('[[error:reserved-ip-address]]');
+ }
+
let fetchImpl = fetch;
if (jar) {
fetchImpl = fetchCookie(fetch, jar);
@@ -46,7 +112,9 @@ async function call(url, method, { body, timeout, jar, ...config } = {}) {
return super.dispatch(opts, handler);
}
}
- opts.dispatcher = new FetchAgent();
+ opts.dispatcher = new FetchAgent({
+ connect: { lookup },
+ });
}
const response = await fetchImpl(url, opts);
@@ -75,6 +143,47 @@ async function call(url, method, { body, timeout, jar, ...config } = {}) {
};
}
+// Checks url to ensure it is not in reserved IP range (private, etc.)
+async function check(url) {
+ await init();
+
+ const { host } = new URL(url);
+ const cached = checkCache.get(url);
+ if (cached !== undefined) {
+ return cached;
+ }
+ if (allowList.has(host)) {
+ const payload = { ok: true };
+ checkCache.set(host, payload);
+ return payload;
+ }
+
+ const addresses = new Set();
+ let lookup;
+ if (ipaddr.isValid(url)) {
+ addresses.add(url);
+ } else {
+ lookup = await dns.lookup(host, { all: true });
+ lookup.forEach(({ address, family }) => {
+ addresses.add({ address, family });
+ });
+ }
+
+ if (addresses.size < 1) {
+ return { ok: false };
+ }
+
+ // Every IP address that the host resolves to should be a unicast address
+ const ok = Array.from(addresses).every(({ address: ip }) => {
+ const parsed = ipaddr.parse(ip);
+ return parsed.range() === 'unicast';
+ });
+
+ const payload = { ok, lookup };
+ checkCache.set(host, payload);
+ return payload;
+}
+
/*
const { body, response } = await request.get('someurl?foo=1&baz=2')
*/
diff --git a/src/routes/activitypub.js b/src/routes/activitypub.js
index 760287e102..c09b7b9821 100644
--- a/src/routes/activitypub.js
+++ b/src/routes/activitypub.js
@@ -3,8 +3,14 @@
const helpers = require('./helpers');
module.exports = function (app, middleware, controllers) {
- helpers.setupPageRoute(app, '/world', [middleware.activitypub.enabled], controllers.activitypub.topics.list);
- helpers.setupPageRoute(app, '/ap', [middleware.activitypub.enabled], controllers.activitypub.fetch);
+ helpers.setupPageRoute(app, '/world', [
+ middleware.activitypub.enabled,
+ middleware.activitypub.pageview,
+ ], controllers.activitypub.topics.list);
+ helpers.setupPageRoute(app, '/ap', [
+ middleware.activitypub.enabled,
+ middleware.activitypub.pageview,
+ ], controllers.activitypub.fetch);
/**
* The following controllers only respond if the sender is making an json+activitypub style call (i.e. S2S-only)
@@ -14,6 +20,7 @@ module.exports = function (app, middleware, controllers) {
const middlewares = [
middleware.activitypub.enabled,
+ middleware.activitypub.pageview,
middleware.activitypub.assertS2S,
middleware.activitypub.verify,
middleware.activitypub.configureResponse,
diff --git a/src/routes/admin.js b/src/routes/admin.js
index 89a3050ee6..967746b304 100644
--- a/src/routes/admin.js
+++ b/src/routes/admin.js
@@ -81,11 +81,19 @@ function apiRoutes(router, name, middleware, controllers) {
router.get(`/api/${name}/groups/:groupname/csv`, middleware.ensureLoggedIn, helpers.tryRoute(controllers.admin.groups.getCSV));
router.get(`/api/${name}/analytics`, middleware.ensureLoggedIn, helpers.tryRoute(controllers.admin.dashboard.getAnalytics));
router.get(`/api/${name}/advanced/cache/dump`, middleware.ensureLoggedIn, helpers.tryRoute(controllers.admin.cache.dump));
+ router.post(`/api/${name}/manage/categories`, middleware.ensureLoggedIn, helpers.tryRoute(controllers.admin.categories.addRemote));
+ router.delete(`/api/${name}/manage/categories/:cid`, middleware.ensureLoggedIn, helpers.tryRoute(controllers.admin.categories.removeRemote));
- const multipart = require('connect-multiparty');
- const multipartMiddleware = multipart();
+ const multer = require('multer');
+ const storage = multer.diskStorage({});
+ const upload = multer({ storage });
- const middlewares = [multipartMiddleware, middleware.validateFiles, middleware.applyCSRF, middleware.ensureLoggedIn];
+ const middlewares = [
+ upload.array('files[]', 20),
+ middleware.validateFiles,
+ middleware.applyCSRF,
+ middleware.ensureLoggedIn,
+ ];
router.post(`/api/${name}/category/uploadpicture`, middlewares, helpers.tryRoute(controllers.admin.uploads.uploadCategoryPicture));
router.post(`/api/${name}/uploadfavicon`, middlewares, helpers.tryRoute(controllers.admin.uploads.uploadFavicon));
diff --git a/src/routes/api.js b/src/routes/api.js
index 0fe575a326..4424d9a979 100644
--- a/src/routes/api.js
+++ b/src/routes/api.js
@@ -23,17 +23,20 @@ module.exports = function (app, middleware, controllers) {
router.get('/topic/teaser/:topic_id', [...middlewares], helpers.tryRoute(controllers.topics.teaser));
router.get('/topic/pagination/:topic_id', [...middlewares], helpers.tryRoute(controllers.topics.pagination));
- const multipart = require('connect-multiparty');
- const multipartMiddleware = multipart();
+ const multer = require('multer');
+ const storage = multer.diskStorage({});
+ const upload = multer({ storage });
+
const postMiddlewares = [
middleware.maintenanceMode,
- multipartMiddleware,
+ upload.array('files[]', 20),
middleware.validateFiles,
middleware.uploads.ratelimit,
middleware.applyCSRF,
];
router.post('/post/upload', postMiddlewares, helpers.tryRoute(uploadsController.uploadPost));
+ router.post('/topic/thumb/upload', postMiddlewares, helpers.tryRoute(uploadsController.uploadThumb));
router.post('/user/:userslug/uploadpicture', [
...middlewares,
...postMiddlewares,
diff --git a/src/routes/authentication.js b/src/routes/authentication.js
index 9d89df90e1..720675b29d 100644
--- a/src/routes/authentication.js
+++ b/src/routes/authentication.js
@@ -154,9 +154,15 @@ Auth.reloadRoutes = async function (params) {
});
});
- const multipart = require('connect-multiparty');
- const multipartMiddleware = multipart();
- const middlewares = [multipartMiddleware, Auth.middleware.applyCSRF, Auth.middleware.applyBlacklist];
+
+ const multer = require('multer');
+ const storage = multer.diskStorage({});
+ const upload = multer({ storage });
+ const middlewares = [
+ upload.any(),
+ Auth.middleware.applyCSRF,
+ Auth.middleware.applyBlacklist,
+ ];
router.post('/register', middlewares, controllers.authentication.register);
router.post('/register/complete', middlewares, controllers.authentication.registerComplete);
diff --git a/src/routes/helpers.js b/src/routes/helpers.js
index 34a455076e..2109a0bd9c 100644
--- a/src/routes/helpers.js
+++ b/src/routes/helpers.js
@@ -54,7 +54,9 @@ helpers.setupApiRoute = function (...args) {
const [router, verb, name] = args;
let middlewares = args.length > 4 ? args[args.length - 2] : [];
const controller = args[args.length - 1];
-
+ const multer = require('multer');
+ const storage = multer.diskStorage({});
+ const upload = multer({ storage });
middlewares = [
middleware.autoLocale,
middleware.applyBlacklist,
@@ -63,7 +65,7 @@ helpers.setupApiRoute = function (...args) {
middleware.registrationComplete,
middleware.pluginHooks,
middleware.logApiUsage,
- middleware.handleMultipart,
+ upload.any(),
...middlewares,
];
diff --git a/src/routes/write/admin.js b/src/routes/write/admin.js
index 4a70e48022..050e1ecf7a 100644
--- a/src/routes/write/admin.js
+++ b/src/routes/write/admin.js
@@ -25,5 +25,10 @@ module.exports = function () {
setupApiRoute(router, 'get', '/groups', [...middlewares], controllers.write.admin.listGroups);
+ setupApiRoute(router, 'post', '/activitypub/rules', [...middlewares, middleware.checkRequired.bind(null, ['cid', 'value', 'type'])], controllers.write.admin.activitypub.addRule);
+ setupApiRoute(router, 'delete', '/activitypub/rules/:rid', [...middlewares], controllers.write.admin.activitypub.deleteRule);
+ setupApiRoute(router, 'post', '/activitypub/relays', [...middlewares, middleware.checkRequired.bind(null, ['url'])], controllers.write.admin.activitypub.addRelay);
+ setupApiRoute(router, 'delete', '/activitypub/relays/:url', [...middlewares], controllers.write.admin.activitypub.removeRelay);
+
return router;
};
diff --git a/src/routes/write/topics.js b/src/routes/write/topics.js
index df10f66633..2b159ee3c0 100644
--- a/src/routes/write/topics.js
+++ b/src/routes/write/topics.js
@@ -10,9 +10,6 @@ const { setupApiRoute } = routeHelpers;
module.exports = function () {
const middlewares = [middleware.ensureLoggedIn];
- const multipart = require('connect-multiparty');
- const multipartMiddleware = multipart();
-
setupApiRoute(router, 'post', '/', [middleware.checkRequired.bind(null, ['cid', 'title', 'content'])], controllers.write.topics.create);
setupApiRoute(router, 'get', '/:tid', [], controllers.write.topics.get);
setupApiRoute(router, 'post', '/:tid', [middleware.checkRequired.bind(null, ['content']), middleware.assert.topic], controllers.write.topics.reply);
@@ -37,8 +34,14 @@ module.exports = function () {
setupApiRoute(router, 'delete', '/:tid/tags', [...middlewares, middleware.assert.topic], controllers.write.topics.deleteTags);
setupApiRoute(router, 'get', '/:tid/thumbs', [], controllers.write.topics.getThumbs);
- setupApiRoute(router, 'post', '/:tid/thumbs', [multipartMiddleware, middleware.validateFiles, middleware.uploads.ratelimit, ...middlewares], controllers.write.topics.addThumb);
- setupApiRoute(router, 'put', '/:tid/thumbs', [...middlewares, middleware.checkRequired.bind(null, ['tid'])], controllers.write.topics.migrateThumbs);
+
+ setupApiRoute(router, 'post', '/:tid/thumbs', [
+ middleware.validateFiles,
+ middleware.uploads.ratelimit,
+ ...middlewares,
+ ], controllers.write.topics.addThumb);
+
+
setupApiRoute(router, 'delete', '/:tid/thumbs', [...middlewares, middleware.checkRequired.bind(null, ['path'])], controllers.write.topics.deleteThumb);
setupApiRoute(router, 'put', '/:tid/thumbs/order', [...middlewares, middleware.checkRequired.bind(null, ['path', 'order'])], controllers.write.topics.reorderThumbs);
diff --git a/src/socket.io/admin/analytics.js b/src/socket.io/admin/analytics.js
index 8af8881873..03cffb6d20 100644
--- a/src/socket.io/admin/analytics.js
+++ b/src/socket.io/admin/analytics.js
@@ -30,6 +30,7 @@ Analytics.get = async function (socket, data) {
pageviewsRegistered: getStats('analytics:pageviews:registered', until, data.amount),
pageviewsGuest: getStats('analytics:pageviews:guest', until, data.amount),
pageviewsBot: getStats('analytics:pageviews:bot', until, data.amount),
+ appageviews: getStats('analytics:pageviews:ap', until, data.amount),
summary: analytics.getSummary(),
});
result.pastDay = result.pageviews.reduce((a, b) => parseInt(a, 10) + parseInt(b, 10));
diff --git a/src/socket.io/notifications.js b/src/socket.io/notifications.js
index 2b0df88114..263193260a 100644
--- a/src/socket.io/notifications.js
+++ b/src/socket.io/notifications.js
@@ -34,8 +34,9 @@ SocketNotifs.markUnread = async function (socket, nid) {
user.notifications.pushCount(socket.uid);
};
-SocketNotifs.markAllRead = async function (socket) {
- await notifications.markAllRead(socket.uid);
+SocketNotifs.markAllRead = async function (socket, data) {
+ const filter = data && data.filter ? data.filter : '';
+ await notifications.markAllRead(socket.uid, filter);
user.notifications.pushCount(socket.uid);
};
diff --git a/src/topics/create.js b/src/topics/create.js
index 352823a202..2f41c822b1 100644
--- a/src/topics/create.js
+++ b/src/topics/create.js
@@ -41,6 +41,12 @@ module.exports = function (Topics) {
topicData.tags = data.tags.join(',');
}
+ if (Array.isArray(data.thumbs) && data.thumbs.length) {
+ const thumbs = Topics.thumbs.filterThumbs(data.thumbs);
+ topicData.thumbs = JSON.stringify(thumbs);
+ topicData.numThumbs = thumbs.length;
+ }
+
const result = await plugins.hooks.fire('filter:topic.create', { topic: topicData, data: data });
topicData = result.topic;
await db.setObject(`topic:${topicData.tid}`, topicData);
diff --git a/src/topics/data.js b/src/topics/data.js
index 76c027121d..a5801e0475 100644
--- a/src/topics/data.js
+++ b/src/topics/data.js
@@ -140,4 +140,12 @@ function modifyTopic(topic, fields) {
};
});
}
+
+ if (fields.includes('thumbs') || !fields.length) {
+ try {
+ topic.thumbs = topic.thumbs ? JSON.parse(String(topic.thumbs || '[]')) : [];
+ } catch (e) {
+ topic.thumbs = [];
+ }
+ }
}
diff --git a/src/topics/posts.js b/src/topics/posts.js
index e32c18e727..8201bcad02 100644
--- a/src/topics/posts.js
+++ b/src/topics/posts.js
@@ -442,7 +442,7 @@ module.exports = function (Topics) {
let { content } = postData;
// ignore lines that start with `>`
- content = content.split('\n').filter(line => !line.trim().startsWith('>')).join('\n');
+ content = (content || '').split('\n').filter(line => !line.trim().startsWith('>')).join('\n');
// Scan post content for topic links
const matches = [...content.matchAll(backlinkRegex)];
if (!matches) {
diff --git a/src/topics/sorted.js b/src/topics/sorted.js
index 07a6215218..2112fe3ad1 100644
--- a/src/topics/sorted.js
+++ b/src/topics/sorted.js
@@ -282,7 +282,10 @@ module.exports = function (Topics) {
(!tags.length || tags.every(tag => t.tags.find(topicTag => topicTag.value === tag)))
)).map(t => t.tid);
- const result = await plugins.hooks.fire('filter:topics.filterSortedTids', { tids: tids, params: params });
+ const result = await plugins.hooks.fire('filter:topics.filterSortedTids', {
+ tids,
+ params,
+ });
return result.tids;
}
diff --git a/src/topics/thumbs.js b/src/topics/thumbs.js
index be2916a05d..22fa2c48bd 100644
--- a/src/topics/thumbs.js
+++ b/src/topics/thumbs.js
@@ -5,29 +5,26 @@ const _ = require('lodash');
const nconf = require('nconf');
const path = require('path');
const mime = require('mime');
-
-const db = require('../database');
-const file = require('../file');
const plugins = require('../plugins');
const posts = require('../posts');
const meta = require('../meta');
-const cache = require('../cache');
const topics = module.parent.exports;
const Thumbs = module.exports;
-Thumbs.exists = async function (id, path) {
- const isDraft = !await topics.exists(id);
- const set = `${isDraft ? 'draft' : 'topic'}:${id}:thumbs`;
+const upload_url = nconf.get('relative_path') + nconf.get('upload_url');
+const upload_path = nconf.get('upload_path');
- return db.isSortedSetMember(set, path);
+Thumbs.exists = async function (tid, path) {
+ const thumbs = await topics.getTopicField(tid, 'thumbs');
+ return thumbs.includes(path);
};
Thumbs.load = async function (topicData) {
const mainPids = topicData.filter(Boolean).map(t => t.mainPid);
- let hashes = await posts.getPostsFields(mainPids, ['attachments']);
- const hasUploads = await db.exists(mainPids.map(pid => `post:${pid}:uploads`));
- hashes = hashes.map(o => o.attachments);
+ const mainPostData = await posts.getPostsFields(mainPids, ['attachments', 'uploads']);
+ const hasUploads = mainPostData.map(p => Array.isArray(p.uploads) && p.uploads.length > 0);
+ const hashes = mainPostData.map(o => o.attachments);
let hasThumbs = topicData.map((t, idx) => t &&
(parseInt(t.numThumbs, 10) > 0 ||
!!(hashes[idx] && hashes[idx].length) ||
@@ -36,11 +33,70 @@ Thumbs.load = async function (topicData) {
const topicsWithThumbs = topicData.filter((tid, idx) => hasThumbs[idx]);
const tidsWithThumbs = topicsWithThumbs.map(t => t.tid);
- const thumbs = await Thumbs.get(tidsWithThumbs);
+
+ const thumbs = await loadFromTopicData(topicsWithThumbs);
+
const tidToThumbs = _.zipObject(tidsWithThumbs, thumbs);
return topicData.map(t => (t && t.tid ? (tidToThumbs[t.tid] || []) : []));
};
+async function loadFromTopicData(topicData, options = {}) {
+ const tids = topicData.map(t => t && t.tid);
+ const thumbs = topicData.map(t => t && Array.isArray(t.thumbs) ? t.thumbs : []);
+
+ if (!options.thumbsOnly) {
+ const mainPids = topicData.map(t => t.mainPid);
+ const [mainPidUploads, mainPidAttachments] = await Promise.all([
+ posts.uploads.list(mainPids),
+ posts.attachments.get(mainPids),
+ ]);
+
+ // Add uploaded media to thumb sets
+ mainPidUploads.forEach((uploads, idx) => {
+ uploads = uploads.filter((upload) => {
+ const type = mime.getType(upload);
+ return !thumbs[idx].includes(upload) && type && type.startsWith('image/');
+ });
+
+ if (uploads.length) {
+ thumbs[idx].push(...uploads);
+ }
+ });
+
+ // Add attachments to thumb sets
+ mainPidAttachments.forEach((attachments, idx) => {
+ attachments = attachments.filter(
+ attachment => !thumbs[idx].includes(attachment.url) && (attachment.mediaType && attachment.mediaType.startsWith('image/'))
+ );
+
+ if (attachments.length) {
+ thumbs[idx].push(...attachments.map(attachment => attachment.url));
+ }
+ });
+ }
+
+ const hasTimestampPrefix = /^\d+-/;
+
+ let response = thumbs.map((thumbSet, idx) => thumbSet.map(thumb => ({
+ id: String(tids[idx]),
+ name: (() => {
+ const name = path.basename(thumb);
+ return hasTimestampPrefix.test(name) ? name.slice(14) : name;
+ })(),
+ path: thumb,
+ url: thumb.startsWith('http') ?
+ thumb :
+ path.posix.join(upload_url, thumb.replace(/\\/g, '/')),
+ })));
+
+ ({ thumbs: response } = await plugins.hooks.fire('filter:topics.getThumbs', {
+ tids,
+ thumbsOnly: options.thumbsOnly,
+ thumbs: response,
+ }));
+ return response;
+};
+
Thumbs.get = async function (tids, options) {
// Allow singular or plural usage
let singular = false;
@@ -54,118 +110,77 @@ Thumbs.get = async function (tids, options) {
thumbsOnly: false,
};
}
-
- const isDraft = (await topics.exists(tids)).map(exists => !exists);
-
if (!meta.config.allowTopicsThumbnail || !tids.length) {
return singular ? [] : tids.map(() => []);
}
- const hasTimestampPrefix = /^\d+-/;
- const upload_url = nconf.get('relative_path') + nconf.get('upload_url');
- const sets = tids.map((tid, idx) => `${isDraft[idx] ? 'draft' : 'topic'}:${tid}:thumbs`);
- const thumbs = await Promise.all(sets.map(getThumbs));
-
- let mainPids = await topics.getTopicsFields(tids, ['mainPid']);
- mainPids = mainPids.map(o => o.mainPid);
-
- if (!options.thumbsOnly) {
- // Add uploaded media to thumb sets
- const mainPidUploads = await Promise.all(mainPids.map(posts.uploads.list));
- mainPidUploads.forEach((uploads, idx) => {
- uploads = uploads.filter((upload) => {
- const type = mime.getType(upload);
- return !thumbs[idx].includes(upload) && type && type.startsWith('image/');
- });
-
- if (uploads.length) {
- thumbs[idx].push(...uploads);
- }
- });
-
- // Add attachments to thumb sets
- const mainPidAttachments = await posts.attachments.get(mainPids);
- mainPidAttachments.forEach((attachments, idx) => {
- attachments = attachments.filter(
- attachment => !thumbs[idx].includes(attachment.url) && (attachment.mediaType && attachment.mediaType.startsWith('image/'))
- );
-
- if (attachments.length) {
- thumbs[idx].push(...attachments.map(attachment => attachment.url));
- }
- });
- }
-
- let response = thumbs.map((thumbSet, idx) => thumbSet.map(thumb => ({
- id: tids[idx],
- name: (() => {
- const name = path.basename(thumb);
- return hasTimestampPrefix.test(name) ? name.slice(14) : name;
- })(),
- path: thumb,
- url: thumb.startsWith('http') ? thumb : path.posix.join(upload_url, thumb.replace(/\\/g, '/')),
- })));
-
- ({ thumbs: response } = await plugins.hooks.fire('filter:topics.getThumbs', {
- tids,
- thumbsOnly: options.thumbsOnly,
- thumbs: response,
- }));
- return singular ? response.pop() : response;
+ const topicData = await topics.getTopicsFields(tids, ['tid', 'mainPid', 'thumbs']);
+ const response = await loadFromTopicData(topicData, options);
+ return singular ? response[0] : response;
};
-async function getThumbs(set) {
- const cached = cache.get(set);
- if (cached !== undefined) {
- return cached.slice();
- }
- const thumbs = await db.getSortedSetRange(set, 0, -1);
- cache.set(set, thumbs);
- return thumbs.slice();
-}
Thumbs.associate = async function ({ id, path, score }) {
- // Associates a newly uploaded file as a thumb to the passed-in draft or topic
- const isDraft = !await topics.exists(id);
+ // Associates a newly uploaded file as a thumb to the passed-in topic
+ const topicData = await topics.getTopicData(id);
+ if (!topicData) {
+ return;
+ }
const isLocal = !path.startsWith('http');
- const set = `${isDraft ? 'draft' : 'topic'}:${id}:thumbs`;
- const numThumbs = await db.sortedSetCard(set);
// Normalize the path to allow for changes in upload_path (and so upload_url can be appended if needed)
if (isLocal) {
path = path.replace(nconf.get('relative_path'), '');
path = path.replace(nconf.get('upload_url'), '');
}
- await db.sortedSetAdd(set, isFinite(score) ? score : numThumbs, path);
- if (!isDraft) {
- const numThumbs = await db.sortedSetCard(set);
- await topics.setTopicField(id, 'numThumbs', numThumbs);
- }
- cache.del(set);
- // Associate thumbnails with the main pid (only on local upload)
- if (!isDraft && isLocal) {
- const mainPid = (await topics.getMainPids([id]))[0];
- await posts.uploads.associate(mainPid, path);
+ if (Array.isArray(topicData.thumbs)) {
+ const currentIdx = topicData.thumbs.indexOf(path);
+ const insertIndex = (typeof score === 'number' && score >= 0 && score < topicData.thumbs.length) ?
+ score :
+ topicData.thumbs.length;
+
+ if (currentIdx !== -1) {
+ // Remove from current position
+ topicData.thumbs.splice(currentIdx, 1);
+ // Adjust insertIndex if needed
+ const adjustedIndex = currentIdx < insertIndex ? insertIndex - 1 : insertIndex;
+ topicData.thumbs.splice(adjustedIndex, 0, path);
+ } else {
+ topicData.thumbs.splice(insertIndex, 0, path);
+ }
+
+ await topics.setTopicFields(id, {
+ thumbs: JSON.stringify(topicData.thumbs),
+ numThumbs: topicData.thumbs.length,
+ });
+ // Associate thumbnails with the main pid (only on local upload)
+ if (isLocal && currentIdx === -1) {
+ await posts.uploads.associate(topicData.mainPid, path);
+ }
}
};
-Thumbs.migrate = async function (uuid, id) {
- // Converts the draft thumb zset to the topic zset (combines thumbs if applicable)
- const set = `draft:${uuid}:thumbs`;
- const thumbs = await db.getSortedSetRangeWithScores(set, 0, -1);
- await Promise.all(thumbs.map(async thumb => await Thumbs.associate({
- id,
- path: thumb.value,
- score: thumb.score,
- })));
- await db.delete(set);
- cache.del(set);
+Thumbs.filterThumbs = function (thumbs) {
+ if (!Array.isArray(thumbs)) {
+ return [];
+ }
+ thumbs = thumbs.filter((thumb) => {
+ if (thumb.startsWith('http')) {
+ return true;
+ }
+ // ensure it is in upload path
+ const fullPath = path.join(upload_path, thumb);
+ return fullPath.startsWith(upload_path);
+ });
+ return thumbs;
};
-Thumbs.delete = async function (id, relativePaths) {
- const isDraft = !await topics.exists(id);
- const set = `${isDraft ? 'draft' : 'topic'}:${id}:thumbs`;
+Thumbs.delete = async function (tid, relativePaths) {
+ const topicData = await topics.getTopicData(tid);
+ if (!topicData) {
+ return;
+ }
if (typeof relativePaths === 'string') {
relativePaths = [relativePaths];
@@ -173,48 +188,28 @@ Thumbs.delete = async function (id, relativePaths) {
throw new Error('[[error:invalid-data]]');
}
- const absolutePaths = relativePaths.map(relativePath => path.join(nconf.get('upload_path'), relativePath));
- const [associated, existsOnDisk] = await Promise.all([
- db.isSortedSetMembers(set, relativePaths),
- Promise.all(absolutePaths.map(async absolutePath => file.exists(absolutePath))),
- ]);
+ const toRemove = relativePaths.map(
+ relativePath => topicData.thumbs.includes(relativePath) ? relativePath : null
+ ).filter(Boolean);
- const toRemove = [];
- const toDelete = [];
- relativePaths.forEach((relativePath, idx) => {
- if (associated[idx]) {
- toRemove.push(relativePath);
- }
-
- if (existsOnDisk[idx]) {
- toDelete.push(absolutePaths[idx]);
- }
- });
-
- await db.sortedSetRemove(set, toRemove);
-
- if (isDraft && toDelete.length) { // drafts only; post upload dissociation handles disk deletion for topics
- await Promise.all(toDelete.map(path => file.delete(path)));
- }
-
- if (toRemove.length && !isDraft) {
- const topics = require('.');
- const mainPid = (await topics.getMainPids([id]))[0];
+ if (toRemove.length) {
+ const { mainPid } = topicData.mainPid;
+ topicData.thumbs = topicData.thumbs.filter(thumb => !toRemove.includes(thumb));
await Promise.all([
- db.incrObjectFieldBy(`topic:${id}`, 'numThumbs', -toRemove.length),
+ topics.setTopicFields(tid, {
+ thumbs: JSON.stringify(topicData.thumbs),
+ numThumbs: topicData.thumbs.length,
+ }),
Promise.all(toRemove.map(async relativePath => posts.uploads.dissociate(mainPid, relativePath))),
]);
}
- if (toRemove.length) {
- cache.del(set);
+};
+
+Thumbs.deleteAll = async (tid) => {
+ const topicData = await topics.getTopicData(tid);
+ if (!topicData) {
+ return;
}
-};
-
-Thumbs.deleteAll = async (id) => {
- const isDraft = !await topics.exists(id);
- const set = `${isDraft ? 'draft' : 'topic'}:${id}:thumbs`;
-
- const thumbs = await db.getSortedSetRange(set, 0, -1);
- await Thumbs.delete(id, thumbs);
+ await Thumbs.delete(tid, topicData.thumbs);
};
diff --git a/src/topics/unread.js b/src/topics/unread.js
index db0e9c0f9e..4dd5a94435 100644
--- a/src/topics/unread.js
+++ b/src/topics/unread.js
@@ -351,8 +351,23 @@ module.exports = function (Topics) {
if (!(parseInt(uid, 10) > 0)) {
return tids.map(() => false);
}
- const [topicScores, userScores, tids_unread, blockedUids] = await Promise.all([
+
+ // Remote tids do not get slotted into topics:recent; separate calculation follows
+ async function getRemoteTopicScores(tids) {
+ let cids = await Topics.getTopicsFields(tids, ['cid']);
+ cids = cids.map(({ cid }) => cid);
+ return await Promise.all(tids.map(async (tid, idx) => {
+ const cid = cids[idx];
+ if (utils.isNumber(tid) || !cid) {
+ return null;
+ }
+ return await db.sortedSetScore(`cid:${cid}:tids`, tid);
+ }));
+ }
+
+ const [topicScores, remoteTopicScores, userScores, tids_unread, blockedUids] = await Promise.all([
db.sortedSetScores('topics:recent', tids),
+ getRemoteTopicScores(tids),
db.sortedSetScores(`uid:${uid}:tids_read`, tids),
db.sortedSetScores(`uid:${uid}:tids_unread`, tids),
user.blocks.list(uid),
@@ -361,7 +376,7 @@ module.exports = function (Topics) {
const cutoff = await Topics.unreadCutoff(uid);
const result = tids.map((tid, index) => {
const read = !tids_unread[index] &&
- (topicScores[index] < cutoff ||
+ ((topicScores[index] || remoteTopicScores[index]) < cutoff ||
!!(userScores[index] && userScores[index] >= topicScores[index]));
return { tid: tid, read: read, index: index };
});
diff --git a/src/upgrades/4.3.0/normalize_thumbs_uploads.js b/src/upgrades/4.3.0/normalize_thumbs_uploads.js
index ef12d54f81..3a33daee9f 100644
--- a/src/upgrades/4.3.0/normalize_thumbs_uploads.js
+++ b/src/upgrades/4.3.0/normalize_thumbs_uploads.js
@@ -89,34 +89,36 @@ module.exports = {
const keys = uids.map(uid => `uid:${uid}:uploads`);
const userUploadData = await db.getSortedSetsMembersWithScores(keys);
- const bulkAdd = [];
- const bulkRemove = [];
- const promises = [];
- userUploadData.forEach((userUploads, idx) => {
+ await Promise.all(userUploadData.map(async (allUserUploads, idx) => {
const uid = uids[idx];
- if (Array.isArray(userUploads)) {
- userUploads.forEach((userUpload) => {
- const normalizedPath = normalizePath(userUpload.value);
- if (normalizedPath !== userUpload.value) {
- bulkAdd.push([`uid:${uid}:uploads`, userUpload.score, normalizedPath]);
- promises.push(db.setObjectField(`upload:${md5(normalizedPath)}`, 'uid', uid));
+ if (Array.isArray(allUserUploads)) {
+ await batch.processArray(allUserUploads, async (userUploads) => {
+ const bulkAdd = [];
+ const bulkRemove = [];
+ const promises = [];
+ userUploads.forEach((userUpload) => {
+ const normalizedPath = normalizePath(userUpload.value);
+ if (normalizedPath !== userUpload.value) {
+ bulkAdd.push([`uid:${uid}:uploads`, userUpload.score, normalizedPath]);
+ promises.push(db.setObjectField(`upload:${md5(normalizedPath)}`, 'uid', uid));
- bulkRemove.push([`uid:${uid}:uploads`, userUpload.value]);
- promises.push(db.delete(`upload:${md5(userUpload.value)}`));
- }
+ bulkRemove.push([`uid:${uid}:uploads`, userUpload.value]);
+ promises.push(db.delete(`upload:${md5(userUpload.value)}`));
+ }
+ });
+ await Promise.all(promises);
+ await db.sortedSetRemoveBulk(bulkRemove);
+ await db.sortedSetAddBulk(bulkAdd);
+ }, {
+ batch: 500,
});
-
}
- });
-
- await Promise.all(promises);
- await db.sortedSetRemoveBulk(bulkRemove);
- await db.sortedSetAddBulk(bulkAdd);
+ }));
progress.incr(uids.length);
}, {
- batch: 500,
+ batch: 100,
});
},
};
diff --git a/src/upgrades/4.5.0/post-uploads-to-hash.js b/src/upgrades/4.5.0/post-uploads-to-hash.js
new file mode 100644
index 0000000000..bef3b72df7
--- /dev/null
+++ b/src/upgrades/4.5.0/post-uploads-to-hash.js
@@ -0,0 +1,39 @@
+'use strict';
+
+const db = require('../../database');
+const batch = require('../../batch');
+
+module.exports = {
+ name: 'Move post:[[admin/settings/activitypub:relays.warning]]
+[[admin/settings/activitypub:relays.litepub]]
+ +[[admin/settings/activitypub:rules.modal.title]]
+[[admin/settings/activitypub:rules.modal.instructions]]
+ +
- {./name}
+ {uploadBasename(@value)}
test title
abc', 'test title'], - - // other tags like h1 or span - ['consectetur adipiscing elit. Integer tincidunt metus scelerisque, dignissim risus a, fermentum leo. Pellentesque eleifend ullamcorper risus tempus vestibulum. Proin mollis ipsum et magna lobortis, at pretium enim pharetra. Ut vel ex metus. Mauris faucibus lectus et nulla iaculis, et pellentesque elit pellentesque. Aliquam rhoncus nec nulla eu lacinia. Maecenas cursus iaculis ligula, eu pharetra ex suscipit sit amet.
', 'Lorem ipsum dolor sit amet'], - ['Lorem ipsum dolor sit ametconsectetur adipiscing elit. Integer tincidunt metus scelerisque, dignissim risus a, fermentum leo. Pellentesque eleifend ullamcorper risus tempus vestibulum. Proin mollis ipsum et magna lobortis, at pretium enim pharetra. Ut vel ex metus. Mauris faucibus lectus et nulla iaculis, et pellentesque elit pellentesque. Aliquam rhoncus nec nulla eu lacinia. Maecenas cursus iaculis ligula, eu pharetra ex suscipit sit amet.
', 'Lorem ipsum dolor sit amet'], - - // first line's text otherwise - ['Lorem ipsum dolor sit amet\n\nconsectetur adipiscing elit. Integer tincidunt metus scelerisque, dignissim risus a, fermentum leo. Pellentesque eleifend ullamcorper risus tempus vestibulum. Proin mollis ipsum et magna lobortis, at pretium enim pharetra. Ut vel ex metus. Mauris faucibus lectus et nulla iaculis, et pellentesque elit pellentesque. Aliquam rhoncus nec nulla eu lacinia. Maecenas cursus iaculis ligula, eu pharetra ex suscipit sit amet.', 'Lorem ipsum dolor sit amet'], - - // first sentence of matched line/element - ['Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam a ex pellentesque, fringilla lorem non, blandit est. Nulla facilisi. Curabitur cursus neque vel enim semper, id lacinia elit facilisis. Vestibulum turpis orci, efficitur ut semper eu, faucibus eu turpis. Praesent eu odio non libero gravida tempor. Ut porta pellentesque orci. In porta nunc eget tincidunt interdum. Curabitur vel dui nec libero tempus porttitor. Phasellus tincidunt, diam id viverra suscipit, est diam maximus purus, in vestibulum dui ligula vel libero. Sed tempus finibus ante, sit amet consequat magna facilisis eget. Proin ullamcorper, velit sit amet feugiat varius, massa sem aliquam dui, non aliquam augue velit vel est. Phasellus eu sapien in purus feugiat scelerisque congue id velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'], - - // other sentence ending symbols - ['Lorem ipsum dolor sit amet, consectetur adipiscing elit? Etiam a ex pellentesque, fringilla lorem non, blandit est. Nulla facilisi. Curabitur cursus neque vel enim semper, id lacinia elit facilisis. Vestibulum turpis orci, efficitur ut semper eu, faucibus eu turpis. Praesent eu odio non libero gravida tempor. Ut porta pellentesque orci. In porta nunc eget tincidunt interdum. Curabitur vel dui nec libero tempus porttitor. Phasellus tincidunt, diam id viverra suscipit, est diam maximus purus, in vestibulum dui ligula vel libero. Sed tempus finibus ante, sit amet consequat magna facilisis eget. Proin ullamcorper, velit sit amet feugiat varius, massa sem aliquam dui, non aliquam augue velit vel est. Phasellus eu sapien in purus feugiat scelerisque congue id velit. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit?'], - - // Content after line breaks can be discarded - ['Intro text
example.org/
more text
', 'Intro text'], - - // HTML without outer wrapping element - ['Lorem ipsum dolor sit amet', 'Lorem ipsum dolor sit amet'], - - // Two sentences with punctuation - ['Lorem ipsum. Dolor sit amet.', 'Lorem ipsum.'], - - // Additional tests? - // ['', ''], - ]); - - cases.forEach((value, key) => { - it('should convert as expected', () => { - const title = activitypub.helpers.generateTitle(key); - assert.strictEqual(title, value); - }); - }); - }); - - it('should trim down the title if it is too long per settings', () => { - const value = meta.config.maximumTitleLength; - meta.config.maximumTitleLength = 10; - const source = '@@@@@@@@@@@@@@@@@@@@'; - const title = activitypub.helpers.generateTitle(source); - assert.strictEqual(title, '@@@@@@@...'); - meta.config.maximumTitleLength = value; - }); - }); - describe('.remoteAnchorToLocalProfile', () => { const uuid1 = utils.generateUUID(); const id1 = `https://example.org/uuid/${uuid1}`; diff --git a/test/activitypub/actors.js b/test/activitypub/actors.js index 37fc74280d..8c3c02e905 100644 --- a/test/activitypub/actors.js +++ b/test/activitypub/actors.js @@ -449,7 +449,6 @@ describe('Inbox resolution', () => { await activitypub.actors.assert(id); const inboxes = await activitypub.resolveInboxes([id]); - assert(inboxes && Array.isArray(inboxes)); assert.strictEqual(inboxes.length, 1); assert.strictEqual(inboxes[0], actor.inbox); @@ -829,7 +828,7 @@ describe('Pruning', () => { assert.strictEqual(result.counts.missing, 0); meta.config.activitypubUserPruneDays = 0; - user.deleteAccount(uid); + await user.deleteAccount(uid); }); it('should purge the user if they have no content (posts, likes, etc.)', async () => { diff --git a/test/activitypub/feps.js b/test/activitypub/feps.js index 65520f0f4e..705df788e1 100644 --- a/test/activitypub/feps.js +++ b/test/activitypub/feps.js @@ -269,20 +269,18 @@ describe('FEPs', () => { }); it('should be called when a post is moved to another topic', async () => { - const [{ topicData: topic1 }, { topicData: topic2 }] = await Promise.all([ - topics.post({ - uid, - cid, - title: utils.generateUUID(), - content: utils.generateUUID(), - }), - topics.post({ - uid, - cid, - title: utils.generateUUID(), - content: utils.generateUUID(), - }), - ]); + const { topicData: topic1 } = await topics.post({ + uid, + cid, + title: utils.generateUUID(), + content: utils.generateUUID(), + }); + const { topicData: topic2 } = await topics.post({ + uid, + cid, + title: utils.generateUUID(), + content: utils.generateUUID(), + }); assert(topic1 && topic2); diff --git a/test/activitypub/notes.js b/test/activitypub/notes.js index e8672eb283..a00116fbb5 100644 --- a/test/activitypub/notes.js +++ b/test/activitypub/notes.js @@ -467,20 +467,30 @@ describe('Notes', () => { }); it('should create a new topic in cid -1 if a non-same origin remote category is addressed', async function () { - this.timeout(60000); + this.timeout(30000); + const start = Date.now(); const { id: remoteCid } = helpers.mocks.group({ id: `https://example.com/${utils.generateUUID()}`, }); + console.log('1', Date.now() - start); const { note, id } = helpers.mocks.note({ audience: [remoteCid], }); + console.log('2', Date.now() - start); const { activity } = helpers.mocks.create(note); + console.log('3', Date.now() - start); + try { + await activitypub.inbox.create({ body: activity }); + } catch (err) { + console.log('error in test', err.stack); + assert(false); + } - await activitypub.inbox.create({ body: activity }); - + console.log('4', Date.now() - start); assert(await posts.exists(id)); - + console.log('5', Date.now() - start); const cid = await posts.getCidByPid(id); + console.log('6', Date.now() - start); assert.strictEqual(cid, -1); }); }); @@ -651,7 +661,7 @@ describe('Notes', () => { it('should upvote an asserted remote post', async () => { const { id } = helpers.mocks.note(); - await activitypub.notes.assert(0, [id], { skipChecks: true }); + await activitypub.notes.assert(0, id, { skipChecks: true }); const { activity: like } = helpers.mocks.like({ object: id, }); @@ -673,7 +683,7 @@ describe('Notes', () => { it('should update a note\'s content', async () => { const { id: actor } = helpers.mocks.person(); const { id, note } = helpers.mocks.note({ attributedTo: actor }); - await activitypub.notes.assert(0, [id], { skipChecks: true }); + await activitypub.notes.assert(0, id, { skipChecks: true }); note.content = utils.generateUUID(); const { activity: update } = helpers.mocks.update({ object: note }); const { activity } = helpers.mocks.announce({ object: update }); diff --git a/test/api.js b/test/api.js index d10433a88b..80d88f0194 100644 --- a/test/api.js +++ b/test/api.js @@ -11,8 +11,8 @@ const util = require('util'); const wait = util.promisify(setTimeout); -const request = require('../src/request'); const db = require('./mocks/databasemock'); +const request = require('../src/request'); const helpers = require('./helpers'); const meta = require('../src/meta'); const user = require('../src/user'); @@ -282,8 +282,13 @@ describe('API', async () => { await flags.appendNote(flagId, 1, 'test note', 1626446956652); await flags.create('post', 2, unprivUid, 'sample reasons', Date.now()); // for testing flag notes (since flag 1 deleted) - // Create a new chat room - await messaging.newRoom(adminUid, { uids: [unprivUid] }); + // Create a new chat room & send a message + const roomId = await messaging.newRoom(adminUid, { uids: [unprivUid] }); + await messaging.sendMessage({ + roomId, + uid: adminUid, + content: 'this is a chat message', + }); // Create an empty file to test DELETE /files and thumb deletion fs.closeSync(fs.openSync(path.resolve(nconf.get('upload_path'), 'files/test.txt'), 'w')); @@ -486,7 +491,8 @@ describe('API', async () => { } }); - it('should not error out when called', async () => { + it('should not error out when called', async function () { + this.timeout(0); await setupData(); if (csrfToken) { @@ -513,6 +519,7 @@ describe('API', async () => { redirect: 'manual', headers: headers, body: body, + timeout: 30000, }); } else if (type === 'form') { result = await helpers.uploadFile(url, pathLib.join(__dirname, './files/test.png'), {}, jar, csrfToken); diff --git a/test/database/hash.js b/test/database/hash.js index 571cf8bb95..f1bace299e 100644 --- a/test/database/hash.js +++ b/test/database/hash.js @@ -117,6 +117,12 @@ describe('Hash methods', () => { const result = await db.getObject('emptykey'); assert.deepStrictEqual(result, null); }); + + it('should return null if a field is set to null', async () => { + await db.setObject('nullFieldTest', { baz: 'baz', foo: null }); + const data = await db.getObjectFields('nullFieldTest', ['baz', 'foo']); + assert.deepStrictEqual(data, { baz: 'baz', foo: null }); + }); }); describe('setObjectField()', () => { @@ -279,6 +285,16 @@ describe('Hash methods', () => { }); }); + it('should return null if field is falsy', async () => { + const values = await Promise.all([ + db.getObjectField('hashTestObject', ''), + db.getObjectField('hashTestObject', null), + db.getObjectField('hashTestObject', false), + db.getObjectField('hashTestObject', undefined), + ]); + assert.deepStrictEqual(values, [null, null, null, null]); + }); + it('should return null and not error', async () => { const data = await db.getObjectField('hashTestObject', ['field1', 'field2']); assert.strictEqual(data, null); diff --git a/test/database/sorted.js b/test/database/sorted.js index b98d969730..a375b2ec48 100644 --- a/test/database/sorted.js +++ b/test/database/sorted.js @@ -501,7 +501,9 @@ NUMERIC)-- WsPn&query[cid]=-1&parentCid=0&selectedCids[]=-1&privilege=topics:rea ['byScoreWithScoresKeys1', 1, 'value1'], ['byScoreWithScoresKeys2', 2, 'value2'], ]); - const data = await db.getSortedSetRevRangeByScoreWithScores(['byScoreWithScoresKeys1', 'byScoreWithScoresKeys2'], 0, -1, 5, -5); + const data = await db.getSortedSetRevRangeByScoreWithScores([ + 'byScoreWithScoresKeys1', 'byScoreWithScoresKeys2', + ], 0, -1, 5, -5); assert.deepStrictEqual(data, [{ value: 'value2', score: 2 }, { value: 'value1', score: 1 }]); }); }); @@ -1144,23 +1146,17 @@ NUMERIC)-- WsPn&query[cid]=-1&parentCid=0&selectedCids[]=-1&privilege=topics:rea assert.strictEqual(await db.exists('sorted3'), false); }); - it('should remove multiple values from multiple keys', (done) => { - db.sortedSetAdd('multiTest1', [1, 2, 3, 4], ['one', 'two', 'three', 'four'], (err) => { - assert.ifError(err); - db.sortedSetAdd('multiTest2', [3, 4, 5, 6], ['three', 'four', 'five', 'six'], (err) => { - assert.ifError(err); - db.sortedSetRemove(['multiTest1', 'multiTest2'], ['two', 'three', 'four', 'five', 'doesnt exist'], (err) => { - assert.ifError(err); - db.getSortedSetsMembers(['multiTest1', 'multiTest2'], (err, members) => { - assert.ifError(err); - assert.equal(members[0].length, 1); - assert.equal(members[1].length, 1); - assert.deepEqual(members, [['one'], ['six']]); - done(); - }); - }); - }); - }); + it('should remove multiple values from multiple keys', async () => { + await db.sortedSetAdd('multiTest1', [1, 2, 3, 4], ['one', 'two', 'three', 'four']); + await db.sortedSetAdd('multiTest2', [3, 4, 5, 6], ['three', 'four', 'five', 'six']); + + await db.sortedSetRemove(['multiTest1', 'multiTest2'], ['two', 'three', 'four', 'five', 'doesnt exist']); + + const members = await db.getSortedSetsMembers(['multiTest1', 'multiTest2']); + + assert.equal(members[0].length, 1); + assert.equal(members[1].length, 1); + assert.deepEqual(members, [['one'], ['six']]); }); it('should remove value from multiple keys', async () => { @@ -1171,24 +1167,15 @@ NUMERIC)-- WsPn&query[cid]=-1&parentCid=0&selectedCids[]=-1&privilege=topics:rea assert.deepStrictEqual(await db.getSortedSetRange('multiTest4', 0, -1), ['four', 'five', 'six']); }); - it('should remove multiple values from multiple keys', (done) => { - db.sortedSetAdd('multiTest5', [1], ['one'], (err) => { - assert.ifError(err); - db.sortedSetAdd('multiTest6', [2], ['two'], (err) => { - assert.ifError(err); - db.sortedSetAdd('multiTest7', [3], [333], (err) => { - assert.ifError(err); - db.sortedSetRemove(['multiTest5', 'multiTest6', 'multiTest7'], ['one', 'two', 333], (err) => { - assert.ifError(err); - db.getSortedSetsMembers(['multiTest5', 'multiTest6', 'multiTest7'], (err, members) => { - assert.ifError(err); - assert.deepEqual(members, [[], [], []]); - done(); - }); - }); - }); - }); - }); + it('should remove multiple values from multiple keys', async () => { + await db.sortedSetAdd('multiTest5', [1], ['one']); + await db.sortedSetAdd('multiTest6', [2], ['two']); + await db.sortedSetAdd('multiTest7', [3], [333]); + + await db.sortedSetRemove(['multiTest5', 'multiTest6', 'multiTest7'], ['one', 'two', 333]); + + const members = await db.getSortedSetsMembers(['multiTest5', 'multiTest6', 'multiTest7']); + assert.deepEqual(members, [[], [], []]); }); it('should not remove anything if values is empty array', (done) => { @@ -1379,7 +1366,10 @@ NUMERIC)-- WsPn&query[cid]=-1&parentCid=0&selectedCids[]=-1&privilege=topics:rea weights: [1, 0.5], }, (err, data) => { assert.ifError(err); - assert.deepEqual([{ value: 'value2', score: 4 }, { value: 'value3', score: 5.5 }], data); + assert.deepEqual([ + { value: 'value2', score: 4 }, + { value: 'value3', score: 5.5 }, + ], data); done(); }); }); diff --git a/test/helpers/index.js b/test/helpers/index.js index e71a05edaa..89de878ce8 100644 --- a/test/helpers/index.js +++ b/test/helpers/index.js @@ -95,7 +95,7 @@ helpers.uploadFile = async function (uploadEndPoint, filePath, data, jar, csrf_t const file = await fs.promises.readFile(filePath); const blob = new Blob([file], { type: mime.getType(filePath) }); - form.append('files', blob, path.basename(filePath)); + form.append('files[]', blob, path.basename(filePath)); if (data && data.params) { form.append('params', data.params); diff --git a/test/messaging.js b/test/messaging.js index 4429fd6cd7..fbde5d72f6 100644 --- a/test/messaging.js +++ b/test/messaging.js @@ -184,6 +184,7 @@ describe('Messaging Library', () => { await User.setSetting(mocks.users.baz.uid, 'disableIncomingMessages', '0'); const { body } = await callv3API('post', `/chats`, { uids: [mocks.users.baz.uid], + joinLeaveMessages: 1, }, 'foo'); await User.setSetting(mocks.users.baz.uid, 'disableIncomingMessages', '1'); @@ -803,7 +804,7 @@ describe('Messaging Library', () => { assert.equal(response.statusCode, 200); assert(Array.isArray(body.rooms)); - assert.equal(body.rooms.length, 3); + assert.equal(body.rooms.length, 2); assert.equal(body.title, '[[pages:chats]]'); }); diff --git a/test/meta.js b/test/meta.js index 77667ddbf2..93b5b443bb 100644 --- a/test/meta.js +++ b/test/meta.js @@ -257,15 +257,10 @@ describe('meta', () => { }); }); - it('should use default value if value is null', (done) => { - meta.configs.set('teaserPost', null, (err) => { - assert.ifError(err); - meta.configs.get('teaserPost', (err, value) => { - assert.ifError(err); - assert.strictEqual(value, 'last-reply'); - done(); - }); - }); + it('should use default value if value is null', async () => { + await meta.configs.set('teaserPost', null); + const value = await meta.configs.get('teaserPost'); + assert.strictEqual(value, 'last-post'); }); it('should fail if field is invalid', (done) => { diff --git a/test/mocks/databasemock.js b/test/mocks/databasemock.js index 6a568109b7..179288bbcf 100644 --- a/test/mocks/databasemock.js +++ b/test/mocks/databasemock.js @@ -14,8 +14,9 @@ const util = require('util'); process.env.NODE_ENV = process.env.TEST_ENV || 'production'; global.env = process.env.NODE_ENV || 'production'; - - +if (!process.env.hasOwnProperty('CI')) { + process.env.CI = 'true'; +} const winston = require('winston'); const packageInfo = require('../../package.json'); @@ -171,7 +172,6 @@ before(async function () { require('../../src/user').startJobs(); await webserver.listen(); - // Iterate over all of the test suites/contexts this.test.parent.suites.forEach((suite) => { // Attach an afterAll listener that resets the defaults diff --git a/test/posts.js b/test/posts.js index 2fc22904fc..2ba85734d4 100644 --- a/test/posts.js +++ b/test/posts.js @@ -763,18 +763,18 @@ describe('Post\'s', () => { it('should turn relative links in post body to absolute urls', (done) => { const nconf = require('nconf'); - const content = 'test youtube'; + const content = 'test youtube'; const parsedContent = posts.relativeToAbsolute(content, posts.urlRegex); - assert.equal(parsedContent, `test youtube`); + assert.equal(parsedContent, `test youtube`); done(); }); it('should turn relative links in post body to absolute urls', (done) => { const nconf = require('nconf'); - const content = 'test youtube some test