Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ec4754078b | ||
|
|
93c6b014b0 | ||
|
|
12337302a7 | ||
|
|
f2011aafac | ||
|
|
74caf7e70d | ||
|
|
6d3278cc23 | ||
|
|
2b68baf438 | ||
|
|
2e002c257e | ||
|
|
da38b1fac2 | ||
|
|
cf9de8a0c7 | ||
|
|
136aa65d1b |
@@ -1,22 +1,8 @@
|
||||
# Save as .codeclimate.yml (note leading .) in project root directory
|
||||
version: "2"
|
||||
languages:
|
||||
Ruby: false
|
||||
Ruby: true
|
||||
JavaScript: true
|
||||
PHP: false
|
||||
checks:
|
||||
file-lines:
|
||||
config:
|
||||
threshold: 500
|
||||
method-lines:
|
||||
config:
|
||||
threshold: 50
|
||||
method-complexity:
|
||||
config:
|
||||
threshold: 10
|
||||
similar-code:
|
||||
config:
|
||||
threshold: 65
|
||||
PHP: true
|
||||
exclude_paths:
|
||||
- "public/vendor/*"
|
||||
- "test/*"
|
||||
@@ -7,6 +7,7 @@ node_modules/
|
||||
logs/
|
||||
/public/templates
|
||||
/public/uploads
|
||||
/public/sounds
|
||||
/public/vendor
|
||||
/public/src/modules/string.js
|
||||
.idea/
|
||||
@@ -17,4 +18,3 @@ logs/
|
||||
/build
|
||||
.eslintrc
|
||||
test/files
|
||||
*.min.js
|
||||
|
||||
14
.eslintrc
@@ -14,17 +14,16 @@
|
||||
"exports": "always-multiline",
|
||||
"functions": "never"
|
||||
}],
|
||||
"no-return-await": "off",
|
||||
"no-constant-condition": "off",
|
||||
"no-empty": ["error", { "allowEmptyCatch": true }],
|
||||
"no-underscore-dangle": "off",
|
||||
"newline-per-chained-call": "off",
|
||||
"no-console": "off",
|
||||
"no-mixed-operators": ["error", { "allowSamePrecedence": true }],
|
||||
"strict": ["error", "global"],
|
||||
"consistent-return": "off",
|
||||
"func-names": "off",
|
||||
"no-tabs": "off",
|
||||
"indent": ["error", "tab", { "SwitchCase": 1 }],
|
||||
"indent": ["error", "tab"],
|
||||
"no-eq-null": "off",
|
||||
"camelcase": "off",
|
||||
"no-new": "off",
|
||||
@@ -37,13 +36,6 @@
|
||||
"no-restricted-globals": "off",
|
||||
"function-paren-newline": "off",
|
||||
"import/no-unresolved": "error",
|
||||
"quotes": ["error", "single", {
|
||||
"avoidEscape": true,
|
||||
"allowTemplateLiterals": true
|
||||
}],
|
||||
"no-else-return": [ "error", { "allowElseIf": true } ],
|
||||
"operator-linebreak": [ "error", "after" ],
|
||||
"arrow-parens": ["error", "as-needed", { "requireForBlockBody": true }],
|
||||
|
||||
// ES6
|
||||
"prefer-rest-params": "off",
|
||||
@@ -121,7 +113,7 @@
|
||||
// "comma-spacing": "off",
|
||||
// "no-trailing-spaces": "off",
|
||||
// "key-spacing": "off",
|
||||
"no-multiple-empty-lines": "off"
|
||||
// "no-multiple-empty-lines": "off",
|
||||
// "spaced-comment": "off",
|
||||
// "space-in-parens": "off",
|
||||
// "block-spacing": "off",
|
||||
|
||||
7
.github/ISSUE_TEMPLATE.md
vendored
@@ -9,13 +9,10 @@
|
||||
|
||||
- **NodeBB version:**
|
||||
- **NodeBB git hash:**
|
||||
- **NodeJS version:**
|
||||
<!-- (to find your git hash, execute `git rev-parse HEAD` from the main NodeBB directory) -->
|
||||
- **Installed NodeBB Plugins:**
|
||||
<!-- (to find installed plugins run ./nodebb plugins) -->
|
||||
- **Database type:** mongo, redis, or postgres
|
||||
- **Database type:** mongo or redis
|
||||
- **Database version:**
|
||||
<!-- `mongod --version`, `redis-server --version`, or `postgres --version` -->
|
||||
<!-- `mongod --version` or `redis-server --version` -->
|
||||
- **Exact steps to cause this issue:**
|
||||
<!--
|
||||
1. First I did this...
|
||||
|
||||
9
.github/SECURITY.md
vendored
@@ -1,9 +0,0 @@
|
||||
# Reporting a security vulnerability
|
||||
|
||||
NodeBB's security policy is based around a private bug bounty program. Users are invited to explore NodeBB for vulnerabilities, and report them to the NodeBB team so that they can be patched.
|
||||
|
||||
If you have found a security vulnerability, **do not post it onto our GitHub tracker**. Some security vulnerabilities are quite severe and discretion is recommended. Email the NodeBB Security Team at security@nodebb.org, instead.
|
||||
|
||||
# Bug Bounty Program
|
||||
|
||||
Security vulnerability reports may be eligible for a bounty based on severity and confirmation from NodeBB team members. For full details regarding our bug bounty program, including the bounty amounts, please consult the following page: https://blog.nodebb.org/bounty
|
||||
204
.github/workflows/test.yaml
vendored
@@ -1,204 +0,0 @@
|
||||
name: Lint and test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Lint and test
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
node: [10, 12, 14]
|
||||
database: [mongo-dev, mongo, redis, postgres]
|
||||
include:
|
||||
# only run coverage once
|
||||
- os: ubuntu-latest
|
||||
node: 14
|
||||
coverage: true
|
||||
# test under development once
|
||||
- database: mongo-dev
|
||||
test_env: development
|
||||
# only run eslint once
|
||||
- os: ubuntu-latest
|
||||
node: 14
|
||||
database: mongo-dev
|
||||
lint: true
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
TEST_ENV: ${{ matrix.test_env || 'production' }}
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: 'postgres:10-alpine'
|
||||
env:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
# Set health checks to wait until postgres has started
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
ports:
|
||||
# Maps port 5432 on service container to the host
|
||||
- 5432:5432
|
||||
|
||||
redis:
|
||||
image: 'redis:2.8.9'
|
||||
# Set health checks to wait until redis has started
|
||||
options: >-
|
||||
--health-cmd "redis-cli ping"
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
ports:
|
||||
# Maps port 6379 on service container to the host
|
||||
- 6379:6379
|
||||
|
||||
mongo:
|
||||
image: 'mongo:3.2'
|
||||
ports:
|
||||
# Maps port 27017 on service container to the host
|
||||
- 27017:27017
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- run: cp install/package.json package.json
|
||||
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
|
||||
- name: NPM Install
|
||||
uses: bahmutov/npm-install@v1
|
||||
with:
|
||||
useLockFile: false
|
||||
|
||||
- name: Setup on MongoDB
|
||||
if: startsWith(matrix.database, 'mongo')
|
||||
env:
|
||||
SETUP: >-
|
||||
{
|
||||
"url": "http://127.0.0.1:4567",
|
||||
"secret": "abcdef",
|
||||
"admin:username": "admin",
|
||||
"admin:email": "test@example.org",
|
||||
"admin:password": "hAN3Eg8W",
|
||||
"admin:password:confirm": "hAN3Eg8W",
|
||||
|
||||
"database": "mongo",
|
||||
"mongo:host": "127.0.0.1",
|
||||
"mongo:port": 27017,
|
||||
"mongo:username": "",
|
||||
"mongo:password": "",
|
||||
"mongo:database": "nodebb"
|
||||
}
|
||||
CI: >-
|
||||
{
|
||||
"host": "127.0.0.1",
|
||||
"port": 27017,
|
||||
"database": "ci_test"
|
||||
}
|
||||
run: |
|
||||
node app --setup="${SETUP}" --ci="${CI}"
|
||||
|
||||
- name: Setup on PostgreSQL
|
||||
if: startsWith(matrix.database, 'postgres')
|
||||
env:
|
||||
SETUP: >-
|
||||
{
|
||||
"url": "http://127.0.0.1:4567",
|
||||
"secret": "abcdef",
|
||||
"admin:username": "admin",
|
||||
"admin:email": "test@example.org",
|
||||
"admin:password": "hAN3Eg8W",
|
||||
"admin:password:confirm": "hAN3Eg8W",
|
||||
|
||||
"database": "postgres",
|
||||
"postgres:host": "127.0.0.1",
|
||||
"postgres:port": 5432,
|
||||
"postgres:username": "postgres",
|
||||
"postgres:password": "postgres",
|
||||
"postgres:database": "nodebb"
|
||||
}
|
||||
CI: >-
|
||||
{
|
||||
"host": "127.0.0.1",
|
||||
"database": "ci_test",
|
||||
"port": 5432,
|
||||
"username": "postgres",
|
||||
"password": "postgres"
|
||||
}
|
||||
run: |
|
||||
node -e "const { Client } = require('pg'); const c = new Client({ host: '127.0.0.1', port: 5432, user: 'postgres', password: 'postgres' }); c.connect().then(() => c.query('CREATE DATABASE nodebb')).then(() => c.query('CREATE DATABASE ci_test')).then(() => c.end())"
|
||||
node app --setup="${SETUP}" --ci="${CI}"
|
||||
|
||||
- name: Setup on Redis
|
||||
if: startsWith(matrix.database, 'redis')
|
||||
env:
|
||||
SETUP: >-
|
||||
{
|
||||
"url": "http://127.0.0.1:4567/forum",
|
||||
"secret": "abcdef",
|
||||
"admin:username": "admin",
|
||||
"admin:email": "test@example.org",
|
||||
"admin:password": "hAN3Eg8W",
|
||||
"admin:password:confirm": "hAN3Eg8W",
|
||||
|
||||
"database": "redis",
|
||||
"redis:host": "127.0.0.1",
|
||||
"redis:port": 6379,
|
||||
"redis:password": "",
|
||||
"redis:database": 0
|
||||
}
|
||||
CI: >-
|
||||
{
|
||||
"host": "127.0.0.1",
|
||||
"database": 1,
|
||||
"port": 6379
|
||||
}
|
||||
run: |
|
||||
node app --setup="${SETUP}" --ci="${CI}"
|
||||
|
||||
- name: Run ESLint
|
||||
if: matrix.lint
|
||||
run: npm run lint
|
||||
|
||||
- name: Node tests
|
||||
run: npm test
|
||||
|
||||
- name: Extract coverage info
|
||||
run: npm run coverage
|
||||
|
||||
- name: Test coverage
|
||||
uses: coverallsapp/github-action@v1.1.2
|
||||
if: matrix.coverage
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
flag-name: ${{ matrix.os }}-node-${{ matrix.node }}-db-${{ matrix.database }}
|
||||
parallel: true
|
||||
|
||||
finish:
|
||||
needs: test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Coveralls Finished
|
||||
uses: coverallsapp/github-action@v1.1.2
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
parallel-finished: true
|
||||
5
.gitignore
vendored
@@ -1,4 +1,3 @@
|
||||
dist/
|
||||
yarn.lock
|
||||
npm-debug.log
|
||||
node_modules/
|
||||
@@ -28,6 +27,7 @@ pidfile
|
||||
# templates
|
||||
/public/templates
|
||||
|
||||
/public/sounds
|
||||
/public/uploads
|
||||
/test/uploads
|
||||
|
||||
@@ -40,7 +40,6 @@ pidfile
|
||||
/public/acp.min.js.map
|
||||
/public/installer.css
|
||||
/public/installer.min.js
|
||||
/public/bootstrap.min.css
|
||||
/public/logo.png
|
||||
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio
|
||||
@@ -67,5 +66,3 @@ test/files/normalise.jpg.png
|
||||
test/files/normalise-resized.jpg
|
||||
package-lock.json
|
||||
/package.json
|
||||
*.mongodb
|
||||
link-plugins.sh
|
||||
@@ -4,8 +4,6 @@
|
||||
|
||||
"maxerr" : 50, // {int} Maximum error before stopping
|
||||
|
||||
"esversion": 6,
|
||||
|
||||
// Enforcing
|
||||
"bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.)
|
||||
"camelcase" : false, // true: Identifiers must be in camelCase
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
reporter: dot
|
||||
timeout: 25000
|
||||
exit: true
|
||||
bail: true
|
||||
37
.travis.yml
Normal file
@@ -0,0 +1,37 @@
|
||||
services:
|
||||
- mongodb
|
||||
- redis-server
|
||||
before_install:
|
||||
- cp install/package.json package.json
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
before_script:
|
||||
- sleep 15 # wait for mongodb to be ready
|
||||
- sh -c "if [ '$DB' = 'mongodb' ]; then node app --setup=\"{\\\"url\\\":\\\"http://127.0.0.1:4567\\\",\\\"secret\\\":\\\"abcdef\\\",\\\"database\\\":\\\"mongo\\\",\\\"mongo:host\\\":\\\"127.0.0.1\\\",\\\"mongo:port\\\":27017,\\\"mongo:username\\\":\\\"\\\",\\\"mongo:password\\\":\\\"\\\",\\\"mongo:database\\\":0,\\\"redis:host\\\":\\\"127.0.0.1\\\",\\\"redis:port\\\":6379,\\\"redis:password\\\":\\\"\\\",\\\"redis:database\\\":0,\\\"admin:username\\\":\\\"admin\\\",\\\"admin:email\\\":\\\"test@example.org\\\",\\\"admin:password\\\":\\\"abcdef\\\",\\\"admin:password:confirm\\\":\\\"abcdef\\\"}\" --ci=\"{\\\"host\\\":\\\"127.0.0.1\\\",\\\"port\\\":27017,\\\"database\\\":0}\"; fi"
|
||||
- sh -c "if [ '$DB' = 'redis' ]; then node app --setup=\"{\\\"url\\\":\\\"http://127.0.0.1:4567\\\",\\\"secret\\\":\\\"abcdef\\\",\\\"database\\\":\\\"redis\\\",\\\"mongo:host\\\":\\\"127.0.0.1\\\",\\\"mongo:port\\\":27017,\\\"mongo:username\\\":\\\"\\\",\\\"mongo:password\\\":\\\"\\\",\\\"mongo:database\\\":0,\\\"redis:host\\\":\\\"127.0.0.1\\\",\\\"redis:port\\\":6379,\\\"redis:password\\\":\\\"\\\",\\\"redis:database\\\":0,\\\"admin:username\\\":\\\"admin\\\",\\\"admin:email\\\":\\\"test@example.org\\\",\\\"admin:password\\\":\\\"abcdef\\\",\\\"admin:password:confirm\\\":\\\"abcdef\\\"}\" --ci=\"{\\\"host\\\":\\\"127.0.0.1\\\",\\\"port\\\":6379,\\\"database\\\":0}\"; fi"
|
||||
after_success:
|
||||
- "npm run coveralls"
|
||||
language: node_js
|
||||
sudo: false
|
||||
dist: trusty
|
||||
env:
|
||||
- CXX=g++-4.8 DB=mongodb
|
||||
- CXX=g++-4.8 DB=redis
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
- mongodb-3.4-trusty
|
||||
packages:
|
||||
- g++-4.8
|
||||
- mongodb-org-server
|
||||
node_js:
|
||||
- "9"
|
||||
- "8"
|
||||
- "7"
|
||||
- "6"
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- develop
|
||||
1009
.tx/config
3492
CHANGELOG.md
14
Dockerfile
@@ -1,22 +1,20 @@
|
||||
FROM node:lts
|
||||
# The base image is the latest 8.x node (LTS)
|
||||
FROM node:8.9.0
|
||||
|
||||
RUN mkdir -p /usr/src/app
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
ARG NODE_ENV
|
||||
ENV NODE_ENV $NODE_ENV
|
||||
|
||||
COPY install/package.json /usr/src/app/package.json
|
||||
|
||||
RUN npm install --only=prod && \
|
||||
npm cache clean --force
|
||||
|
||||
RUN npm install && npm cache clean --force
|
||||
COPY . /usr/src/app
|
||||
|
||||
ENV NODE_ENV=production \
|
||||
daemon=false \
|
||||
silent=false
|
||||
|
||||
EXPOSE 4567
|
||||
CMD ./nodebb start
|
||||
|
||||
CMD node ./nodebb build ; node ./nodebb start
|
||||
# the default port for NodeBB is exposed outside the container
|
||||
EXPOSE 4567
|
||||
237
Gruntfile.js
@@ -1,86 +1,94 @@
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
const nconf = require('nconf');
|
||||
nconf.argv().env({
|
||||
separator: '__',
|
||||
});
|
||||
const winston = require('winston');
|
||||
const fork = require('child_process').fork;
|
||||
const env = process.env;
|
||||
var fork = require('child_process').fork;
|
||||
var env = process.env;
|
||||
var worker;
|
||||
var updateWorker;
|
||||
var initWorker;
|
||||
var incomplete = [];
|
||||
var running = 0;
|
||||
|
||||
env.NODE_ENV = env.NODE_ENV || 'development';
|
||||
|
||||
const configFile = path.resolve(__dirname, nconf.any(['config', 'CONFIG']) || 'config.json');
|
||||
const prestart = require('./src/prestart');
|
||||
prestart.loadConfig(configFile);
|
||||
|
||||
var db = require('./src/database');
|
||||
|
||||
module.exports = function (grunt) {
|
||||
var args = [];
|
||||
|
||||
var initArgs = ['--build'];
|
||||
if (!grunt.option('verbose')) {
|
||||
args.push('--log-level=info');
|
||||
nconf.set('log-level', 'info');
|
||||
initArgs.push('--log-level=info');
|
||||
}
|
||||
prestart.setupWinston();
|
||||
|
||||
grunt.initConfig({
|
||||
watch: {},
|
||||
});
|
||||
function update(action, filepath, target) {
|
||||
var updateArgs = args.slice();
|
||||
var compiling;
|
||||
var time = Date.now();
|
||||
|
||||
grunt.loadNpmTasks('grunt-contrib-watch');
|
||||
|
||||
grunt.registerTask('default', ['watch']);
|
||||
|
||||
grunt.registerTask('init', async function () {
|
||||
var done = this.async();
|
||||
let plugins = [];
|
||||
if (!process.argv.includes('--core')) {
|
||||
await db.init();
|
||||
plugins = await db.getSortedSetRange('plugins:active', 0, -1);
|
||||
addBaseThemes(plugins);
|
||||
if (!plugins.includes('nodebb-plugin-composer-default')) {
|
||||
plugins.push('nodebb-plugin-composer-default');
|
||||
}
|
||||
if (!plugins.includes('nodebb-theme-persona')) {
|
||||
plugins.push('nodebb-theme-persona');
|
||||
}
|
||||
if (target === 'lessUpdated_Client') {
|
||||
compiling = 'clientCSS';
|
||||
} else if (target === 'lessUpdated_Admin') {
|
||||
compiling = 'acpCSS';
|
||||
} else if (target === 'clientUpdated') {
|
||||
compiling = 'js';
|
||||
} else if (target === 'templatesUpdated') {
|
||||
compiling = 'tpl';
|
||||
} else if (target === 'langUpdated') {
|
||||
compiling = 'lang';
|
||||
} else if (target === 'serverUpdated') {
|
||||
// Do nothing, just restart
|
||||
}
|
||||
|
||||
const styleUpdated_Client = plugins.map(p => 'node_modules/' + p + '/*.less')
|
||||
.concat(plugins.map(p => 'node_modules/' + p + '/*.css'))
|
||||
.concat(plugins.map(p => 'node_modules/' + p + '/+(public|static|less)/**/*.less'))
|
||||
.concat(plugins.map(p => 'node_modules/' + p + '/+(public|static)/**/*.css'));
|
||||
if (compiling && incomplete.indexOf(compiling) === -1) {
|
||||
incomplete.push(compiling);
|
||||
}
|
||||
|
||||
const styleUpdated_Admin = plugins.map(p => 'node_modules/' + p + '/*.less')
|
||||
.concat(plugins.map(p => 'node_modules/' + p + '/*.css'))
|
||||
.concat(plugins.map(p => 'node_modules/' + p + '/+(public|static|less)/**/*.less'))
|
||||
.concat(plugins.map(p => 'node_modules/' + p + '/+(public|static)/**/*.css'));
|
||||
updateArgs.push('--build');
|
||||
updateArgs.push(incomplete.join(','));
|
||||
|
||||
const clientUpdated = plugins.map(p => 'node_modules/' + p + '/+(public|static)/**/*.js');
|
||||
const serverUpdated = plugins.map(p => 'node_modules/' + p + '/*.js')
|
||||
.concat(plugins.map(p => 'node_modules/' + p + '/+(lib|src)/**/*.js'));
|
||||
worker.kill();
|
||||
if (updateWorker) {
|
||||
updateWorker.kill('SIGKILL');
|
||||
}
|
||||
updateWorker = fork('app.js', updateArgs, { env: env });
|
||||
running += 1;
|
||||
updateWorker.on('exit', function () {
|
||||
running -= 1;
|
||||
if (running === 0) {
|
||||
worker = fork('app.js', args, {
|
||||
env: env,
|
||||
});
|
||||
worker.on('message', function () {
|
||||
if (incomplete.length) {
|
||||
incomplete = [];
|
||||
|
||||
const templatesUpdated = plugins.map(p => 'node_modules/' + p + '/+(public|static|templates)/**/*.tpl');
|
||||
const langUpdated = plugins.map(p => 'node_modules/' + p + '/+(public|static|languages)/**/*.json');
|
||||
if (grunt.option('verbose')) {
|
||||
grunt.log.writeln('NodeBB restarted in ' + (Date.now() - time) + ' ms');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
grunt.config(['watch'], {
|
||||
styleUpdated_Client: {
|
||||
grunt.initConfig({
|
||||
watch: {
|
||||
lessUpdated_Client: {
|
||||
files: [
|
||||
'public/less/**/*.less',
|
||||
...styleUpdated_Client,
|
||||
'public/less/*.less',
|
||||
'!public/less/admin/**/*.less',
|
||||
'node_modules/nodebb-*/**/*.less',
|
||||
'!node_modules/nodebb-*/node_modules/**',
|
||||
'!node_modules/nodebb-*/.git/**',
|
||||
],
|
||||
options: {
|
||||
interval: 1000,
|
||||
},
|
||||
},
|
||||
styleUpdated_Admin: {
|
||||
lessUpdated_Admin: {
|
||||
files: [
|
||||
'public/less/**/*.less',
|
||||
...styleUpdated_Admin,
|
||||
'public/less/admin/**/*.less',
|
||||
'node_modules/nodebb-*/**/*.less',
|
||||
'!node_modules/nodebb-*/node_modules/**',
|
||||
'!node_modules/nodebb-*/.git/**',
|
||||
],
|
||||
options: {
|
||||
interval: 1000,
|
||||
@@ -89,25 +97,17 @@ module.exports = function (grunt) {
|
||||
clientUpdated: {
|
||||
files: [
|
||||
'public/src/**/*.js',
|
||||
'public/vendor/**/*.js',
|
||||
...clientUpdated,
|
||||
'node_modules/nodebb-*/**/*.js',
|
||||
'!node_modules/nodebb-*/node_modules/**',
|
||||
'node_modules/benchpressjs/build/benchpress.js',
|
||||
'!node_modules/nodebb-*/.git/**',
|
||||
],
|
||||
options: {
|
||||
interval: 1000,
|
||||
},
|
||||
},
|
||||
serverUpdated: {
|
||||
files: [
|
||||
'app.js',
|
||||
'install/*.js',
|
||||
'src/**/*.js',
|
||||
'public/src/modules/translator.js',
|
||||
'public/src/modules/helpers.js',
|
||||
'public/src/utils.js',
|
||||
serverUpdated,
|
||||
'!src/upgrades/**',
|
||||
],
|
||||
files: ['*.js', 'install/*.js', 'src/**/*.js'],
|
||||
options: {
|
||||
interval: 1000,
|
||||
},
|
||||
@@ -115,7 +115,9 @@ module.exports = function (grunt) {
|
||||
templatesUpdated: {
|
||||
files: [
|
||||
'src/views/**/*.tpl',
|
||||
...templatesUpdated,
|
||||
'node_modules/nodebb-*/**/*.tpl',
|
||||
'!node_modules/nodebb-*/node_modules/**',
|
||||
'!node_modules/nodebb-*/.git/**',
|
||||
],
|
||||
options: {
|
||||
interval: 1000,
|
||||
@@ -125,89 +127,40 @@ module.exports = function (grunt) {
|
||||
files: [
|
||||
'public/language/en-GB/*.json',
|
||||
'public/language/en-GB/**/*.json',
|
||||
...langUpdated,
|
||||
'node_modules/nodebb-*/**/*.json',
|
||||
'!node_modules/nodebb-*/node_modules/**',
|
||||
'!node_modules/nodebb-*/.git/**',
|
||||
'!node_modules/nodebb-*/plugin.json',
|
||||
'!node_modules/nodebb-*/package.json',
|
||||
'!node_modules/nodebb-*/theme.json',
|
||||
],
|
||||
options: {
|
||||
interval: 1000,
|
||||
},
|
||||
},
|
||||
});
|
||||
const build = require('./src/meta/build');
|
||||
if (!grunt.option('skip')) {
|
||||
await build.build(true);
|
||||
}
|
||||
run();
|
||||
done();
|
||||
},
|
||||
});
|
||||
|
||||
function run() {
|
||||
if (worker) {
|
||||
worker.kill();
|
||||
}
|
||||
grunt.loadNpmTasks('grunt-contrib-watch');
|
||||
|
||||
const execArgv = [];
|
||||
const inspect = process.argv.find(a => a.startsWith('--inspect'));
|
||||
|
||||
if (inspect) {
|
||||
execArgv.push(inspect);
|
||||
}
|
||||
grunt.registerTask('default', ['watch']);
|
||||
env.NODE_ENV = 'development';
|
||||
|
||||
if (grunt.option('skip')) {
|
||||
worker = fork('app.js', args, {
|
||||
env,
|
||||
execArgv,
|
||||
env: env,
|
||||
});
|
||||
} else {
|
||||
initWorker = fork('app.js', initArgs, {
|
||||
env: env,
|
||||
});
|
||||
|
||||
initWorker.on('exit', function () {
|
||||
worker = fork('app.js', args, {
|
||||
env: env,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
grunt.task.run('init');
|
||||
|
||||
grunt.event.removeAllListeners('watch');
|
||||
grunt.event.on('watch', function update(action, filepath, target) {
|
||||
var compiling;
|
||||
if (target === 'styleUpdated_Client') {
|
||||
compiling = 'clientCSS';
|
||||
} else if (target === 'styleUpdated_Admin') {
|
||||
compiling = 'acpCSS';
|
||||
} else if (target === 'clientUpdated') {
|
||||
compiling = 'js';
|
||||
} else if (target === 'templatesUpdated') {
|
||||
compiling = 'tpl';
|
||||
} else if (target === 'langUpdated') {
|
||||
compiling = 'lang';
|
||||
} else if (target === 'serverUpdated') {
|
||||
// empty require cache
|
||||
const paths = ['./src/meta/build.js', './src/meta/index.js'];
|
||||
paths.forEach(p => delete require.cache[require.resolve(p)]);
|
||||
return run();
|
||||
}
|
||||
|
||||
require('./src/meta/build').build([compiling], function (err) {
|
||||
if (err) {
|
||||
winston.error(err.stack);
|
||||
}
|
||||
if (worker) {
|
||||
worker.send({ compiling: compiling });
|
||||
}
|
||||
});
|
||||
});
|
||||
grunt.event.on('watch', update);
|
||||
};
|
||||
|
||||
function addBaseThemes(plugins) {
|
||||
let themeId = plugins.find(p => p.includes('nodebb-theme-'));
|
||||
if (!themeId) {
|
||||
return plugins;
|
||||
}
|
||||
let baseTheme;
|
||||
do {
|
||||
try {
|
||||
baseTheme = require(themeId + '/theme').baseTheme;
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
if (baseTheme) {
|
||||
plugins.push(baseTheme);
|
||||
themeId = baseTheme;
|
||||
}
|
||||
} while (baseTheme);
|
||||
return plugins;
|
||||
}
|
||||
|
||||
15
README.md
@@ -1,16 +1,17 @@
|
||||
# 
|
||||
# <img alt="NodeBB" src="http://i.imgur.com/mYxPPtB.png" />
|
||||
|
||||
[](https://gitter.im/NodeBB/NodeBB?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](https://travis-ci.org/NodeBB/NodeBB)
|
||||
[](https://coveralls.io/github/NodeBB/NodeBB?branch=master)
|
||||
[](https://david-dm.org/nodebb/nodebb?path=install)
|
||||
[](https://codeclimate.com/github/NodeBB/NodeBB)
|
||||
[](https://readthedocs.org/projects/nodebb/?badge=latest)
|
||||
|
||||
[**NodeBB Forum Software**](https://nodebb.org) is powered by Node.js and supports either Redis, MongoDB, or a PostgreSQL database. It utilizes web sockets for instant interactions and real-time notifications. NodeBB has many modern features out of the box such as social network integration and streaming discussions, while still making sure to be compatible with older browsers.
|
||||
[**NodeBB Forum Software**](https://nodebb.org) is powered by Node.js and built on either a Redis or MongoDB database. It utilizes web sockets for instant interactions and real-time notifications. NodeBB has many modern features out of the box such as social network integration and streaming discussions, while still making sure to be compatible with older browsers.
|
||||
|
||||
Additional functionality is enabled through the use of third-party plugins.
|
||||
|
||||
* [Demo](https://try.nodebb.org)
|
||||
* [Developer Community](http://community.nodebb.org)
|
||||
* [Demo & Meta Discussion](http://community.nodebb.org)
|
||||
* [Documentation & Installation Instructions](http://docs.nodebb.org)
|
||||
* [Help translate NodeBB](https://www.transifex.com/projects/p/nodebb/)
|
||||
* [NodeBB Blog](http://blog.nodebb.org)
|
||||
@@ -41,7 +42,7 @@ Our minimalist "Persona" theme gets you going right away, no coding experience r
|
||||
## How can I follow along/contribute?
|
||||
|
||||
* If you are a developer, feel free to check out the source and submit pull requests. We also have a wide array of [plugins](http://community.nodebb.org/category/7/nodebb-plugins) which would be a great starting point for learning the codebase.
|
||||
* If you are a designer, [NodeBB needs themes](http://community.nodebb.org/category/10/nodebb-themes)! NodeBB's theming system allows extension of the base templates as well as styling via LESS or CSS. NodeBB's base theme utilizes [Bootstrap 3](http://getbootstrap.com/) but themes can choose to use a different framework altogether.
|
||||
* If you are a designer, [NodeBB needs themes](http://community.nodebb.org/category/10/nodebb-themes)! NodeBB's theming system allows extention of the base templates as well as styling via LESS or CSS. NodeBB's base theme utilizes [Bootstrap 3](http://getbootstrap.com/) but themes can choose to use a different framework altogether.
|
||||
* If you know languages other than English you can help us translate NodeBB. We use [Transifex](https://www.transifex.com/projects/p/nodebb/) for internationalization.
|
||||
* Please don't forget to **like**, **follow**, and **star our repo**! Join our growing [community](http://community.nodebb.org) to keep up to date with the latest NodeBB development.
|
||||
|
||||
@@ -49,8 +50,8 @@ Our minimalist "Persona" theme gets you going right away, no coding experience r
|
||||
|
||||
NodeBB requires the following software to be installed:
|
||||
|
||||
* A version of Node.js at least 12 or greater ([installation/upgrade instructions](https://github.com/nodesource/distributions))
|
||||
* MongoDB, version 2.6 or greater **or** Redis, version 2.8.9 or greater
|
||||
* A version of Node.js at least 6 or greater ([installation/upgrade instructions](https://github.com/nodesource/distributions))
|
||||
* Redis, version 2.8.9 or greater **or** MongoDB, version 2.6 or greater
|
||||
* nginx, version 1.3.13 or greater (**only if** intending to use nginx to proxy requests to a NodeBB)
|
||||
|
||||
## Installation
|
||||
|
||||
41
app.js
@@ -19,31 +19,34 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
require('./require-main');
|
||||
if (require.main !== module) {
|
||||
require.main.require = function (path) {
|
||||
return require(path);
|
||||
};
|
||||
}
|
||||
|
||||
const nconf = require('nconf');
|
||||
var nconf = require('nconf');
|
||||
nconf.argv().env({
|
||||
separator: '__',
|
||||
});
|
||||
|
||||
const winston = require('winston');
|
||||
const path = require('path');
|
||||
var async = require('async');
|
||||
var winston = require('winston');
|
||||
var path = require('path');
|
||||
|
||||
const file = require('./src/file');
|
||||
var file = require('./src/file');
|
||||
|
||||
process.env.NODE_ENV = process.env.NODE_ENV || 'production';
|
||||
global.env = process.env.NODE_ENV || 'production';
|
||||
|
||||
// Alternate configuration file support
|
||||
const configFile = path.resolve(__dirname, nconf.any(['config', 'CONFIG']) || 'config.json');
|
||||
var configFile = path.resolve(__dirname, nconf.any(['config', 'CONFIG']) || 'config.json');
|
||||
|
||||
const configExists = file.existsSync(configFile) || (nconf.get('url') && nconf.get('secret') && nconf.get('database'));
|
||||
var configExists = file.existsSync(configFile) || (nconf.get('url') && nconf.get('secret') && nconf.get('database'));
|
||||
|
||||
const prestart = require('./src/prestart');
|
||||
var prestart = require('./src/prestart');
|
||||
prestart.loadConfig(configFile);
|
||||
prestart.setupWinston();
|
||||
prestart.versionCheck();
|
||||
winston.verbose('* using configuration stored in: %s', configFile);
|
||||
prestart.setupWinston();
|
||||
|
||||
if (!process.send) {
|
||||
// If run using `node app`, log GNU copyright info along with server info
|
||||
@@ -60,21 +63,33 @@ if (nconf.get('setup') || nconf.get('install')) {
|
||||
} else if (nconf.get('upgrade')) {
|
||||
require('./src/cli/upgrade').upgrade(true);
|
||||
} else if (nconf.get('reset')) {
|
||||
require('./src/cli/reset').reset({
|
||||
var options = {
|
||||
theme: nconf.get('t'),
|
||||
plugin: nconf.get('p'),
|
||||
widgets: nconf.get('w'),
|
||||
settings: nconf.get('s'),
|
||||
all: nconf.get('a'),
|
||||
};
|
||||
|
||||
async.series([
|
||||
async.apply(require('./src/cli/reset').reset, options),
|
||||
require('./src/meta/build').buildAll,
|
||||
], function (err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
});
|
||||
} else if (nconf.get('activate')) {
|
||||
require('./src/cli/manage').activate(nconf.get('activate'));
|
||||
} else if (nconf.get('plugins')) {
|
||||
require('./src/cli/manage').listPlugins();
|
||||
} else if (nconf.get('build')) {
|
||||
require('./src/cli/manage').build(nconf.get('build'));
|
||||
require('./src/meta/build').build(nconf.get('build'));
|
||||
} else if (nconf.get('events')) {
|
||||
require('./src/cli/manage').listEvents();
|
||||
} else {
|
||||
require('./src/start').start();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
extends: ['@commitlint/config-angular'],
|
||||
rules: {
|
||||
'header-max-length': [1, 'always', 72],
|
||||
'type-enum': [
|
||||
2,
|
||||
'always',
|
||||
[
|
||||
'breaking',
|
||||
'build',
|
||||
'chore',
|
||||
'ci',
|
||||
'docs',
|
||||
'feat',
|
||||
'fix',
|
||||
'perf',
|
||||
'refactor',
|
||||
'revert',
|
||||
'style',
|
||||
'test',
|
||||
],
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -1,24 +0,0 @@
|
||||
version: '3.5'
|
||||
|
||||
services:
|
||||
node:
|
||||
build: .
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- db
|
||||
expose:
|
||||
- 4567 # use a reverse proxy like Traefik
|
||||
|
||||
db:
|
||||
image: mongo:bionic
|
||||
restart: unless-stopped
|
||||
expose:
|
||||
- 27017
|
||||
environment:
|
||||
MONGO_INITDB_ROOT_USERNAME: root
|
||||
MONGO_INITDB_ROOT_PASSWORD: root
|
||||
volumes:
|
||||
- mongo:/data/db
|
||||
|
||||
volumes:
|
||||
mongo:
|
||||
@@ -1,148 +1,54 @@
|
||||
{
|
||||
"title": "NodeBB",
|
||||
"showSiteTitle": 1,
|
||||
"defaultLang": "en-GB",
|
||||
"loginDays": 14,
|
||||
"loginSeconds": 0,
|
||||
"loginAttempts": 5,
|
||||
"lockoutDuration": 60,
|
||||
"adminReloginDuration": 60,
|
||||
"postDelay": 10,
|
||||
"initialPostDelay": 10,
|
||||
"newbiePostDelay": 120,
|
||||
"postEditDuration": 0,
|
||||
"newbiePostEditDuration": 3600,
|
||||
"postDeleteDuration": 0,
|
||||
"enablePostHistory": 1,
|
||||
"postCacheSize": 10485760,
|
||||
"disableChat": 0,
|
||||
"chatEditDuration": 0,
|
||||
"chatDeleteDuration": 0,
|
||||
"chatMessageDelay": 200,
|
||||
"newbiePostDelayThreshold": 3,
|
||||
"postQueueReputationThreshold": 0,
|
||||
"groupsExemptFromPostQueue": ["administrators", "Global Moderators"],
|
||||
"minimumPostLength": 8,
|
||||
"maximumPostLength": 32767,
|
||||
"minimumTagsPerTopic": 0,
|
||||
"maximumTagsPerTopic": 5,
|
||||
"minimumTagLength": 3,
|
||||
"maximumTagLength": 15,
|
||||
"allowGuestSearching": 0,
|
||||
"allowTopicsThumbnail": 0,
|
||||
"registrationType": "normal",
|
||||
"registrationApprovalType": "normal",
|
||||
"allowLocalLogin": 1,
|
||||
"allowAccountDelete": 1,
|
||||
"privateUploads": 0,
|
||||
"allowedFileExtensions": "png,jpg,bmp,txt",
|
||||
"allowFileUploads": 0,
|
||||
"allowedFileExtensions": "png,jpg,bmp",
|
||||
"allowUserHomePage": 1,
|
||||
"allowMultipleBadges": 0,
|
||||
"maximumFileSize": 2048,
|
||||
"stripEXIFData": 1,
|
||||
"resizeImageWidthThreshold": 2000,
|
||||
"resizeImageWidth": 760,
|
||||
"rejectImageWidth": 5000,
|
||||
"rejectImageHeight": 5000,
|
||||
"resizeImageQuality": 80,
|
||||
"topicThumbSize": 120,
|
||||
"minimumTitleLength": 3,
|
||||
"maximumTitleLength": 255,
|
||||
"minimumUsernameLength": 2,
|
||||
"maximumUsernameLength": 16,
|
||||
"minimumPasswordLength": 6,
|
||||
"minimumPasswordStrength": 1,
|
||||
"maximumSignatureLength": 255,
|
||||
"maximumAboutMeLength": 1000,
|
||||
"maximumUsersInChatRoom": 0,
|
||||
"maximumChatMessageLength": 1000,
|
||||
"maximumProfileImageSize": 256,
|
||||
"maximumCoverImageSize": 2048,
|
||||
"profileImageDimension": 200,
|
||||
"profile:convertProfileImageToPNG": 0,
|
||||
"profile:keepAllUserImages": 0,
|
||||
"requireEmailConfirmation": 0,
|
||||
"gdpr_enabled": 1,
|
||||
"allowProfileImageUploads": 1,
|
||||
"teaserPost": "last-reply",
|
||||
"allowPrivateGroups": 1,
|
||||
"unreadCutoff": 2,
|
||||
"bookmarkThreshold": 5,
|
||||
"topicsPerList": 20,
|
||||
"autoDetectLang": 1,
|
||||
"reputation:disabled": 0,
|
||||
"downvote:disabled": 0,
|
||||
"disableSignatures": 0,
|
||||
"downvotesPerDay": 10,
|
||||
"downvotesPerUserPerDay": 3,
|
||||
"min:rep:downvote": 0,
|
||||
"min:rep:flag": 0,
|
||||
"min:rep:profile-picture": 0,
|
||||
"min:rep:cover-picture": 0,
|
||||
"min:rep:website": 0,
|
||||
"min:rep:aboutme": 0,
|
||||
"min:rep:signature": 0,
|
||||
"flags:limitPerTarget": 0,
|
||||
"notificationType_upvote": "notification",
|
||||
"notificationType_new-topic": "notification",
|
||||
"notificationType_new-reply": "notification",
|
||||
"notificationType_post-edit": "notification",
|
||||
"notificationType_follow": "notification",
|
||||
"notificationType_new-chat": "notification",
|
||||
"notificationType_new-group-chat": "notification",
|
||||
"notificationType_group-invite": "notification",
|
||||
"notificationType_group-request-membership": "notification",
|
||||
"notificationType_mention": "notification",
|
||||
"notificationType_new-register": "notification",
|
||||
"notificationType_post-queue": "notification",
|
||||
"notificationType_new-post-flag": "notification",
|
||||
"notificationType_new-user-flag": "notification",
|
||||
"topicStaleDays": 60,
|
||||
"maxTopicsPerPage": 20,
|
||||
"maxPostsPerPage": 20,
|
||||
"topicsPerPage": 20,
|
||||
"postsPerPage": 20,
|
||||
"userSearchResultsPerPage": 50,
|
||||
"maximumGroupNameLength": 255,
|
||||
"maximumGroupTitleLength": 40,
|
||||
"preventTopicDeleteAfterReplies": 0,
|
||||
"feeds:disableSitemap": 0,
|
||||
"sitemapTopics": 500,
|
||||
"maintenanceMode": 0,
|
||||
"maintenanceModeStatus": 503,
|
||||
"votesArePublic": 0,
|
||||
"maximumInvites": 0,
|
||||
"username:disableEdit": 0,
|
||||
"email:disableEdit": 0,
|
||||
"email:smtpTransport:pool": false,
|
||||
"hideFullname": 0,
|
||||
"hideEmail": 0,
|
||||
"showFullnameAsDisplayName": 0,
|
||||
"allowGuestHandles": 0,
|
||||
"guestsIncrementTopicViews": 1,
|
||||
"allowGuestReplyNotifications": 1,
|
||||
"incrementTopicViewsInterval": 60,
|
||||
"recentMaxTopics": 200,
|
||||
"disableRecentCategoryFilter": 0,
|
||||
"maximumRelatedTopics": 0,
|
||||
"disableEmailSubscriptions": 0,
|
||||
"emailConfirmInterval": 10,
|
||||
"removeEmailNotificationImages": 0,
|
||||
"inviteExpiration": 7,
|
||||
"digestHour": 17,
|
||||
"passwordExpiryDays": 0,
|
||||
"hsts-maxage": 31536000,
|
||||
"hsts-subdomains": 0,
|
||||
"hsts-preload": 0,
|
||||
"hsts-enabled": 0,
|
||||
"eventLoopCheckEnabled": 1,
|
||||
"eventLoopLagThreshold": 100,
|
||||
"eventLoopInterval": 500,
|
||||
"onlineCutoff": 30,
|
||||
"timeagoCutoff": 30,
|
||||
"necroThreshold": 7,
|
||||
"categoryWatchState": "watching",
|
||||
"submitPluginUsage": 1,
|
||||
"showAverageApprovalTime": true,
|
||||
"autoApproveTime": 0,
|
||||
"maxUserSessions": 10,
|
||||
"useCompression": 0,
|
||||
"updateUrlWithPostIndex": 1
|
||||
}
|
||||
"notificationType_post-queue": "notification",
|
||||
"notificationType_new-post-flag": "notification",
|
||||
"notificationType_new-user-flag": "notification"
|
||||
}
|
||||
|
||||
@@ -15,7 +15,9 @@
|
||||
"iconClass": "fa-inbox",
|
||||
"textClass": "visible-xs-inline",
|
||||
"text": "[[global:header.unread]]",
|
||||
"groups": ["registered-users"]
|
||||
"properties": {
|
||||
"loggedIn": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"route": "/recent",
|
||||
@@ -64,9 +66,9 @@
|
||||
"iconClass": "fa-cogs",
|
||||
"textClass": "visible-xs-inline",
|
||||
"text": "[[global:header.admin]]",
|
||||
"groups": ["administrators"],
|
||||
"properties": {
|
||||
"targetBlank": false
|
||||
"targetBlank": false,
|
||||
"adminOnly": true
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -1,51 +1,51 @@
|
||||
'use strict';
|
||||
|
||||
const prompt = require('prompt');
|
||||
const winston = require('winston');
|
||||
var async = require('async');
|
||||
var prompt = require('prompt');
|
||||
var winston = require('winston');
|
||||
|
||||
const util = require('util');
|
||||
|
||||
const promptGet = util.promisify((schema, callback) => prompt.get(schema, callback));
|
||||
|
||||
const questions = {
|
||||
var questions = {
|
||||
redis: require('../src/database/redis').questions,
|
||||
mongo: require('../src/database/mongo').questions,
|
||||
postgres: require('../src/database/postgres').questions,
|
||||
};
|
||||
|
||||
module.exports = async function (config) {
|
||||
winston.info('\nNow configuring ' + config.database + ' database:');
|
||||
const databaseConfig = await getDatabaseConfig(config);
|
||||
return saveDatabaseConfig(config, databaseConfig);
|
||||
module.exports = function (config, callback) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
winston.info('\nNow configuring ' + config.database + ' database:');
|
||||
getDatabaseConfig(config, next);
|
||||
},
|
||||
function (databaseConfig, next) {
|
||||
saveDatabaseConfig(config, databaseConfig, next);
|
||||
},
|
||||
], callback);
|
||||
};
|
||||
|
||||
async function getDatabaseConfig(config) {
|
||||
function getDatabaseConfig(config, callback) {
|
||||
if (!config) {
|
||||
throw new Error('invalid config, aborted');
|
||||
return callback(new Error('aborted'));
|
||||
}
|
||||
|
||||
if (config.database === 'redis') {
|
||||
if (config['redis:host'] && config['redis:port']) {
|
||||
return config;
|
||||
callback(null, config);
|
||||
} else {
|
||||
prompt.get(questions.redis, callback);
|
||||
}
|
||||
return await promptGet(questions.redis);
|
||||
} else if (config.database === 'mongo') {
|
||||
if ((config['mongo:host'] && config['mongo:port']) || config['mongo:uri']) {
|
||||
return config;
|
||||
callback(null, config);
|
||||
} else {
|
||||
prompt.get(questions.mongo, callback);
|
||||
}
|
||||
return await promptGet(questions.mongo);
|
||||
} else if (config.database === 'postgres') {
|
||||
if (config['postgres:host'] && config['postgres:port']) {
|
||||
return config;
|
||||
}
|
||||
return await promptGet(questions.postgres);
|
||||
} else {
|
||||
return callback(new Error('unknown database : ' + config.database));
|
||||
}
|
||||
throw new Error('unknown database : ' + config.database);
|
||||
}
|
||||
|
||||
function saveDatabaseConfig(config, databaseConfig) {
|
||||
function saveDatabaseConfig(config, databaseConfig, callback) {
|
||||
if (!databaseConfig) {
|
||||
throw new Error('invalid config, aborted');
|
||||
return callback(new Error('aborted'));
|
||||
}
|
||||
|
||||
// Translate redis properties into redis object
|
||||
@@ -69,23 +69,14 @@ function saveDatabaseConfig(config, databaseConfig) {
|
||||
database: databaseConfig['mongo:database'],
|
||||
uri: databaseConfig['mongo:uri'],
|
||||
};
|
||||
} else if (config.database === 'postgres') {
|
||||
config.postgres = {
|
||||
host: databaseConfig['postgres:host'],
|
||||
port: databaseConfig['postgres:port'],
|
||||
username: databaseConfig['postgres:username'],
|
||||
password: databaseConfig['postgres:password'],
|
||||
database: databaseConfig['postgres:database'],
|
||||
ssl: databaseConfig['postgres:ssl'],
|
||||
};
|
||||
} else {
|
||||
throw new Error('unknown database : ' + config.database);
|
||||
return callback(new Error('unknown database : ' + config.database));
|
||||
}
|
||||
|
||||
const allQuestions = questions.redis.concat(questions.mongo).concat(questions.postgres);
|
||||
var allQuestions = questions.redis.concat(questions.mongo);
|
||||
for (var x = 0; x < allQuestions.length; x += 1) {
|
||||
delete config[allQuestions[x].name];
|
||||
}
|
||||
|
||||
return config;
|
||||
callback(null, config);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "nodebb",
|
||||
"license": "GPL-3.0",
|
||||
"description": "NodeBB Forum",
|
||||
"version": "1.15.3",
|
||||
"version": "1.9.3",
|
||||
"homepage": "http://www.nodebb.org",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -11,170 +11,122 @@
|
||||
"main": "app.js",
|
||||
"scripts": {
|
||||
"start": "node loader.js",
|
||||
"lint": "npx eslint --cache ./nodebb .",
|
||||
"test": "npx nyc --reporter=html --reporter=text-summary npx mocha",
|
||||
"coverage": "nyc report --reporter=text-lcov > ./coverage/lcov.info",
|
||||
"lint": "eslint --cache ./nodebb .",
|
||||
"pretest": "npm run lint",
|
||||
"test": "nyc --reporter=html --reporter=text-summary mocha",
|
||||
"coveralls": "nyc report --reporter=text-lcov | coveralls && rm -r coverage"
|
||||
},
|
||||
"nyc": {
|
||||
"exclude": [
|
||||
"src/upgrades/*",
|
||||
"test/*"
|
||||
]
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "npx lint-staged",
|
||||
"commit-msg": "npx commitlint -E HUSKY_GIT_PARAMS"
|
||||
}
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.js": [
|
||||
"eslint --fix",
|
||||
"git add"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@adactive/bootstrap-tagsinput": "^0.8.2",
|
||||
"ace-builds": "^1.4.9",
|
||||
"archiver": "^5.0.0",
|
||||
"async": "^3.2.0",
|
||||
"autoprefixer": "10.0.2",
|
||||
"ace-builds": "^1.2.9",
|
||||
"archiver": "^2.1.1",
|
||||
"async": "2.6.0",
|
||||
"autoprefixer": "7.2.4",
|
||||
"bcryptjs": "2.4.3",
|
||||
"benchpressjs": "2.2.2",
|
||||
"body-parser": "^1.19.0",
|
||||
"bootbox": "4.4.0",
|
||||
"bootstrap": "^3.4.1",
|
||||
"chart.js": "^2.9.3",
|
||||
"benchpressjs": "^1.2.1",
|
||||
"body-parser": "^1.18.2",
|
||||
"bootstrap": "^3.3.7",
|
||||
"chart.js": "^2.7.1",
|
||||
"cli-graph": "^3.2.2",
|
||||
"clipboard": "^2.0.6",
|
||||
"colors": "^1.4.0",
|
||||
"commander": "^6.0.0",
|
||||
"compare-versions": "3.6.0",
|
||||
"compression": "^1.7.4",
|
||||
"clipboard": "^2.0.1",
|
||||
"colors": "^1.1.2",
|
||||
"compression": "^1.7.1",
|
||||
"commander": "^2.12.2",
|
||||
"connect-ensure-login": "^0.1.1",
|
||||
"connect-flash": "^0.1.1",
|
||||
"connect-mongo": "3.2.0",
|
||||
"connect-multiparty": "^2.2.0",
|
||||
"connect-pg-simple": "^6.1.0",
|
||||
"connect-redis": "5.0.0",
|
||||
"cookie-parser": "^1.4.5",
|
||||
"cron": "^1.8.2",
|
||||
"cropperjs": "^1.5.6",
|
||||
"csurf": "^1.11.0",
|
||||
"connect-mongo": "2.0.1",
|
||||
"connect-multiparty": "^2.1.0",
|
||||
"connect-redis": "3.3.3",
|
||||
"cookie-parser": "^1.4.3",
|
||||
"cron": "^1.3.0",
|
||||
"cropperjs": "^1.2.2",
|
||||
"csurf": "^1.9.0",
|
||||
"daemon": "^1.1.0",
|
||||
"diff": "^5.0.0",
|
||||
"express": "^4.17.1",
|
||||
"express-session": "^1.17.0",
|
||||
"express-useragent": "^1.0.13",
|
||||
"graceful-fs": "^4.2.3",
|
||||
"helmet": "^4.0.0",
|
||||
"html-to-text": "^5.1.1",
|
||||
"ipaddr.js": "^2.0.0",
|
||||
"jquery": "3.5.1",
|
||||
"jquery-deserialize": "2.0.0-rc1",
|
||||
"jquery-form": "4.3.0",
|
||||
"jquery-serializeobject": "1.0.0",
|
||||
"jquery-ui": "1.12.1",
|
||||
"jsesc": "3.0.2",
|
||||
"json2csv": "5.0.5",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"less": "^3.11.1",
|
||||
"lodash": "^4.17.15",
|
||||
"logrotate-stream": "^0.2.6",
|
||||
"lru-cache": "6.0.0",
|
||||
"diff": "^3.4.0",
|
||||
"express": "^4.16.2",
|
||||
"express-session": "^1.15.6",
|
||||
"express-useragent": "^1.0.12",
|
||||
"graceful-fs": "^4.1.11",
|
||||
"helmet": "^3.11.0",
|
||||
"html-to-text": "^4.0.0",
|
||||
"ipaddr.js": "^1.5.4",
|
||||
"jimp": "0.2.28",
|
||||
"jquery": "^3.2.1",
|
||||
"jsesc": "2.5.1",
|
||||
"json-2-csv": "^2.1.2",
|
||||
"less": "^2.7.3",
|
||||
"lodash": "^4.17.10",
|
||||
"logrotate-stream": "^0.2.5",
|
||||
"lru-cache": "4.1.3",
|
||||
"material-design-lite": "^1.3.0",
|
||||
"mime": "^2.4.4",
|
||||
"mkdirp": "^1.0.4",
|
||||
"mongodb": "3.6.3",
|
||||
"morgan": "^1.10.0",
|
||||
"mousetrap": "^1.6.5",
|
||||
"@nodebb/bootswatch": "3.4.2",
|
||||
"@nodebb/mubsub": "1.7.1",
|
||||
"@nodebb/socket.io-adapter-mongo": "3.1.1",
|
||||
"nconf": "^0.11.0",
|
||||
"nodebb-plugin-composer-default": "6.4.7",
|
||||
"nodebb-plugin-dbsearch": "4.1.2",
|
||||
"nodebb-plugin-emoji": "^3.3.0",
|
||||
"mime": "^2.2.0",
|
||||
"mkdirp": "^0.5.1",
|
||||
"mongodb": "3.0.8",
|
||||
"morgan": "^1.9.0",
|
||||
"mousetrap": "^1.6.1",
|
||||
"mubsub-nbb": "^1.5.0",
|
||||
"nconf": "^0.10.0",
|
||||
"nodebb-plugin-composer-default": "6.0.23",
|
||||
"nodebb-plugin-dbsearch": "2.0.16",
|
||||
"nodebb-plugin-emoji": "^2.2.2",
|
||||
"nodebb-plugin-emoji-android": "2.0.0",
|
||||
"nodebb-plugin-markdown": "8.12.1",
|
||||
"nodebb-plugin-mentions": "2.13.5",
|
||||
"nodebb-plugin-markdown": "8.4.3",
|
||||
"nodebb-plugin-mentions": "2.2.6",
|
||||
"nodebb-plugin-soundpack-default": "1.0.0",
|
||||
"nodebb-plugin-spam-be-gone": "0.7.6",
|
||||
"nodebb-rewards-essentials": "0.1.4",
|
||||
"nodebb-theme-lavender": "5.0.14",
|
||||
"nodebb-theme-persona": "10.2.88",
|
||||
"nodebb-theme-slick": "1.3.3",
|
||||
"nodebb-theme-vanilla": "11.3.4",
|
||||
"nodebb-widget-essentials": "4.1.2",
|
||||
"nodemailer": "^6.4.6",
|
||||
"nprogress": "0.2.0",
|
||||
"passport": "^0.4.1",
|
||||
"passport-http-bearer": "^1.0.1",
|
||||
"nodebb-plugin-spam-be-gone": "0.5.3",
|
||||
"nodebb-rewards-essentials": "0.0.11",
|
||||
"nodebb-theme-lavender": "5.0.4",
|
||||
"nodebb-theme-persona": "9.0.5",
|
||||
"nodebb-theme-slick": "1.2.1",
|
||||
"nodebb-theme-vanilla": "10.0.5",
|
||||
"nodebb-widget-essentials": "4.0.4",
|
||||
"nodemailer": "4.6.4",
|
||||
"passport": "^0.4.0",
|
||||
"passport-local": "1.0.0",
|
||||
"pg": "^8.0.2",
|
||||
"pg-cursor": "^2.1.9",
|
||||
"postcss": "8.1.10",
|
||||
"postcss": "6.0.22",
|
||||
"postcss-clean": "1.1.0",
|
||||
"promise-polyfill": "^8.1.3",
|
||||
"promise-polyfill": "^7.0.0",
|
||||
"prompt": "^1.0.0",
|
||||
"redis": "3.0.2",
|
||||
"request": "2.88.2",
|
||||
"request-promise-native": "^1.0.8",
|
||||
"requirejs": "2.3.6",
|
||||
"rimraf": "3.0.2",
|
||||
"redis": "2.8.0",
|
||||
"request": "2.85.0",
|
||||
"rimraf": "2.6.2",
|
||||
"rss": "^1.2.2",
|
||||
"sanitize-html": "^2.0.0",
|
||||
"semver": "^7.2.1",
|
||||
"serve-favicon": "^2.5.0",
|
||||
"sharp": "0.26.3",
|
||||
"sitemap": "^6.1.0",
|
||||
"slideout": "1.0.1",
|
||||
"socket.io": "2.3.0",
|
||||
"socket.io-adapter-cluster": "^1.0.1",
|
||||
"socket.io-adapter-postgres": "^1.2.1",
|
||||
"socket.io-client": "2.3.1",
|
||||
"socket.io-redis": "5.4.0",
|
||||
"sanitize-html": "^1.16.3",
|
||||
"semver": "^5.4.1",
|
||||
"serve-favicon": "^2.4.5",
|
||||
"sitemap": "^1.13.0",
|
||||
"socket.io": "2.1.0",
|
||||
"socket.io-adapter-mongo": "^2.0.1",
|
||||
"socket.io-client": "2.1.0",
|
||||
"socket.io-redis": "5.2.0",
|
||||
"socketio-wildcard": "2.0.0",
|
||||
"sortablejs": "1.10.2",
|
||||
"spdx-license-list": "^6.1.0",
|
||||
"spider-detector": "2.0.0",
|
||||
"textcomplete": "^0.17.1",
|
||||
"textcomplete.contenteditable": "^0.1.1",
|
||||
"timeago": "^1.6.7",
|
||||
"tinycon": "0.6.8",
|
||||
"spdx-license-list": "^4.0.0",
|
||||
"spider-detector": "1.0.18",
|
||||
"toobusy-js": "^0.5.1",
|
||||
"uglify-es": "^3.3.9",
|
||||
"validator": "13.1.17",
|
||||
"visibilityjs": "2.0.2",
|
||||
"winston": "3.3.3",
|
||||
"validator": "10.2.0",
|
||||
"winston": "^2.4.0",
|
||||
"xml": "^1.0.1",
|
||||
"xregexp": "^4.3.0",
|
||||
"xregexp": "^4.1.1",
|
||||
"zxcvbn": "^4.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@apidevtools/swagger-parser": "10.0.2",
|
||||
"@commitlint/cli": "11.0.0",
|
||||
"@commitlint/config-angular": "11.0.0",
|
||||
"coveralls": "3.1.0",
|
||||
"eslint": "7.14.0",
|
||||
"eslint-config-airbnb-base": "14.2.1",
|
||||
"eslint-plugin-import": "2.22.1",
|
||||
"grunt": "1.3.0",
|
||||
"grunt-contrib-watch": "1.1.0",
|
||||
"husky": "4.3.0",
|
||||
"jsdom": "16.4.0",
|
||||
"lint-staged": "10.5.2",
|
||||
"mocha": "8.2.1",
|
||||
"mocha-lcov-reporter": "1.3.0",
|
||||
"nyc": "15.1.0",
|
||||
"smtp-server": "3.8.0"
|
||||
"coveralls": "^3.0.0",
|
||||
"eslint": "^4.14.0",
|
||||
"eslint-config-airbnb-base": "^12.1.0",
|
||||
"eslint-plugin-import": "^2.8.0",
|
||||
"grunt": "^1.0.1",
|
||||
"grunt-contrib-watch": "^1.0.0",
|
||||
"jsdom": "^11.5.1",
|
||||
"mocha": "^4.1.0",
|
||||
"mocha-lcov-reporter": "^1.3.0",
|
||||
"nyc": "^11.4.1",
|
||||
"smtp-server": "^3.4.1"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/NodeBB/NodeBB/issues"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
"node": ">=6"
|
||||
},
|
||||
"maintainers": [
|
||||
{
|
||||
|
||||
323
install/web.js
@@ -1,99 +1,72 @@
|
||||
'use strict';
|
||||
|
||||
const winston = require('winston');
|
||||
const express = require('express');
|
||||
const bodyParser = require('body-parser');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const childProcess = require('child_process');
|
||||
const less = require('less');
|
||||
const util = require('util');
|
||||
const lessRenderAsync = util.promisify(
|
||||
(style, opts, cb) => less.render(String(style), opts, cb)
|
||||
);
|
||||
const uglify = require('uglify-es');
|
||||
const nconf = require('nconf');
|
||||
var winston = require('winston');
|
||||
var express = require('express');
|
||||
var bodyParser = require('body-parser');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var childProcess = require('child_process');
|
||||
var less = require('less');
|
||||
var async = require('async');
|
||||
var uglify = require('uglify-es');
|
||||
var nconf = require('nconf');
|
||||
var Benchpress = require('benchpressjs');
|
||||
|
||||
const Benchpress = require('benchpressjs');
|
||||
const mkdirp = require('mkdirp');
|
||||
const { paths } = require('../src/constants');
|
||||
var app = express();
|
||||
var server;
|
||||
|
||||
const app = express();
|
||||
let server;
|
||||
|
||||
const formats = [
|
||||
winston.format.colorize(),
|
||||
];
|
||||
|
||||
const timestampFormat = winston.format((info) => {
|
||||
var dateString = new Date().toISOString() + ' [' + global.process.pid + ']';
|
||||
info.level = dateString + ' - ' + info.level;
|
||||
return info;
|
||||
});
|
||||
formats.push(timestampFormat());
|
||||
formats.push(winston.format.splat());
|
||||
formats.push(winston.format.simple());
|
||||
|
||||
winston.configure({
|
||||
winston.add(winston.transports.File, {
|
||||
filename: 'logs/webinstall.log',
|
||||
colorize: true,
|
||||
timestamp: function () {
|
||||
var date = new Date();
|
||||
return date.getDate() + '/' + (date.getMonth() + 1) + ' ' + date.toTimeString().substr(0, 5) + ' [' + global.process.pid + ']';
|
||||
},
|
||||
level: 'verbose',
|
||||
format: winston.format.combine.apply(null, formats),
|
||||
transports: [
|
||||
new winston.transports.Console({
|
||||
handleExceptions: true,
|
||||
}),
|
||||
new winston.transports.File({
|
||||
filename: 'logs/webinstall.log',
|
||||
handleExceptions: true,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const web = module.exports;
|
||||
var web = module.exports;
|
||||
|
||||
const scripts = [
|
||||
var scripts = [
|
||||
'node_modules/jquery/dist/jquery.js',
|
||||
'node_modules/xregexp/xregexp-all.js',
|
||||
'public/src/modules/slugify.js',
|
||||
'public/vendor/xregexp/xregexp.js',
|
||||
'public/vendor/xregexp/unicode/unicode-base.js',
|
||||
'public/src/utils.js',
|
||||
'public/src/installer/install.js',
|
||||
'node_modules/zxcvbn/dist/zxcvbn.js',
|
||||
];
|
||||
|
||||
let installing = false;
|
||||
let success = false;
|
||||
let error = false;
|
||||
let launchUrl;
|
||||
var installing = false;
|
||||
var success = false;
|
||||
var error = false;
|
||||
|
||||
const viewsDir = path.join(paths.baseDir, 'build/public/templates');
|
||||
|
||||
web.install = async function (port) {
|
||||
web.install = function (port) {
|
||||
port = port || 4567;
|
||||
winston.info('Launching web installer on port ' + port);
|
||||
winston.info('Launching web installer on port', port);
|
||||
|
||||
app.use(express.static('public', {}));
|
||||
app.engine('tpl', function (filepath, options, callback) {
|
||||
filepath = filepath.replace(/\.tpl$/, '.js');
|
||||
|
||||
Benchpress.__express(filepath, options, callback);
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
fs.readFile(filepath, 'utf-8', next);
|
||||
},
|
||||
function (buffer, next) {
|
||||
Benchpress.compileParse(buffer.toString(), options, next);
|
||||
},
|
||||
], callback);
|
||||
});
|
||||
app.set('view engine', 'tpl');
|
||||
app.set('views', viewsDir);
|
||||
app.set('views', path.join(__dirname, '../src/views'));
|
||||
app.use(bodyParser.urlencoded({
|
||||
extended: true,
|
||||
}));
|
||||
try {
|
||||
await Promise.all([
|
||||
compileTemplate(),
|
||||
compileLess(),
|
||||
compileJS(),
|
||||
copyCSS(),
|
||||
loadDefaults(),
|
||||
]);
|
||||
|
||||
async.parallel([compileLess, compileJS, copyCSS, loadDefaults], function (err) {
|
||||
if (err) {
|
||||
winston.error(err);
|
||||
}
|
||||
setupRoutes();
|
||||
launchExpress(port);
|
||||
} catch (err) {
|
||||
winston.error(err.stack);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -116,7 +89,7 @@ function ping(req, res) {
|
||||
}
|
||||
|
||||
function welcome(req, res) {
|
||||
var dbs = ['redis', 'mongo', 'postgres'];
|
||||
var dbs = ['redis', 'mongo'];
|
||||
var databases = dbs.map(function (databaseName) {
|
||||
var questions = require('../src/database/' + databaseName).questions.filter(function (question) {
|
||||
return question && !question.hideOnWebInstall;
|
||||
@@ -132,7 +105,6 @@ function welcome(req, res) {
|
||||
|
||||
res.render('install/index', {
|
||||
url: nconf.get('url') || (req.protocol + '://' + req.get('host')),
|
||||
launchUrl: launchUrl,
|
||||
skipGeneralSetup: !!nconf.get('url'),
|
||||
databases: databases,
|
||||
skipDatabaseSetup: !!nconf.get('database'),
|
||||
@@ -140,7 +112,6 @@ function welcome(req, res) {
|
||||
success: success,
|
||||
values: req.body,
|
||||
minimumPasswordLength: defaults.minimumPasswordLength,
|
||||
minimumPasswordStrength: defaults.minimumPasswordStrength,
|
||||
installing: installing,
|
||||
});
|
||||
}
|
||||
@@ -173,7 +144,6 @@ function install(req, res) {
|
||||
|
||||
winston.info('Starting setup process');
|
||||
winston.info(setupEnvVars);
|
||||
launchUrl = setupEnvVars.url;
|
||||
|
||||
var child = require('child_process').fork('app', ['--setup'], {
|
||||
env: setupEnvVars,
|
||||
@@ -188,116 +158,117 @@ function install(req, res) {
|
||||
});
|
||||
}
|
||||
|
||||
async function launch(req, res) {
|
||||
try {
|
||||
res.json({});
|
||||
server.close();
|
||||
req.setTimeout(0);
|
||||
var child;
|
||||
function launch(req, res) {
|
||||
res.json({});
|
||||
server.close();
|
||||
|
||||
if (!nconf.get('launchCmd')) {
|
||||
child = childProcess.spawn('node', ['loader.js'], {
|
||||
detached: true,
|
||||
stdio: ['ignore', 'ignore', 'ignore'],
|
||||
});
|
||||
var child;
|
||||
|
||||
console.log('\nStarting NodeBB');
|
||||
console.log(' "./nodebb stop" to stop the NodeBB server');
|
||||
console.log(' "./nodebb log" to view server output');
|
||||
console.log(' "./nodebb restart" to restart NodeBB');
|
||||
} else {
|
||||
// Use launchCmd instead, if specified
|
||||
child = childProcess.exec(nconf.get('launchCmd'), {
|
||||
detached: true,
|
||||
stdio: ['ignore', 'ignore', 'ignore'],
|
||||
});
|
||||
if (!nconf.get('launchCmd')) {
|
||||
child = childProcess.spawn('node', ['loader.js'], {
|
||||
detached: true,
|
||||
stdio: ['ignore', 'ignore', 'ignore'],
|
||||
});
|
||||
|
||||
console.log('\nStarting NodeBB');
|
||||
console.log(' "./nodebb stop" to stop the NodeBB server');
|
||||
console.log(' "./nodebb log" to view server output');
|
||||
console.log(' "./nodebb restart" to restart NodeBB');
|
||||
} else {
|
||||
// Use launchCmd instead, if specified
|
||||
child = childProcess.exec(nconf.get('launchCmd'), {
|
||||
detached: true,
|
||||
stdio: ['ignore', 'ignore', 'ignore'],
|
||||
});
|
||||
}
|
||||
|
||||
var filesToDelete = [
|
||||
'installer.css',
|
||||
'installer.min.js',
|
||||
'bootstrap.min.css',
|
||||
];
|
||||
|
||||
async.each(filesToDelete, function (filename, next) {
|
||||
fs.unlink(path.join(__dirname, '../public', filename), next);
|
||||
}, function (err) {
|
||||
if (err) {
|
||||
winston.warn('Unable to remove installer files');
|
||||
}
|
||||
|
||||
const filesToDelete = [
|
||||
'installer.css',
|
||||
'installer.min.js',
|
||||
'bootstrap.min.css',
|
||||
];
|
||||
await Promise.all(
|
||||
filesToDelete.map(
|
||||
filename => fs.promises.unlink(path.join(__dirname, '../public', filename))
|
||||
)
|
||||
);
|
||||
child.unref();
|
||||
process.exit(0);
|
||||
} catch (err) {
|
||||
winston.error(err.stack);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
// this is necessary because otherwise the compiled templates won't be available on a clean install
|
||||
async function compileTemplate() {
|
||||
const sourceFile = path.join(__dirname, '../src/views/install/index.tpl');
|
||||
const destTpl = path.join(viewsDir, 'install/index.tpl');
|
||||
const destJs = path.join(viewsDir, 'install/index.js');
|
||||
|
||||
const source = await fs.promises.readFile(sourceFile, 'utf8');
|
||||
|
||||
const [compiled] = await Promise.all([
|
||||
Benchpress.precompile(source, { filename: 'install/index.tpl' }),
|
||||
mkdirp(path.dirname(destJs)),
|
||||
]);
|
||||
|
||||
await Promise.all([
|
||||
fs.promises.writeFile(destJs, compiled),
|
||||
fs.promises.writeFile(destTpl, source),
|
||||
]);
|
||||
}
|
||||
|
||||
async function compileLess() {
|
||||
try {
|
||||
const installSrc = path.join(__dirname, '../public/less/install.less');
|
||||
const style = await fs.promises.readFile(installSrc);
|
||||
const css = await lessRenderAsync(style, { filename: path.resolve(installSrc) });
|
||||
await fs.promises.writeFile(path.join(__dirname, '../public/installer.css'), css.css);
|
||||
} catch (err) {
|
||||
winston.error('Unable to compile LESS: \n' + err.stack);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async function compileJS() {
|
||||
let code = '';
|
||||
|
||||
for (const srcPath of scripts) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const buffer = await fs.promises.readFile(path.join(__dirname, '..', srcPath));
|
||||
code += buffer.toString();
|
||||
}
|
||||
const minified = uglify.minify(code, {
|
||||
compress: false,
|
||||
});
|
||||
if (!minified.code) {
|
||||
throw new Error('[[error:failed-to-minify]]');
|
||||
}
|
||||
await fs.promises.writeFile(path.join(__dirname, '../public/installer.min.js'), minified.code);
|
||||
}
|
||||
|
||||
async function copyCSS() {
|
||||
const src = await fs.promises.readFile(
|
||||
path.join(__dirname, '../node_modules/bootstrap/dist/css/bootstrap.min.css'), 'utf8'
|
||||
);
|
||||
await fs.promises.writeFile(path.join(__dirname, '../public/bootstrap.min.css'), src);
|
||||
}
|
||||
|
||||
async function loadDefaults() {
|
||||
const setupDefaultsPath = path.join(__dirname, '../setup.json');
|
||||
try {
|
||||
await fs.promises.access(setupDefaultsPath, fs.constants.F_OK | fs.constants.R_OK);
|
||||
} catch (err) {
|
||||
// setup.json not found or inaccessible, proceed with no defaults
|
||||
if (err.code !== 'ENOENT') {
|
||||
throw err;
|
||||
function compileLess(callback) {
|
||||
fs.readFile(path.join(__dirname, '../public/less/install.less'), function (err, style) {
|
||||
if (err) {
|
||||
return winston.error('Unable to read LESS install file: ', err);
|
||||
}
|
||||
}
|
||||
winston.info('[installer] Found setup.json, populating default values');
|
||||
nconf.file({
|
||||
file: setupDefaultsPath,
|
||||
|
||||
less.render(style.toString(), function (err, css) {
|
||||
if (err) {
|
||||
return winston.error('Unable to compile LESS: ', err);
|
||||
}
|
||||
|
||||
fs.writeFile(path.join(__dirname, '../public/installer.css'), css.css, callback);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function compileJS(callback) {
|
||||
var code = '';
|
||||
async.eachSeries(scripts, function (srcPath, next) {
|
||||
fs.readFile(path.join(__dirname, '..', srcPath), function (err, buffer) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
code += buffer.toString();
|
||||
next();
|
||||
});
|
||||
}, function (err) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
try {
|
||||
var minified = uglify.minify(code, {
|
||||
compress: false,
|
||||
});
|
||||
if (!minified.code) {
|
||||
return callback(new Error('[[error:failed-to-minify]]'));
|
||||
}
|
||||
fs.writeFile(path.join(__dirname, '../public/installer.min.js'), minified.code, callback);
|
||||
} catch (e) {
|
||||
callback(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function copyCSS(next) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
fs.readFile(path.join(__dirname, '../node_modules/bootstrap/dist/css/bootstrap.min.css'), 'utf8', next);
|
||||
},
|
||||
function (src, next) {
|
||||
fs.writeFile(path.join(__dirname, '../public/bootstrap.min.css'), src, next);
|
||||
},
|
||||
], next);
|
||||
}
|
||||
|
||||
function loadDefaults(next) {
|
||||
var setupDefaultsPath = path.join(__dirname, '../setup.json');
|
||||
fs.access(setupDefaultsPath, fs.constants.F_OK | fs.constants.R_OK, function (err) {
|
||||
if (err) {
|
||||
// setup.json not found or inaccessible, proceed with no defaults
|
||||
return setImmediate(next);
|
||||
}
|
||||
|
||||
winston.info('[installer] Found setup.json, populating default values');
|
||||
nconf.file({
|
||||
file: setupDefaultsPath,
|
||||
});
|
||||
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
123
loader.js
@@ -1,13 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
var nconf = require('nconf');
|
||||
var nconf = require('nconf');
|
||||
var fs = require('fs');
|
||||
var url = require('url');
|
||||
var path = require('path');
|
||||
var fork = require('child_process').fork;
|
||||
var async = require('async');
|
||||
var logrotate = require('logrotate-stream');
|
||||
var mkdirp = require('mkdirp');
|
||||
|
||||
var file = require('./src/file');
|
||||
var pkg = require('./package.json');
|
||||
@@ -18,15 +17,8 @@ nconf.argv().env().file({
|
||||
file: pathToConfig,
|
||||
});
|
||||
|
||||
var pidFilePath = path.join(__dirname, 'pidfile');
|
||||
|
||||
var outputLogFilePath = path.join(__dirname, nconf.get('logFile') || 'logs/output.log');
|
||||
|
||||
var logDir = path.dirname(outputLogFilePath);
|
||||
if (!fs.existsSync(logDir)) {
|
||||
mkdirp.sync(path.dirname(outputLogFilePath));
|
||||
}
|
||||
|
||||
var pidFilePath = path.join(__dirname, 'pidfile');
|
||||
var outputLogFilePath = path.join(__dirname, 'logs/output.log');
|
||||
var output = logrotate({ file: outputLogFilePath, size: '1m', keep: 3, compress: true });
|
||||
var silent = nconf.get('silent') === 'false' ? false : nconf.get('silent') !== false;
|
||||
var numProcs;
|
||||
@@ -45,6 +37,7 @@ Loader.init = function (callback) {
|
||||
}
|
||||
|
||||
process.on('SIGHUP', Loader.restart);
|
||||
process.on('SIGUSR2', Loader.reload);
|
||||
process.on('SIGTERM', Loader.stop);
|
||||
callback();
|
||||
};
|
||||
@@ -87,22 +80,14 @@ Loader.addWorkerEvents = function (worker) {
|
||||
worker.on('message', function (message) {
|
||||
if (message && typeof message === 'object' && message.action) {
|
||||
switch (message.action) {
|
||||
case 'restart':
|
||||
console.log('[cluster] Restarting...');
|
||||
Loader.restart();
|
||||
break;
|
||||
case 'pubsub':
|
||||
workers.forEach(function (w) {
|
||||
w.send(message);
|
||||
});
|
||||
break;
|
||||
case 'socket.io':
|
||||
workers.forEach(function (w) {
|
||||
if (w !== worker) {
|
||||
w.send(message);
|
||||
}
|
||||
});
|
||||
break;
|
||||
case 'restart':
|
||||
console.log('[cluster] Restarting...');
|
||||
Loader.restart();
|
||||
break;
|
||||
case 'reload':
|
||||
console.log('[cluster] Reloading...');
|
||||
Loader.reload();
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -191,6 +176,14 @@ Loader.restart = function () {
|
||||
});
|
||||
};
|
||||
|
||||
Loader.reload = function () {
|
||||
workers.forEach(function (worker) {
|
||||
worker.send({
|
||||
action: 'reload',
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Loader.stop = function () {
|
||||
killWorkers();
|
||||
|
||||
@@ -207,41 +200,53 @@ function killWorkers() {
|
||||
});
|
||||
}
|
||||
|
||||
fs.open(pathToConfig, 'r', function (err) {
|
||||
if (err) {
|
||||
// No config detected, kickstart web installer
|
||||
fork('app');
|
||||
return;
|
||||
}
|
||||
|
||||
if (nconf.get('daemon') !== 'false' && nconf.get('daemon') !== false) {
|
||||
if (file.existsSync(pidFilePath)) {
|
||||
Loader.notifyWorkers = function (msg, worker_pid) {
|
||||
worker_pid = parseInt(worker_pid, 10);
|
||||
workers.forEach(function (worker) {
|
||||
if (parseInt(worker.pid, 10) !== worker_pid) {
|
||||
try {
|
||||
var pid = fs.readFileSync(pidFilePath, { encoding: 'utf-8' });
|
||||
process.kill(pid, 0);
|
||||
process.exit();
|
||||
worker.send(msg);
|
||||
} catch (e) {
|
||||
fs.unlinkSync(pidFilePath);
|
||||
console.log('[cluster/notifyWorkers] Failed to reach pid ' + worker_pid);
|
||||
}
|
||||
}
|
||||
|
||||
require('daemon')({
|
||||
stdout: process.stdout,
|
||||
stderr: process.stderr,
|
||||
cwd: process.cwd(),
|
||||
});
|
||||
|
||||
fs.writeFileSync(pidFilePath, String(process.pid));
|
||||
}
|
||||
|
||||
async.series([
|
||||
Loader.init,
|
||||
Loader.displayStartupMessages,
|
||||
Loader.start,
|
||||
], function (err) {
|
||||
if (err) {
|
||||
console.error('[loader] Error during startup');
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
fs.open(pathToConfig, 'r', function (err) {
|
||||
if (!err) {
|
||||
if (nconf.get('daemon') !== 'false' && nconf.get('daemon') !== false) {
|
||||
if (file.existsSync(pidFilePath)) {
|
||||
try {
|
||||
var pid = fs.readFileSync(pidFilePath, { encoding: 'utf-8' });
|
||||
process.kill(pid, 0);
|
||||
process.exit();
|
||||
} catch (e) {
|
||||
fs.unlinkSync(pidFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
require('daemon')({
|
||||
stdout: process.stdout,
|
||||
stderr: process.stderr,
|
||||
cwd: process.cwd(),
|
||||
});
|
||||
|
||||
fs.writeFileSync(pidFilePath, process.pid);
|
||||
}
|
||||
|
||||
async.series([
|
||||
Loader.init,
|
||||
Loader.displayStartupMessages,
|
||||
Loader.start,
|
||||
], function (err) {
|
||||
if (err) {
|
||||
console.error('[loader] Error during startup');
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// No config detected, kickstart web installer
|
||||
fork('app');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1 +1 @@
|
||||
@echo off && cd %~dp0 && node ./nodebb %*
|
||||
@echo off && cd %~dp0 && node ./src/cli %*
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"socket": true,
|
||||
"ajaxify": true,
|
||||
"config": true,
|
||||
"RELATIVE_PATH": true,
|
||||
"utils": true,
|
||||
"overrides": true,
|
||||
"componentHandler": true,
|
||||
@@ -17,24 +18,24 @@
|
||||
"jquery": true,
|
||||
"amd": true,
|
||||
"browser": true,
|
||||
"es6": true
|
||||
"es6": false
|
||||
},
|
||||
"rules": {
|
||||
"block-scoped-var": "off",
|
||||
"no-dupe-class-members": "off",
|
||||
"no-var": "off",
|
||||
"object-shorthand": "off",
|
||||
"prefer-arrow-callback": "off",
|
||||
"prefer-spread": "off",
|
||||
"prefer-object-spread": "off",
|
||||
"prefer-reflect": "off",
|
||||
"prefer-template": "off"
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 6,
|
||||
"ecmaVersion": 5,
|
||||
"ecmaFeatures": {
|
||||
"arrowFunctions": false,
|
||||
"classes": false,
|
||||
"defaultParams": false,
|
||||
"destructuring": false,
|
||||
"experimentalObjectRestSpread": false,
|
||||
"blockBindings": false,
|
||||
"forOf": false,
|
||||
@@ -48,7 +49,9 @@
|
||||
"objectLiteralShorthandProperties": false,
|
||||
"impliedStrict": false,
|
||||
"restParams": false,
|
||||
"superInFunctions": false
|
||||
"spread": false,
|
||||
"superInFunctions": false,
|
||||
"templateStrings": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@
|
||||
"socket": true,
|
||||
"ajaxify": true,
|
||||
"config": true,
|
||||
"RELATIVE_PATH": true,
|
||||
"utils": true,
|
||||
"overrides": true,
|
||||
"componentHandler": true,
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Excessive Load Warning</title>
|
||||
<link href='https://fonts.googleapis.com/css?family=Ubuntu:400,500,700' rel='stylesheet' type='text/css'>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style type="text/css">
|
||||
body {
|
||||
background: #00A9EA;
|
||||
color: white;
|
||||
/* see public/less/admin/vars.less for documentation on system font family */
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", Helvetica, Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
font-family: 'Ubuntu', sans-serif;
|
||||
text-align: center;
|
||||
-webkit-transform-style: preserve-3d;
|
||||
-moz-transform-style: preserve-3d;
|
||||
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
BIN
public/images/emails/banneduser.png
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
BIN
public/images/emails/digestheader.jpg
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
public/images/emails/emailconfirm.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
public/images/emails/invitation.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
public/images/emails/newtopic.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
public/images/emails/nodebb.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
public/images/emails/notification.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
public/images/emails/password.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
public/images/emails/triangularbackground.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
public/images/emails/unreadpost.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 5.9 KiB |
@@ -1,16 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="321" height="75" viewBox="0 0 321 75">
|
||||
<defs>
|
||||
<radialGradient id="nodebb-a" cx="65.599%" cy="0%" r="169.036%" fx="65.599%" fy="0%" gradientTransform="matrix(-.5183 .5916 -.38031 -.80624 .996 -.388)">
|
||||
<stop offset="0%" stop-color="#2A6CBE"/>
|
||||
<stop offset="38.688%" stop-color="#2062BC"/>
|
||||
<stop offset="49.182%" stop-color="#1F5FBC"/>
|
||||
<stop offset="66.583%" stop-color="#1C5ABD"/>
|
||||
<stop offset="85.354%" stop-color="#1956BC"/>
|
||||
<stop offset="100%" stop-color="#1851BE"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
<g fill="none" transform="translate(1 1)">
|
||||
<path fill="#000306" stroke="#000306" stroke-width=".275" d="M195.810017,20 C210.691849,20 221.256813,28.7272272 221.484017,45.845679 C221.490864,46.3615394 221.461776,47.1194739 221.396754,48.1194827 C221.328352,49.1714504 220.455158,49.9897119 219.400969,49.9897119 L179.110557,49.9897119 C179.110557,56.6336943 182.723978,64.2757202 196.378026,64.2757202 C204.088827,64.2757202 211.625406,61.928339 218.987763,57.2335766 L219.553745,56.8677968 C220.246813,56.4139645 221.17656,56.6079034 221.630392,57.3009716 C221.757029,57.4943637 221.837254,57.7144077 221.864806,57.943925 C222.131013,60.1615294 222.004084,61.5832211 221.484017,62.2089999 C220.452051,63.4507301 210.901843,73 196.378026,73 C180.58738,73 169,61.8765432 169,46.3909465 C169,31.3415638 180.019372,20 195.810017,20 Z M162.92733,0 C164.031899,-4.2495073e-16 164.92733,0.8954305 164.92733,2 L164.928075,44.1266361 C164.975781,44.8118124 165,45.5058194 165,46.2079256 C165,61.5334332 153.460581,73 138,73 C122.427386,73 111,61.5334332 111,46.2079256 C111,30.882418 122.427386,19.4158511 138,19.4158511 C144.981278,19.4158511 151.163029,21.7538895 155.843826,25.7295173 L155.843557,4.54188628 C155.843557,2.03347175 157.877029,4.60788274e-16 160.385444,0 L162.92733,0 Z M81,20 C96.4605809,20 108,31.3415638 108,46.5 C108,61.6584362 96.4605809,73 81,73 C65.4273859,73 54,61.6584362 54,46.5 C54,31.3415638 65.4273859,20 81,20 Z M27.324263,20 C41.6099773,20 50,29.1649049 50,41.7801268 L50,67.5 C50,69.4329966 48.4329966,71 46.5,71 L42.2494331,71 C41.1448636,71 40.2494331,70.1045695 40.2494331,69 L40.2494331,42.5348837 C40.2494331,34.3403805 35.8276644,28.6257928 26.3038549,28.6257928 C10.0816327,28.6257928 9.75718821,35.7795705 9.75069932,44.6831843 L9.75056689,71 L2,71 C0.8954305,71 1.3527075e-16,70.1045695 0,69 L0,35.2932975 L0,35.2932975 C0,24.3935718 19.1609977,20 27.324263,20 Z M81,28.7242798 C70.8049793,28.7242798 63.6348548,36.4670782 63.6348548,46.5 C63.6348548,56.3148148 70.8049793,64.2757202 81,64.2757202 C91.1950207,64.2757202 98.3651452,56.3148148 98.3651452,46.5 C98.3651452,36.4670782 91.1950207,28.7242798 81,28.7242798 Z M138,28.2362872 C127.804979,28.2362872 120.634855,36.0644241 120.634855,46.2079256 C120.634855,56.1309161 127.804979,64.179564 138,64.179564 C148.195021,64.179564 155.365145,56.1309161 155.365145,46.2079256 C155.365145,36.0644241 148.195021,28.2362872 138,28.2362872 Z M195.810017,28.7242798 C186.721876,28.7242798 180.473779,33.9588477 179.224159,41.8106996 L211.259858,41.8106996 C210.010238,33.9588477 204.443752,28.7242798 195.810017,28.7242798 Z"/>
|
||||
<path fill="url(#nodebb-a)" d="M277,19 L276.999615,57.7303365 C276.99251,64.180962 276.88949,68.6242617 276.88949,73 L276.88949,73 L255.649055,73 C242.962262,73 236,67.06 236,57.7257143 C236,51.4771429 239.558488,46.9257143 245.1283,45.0742857 C240.564149,43.1457143 237.779243,38.8257143 237.779243,33.5028571 C237.779243,24.8628571 244.664149,19 256.732074,19 L256.732074,19 L277,19 Z M299.267926,19 C311.335851,19 318.220757,24.8628571 318.220757,33.5028571 C318.220757,38.8257143 315.435851,43.1457143 310.8717,45.0742857 C316.441512,46.9257143 320,51.4771429 320,57.7257143 C320,67.06 313.037738,73 300.350945,73 L279.11051,73 L279.11051,73 C279.11051,68.6242617 279.00749,64.180962 279.000385,57.7303365 L279,19 L299.267926,19 Z M266.092452,49.8571429 L256.499999,49.8571429 C250.156602,49.8571429 246.984904,52.2485714 246.984904,56.9542857 C246.984904,61.8914286 249.924527,64.36 255.726414,64.36 L255.726414,64.36 L260.754716,64.36 C264.777358,64.36 266.092452,62.4314286 266.092452,56.5685714 L266.092452,56.5685714 L266.092452,49.8571429 Z M299.500001,49.8571429 L289.907548,49.8571429 L289.907548,56.5685714 C289.907548,62.4314286 291.222642,64.36 295.245284,64.36 L300.273586,64.36 C306.075473,64.36 309.015096,61.8914286 309.015096,56.9542857 C309.015096,52.2485714 305.843398,49.8571429 299.500001,49.8571429 Z M266.169811,27.64 L257.350942,27.64 C251.703772,27.64 248.764149,30.1857143 248.764149,34.5057143 C248.764149,38.8257143 251.703772,41.3714286 257.350942,41.3714286 L257.350942,41.3714286 L266.169811,41.3714286 L266.169811,27.64 Z M298.649058,27.64 L289.830189,27.64 L289.830189,41.3714286 L298.649058,41.3714286 C304.296228,41.3714286 307.235851,38.8257143 307.235851,34.5057143 C307.235851,30.1857143 304.296228,27.64 298.649058,27.64 Z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 9.9 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 86 B |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
7
public/js-enabled.css
Normal file
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
The following stylesheet is only included on pages that can execute javascript
|
||||
*/
|
||||
|
||||
.page-topic [component="post/content"] img:not(.not-responsive):not([data-state]) {
|
||||
display: none !important;
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
# Important note about localising NodeBB
|
||||
|
||||
The files here are read-only and overwritten daily (if there are changes) by the
|
||||
helper bot [Misty](https://github.com/nodebb-misty).
|
||||
|
||||
Our localisation efforts are handled via [our Transifex Project](https://www.transifex.com/nodebb/nodebb/),
|
||||
and any pull requests made to this directory will be automatically closed because
|
||||
localisations can go out-of-sync when edited directly.
|
||||
|
||||
If there are non-localised strings and you cannot find them in Transifex, please
|
||||
[open a new issue on our bug tracker](https://github.com/NodeBB/NodeBB/issues/new)
|
||||
so we can take a look.
|
||||
|
||||
Thank you for helping localise NodeBB!
|
||||
3
public/language/TODO
Normal file
@@ -0,0 +1,3 @@
|
||||
NPM INSTALL
|
||||
For now, language packs will be stored here. Eventually, will be moved to server side to allow for npm install-ability.
|
||||
When that happens, server code will generate compressed JSON language files in this folder.
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"alert.confirm-rebuild-and-restart": "هل أنت متأكد إنك تريد إعادة بناء وتشغيل الـ NodeBB؟",
|
||||
"alert.confirm-rebuild-and-restart": "Are you sure you wish to rebuild and restart NodeBB?",
|
||||
"alert.confirm-restart": "هل تريد بالتأكيد إعادة تشغيل NodeBB؟",
|
||||
|
||||
"acp-title": "لوحة تحكم إدارة NodeBB | %1",
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
{
|
||||
"post-cache": "التخزين المؤقت للمشاركات",
|
||||
"percent-full": "1% كاملة",
|
||||
"posts-in-cache": "المشاركات المخزنة مؤقتاً",
|
||||
"average-post-size": "متوسط حجم المشاركة",
|
||||
"length-to-max": "الطول / أقصى حد",
|
||||
"percent-full": "%1% Full",
|
||||
"post-cache-size": "حجم التخزين المؤقت للمشاركات",
|
||||
"items-in-cache": "العناصر في التخزين المؤقت"
|
||||
"items-in-cache": "العناصر في التخزين المؤقت",
|
||||
"control-panel": "لوحة التحكم",
|
||||
"update-settings": "تحديث إعدادات التخزين المؤقت"
|
||||
}
|
||||
@@ -18,17 +18,10 @@
|
||||
"mongo.resident-memory": "Resident Memory",
|
||||
"mongo.virtual-memory": "الذاكرة الإفتراضية",
|
||||
"mongo.mapped-memory": "Mapped Memory",
|
||||
"mongo.bytes-in": "Bytes In",
|
||||
"mongo.bytes-out": "Bytes Out",
|
||||
"mongo.num-requests": "Number of Requests",
|
||||
"mongo.raw-info": "MongoDB Raw Info",
|
||||
"mongo.unauthorized": "NodeBB was unable to query the MongoDB database for relevant statistics. Please ensure that the user in use by NodeBB contains the "clusterMonitor" role for the "admin" database.",
|
||||
|
||||
"redis": "Redis",
|
||||
"redis.version": "Redis Version",
|
||||
"redis.keys": "Keys",
|
||||
"redis.expires": "Expires",
|
||||
"redis.avg-ttl": "Average TTL",
|
||||
"redis.connected-clients": "Connected Clients",
|
||||
"redis.connected-slaves": "Connected Slaves",
|
||||
"redis.blocked-clients": "Blocked Clients",
|
||||
@@ -37,16 +30,7 @@
|
||||
"redis.total-connections-recieved": "إجمالي الاتصالات المستلمة",
|
||||
"redis.total-commands-processed": "إجمالي الأوامر التي تمت معالجتها",
|
||||
"redis.iops": "Instantaneous Ops. Per Second",
|
||||
"redis.iinput": "Instantaneous Input Per Second",
|
||||
"redis.ioutput": "Instantaneous Output Per Second",
|
||||
"redis.total-input": "Total Input",
|
||||
"redis.total-output": "Total Ouput",
|
||||
|
||||
"redis.keyspace-hits": "Keyspace Hits",
|
||||
"redis.keyspace-misses": "Keyspace Misses",
|
||||
"redis.raw-info": "Redis Raw Info",
|
||||
|
||||
"postgres": "Postgres",
|
||||
"postgres.version": "PostgreSQL Version",
|
||||
"postgres.raw-info": "Postgres Raw Info"
|
||||
}
|
||||
"redis.raw-info": "Redis Raw Info"
|
||||
}
|
||||
@@ -2,11 +2,5 @@
|
||||
"events": "أحداث",
|
||||
"no-events": "لا توجد أحداث",
|
||||
"control-panel": "لوحة تحكم الأحداث",
|
||||
"delete-events": "Delete Events",
|
||||
"filters": "Filters",
|
||||
"filters-apply": "Apply Filters",
|
||||
"filter-type": "Event Type",
|
||||
"filter-start": "Start Date",
|
||||
"filter-end": "End Date",
|
||||
"filter-perPage": "Per Page"
|
||||
"delete-events": "حذف الأحداث"
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
"custom-js.enable": "تفعيل Javascript المخصصة",
|
||||
|
||||
"custom-header": "ترويسة مخصصة",
|
||||
"custom-header.description": "Enter custom HTML here (ex. Meta Tags, etc.), which will be appended to the <code><head></code> section of your forum's markup. Script tags are allowed, but are discouraged, as the <a href=\"#custom-js\" data-toggle=\"tab\">Custom Javascript</a> tab is available.",
|
||||
"custom-header.description": "أدخل HTML الخاصة بك هنا (مثل العلامات الوصفية، وما إلى ذلك)، والتي سيتم إلحاقها الى<code><head></code> في تصميم المنتدى. يسمح باستخدام علامات النص البرمجي، ولكن سيتم تعطيلها؛ لتوفر تبويبة\n<a href=\"#custom-header\" data-toggle=\"tab\">Javascript المخصصة</a>",
|
||||
"custom-header.enable": "تفعيل الترويسة المخصصة",
|
||||
|
||||
"custom-css.livereload": "تفعيل إعادة التحميل المباشرة",
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
{
|
||||
"forum-traffic": "Forum Traffic",
|
||||
"page-views": "مشاهدات الصفحات",
|
||||
"unique-visitors": "زائرين فريدين",
|
||||
"new-users": "New Users",
|
||||
"posts": "مشاركات",
|
||||
"topics": "مواضيع",
|
||||
"page-views-seven": "آخر 7 ايام",
|
||||
"page-views-thirty": "آخر 30 يوماً",
|
||||
"page-views-last-day": "آخر 24 ساعة",
|
||||
"page-views-custom": "مدة زمنية مخصصة",
|
||||
"page-views-custom-start": "بداية المدة",
|
||||
"page-views-custom-end": "نهاية المده",
|
||||
"page-views-custom-help": "أدخل نطاقا زمنيا لمرات مشاهدة الصفحات التي ترغب في عرضها. إذا لم يظهر منتقي التاريخ، فإن التنسيق المقبول هو <code>YYYY-MM-DD</code>",
|
||||
"page-views-custom-error": "الرجاء إدخال نطاق تاريخ صالح بالتنسيق <code>YYYY-MM-DD</code>",
|
||||
|
||||
"stats.yesterday": "Yesterday",
|
||||
"stats.today": "Today",
|
||||
"stats.last-week": "Last Week",
|
||||
"stats.this-week": "This Week",
|
||||
"stats.last-month": "Last Month",
|
||||
"stats.this-month": "This Month",
|
||||
"stats.all": "كل الوقت",
|
||||
|
||||
"updates": "تحديثات",
|
||||
"running-version": "المنتدى يعمل حاليا على <strong>NodeBB الإصدار<span id=\"version\">%1</span></strong>.",
|
||||
"keep-updated": "تأكد دائما من أن NodeBB يعمل على احدث إصدار للحصول على أحدث التصحيحات الأمنية وإصلاحات الأخطاء.",
|
||||
"up-to-date": "<p>المنتدى <strong>يعمل على أحدث إصدار</strong> <i class=\"fa fa-check\"></i></p>",
|
||||
"upgrade-available": "<p>A new version (v%1) has been released. Consider <a href=\"https://docs.nodebb.org/configuring/upgrade/\" target=\"_blank\">upgrading your NodeBB</a>.</p>",
|
||||
"prerelease-upgrade-available": "<p>This is an outdated pre-release version of NodeBB. A new version (v%1) has been released. Consider <a href=\"https://docs.nodebb.org/configuring/upgrade/\" target=\"_blank\">upgrading your NodeBB</a>.</p>",
|
||||
"prerelease-warning": "<p>هذه نسخة <strong>ماقبل الإصدار</strong> من NodeBB. قد تحدث أخطاء غير مقصودة. <i class=\"fa fa-exclamation-triangle\"></i></p>",
|
||||
"running-in-development": "المنتدى قيد التشغيل في وضع \"المطورين\". وقد تكون هناك ثغرات أمنية مفتوحة؛ من فضلك تواصل مع مسؤول نظامك.",
|
||||
"latest-lookup-failed": "<p>Failed to look up latest available version of NodeBB</p>",
|
||||
|
||||
"notices": "إشعارات",
|
||||
"restart-not-required": "إعادة التشغيل غير مطلوب",
|
||||
"restart-required": "إعادة التشغيل مطلوبة",
|
||||
"search-plugin-installed": "إضافة البحث منصبة",
|
||||
"search-plugin-not-installed": "إضافة البحث غير منصبة",
|
||||
"search-plugin-tooltip": "نصب إضافة البحث من صفحة الإضافات البرمجية لتنشيط وظيفة البحث",
|
||||
|
||||
"control-panel": "التحكم بالنظام",
|
||||
"rebuild-and-restart": "Rebuild & Restart",
|
||||
"restart": "Restart",
|
||||
"restart-warning": "Rebuilding or Restarting your NodeBB will drop all existing connections for a few seconds.",
|
||||
"restart-disabled": "Rebuilding and Restarting your NodeBB has been disabled as you do not seem to be running it via the appropriate daemon.",
|
||||
"maintenance-mode": "وضع الصيانة",
|
||||
"maintenance-mode-title": "انقر هنا لإعداد وضع الصيانة لـNodeBB",
|
||||
"realtime-chart-updates": "التحديث الفوري للرسم البياني",
|
||||
|
||||
"active-users": "المستخدمين النشطين",
|
||||
"active-users.users": "الأعضاء",
|
||||
"active-users.guests": "الزوار",
|
||||
"active-users.total": "المجموع",
|
||||
"active-users.connections": "Connections",
|
||||
|
||||
"anonymous-registered-users": "المجهولين مقابل المستخدمين المسجلين",
|
||||
"anonymous": "مجهول",
|
||||
"registered": "مسجل",
|
||||
|
||||
"user-presence": "تواجد المستخدمين",
|
||||
"on-categories": "في قائمة الأقسام",
|
||||
"reading-posts": "قراءة المشاركات",
|
||||
"browsing-topics": "تصفح المواضيع",
|
||||
"recent": "الأخيرة",
|
||||
"unread": "غير مقروء",
|
||||
|
||||
"high-presence-topics": "مواضيع ذات حضور قوي",
|
||||
|
||||
"graphs.page-views": "مشاهدات الصفحة",
|
||||
"graphs.page-views-registered": "Page Views Registered",
|
||||
"graphs.page-views-guest": "Page Views Guest",
|
||||
"graphs.page-views-bot": "Page Views Bot",
|
||||
"graphs.unique-visitors": "زوار فريدين",
|
||||
"graphs.registered-users": "مستخدمين مسجلين",
|
||||
"graphs.anonymous-users": "مستخدمين مجهولين",
|
||||
"last-restarted-by": "Last restarted by",
|
||||
"no-users-browsing": "No users browsing"
|
||||
}
|
||||
@@ -1,16 +1,13 @@
|
||||
{
|
||||
"you-are-on": "You are on <strong>%1:%2</strong>",
|
||||
"ip": "IP <strong>%1</strong>",
|
||||
"you-are-on": "Info - You are on <strong>%1:%2</strong>",
|
||||
"nodes-responded": "%1 nodes responded within %2ms!",
|
||||
"host": "host",
|
||||
"primary": "primary / run jobs",
|
||||
"pid": "pid",
|
||||
"nodejs": "nodejs",
|
||||
"online": "online",
|
||||
"git": "git",
|
||||
"memory": "memory",
|
||||
"load": "system load",
|
||||
"cpu-usage": "cpu usage",
|
||||
"load": "load",
|
||||
"uptime": "uptime",
|
||||
|
||||
"registered": "Registered",
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
{
|
||||
"trending": "Trending",
|
||||
"installed": "منصبة",
|
||||
"active": "مفعلة",
|
||||
"inactive": "معطلة",
|
||||
@@ -10,7 +9,6 @@
|
||||
|
||||
"plugin-search": "البحث عن الإضافات",
|
||||
"plugin-search-placeholder": "جاري البحث عن الإضافات...",
|
||||
"submit-anonymous-usage": "Submit anonymous plugin usage data.",
|
||||
"reorder-plugins": "إعادة ترتيب الإضافات",
|
||||
"order-active": "ترتيب الإضافات المفعلة",
|
||||
"dev-interested": "هل انته مهتم ببرمجة إضافات لـNodeBB؟",
|
||||
@@ -31,8 +29,6 @@
|
||||
"plugin-item.more-info": "لمزيد من المعلومات:",
|
||||
"plugin-item.unknown": "غير معروف",
|
||||
"plugin-item.unknown-explanation": "تعذر تحديد حالة هذه الإضافة، ربما بسبب خطأ في الإعدادات.",
|
||||
"plugin-item.compatible": "This plugin works on NodeBB %1",
|
||||
"plugin-item.not-compatible": "This plugin has no compatibility data, make sure it works before installing on your production environment.",
|
||||
|
||||
"alert.enabled": "الإضافة مفعلة",
|
||||
"alert.disabled": "الإضافة معطلة",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"available": "Available Widgets",
|
||||
"explanation": "Select a widget from the dropdown menu and then drag and drop it into a template's widget area on the left.",
|
||||
"none-installed": "No widgets found! Activate the widget essentials plugin in the <a href=\"%1\">plugins</a> control panel.",
|
||||
"none-installed": "No widgets found! Activate the essential widgets plugin in the <a href=\"%1\">plugins</a> control panel.",
|
||||
"clone-from": "Clone widgets from",
|
||||
"containers.available": "Available Containers",
|
||||
"containers.explanation": "Drag and drop on top of any active widget",
|
||||
@@ -18,13 +18,5 @@
|
||||
"alert.update-success": "Successfully updated widgets",
|
||||
"alert.clone-success": "Successfully cloned widgets",
|
||||
|
||||
"error.select-clone": "Please select a page to clone from",
|
||||
|
||||
"title": "Title",
|
||||
"title.placeholder": "Title (only shown on some containers)",
|
||||
"container": "Container",
|
||||
"container.placeholder": "Drag and drop a container or enter HTML here.",
|
||||
"show-to-groups": "Show to groups",
|
||||
"hide-from-groups": "Hide from groups",
|
||||
"hide-on-mobile": "Hide on mobile"
|
||||
"error.select-clone": "Please select a page to clone from"
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
"forum-traffic": "Forum Traffic",
|
||||
"page-views": "مشاهدات الصفحات",
|
||||
"unique-visitors": "زائرين فريدين",
|
||||
"new-users": "New Users",
|
||||
"users": "أعضاء",
|
||||
"posts": "مشاركات",
|
||||
"topics": "مواضيع",
|
||||
"page-views-seven": "آخر 7 ايام",
|
||||
@@ -14,12 +14,9 @@
|
||||
"page-views-custom-help": "أدخل نطاقا زمنيا لمرات مشاهدة الصفحات التي ترغب في عرضها. إذا لم يظهر منتقي التاريخ، فإن التنسيق المقبول هو <code>YYYY-MM-DD</code>",
|
||||
"page-views-custom-error": "الرجاء إدخال نطاق تاريخ صالح بالتنسيق <code>YYYY-MM-DD</code>",
|
||||
|
||||
"stats.yesterday": "Yesterday",
|
||||
"stats.today": "Today",
|
||||
"stats.last-week": "Last Week",
|
||||
"stats.this-week": "This Week",
|
||||
"stats.last-month": "Last Month",
|
||||
"stats.this-month": "This Month",
|
||||
"stats.day": "يوم",
|
||||
"stats.week": "إسبوع",
|
||||
"stats.month": "شهر",
|
||||
"stats.all": "كل الوقت",
|
||||
|
||||
"updates": "تحديثات",
|
||||
@@ -68,12 +65,8 @@
|
||||
"high-presence-topics": "مواضيع ذات حضور قوي",
|
||||
|
||||
"graphs.page-views": "مشاهدات الصفحة",
|
||||
"graphs.page-views-registered": "Page Views Registered",
|
||||
"graphs.page-views-guest": "Page Views Guest",
|
||||
"graphs.page-views-bot": "Page Views Bot",
|
||||
"graphs.unique-visitors": "زوار فريدين",
|
||||
"graphs.registered-users": "مستخدمين مسجلين",
|
||||
"graphs.anonymous-users": "مستخدمين مجهولين",
|
||||
"last-restarted-by": "Last restarted by",
|
||||
"no-users-browsing": "No users browsing"
|
||||
"last-restarted-by": "Last restarted by"
|
||||
}
|
||||
|
||||
@@ -5,11 +5,13 @@
|
||||
"tooltip": "Tooltip:",
|
||||
"text": "Text:",
|
||||
"text-class": "Text Class: <small>optional</small>",
|
||||
"class": "Class: <small>optional</small>",
|
||||
"id": "ID: <small>optional</small>",
|
||||
|
||||
"properties": "Properties:",
|
||||
"groups": "Groups:",
|
||||
"only-admins": "Only display to Admins",
|
||||
"only-global-mods-and-admins": "Only display to Global Moderators and Admins",
|
||||
"only-logged-in": "Only display to logged in users",
|
||||
"only-guest": "Only display to guests",
|
||||
"open-new-window": "Open in a new window",
|
||||
|
||||
"btn.delete": "Delete",
|
||||
|
||||
@@ -11,35 +11,28 @@
|
||||
"num-recent-replies": "# of Recent Replies",
|
||||
"ext-link": "External Link",
|
||||
"is-section": "Treat this category as a section",
|
||||
"tag-whitelist": "Tag Whitelist",
|
||||
"upload-image": "Upload Image",
|
||||
"delete-image": "Remove",
|
||||
"category-image": "Category Image",
|
||||
"parent-category": "Parent Category",
|
||||
"optional-parent-category": "(Optional) Parent Category",
|
||||
"parent-category-none": "(None)",
|
||||
"copy-parent": "Copy Parent",
|
||||
"copy-settings": "Copy Settings From",
|
||||
"optional-clone-settings": "(Optional) Clone Settings From Category",
|
||||
"clone-children": "Clone Children Categories And Settings",
|
||||
"purge": "Purge Category",
|
||||
|
||||
"enable": "Enable",
|
||||
"disable": "Disable",
|
||||
"edit": "Edit",
|
||||
"analytics": "Analytics",
|
||||
"view-category": "View category",
|
||||
|
||||
"select-category": "Select Category",
|
||||
"set-parent-category": "Set Parent Category",
|
||||
|
||||
"privileges.description": "You can configure the access control privileges for portions of the site in this section. Privileges can be granted on a per-user or a per-group basis. Select the domain of effect from the dropdown below.",
|
||||
"privileges.category-selector": "Configuring privileges for ",
|
||||
"privileges.description": "You can configure the access control privileges for this category in this section. Privileges can be granted on a per-user or a per-group basis. You can add a new user to this table by searching for them in the form below.",
|
||||
"privileges.warning": "<strong>Note</strong>: Privilege settings take effect immediately. It is not necessary to save the category after adjusting these settings.",
|
||||
"privileges.section-viewing": "Viewing Privileges",
|
||||
"privileges.section-posting": "Posting Privileges",
|
||||
"privileges.section-moderation": "Moderation Privileges",
|
||||
"privileges.section-other": "Other",
|
||||
"privileges.section-user": "User",
|
||||
"privileges.search-user": "Add User",
|
||||
"privileges.no-users": "No user-specific privileges in this category.",
|
||||
@@ -48,12 +41,7 @@
|
||||
"privileges.search-group": "Add Group",
|
||||
"privileges.copy-to-children": "Copy to Children",
|
||||
"privileges.copy-from-category": "Copy from Category",
|
||||
"privileges.copy-privileges-to-all-categories": "Copy to All Categories",
|
||||
"privileges.copy-group-privileges-to-children": "Copy this group's privileges to the children of this category.",
|
||||
"privileges.copy-group-privileges-to-all-categories": "Copy this group's privileges to all categories.",
|
||||
"privileges.copy-group-privileges-from": "Copy this group's privileges from another category.",
|
||||
"privileges.inherit": "If the <code>registered-users</code> group is granted a specific privilege, all other groups receive an <strong>implicit privilege</strong>, even if they are not explicitly defined/checked. This implicit privilege is shown to you because all users are part of the <code>registered-users</code> user group, and so, privileges for additional groups need not be explicitly granted.",
|
||||
"privileges.copy-success": "Privileges copied!",
|
||||
|
||||
"analytics.back": "Back to Categories List",
|
||||
"analytics.title": "Analytics for \"%1\" category",
|
||||
@@ -66,6 +54,7 @@
|
||||
"alert.create-success": "Category successfully created!",
|
||||
"alert.none-active": "You have no active categories.",
|
||||
"alert.create": "Create a Category",
|
||||
"alert.confirm-moderate": "<strong>Are you sure you wish to grant the moderation privilege to this user group?</strong> This group is public, and any users can join at will.",
|
||||
"alert.confirm-purge": "<p class=\"lead\">Do you really want to purge this category \"%1\"?</p><h5><strong class=\"text-danger\">Warning!</strong> All topics and posts in this category will be purged!</h5> <p class=\"help-block\">Purging a category will remove all topics and posts, and delete the category from the database. If you want to remove a category <em>temporarily</em>, you'll want to \"disable\" the category instead.</p>",
|
||||
"alert.purge-success": "Category purged!",
|
||||
"alert.copy-success": "Settings Copied!",
|
||||
@@ -77,9 +66,6 @@
|
||||
"alert.user-search": "Search for a user here...",
|
||||
"alert.find-group": "Find a Group",
|
||||
"alert.group-search": "Search for a group here...",
|
||||
"alert.not-enough-whitelisted-tags": "Whitelisted tags are less than minimum tags, you need to create more whitelisted tags!",
|
||||
"collapse-all": "Collapse All",
|
||||
"expand-all": "Expand All",
|
||||
"disable-on-create": "Disable on create",
|
||||
"no-matches": "No matches"
|
||||
"expand-all": "Expand All"
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"lead": "A listing of digest delivery stats and times is displayed below.",
|
||||
"disclaimer": "Please be advised that email delivery is not guaranteed, due to the nature of email technology. Many variables factor into whether an email sent to the recipient server is ultimately delivered into the user's inbox, including server reputation, blacklisted IP addresses, and whether DKIM/SPF/DMARC is configured.",
|
||||
"disclaimer-continued": "A successful delivery means the message was sent successfully by NodeBB and acknowledged by the recipient server. It does not mean the email landed in the inbox. For best results, we recommend using a third-party email delivery service such as <a href=\"https://sendgrid.com/why-sendgrid/\">SendGrid</a>.",
|
||||
|
||||
"user": "User",
|
||||
"subscription": "Subscription Type",
|
||||
"last-delivery": "Last successful delivery",
|
||||
"default": "System default",
|
||||
"default-help": "<em>System default</em> means the user has not explicitly overridden the global forum setting for digests, which is currently: "<strong>%1</strong>"",
|
||||
"resend": "Resend Digest",
|
||||
"resend-all-confirm": "Are you sure you wish to manually execute this digest run?",
|
||||
"resent-single": "Manual digest resend completed",
|
||||
"resent-day": "Daily digest resent",
|
||||
"resent-week": "Weekly digest resent",
|
||||
"resent-month": "Monthly digest resent",
|
||||
"null": "<em>Never</em>",
|
||||
"manual-run": "Manual digest run:",
|
||||
|
||||
"no-delivery-data": "No delivery data found"
|
||||
}
|
||||
@@ -1,16 +1,9 @@
|
||||
{
|
||||
"name": "اسم المجموعة",
|
||||
"badge": "Badge",
|
||||
"properties": "Properties",
|
||||
"description": "Group Description",
|
||||
"member-count": "Member Count",
|
||||
"system": "System",
|
||||
"hidden": "Hidden",
|
||||
"private": "Private",
|
||||
"system": "System Group",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
"privileges": "Privileges",
|
||||
"download-csv": "CSV",
|
||||
"search-placeholder": "Search",
|
||||
"create": "Create Group",
|
||||
"description-placeholder": "A short description about your group",
|
||||
@@ -24,12 +17,10 @@
|
||||
"edit.user-title": "Title of Members",
|
||||
"edit.icon": "Group Icon",
|
||||
"edit.label-color": "Group Label Color",
|
||||
"edit.text-color": "Group Text Color",
|
||||
"edit.show-badge": "Show Badge",
|
||||
"edit.private-details": "If enabled, joining of groups requires approval from a group owner.",
|
||||
"edit.private-override": "Warning: Private groups is disabled at system level, which overrides this option.",
|
||||
"edit.disable-join": "Disable join requests",
|
||||
"edit.disable-leave": "Disallow users from leaving the group",
|
||||
"edit.disable-requests": "Disable join requests",
|
||||
"edit.hidden": "Hidden",
|
||||
"edit.hidden-details": "If enabled, this group will not be found in the groups listing, and users will have to be invited manually",
|
||||
"edit.add-user": "Add User to Group",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"post-queue": "Post Queue",
|
||||
"description": "There are no posts in the post queue. <br> To enable this feature, go to <a href=\"%1\">Settings → Post → Post Queue</a> and enable <strong>Post Queue</strong>.",
|
||||
"description": "There are no posts in the post queue. <br> To enable this feature, go to <a href=\"%1\">Settings → Post → Posting Restrictions</a> and enable <strong>Post Queue</strong>.",
|
||||
"user": "User",
|
||||
"category": "Category",
|
||||
"title": "Title",
|
||||
@@ -1,54 +0,0 @@
|
||||
{
|
||||
"global": "Global",
|
||||
"admin": "Admin",
|
||||
"group-privileges": "Group Privileges",
|
||||
"user-privileges": "User Privileges",
|
||||
"edit-privileges": "Edit Privileges",
|
||||
"chat": "Chat",
|
||||
"upload-images": "Upload Images",
|
||||
"upload-files": "Upload Files",
|
||||
"signature": "Signature",
|
||||
"ban": "Ban",
|
||||
"invite": "Invite",
|
||||
"search-content": "Search Content",
|
||||
"search-users": "Search Users",
|
||||
"search-tags": "Search Tags",
|
||||
"view-users": "View Users",
|
||||
"view-tags": "View Tags",
|
||||
"view-groups": "View Groups",
|
||||
"allow-local-login": "Local Login",
|
||||
"allow-group-creation": "Group Create",
|
||||
"view-users-info": "View Users Info",
|
||||
"find-category": "Find Category",
|
||||
"access-category": "Access Category",
|
||||
"access-topics": "Access Topics",
|
||||
"create-topics": "Create Topics",
|
||||
"reply-to-topics": "Reply to Topics",
|
||||
"tag-topics": "Tag Topics",
|
||||
"edit-posts": "Edit Posts",
|
||||
"view-edit-history": "View Edit History",
|
||||
"delete-posts": "Delete Posts",
|
||||
"view_deleted": "View Deleted Posts",
|
||||
"upvote-posts": "Upvote Posts",
|
||||
"downvote-posts": "Downvote Posts",
|
||||
"delete-topics": "Delete Topics",
|
||||
"purge": "Purge",
|
||||
"moderate": "Moderate",
|
||||
"admin-dashboard": "Dashboard",
|
||||
"admin-categories": "Categories",
|
||||
"admin-privileges": "Privileges",
|
||||
"admin-users": "Users",
|
||||
"admin-settings": "Settings",
|
||||
|
||||
"alert.confirm-moderate": "<strong>Are you sure you wish to grant the moderation privilege to this user group?</strong> This group is public, and any users can join at will.",
|
||||
"alert.confirm-save": "Please confirm your intention to save these privileges",
|
||||
"alert.saved": "Privilege changes saved and applied",
|
||||
"alert.confirm-discard": "Are you sure you wish to discard your privilege changes?",
|
||||
"alert.discarded": "Privilege changes discarded",
|
||||
"alert.confirm-copyToAll": "Are you sure you wish to apply this privilege set to <strong>all categories</strong>?",
|
||||
"alert.confirm-copyToAllGroup": "Are you sure you wish to apply this group's privilege set to <strong>all categories</strong>?",
|
||||
"alert.confirm-copyToChildren": "Are you sure you wish to apply this privilege set to <strong>all descendant (child) categories</strong>?",
|
||||
"alert.confirm-copyToChildrenGroup": "Are you sure you wish to apply this group's privilege set to <strong>all descendant (child) categories</strong>?",
|
||||
"alert.no-undo": "<em>This action cannot be undone.</em>",
|
||||
"alert.admin-warning": "Administrators implicitly get all privileges"
|
||||
}
|
||||
@@ -3,16 +3,17 @@
|
||||
"bg-color": "Background Colour",
|
||||
"text-color": "Text Colour",
|
||||
"create-modify": "Create & Modify Tags",
|
||||
"description": "Select tags by clicking or dragging, use <code>CTRL</code> to select multiple tags.",
|
||||
"description": "Select tags via clicking and/or dragging, use shift to select multiple.",
|
||||
"create": "Create Tag",
|
||||
"modify": "Modify Tags",
|
||||
"rename": "Rename Tags",
|
||||
"delete": "Delete Selected Tags",
|
||||
"search": "Search for tags...",
|
||||
"settings": "Tags Settings",
|
||||
"settings": "Click <a href=\"%1\">here</a> to visit the tag settings page.",
|
||||
"name": "Tag Name",
|
||||
|
||||
"alerts.editing": "Editing tag(s)",
|
||||
"alerts.editing-multiple": "Editing multiple tags",
|
||||
"alerts.editing-x": "Editing \"%1\" tag",
|
||||
"alerts.confirm-delete": "Do you want to delete the selected tags?",
|
||||
"alerts.update-success": "Tag Updated!"
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"upload-file": "Upload File",
|
||||
"filename": "Filename",
|
||||
"usage": "Post Usage",
|
||||
"orphaned": "Orphaned",
|
||||
"size/filecount": "Size / Filecount",
|
||||
"confirm-delete": "Do you really want to delete this file?",
|
||||
"filecount": "%1 files"
|
||||
}
|
||||
@@ -6,29 +6,26 @@
|
||||
"validate-email": "Validate Email",
|
||||
"send-validation-email": "Send Validation Email",
|
||||
"password-reset-email": "Send Password Reset Email",
|
||||
"force-password-reset": "Force Password Reset & Log User Out",
|
||||
"ban": "Ban User(s)",
|
||||
"temp-ban": "Ban User(s) Temporarily",
|
||||
"unban": "Unban User(s)",
|
||||
"reset-lockout": "Reset Lockout",
|
||||
"reset-flags": "Reset Flags",
|
||||
"delete": "Delete <strong>User(s)</strong>",
|
||||
"delete-content": "Delete User(s) <strong>Content</strong>",
|
||||
"purge": "Delete <strong>User(s)</strong> and <strong>Content</strong>",
|
||||
"delete": "Delete User(s)",
|
||||
"purge": "Delete User(s) and Content",
|
||||
"download-csv": "Download CSV",
|
||||
"manage-groups": "Manage Groups",
|
||||
"add-group": "Add Group",
|
||||
"invite": "Invite",
|
||||
"new": "New User",
|
||||
"filter-by": "Filter by",
|
||||
"pills.unvalidated": "Not Validated",
|
||||
"pills.validated": "Validated",
|
||||
"pills.banned": "Banned",
|
||||
|
||||
"50-per-page": "50 per page",
|
||||
"100-per-page": "100 per page",
|
||||
"250-per-page": "250 per page",
|
||||
"500-per-page": "500 per page",
|
||||
"pills.latest": "Latest Users",
|
||||
"pills.unvalidated": "Not Validated",
|
||||
"pills.no-posts": "No Posts",
|
||||
"pills.top-posters": "Top Posters",
|
||||
"pills.top-rep": "Most Reputation",
|
||||
"pills.inactive": "Inactive",
|
||||
"pills.flagged": "Most Flagged",
|
||||
"pills.banned": "Banned",
|
||||
"pills.search": "User Search",
|
||||
|
||||
"search.uid": "By User ID",
|
||||
"search.uid-placeholder": "Enter a user ID to search",
|
||||
@@ -84,15 +81,11 @@
|
||||
"alerts.confirm-remove-moderator": "Do you really want to remove this moderator?",
|
||||
"alerts.remove-moderator-success": "User is no longer moderator.",
|
||||
"alerts.confirm-validate-email": "Do you want to validate email(s) of these user(s)?",
|
||||
"alerts.confirm-force-password-reset": "Are you sure you want to force the password reset and log out these user(s)?",
|
||||
"alerts.validate-email-success": "Emails validated",
|
||||
"alerts.validate-force-password-reset-success": "User(s) passwords have been reset and their existing sessions have been revoked.",
|
||||
"alerts.password-reset-confirm": "Do you want to send password reset email(s) to these user(s)?",
|
||||
"alerts.confirm-delete": "<strong>Warning!</strong><p>Do you really want to delete <strong>user(s)</strong>?</p><p>This action is not reversible! Only the user account will be deleted, their posts and topics will remain.</p>",
|
||||
"alerts.confirm-delete": "<b>Warning!</b><br/>Do you really want to delete user(s)?<br/> This action is not reversable! Only the user account will be deleted, their posts and topics will remain.",
|
||||
"alerts.delete-success": "User(s) Deleted!",
|
||||
"alerts.confirm-delete-content": "<strong>Warning!</strong><p>Do you really want to delete these user(s) <strong>content</strong>?</p><p>This action is not reversible! The users' accounts will remain, but their posts and topics will be deleted.</p>",
|
||||
"alerts.delete-content-success": "User(s) Content Deleted!",
|
||||
"alerts.confirm-purge": "<strong>Warning!</strong><p>Do you really want to delete <strong>user(s) and their content</strong>?</p><p>This action is not reversible! All user data and content will be erased!</p>",
|
||||
"alerts.confirm-purge": "<b>Warning!</b><br/>Do you really want to delete user(s) and their content?<br/> This action is not reversable! All user data and content will be erased!",
|
||||
"alerts.create": "Create User",
|
||||
"alerts.button-create": "Create",
|
||||
"alerts.button-cancel": "Cancel",
|
||||
@@ -100,7 +93,7 @@
|
||||
"alerts.error-x": "<strong>Error</strong><p>%1</p>",
|
||||
"alerts.create-success": "User created!",
|
||||
|
||||
"alerts.prompt-email": "Emails: ",
|
||||
"alerts.prompt-email": "Email: ",
|
||||
"alerts.email-sent-to": "An invitation email has been sent to %1",
|
||||
"alerts.x-users-found": "%1 user(s) found, (%2 seconds)"
|
||||
"alerts.x-users-found": "%1 user(s) found! Search took %2 ms."
|
||||
}
|
||||
@@ -1,6 +1,11 @@
|
||||
{
|
||||
"dashboard": "Dashboard",
|
||||
"section-general": "عام",
|
||||
"general/dashboard": "اللوحة الرئيسية",
|
||||
"general/homepage": "الصفحة الرئيسية",
|
||||
"general/navigation": "التصفح",
|
||||
"general/languages": "اللغات",
|
||||
"general/sounds": "الأصوات",
|
||||
"general/social": "شبكات التواصل",
|
||||
|
||||
"section-manage": "إدارة",
|
||||
"manage/categories": "الأقسام",
|
||||
@@ -13,27 +18,20 @@
|
||||
"manage/groups": "المجموعات",
|
||||
"manage/ip-blacklist": "قائمة حظر عناوين IP",
|
||||
"manage/uploads": "Uploads",
|
||||
"manage/digest": "Digests",
|
||||
|
||||
"section-settings": "إعدادات",
|
||||
"settings/general": "عامة",
|
||||
"settings/homepage": "Home Page",
|
||||
"settings/navigation": "Navigation",
|
||||
"settings/reputation": "Reputation & Flags",
|
||||
"settings/reputation": "السمعة",
|
||||
"settings/email": "البريد الإلكتروني",
|
||||
"settings/user": "Users",
|
||||
"settings/group": "Groups",
|
||||
"settings/user": "الأعضاء",
|
||||
"settings/group": "المجموعات",
|
||||
"settings/guest": "الزوار",
|
||||
"settings/uploads": "الرفع",
|
||||
"settings/languages": "Languages",
|
||||
"settings/post": "Posts",
|
||||
"settings/chat": "Chats",
|
||||
"settings/post": "المشاركة",
|
||||
"settings/chat": "الدردشة",
|
||||
"settings/pagination": "ترقيم الصفحات",
|
||||
"settings/tags": "الكلمات المفتاحية",
|
||||
"settings/notifications": "التنبيهات",
|
||||
"settings/api": "API Access",
|
||||
"settings/sounds": "Sounds",
|
||||
"settings/social": "Social",
|
||||
"settings/cookies": "Cookies",
|
||||
"settings/web-crawler": "Web Crawler",
|
||||
"settings/sockets": "Sockets",
|
||||
@@ -59,7 +57,6 @@
|
||||
"section-advanced": "Advanced",
|
||||
"advanced/database": "Database",
|
||||
"advanced/events": "Events",
|
||||
"advanced/hooks": "Hooks",
|
||||
"advanced/logs": "Logs",
|
||||
"advanced/errors": "Errors",
|
||||
"advanced/cache": "Cache",
|
||||
@@ -71,7 +68,7 @@
|
||||
"logout": "Log out",
|
||||
"view-forum": "View Forum",
|
||||
|
||||
"search.placeholder": "Press "/" to search for settings",
|
||||
"search.placeholder": "Search for settings",
|
||||
"search.no-results": "No results...",
|
||||
"search.search-forum": "Search the forum for <strong></strong>",
|
||||
"search.keep-typing": "Type more to see results...",
|
||||
|
||||
@@ -1,43 +1,20 @@
|
||||
{
|
||||
"maintenance-mode": "Maintenance Mode",
|
||||
"maintenance-mode.help": "When the forum is in maintenance mode, all requests will be redirected to a static holding page. Administrators are exempt from this redirection, and are able to access the site normally.",
|
||||
"maintenance-mode.status": "Maintenance Mode Status Code",
|
||||
"maintenance-mode.message": "Maintenance Message",
|
||||
"headers": "Headers",
|
||||
"headers.allow-from": "Set ALLOW-FROM to Place NodeBB in an iFrame",
|
||||
"headers.csp-frame-ancestors": "Set Content-Security-Policy frame-ancestors header to Place NodeBB in an iFrame",
|
||||
"headers.csp-frame-ancestors-help": "'none', 'self'(default) or list of URIs to allow.",
|
||||
"headers.powered-by": "Customise the \"Powered By\" header sent by NodeBB",
|
||||
"headers.acao": "Access-Control-Allow-Origin",
|
||||
"headers.acao-regex": "Access-Control-Allow-Origin Regular Expression",
|
||||
"headers.acao-help": "To deny access to all sites, leave empty",
|
||||
"headers.acao-regex-help": "Enter regular expressions here to match dynamic origins. To deny access to all sites, leave empty",
|
||||
"headers.acac": "Access-Control-Allow-Credentials",
|
||||
"headers.acam": "Access-Control-Allow-Methods",
|
||||
"headers.acah": "Access-Control-Allow-Headers",
|
||||
"hsts": "Strict Transport Security",
|
||||
"hsts.enabled": "Enabled HSTS (recommended)",
|
||||
"hsts.maxAge": "HSTS Max Age",
|
||||
"hsts.subdomains": "Include subdomains in HSTS header",
|
||||
"hsts.preload": "Allow preloading of HSTS header",
|
||||
"hsts.help": "If enabled, an HSTS header will be set for this site. You can elect to include subdomains and preloading flags in your header. If in doubt, you can leave these unchecked. <a href=\"%1\">More information <i class=\"fa fa-external-link\"></i></a>",
|
||||
"traffic-management": "Traffic Management",
|
||||
"traffic.help": "NodeBB uses a module that automatically denies requests in high-traffic situations. You can tune these settings here, although the defaults are a good starting point.",
|
||||
"traffic.help": "NodeBB deploys equipped with a module that automatically denies requests in high-traffic situations. You can tune these settings here, although the defaults are a good starting point.",
|
||||
"traffic.enable": "Enable Traffic Management",
|
||||
"traffic.event-lag": "Event Loop Lag Threshold (in milliseconds)",
|
||||
"traffic.event-lag-help": "Lowering this value decreases wait times for page loads, but will also show the \"excessive load\" message to more users. (Restart required)",
|
||||
"traffic.lag-check-interval": "Check Interval (in milliseconds)",
|
||||
"traffic.lag-check-interval-help": "Lowering this value causes NodeBB to become more sensitive to spikes in load, but may also cause the check to become too sensitive. (Restart required)",
|
||||
|
||||
"sockets.settings": "WebSocket Settings",
|
||||
"sockets.max-attempts": "Max Reconnection Attempts",
|
||||
"sockets.default-placeholder": "Default: %1",
|
||||
"sockets.delay": "Reconnection Delay",
|
||||
|
||||
"analytics.settings": "Analytics Settings",
|
||||
"analytics.max-cache": "Analytics Cache Max Value",
|
||||
"analytics.max-cache-help": "On high-traffic installs, the cache could be exhausted continuously if there are more concurrent active users than the Max Cache value. (Restart required)",
|
||||
"compression.settings": "Compression Settings",
|
||||
"compression.enable": "Enable Compression",
|
||||
"compression.help": "This setting enables gzip compression. For a high-traffic website in production, the best way to put compression in place is to implement it at a reverse proxy level. You can enable it here for testing purposes."
|
||||
"traffic.lag-check-interval-help": "Lowering this value causes NodeBB to become more sensitive to spikes in load, but may also cause the check to become too sensitive. (Restart required)"
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"tokens": "Tokens",
|
||||
"settings": "Settings",
|
||||
"lead-text": "From this page you can configure access to the Write API in NodeBB.",
|
||||
"intro": "By default, the Write API authenticates users based on their session cookie, but NodeBB also supports Bearer authentication via tokens generated via this page.",
|
||||
"docs": "Click here to access the full API specification",
|
||||
|
||||
"require-https": "Require API usage via HTTPS only",
|
||||
"require-https-caveat": "<strong>Note</strong>: Some installations involving load balancers may proxy their requests to NodeBB using HTTP, in which case this option should remain disabled.",
|
||||
|
||||
"uid": "User ID",
|
||||
"uid-help-text": "Specify a User ID to associate with this token. If the user ID is <code>0</code>, it will be considered a <em>master</em> token, which can assume the identity of other users based on the <code>_uid</code> parameter",
|
||||
"description": "Description",
|
||||
"no-description": "No description specified.",
|
||||
"token-on-save": "Token will be generated once form is saved"
|
||||
}
|
||||
@@ -6,7 +6,6 @@
|
||||
"max-length": "Maximum length of chat messages",
|
||||
"max-room-size": "Maximum number of users in chat rooms",
|
||||
"delay": "Time between chat messages in milliseconds",
|
||||
"notification-delay": "Notification delay for chat messages. (0 for no delay)",
|
||||
"restrictions.seconds-edit-after": "Number of seconds a chat message will remain editable. (0 disabled)",
|
||||
"restrictions.seconds-delete-after": "Number of seconds a chat message will remain deletable. (0 disabled)"
|
||||
"restrictions.seconds-edit-after": "Number of seconds before users are allowed to edit chat messages after posting. (0 disabled)",
|
||||
"restrictions.seconds-delete-after": "Number of seconds before users are allowed to delete chat messages after posting. (0 disabled)"
|
||||
}
|
||||
@@ -4,10 +4,8 @@
|
||||
"consent.message": "Notification message",
|
||||
"consent.acceptance": "Acceptance message",
|
||||
"consent.link-text": "Policy Link Text",
|
||||
"consent.link-url": "Policy Link URL",
|
||||
"consent.blank-localised-default": "Leave blank to use NodeBB localised defaults",
|
||||
"settings": "Settings",
|
||||
"cookie-domain": "Session cookie domain",
|
||||
"max-user-sessions": "Max active sessions per user",
|
||||
"blank-default": "Leave blank for default"
|
||||
}
|
||||
@@ -22,8 +22,6 @@
|
||||
"smtp-transport.username": "Username",
|
||||
"smtp-transport.username-help": "<b>For the Gmail service,</b> enter the full email address here, especially if you are using a Google Apps managed domain.",
|
||||
"smtp-transport.password": "Password",
|
||||
"smtp-transport.pool": "Enable pooled connections",
|
||||
"smtp-transport.pool-help": "Pooling connections prevents NodeBB from creating a new connection for every email. This option only applies if SMTP Transport is enabled.",
|
||||
|
||||
"template": "Edit Email Template",
|
||||
"template.select": "Select Email Template",
|
||||
@@ -32,10 +30,8 @@
|
||||
"testing.select": "Select Email Template",
|
||||
"testing.send": "Send Test Email",
|
||||
"testing.send-help": "The test email will be sent to the currently logged in user's email address.",
|
||||
"subscriptions": "Email Digests",
|
||||
"subscriptions.disable": "Disable email digests",
|
||||
"subscriptions": "Email Subscriptions",
|
||||
"subscriptions.disable": "Disable subscriber notification emails",
|
||||
"subscriptions.hour": "Digest Hour",
|
||||
"subscriptions.hour-help": "Please enter a number representing the hour to send scheduled email digests (e.g. <code>0</code> for midnight, <code>17</code> for 5:00pm). Keep in mind that this is the hour according to the server itself, and may not exactly match your system clock.<br /> The approximate server time is: <span id=\"serverTime\"></span><br /> The next daily digest is scheduled to be sent <span id=\"nextDigestTime\"></span>",
|
||||
"notifications.settings": "Email notification settings",
|
||||
"notifications.remove-images": "Remove images from email notifications"
|
||||
"subscriptions.hour-help": "Please enter a number representing the hour to send scheduled email digests (e.g. <code>0</code> for midnight, <code>17</code> for 5:00pm). Keep in mind that this is the hour according to the server itself, and may not exactly match your system clock.<br /> The approximate server time is: <span id=\"serverTime\"></span><br /> The next daily digest is scheduled to be sent <span id=\"nextDigestTime\"></span>"
|
||||
}
|
||||
@@ -1,44 +1,35 @@
|
||||
{
|
||||
"site-settings": "اعدادات الموقع",
|
||||
"title": "عنوان الموقع",
|
||||
"title.short": "عنوان قصير",
|
||||
"title.short-placeholder": "ان لم تقم بكتابة عنوان مختصر, سيتم استخدام عنوان الموقع الكلي",
|
||||
"title.url": "الرابط",
|
||||
"site-settings": "Site Settings",
|
||||
"title": "Site Title",
|
||||
"title.url": "URL",
|
||||
"title.url-placeholder": "The URL of the site title",
|
||||
"title.url-help": "When the title is clicked, send users to this address. If left blank, user will be sent to the forum index.",
|
||||
"title.name": "اسم المنتدي",
|
||||
"title.name": "Your Community Name",
|
||||
"title.show-in-header": "Show Site Title in Header",
|
||||
"browser-title": "عنوان المتصفح",
|
||||
"browser-title": "Browser Title",
|
||||
"browser-title-help": "If no browser title is specified, the site title will be used",
|
||||
"title-layout": "Title Layout",
|
||||
"title-layout-help": "Define how the browser title will be structured ie. {pageTitle} | {browserTitle}",
|
||||
"description.placeholder": "A short description about your community",
|
||||
"description": "وصف الموقع",
|
||||
"keywords": "الكلمات الدليله للموقع",
|
||||
"description": "Site Description",
|
||||
"keywords": "Site Keywords",
|
||||
"keywords-placeholder": "Keywords describing your community, comma-separated",
|
||||
"logo": "شعار الموقع",
|
||||
"logo.image": "صورة",
|
||||
"logo": "Site Logo",
|
||||
"logo.image": "Image",
|
||||
"logo.image-placeholder": "Path to a logo to display on forum header",
|
||||
"logo.upload": "رفع",
|
||||
"logo.url": "الرابط",
|
||||
"logo.upload": "Upload",
|
||||
"logo.url": "URL",
|
||||
"logo.url-placeholder": "The URL of the site logo",
|
||||
"logo.url-help": "When the logo is clicked, send users to this address. If left blank, user will be sent to the forum index.",
|
||||
"logo.alt-text": "نص بديل",
|
||||
"logo.alt-text": "Alt Text",
|
||||
"log.alt-text-placeholder": "Alternative text for accessibility",
|
||||
"favicon": "صورة المفضله",
|
||||
"favicon.upload": "رفع",
|
||||
"pwa": "Progressive Web App",
|
||||
"touch-icon": "Touch Icon",
|
||||
"touch-icon.upload": "رفع",
|
||||
"touch-icon.help": "Recommended size and format: 512x512, PNG format only. If no touch icon is specified, NodeBB will fall back to using the favicon.",
|
||||
"maskable-icon": "Maskable (Homescreen) Icon",
|
||||
"maskable-icon.help": "Recommended size and format: 512x512, PNG format only. If no maskable icon is specified, NodeBB will fall back to the Touch Icon.",
|
||||
"favicon": "Favicon",
|
||||
"favicon.upload": "Upload",
|
||||
"touch-icon": "Homescreen/Touch Icon",
|
||||
"touch-icon.upload": "Upload",
|
||||
"touch-icon.help": "Recommended size and format: 192x192, PNG format only. If no touch icon is specified, NodeBB will fall back to using the favicon.",
|
||||
"outgoing-links": "Outgoing Links",
|
||||
"outgoing-links.warning-page": "Use Outgoing Links Warning Page",
|
||||
"search-default-sort-by": "الترتيب الافتراضي للبحث",
|
||||
"outgoing-links.whitelist": "Domains to whitelist for bypassing the warning page",
|
||||
"site-colors": "Site Color Metadata",
|
||||
"theme-color": "لون الثيم",
|
||||
"background-color": "لون الخلفية",
|
||||
"background-color-help": "Color used for splash screen background when website is installed as a PWA"
|
||||
"search-default-sort-by": "Search default sort by",
|
||||
"outgoing-links.whitelist": "Domains to whitelist for bypassing the warning page"
|
||||
}
|
||||
@@ -3,10 +3,10 @@
|
||||
"private-groups": "Private Groups",
|
||||
"private-groups.help": "If enabled, joining of groups requires the approval of the group owner <em>(Default: enabled)</em>",
|
||||
"private-groups.warning": "<strong>Beware!</strong> If this option is disabled and you have private groups, they automatically become public.",
|
||||
"allow-multiple-badges": "Allow Multiple Badges",
|
||||
"allow-creation": "Allow Group Creation",
|
||||
"allow-creation-help": "If enabled, users can create groups <em>(Default: disabled)</em>",
|
||||
"allow-multiple-badges-help": "This flag can be used to allow users to select multiple group badges, requires theme support.",
|
||||
"max-name-length": "Maximum Group Name Length",
|
||||
"max-title-length": "Maximum Group Title Length",
|
||||
"cover-image": "Group Cover Image",
|
||||
"default-cover": "Default Cover Images",
|
||||
"default-cover-help": "Add comma-separated default cover images for groups that don't have an uploaded cover image"
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"settings": "Settings",
|
||||
"handles": "Guest Handles",
|
||||
"handles.enabled": "Allow guest handles",
|
||||
"handles.enabled-help": "This option exposes a new field that allows guests to pick a name to associate with each post they make. If disabled, they will simply be called \"Guest\"",
|
||||
"topic-views.enabled": "Allow guests to increase topic view counts",
|
||||
"reply-notifications.enabled": "Allow guests to generate reply notifications"
|
||||
"privileges": "Guest Privileges",
|
||||
"privileges.can-search": "Allow guests to search without logging in",
|
||||
"privileges.can-search-users": "Allow guests to search users without logging in"
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"language-settings": "اعدادات اللغة",
|
||||
"description": "تُحدد اللغة الافتراضية إعدادات اللغة لجميع المستخدمين الذين يزورون المنتدى. <br />يمكن للأعضاء تجاوز اللغة الافتراضية من خلال صفحة إعدادات الحساب الخاصة بهم.",
|
||||
"default-language": "اللغة الافتراضية",
|
||||
"auto-detect": "الكشف عن إعدادات اللغة للزوار بشكل آلي"
|
||||
}
|
||||
@@ -2,5 +2,6 @@
|
||||
"notifications": "Notifications",
|
||||
"welcome-notification": "Welcome Notification",
|
||||
"welcome-notification-link": "Welcome Notification Link",
|
||||
"welcome-notification-uid": "Welcome Notification User (UID)"
|
||||
"welcome-notification-uid": "Welcome Notification User (UID)",
|
||||
"notification-alert-timeout": "Notification Alert Timeout"
|
||||
}
|
||||
@@ -6,5 +6,6 @@
|
||||
"max-posts-per-page": "Maximum posts per page",
|
||||
"categories": "Category Pagination",
|
||||
"topics-per-page": "Topics per Page",
|
||||
"max-topics-per-page": "Maximum topics per page"
|
||||
"max-topics-per-page": "Maximum topics per page",
|
||||
"initial-num-load": "Initial Number of Topics to Load on Unread, Recent, and Popular"
|
||||
}
|
||||
@@ -7,22 +7,19 @@
|
||||
"sorting.most-posts": "Most Posts",
|
||||
"sorting.topic-default": "Default Topic Sorting",
|
||||
"length": "Post Length",
|
||||
"post-queue": "Post Queue",
|
||||
"restrictions": "Posting Restrictions",
|
||||
"restrictions-new": "New User Restrictions",
|
||||
"restrictions.post-queue": "Enable post queue",
|
||||
"restrictions.post-queue-rep-threshold": "Reputation required to bypass post queue",
|
||||
"restrictions.groups-exempt-from-post-queue": "Select groups that should be exempt from the post queue",
|
||||
"restrictions-new.post-queue": "Enable new user restrictions",
|
||||
"restrictions.post-queue-help": "Enabling post queue will put the posts of new users in a queue for approval",
|
||||
"restrictions-new.post-queue-help": "Enabling new user restrictions will set restrictions on posts created by new users",
|
||||
"restrictions.seconds-between": "Number of seconds between posts",
|
||||
"restrictions.post-queue-help": "Enabling post queue will put the posts of new users in a queue for approval.",
|
||||
"restrictions-new.post-queue-help": "Enabling new user restrictions will set restrictions on posts created by new users.",
|
||||
"restrictions.seconds-between": "Seconds between posts",
|
||||
"restrictions.seconds-between-new": "Seconds between posts for new users",
|
||||
"restrictions.rep-threshold": "Reputation threshold before these restrictions are lifted",
|
||||
"restrictions.seconds-before-new": "Seconds before a new user can make their first post",
|
||||
"restrictions.seconds-edit-after": "Number of seconds a post remains editable (set to 0 to disable)",
|
||||
"restrictions.seconds-delete-after": "Number of seconds a post remains deletable (set to 0 to disable)",
|
||||
"restrictions.replies-no-delete": "Number of replies after users are disallowed to delete their own topics (set to 0 to disable)",
|
||||
"restrictions.seconds-defore-new": "Seconds before new user can post",
|
||||
"restrictions.seconds-edit-after": "Number of seconds before users are allowed to edit posts after posting. (0 disabled)",
|
||||
"restrictions.seconds-delete-after": "Number of seconds before users are allowed to delete posts after posting. (0 disabled)",
|
||||
"restrictions.replies-no-delete": "Number of replies after users are disallowed to delete their own topics. (0 disabled)",
|
||||
"restrictions.min-title-length": "Minimum Title Length",
|
||||
"restrictions.max-title-length": "Maximum Title Length",
|
||||
"restrictions.min-post-length": "Minimum Post Length",
|
||||
@@ -32,10 +29,6 @@
|
||||
"timestamp": "Timestamp",
|
||||
"timestamp.cut-off": "Date cut-off (in days)",
|
||||
"timestamp.cut-off-help": "Dates & times will be shown in a relative manner (e.g. \"3 hours ago\" / \"5 days ago\"), and localised into various\n\t\t\t\t\tlanguages. After a certain point, this text can be switched to display the localised date itself\n\t\t\t\t\t(e.g. 5 Nov 2016 15:30).<br /><em>(Default: <code>30</code>, or one month). Set to 0 to always display dates, leave blank to always display relative times.</em>",
|
||||
"timestamp.necro-threshold": "Necro Threshold (in days)",
|
||||
"timestamp.necro-threshold-help": "A message will be shown between posts if the time between them is longer than the necro threshold. (Default: <code>7</code>, or one week). Set to 0 to disable.</em>",
|
||||
"timestamp.topic-views-interval": "Increment topic views interval (in minutes)",
|
||||
"timestamp.topic-views-interval-help": "Topic views will only increment once every X minutes as defined by this setting.",
|
||||
"teaser": "Teaser Post",
|
||||
"teaser.last-post": "Last – Show the latest post, including the original post, if no replies",
|
||||
"teaser.last-reply": "Last – Show the latest reply, or a \"No replies\" placeholder if no replies",
|
||||
@@ -44,7 +37,6 @@
|
||||
"unread.cutoff": "Unread cutoff days",
|
||||
"unread.min-track-last": "Minimum posts in topic before tracking last read",
|
||||
"recent": "Recent Settings",
|
||||
"recent.max-topics": "Maximum topics on /recent",
|
||||
"recent.categoryFilter.disable": "Disable filtering of topics in ignored categories on the /recent page",
|
||||
"signature": "Signature Settings",
|
||||
"signature.disable": "Disable signatures",
|
||||
|
||||
@@ -5,16 +5,8 @@
|
||||
"votes-are-public": "All Votes Are Public",
|
||||
"thresholds": "Activity Thresholds",
|
||||
"min-rep-downvote": "Minimum reputation to downvote posts",
|
||||
"downvotes-per-day": "Downvotes per day (set to 0 for unlimited downvotes)",
|
||||
"downvotes-per-user-per-day": "Downvotes per user per day (set to 0 for unlimited downvotes)",
|
||||
"min-rep-flag": "Minimum reputation to flag posts",
|
||||
"min-rep-website": "Minimum reputation to add \"Website\" to user profile",
|
||||
"min-rep-aboutme": "Minimum reputation to add \"About me\" to user profile",
|
||||
"min-rep-signature": "Minimum reputation to add \"Signature\" to user profile",
|
||||
"min-rep-profile-picture": "Minimum reputation to add \"Profile Picture\" to user profile",
|
||||
"min-rep-cover-picture": "Minimum reputation to add \"Cover Picture\" to user profile",
|
||||
|
||||
"flags": "Flag Settings",
|
||||
"flags.limit-per-target": "Maximum number of times something can be flagged",
|
||||
"flags.limit-per-target-placeholder": "Default: 0"
|
||||
"min-rep-signature": "Minimum reputation to add \"Signature\" to user profile"
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"notifications": "التنبيهات",
|
||||
"chat-messages": "Chat Messages",
|
||||
"play-sound": "Play",
|
||||
"incoming-message": "Incoming Message",
|
||||
"outgoing-message": "Outgoing Message",
|
||||
"upload-new-sound": "Upload New Sound",
|
||||
"saved": "Settings Saved"
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
{
|
||||
"tag": "Tag Settings",
|
||||
"link-to-manage": "Manage Tags",
|
||||
"min-per-topic": "Minimum Tags per Topic",
|
||||
"max-per-topic": "Maximum Tags per Topic",
|
||||
"min-length": "Minimum Tag Length",
|
||||
"max-length": "Maximum Tag Length",
|
||||
"goto-manage": "Click here to visit the tag management page.",
|
||||
"privacy": "Privacy",
|
||||
"list-private": "Make the tags list private",
|
||||
"related-topics": "Related Topics",
|
||||
"max-related-topics": "Maximum related topics to display (if supported by theme)"
|
||||
}
|
||||
@@ -2,21 +2,12 @@
|
||||
"posts": "المشاركات",
|
||||
"allow-files": "السماح للأعضاء بتحميل الملفات الإعتيادية",
|
||||
"private": "جعل الملفات التي تم رفعها خاصة",
|
||||
"strip-exif-data": "Strip EXIF Data",
|
||||
"private-extensions": "File extensions to make private",
|
||||
"private-uploads-extensions-help": "Enter comma-separated list of file extensions to make private here (e.g. <code>pdf,xls,doc</code>). An empty list means all files are private.",
|
||||
"resize-image-width-threshold": "Resize images if they are wider than specified width",
|
||||
"resize-image-width-threshold-help": "(in pixels, default: 1520 pixels, set to 0 to disable)",
|
||||
"resize-image-width": "Resize images down to specified width",
|
||||
"resize-image-width-help": "(in pixels, default: 760 pixels, set to 0 to disable)",
|
||||
"max-image-width": "تغيير حجم الصور إلى عرض محدد (بالبكسل)",
|
||||
"max-image-width-help": "(بالبكسل، الافتراضي: 760 بكسل، ضع إلى 0 لتعطيل الخاصية)",
|
||||
"resize-image-quality": "Quality to use when resizing images",
|
||||
"resize-image-quality-help": "Use a lower quality setting to reduce the file size of resized images.",
|
||||
"max-file-size": "الحد الأقصى لحجم الملف (بالكيبيبايت)",
|
||||
"max-file-size-help": "(بالكيبيبايت، الافتراضي: 2048)",
|
||||
"reject-image-width": "Maximum Image Width (in pixels)",
|
||||
"reject-image-width-help": "Images wider than this value will be rejected.",
|
||||
"reject-image-height": "Maximum Image Height (in pixels)",
|
||||
"reject-image-height-help": "Images taller than this value will be rejected.",
|
||||
"allow-topic-thumbnails": "السماح للاعضاء برفع الصور المصغرة للموضوع",
|
||||
"topic-thumb-size": "حجم الصورة المصغرة للموضوع",
|
||||
"allowed-file-extensions": "إمتدادات الملفات المسموح بها",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"authentication": "المصادقة",
|
||||
"allow-local-login": "السماح بتسجيل الدخول المحلي",
|
||||
"require-email-confirmation": "يطلب تأكيد البريد الإلكتروني",
|
||||
"email-confirm-interval": "لا يمكن للمستخدم إعادة إرسال رسالة تأكيد البريد الالكتروني حتى مرور",
|
||||
"email-confirm-email2": "دقائق",
|
||||
@@ -8,15 +9,13 @@
|
||||
"allow-login-with.username": "اسم المستخدم فقط",
|
||||
"allow-login-with.email": "البريد الالكتروني فقط",
|
||||
"account-settings": "إعدادت الحساب",
|
||||
"gdpr_enabled": "Enable GDPR consent collection",
|
||||
"gdpr_enabled_help": "When enabled, all new registrants will be required to explicitly give consent for data collection and usage under the <a href=\"https://ec.europa.eu/info/priorities/justice-and-fundamental-rights/data-protection/2018-reform-eu-data-protection-rules/eu-data-protection-rules_en\">General Data Protection Regulation (GDPR)</a>. <strong>Note</strong>: Enabling GDPR does not force pre-existing users to provide consent. To do so, you will need to install the GDPR plugin.",
|
||||
"disable-username-changes": "عدم السماح بتغيير اسم المستخدم",
|
||||
"disable-email-changes": "عدم السماح بتغيير البريد الالكتروني",
|
||||
"disable-password-changes": "عدم السماح بتغيير كلمة المرور",
|
||||
"allow-account-deletion": "السماح بحذف الحساب",
|
||||
"user-info-private": "إخفاء قائمة المستخدم والبيانات عن الزوار",
|
||||
"hide-fullname": "إخفاء الإسم الكامل عن المستخدمين",
|
||||
"hide-email": "إخفاء البريد الإلكتروني عن المستخدمين",
|
||||
"show-fullname-as-displayname": "Show user's full name as their display name if available",
|
||||
"themes": "القوالب",
|
||||
"disable-user-skins": "منع المستخدمين من اختيار سمة مخصص",
|
||||
"account-protection": "حماية الحساب",
|
||||
@@ -27,26 +26,15 @@
|
||||
"lockout-duration": "مدة تأمين الحساب (دقائق)",
|
||||
"login-days": "عدد الأيام لتذكر جلسات تسجيل دخول المستخدم",
|
||||
"password-expiry-days": "فرض عملية تغيير كلمة المرور بعد مرور عدد محدد من الأيام",
|
||||
"session-time": "Session Time",
|
||||
"session-time-days": "Days",
|
||||
"session-time-seconds": "Seconds",
|
||||
"session-time-help": "These values are used to govern how long a user stays logged in when they check "Remember Me" on login. Note that only one of these values will be used. If there is no <i>seconds</i> value we fall back to <i>days</i>. If there is no <i>days</i> value we default to <i>14 days</i>.",
|
||||
"online-cutoff": "Minutes after user is considered inactive",
|
||||
"online-cutoff-help": "If user performs no actions for this duration, they are considered inactive and they do not receive realtime updates.",
|
||||
"registration": "تسجيل المستخدم",
|
||||
"registration-type": "نوع التسجيل",
|
||||
"registration-approval-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": "Normal - Users can register from the /register page.<br/>\nInvite Only - Users can invite others from the <a href=\"%1/users\" target=\"_blank\">users</a> page.<br/>\nAdmin Invite Only - Only administrators can invite others from <a href=\"%1/users\" target=\"_blank\">users</a> and <a href=\"%1/admin/manage/users\">admin/manage/users</a> pages.<br/>\nNo registration - No user registration.<br/>",
|
||||
"registration-approval-type.help": "Normal - Users are registered immediately.<br/>\nAdmin Approval - User registrations are placed in an <a href=\"%1/admin/manage/registration\">approval queue</a> for administrators.<br/>\nAdmin Approval for IPs - Normal for new users, Admin Approval for IP addresses that already have an account.<br/>",
|
||||
"registration-queue-auto-approve-time": "Automatic Approval Time",
|
||||
"registration-queue-auto-approve-time-help": "Hours before user is approved automatically. 0 to disable.",
|
||||
"registration-queue-show-average-time": "Show users average time it takes to approve a new user",
|
||||
"registration-type.help": "عادي - بإمكان المستخدمين التسجيل من خلال صفحة /التسجيل.<br/>\nبموافقة الإدارة - يتم وضع قائمة تسجيلات المستخدمين في <a href=\"%1/admin/manage/registration\">قائمة إنتظار موافقة</a> الإدارة.<br/>\nبموافقة الإدارة لعناوين IP - عادي بالنسبة للمستخدمين الجدد، ويتطلب موافقة الإدارة للتسجيل من عناوين IP تم التسجيل بإستخدامها مسبقاً.<br/>\nبالدعوات فقط - بإمكان المستخدمين إرسال دعوات من خلال صفحة <a href=\"%1/users\" target=\"_blank\">الأعضاء</a>.<br/>\nبالدعوات من قبل الإدارة فقط - فقط المراء بإمكانهم إرسال الدعوات من خلال صفحة <a href=\"%1/users\" target=\"_blank\">الأعضاء</a> و صفحات <a href=\"%1/admin/manage/users\">الإدارة/المشرفين/الأعضاء</a>.<br/>\nلا يوجد تسجيل - إغلاق خيار التسجيل بالكامل.<br/>",
|
||||
"registration.max-invites": "الحد الأقصى للدعوات لكل عضو",
|
||||
"max-invites": "الحد الأقصى للدعوات لكل عضو",
|
||||
"max-invites-help": "0 لعدم تحديد قيود، الإدارة تحصل على دعوات لامحدودة <br> هذا الخيار يعمل فقط عند تحديد خيار \"بالدعوات فقط\"",
|
||||
@@ -66,7 +54,6 @@
|
||||
"restrict-chat": "السماح فقط برسائل الدردشة من المستخدمين الذين أتبعهم",
|
||||
"outgoing-new-tab": "Open outgoing links in new tab",
|
||||
"topic-search": "Enable In-Topic Searching",
|
||||
"update-url-with-post-index": "Update url with post index while browsing topics",
|
||||
"digest-freq": "Subscribe to Digest",
|
||||
"digest-freq.off": "Off",
|
||||
"digest-freq.daily": "Daily",
|
||||
@@ -76,9 +63,5 @@
|
||||
"email-post-notif": "Send an email when replies are made to topics I am subscribed to",
|
||||
"follow-created-topics": "Follow topics you create",
|
||||
"follow-replied-topics": "Follow topics that you reply to",
|
||||
"default-notification-settings": "Default notification settings",
|
||||
"categoryWatchState": "Default category watch state",
|
||||
"categoryWatchState.watching": "Watching",
|
||||
"categoryWatchState.notwatching": "Not Watching",
|
||||
"categoryWatchState.ignoring": "Ignoring"
|
||||
}
|
||||
"default-notification-settings": "Default notification settings"
|
||||
}
|
||||
@@ -7,16 +7,14 @@
|
||||
"browsing": "تصفح",
|
||||
"no_replies": "لم يرد أحد",
|
||||
"no_new_posts": "لا توجد مشاركات جديدة.",
|
||||
"share_this_category": "شارك هذا القسم",
|
||||
"watch": "تابع",
|
||||
"ignore": "تجاهل",
|
||||
"watching": "متابع",
|
||||
"not-watching": "لست متابع",
|
||||
"ignoring": "متجاهل",
|
||||
"watching.description": "أظهر المواضيع في الغير مقروء و الحديث",
|
||||
"not-watching.description": "Do not show topics in unread, show in recent",
|
||||
"ignoring.description": "Do not show topics in unread and recent",
|
||||
"watching.message": "You are now watching updates from this category and all subcategories",
|
||||
"notwatching.message": "You are not watching updates from this category and all subcategories",
|
||||
"ignoring.message": "You are now ignoring updates from this category and all subcategories",
|
||||
"watching.description": "أظهر المواضيع في غير مقروء",
|
||||
"ignoring.description": "لا تظهر المواضيع في غير مقروء",
|
||||
"watch.message": "أنت الآن تتابع التحديثات من هذا القسم وجميع الأقسام الفرعية",
|
||||
"ignore.message": "أنت الآن تتجاهل التحديثات من هذا القسم وجميع الأقسام الفرعية",
|
||||
"watched-categories": "الأقسام المُتابعة"
|
||||
}
|
||||
@@ -1,51 +1,39 @@
|
||||
{
|
||||
"test-email.subject": "Test Email",
|
||||
"password-reset-requested": "Password Reset Requested!",
|
||||
"password-reset-requested": "تم طلب إعادة تعيين كلمة المرور - %1!",
|
||||
"welcome-to": "مرحبًا بك في %1",
|
||||
"invite": "دعوة من %1",
|
||||
"greeting_no_name": "مرحبًا",
|
||||
"greeting_with_name": "مرحبًا بك يا %1",
|
||||
"email.verify-your-email.subject": "Please verify your email",
|
||||
"email.verify.text1": "Your email address has changed!",
|
||||
"welcome.text1": "شكرًا على تسجيلك في %1!",
|
||||
"welcome.text2": "لتفعيل حسابك، نحتاج إلى التأكد من صحة عنوان البريد الإلكتروني الذي سجلت به.",
|
||||
"welcome.text3": "تم قبول نتسجيلك ، يمكنك الدخول باتسخدام اسم المستخدم و كلمة المرور.",
|
||||
"welcome.cta": "انقر هنا لتفعيل عنوان بريدك الإلكتروني",
|
||||
"invitation.text1": "%1 قام بدعوتك للانضمام لـ %2",
|
||||
"invitation.text2": "Your invitation will expire in %1 days.",
|
||||
"invitation.cta": "Click here to create your account.",
|
||||
"invitation.ctr": "إضغط هنا لإنشاء حسابك",
|
||||
"reset.text1": "لقد توصلنا بطلب إعادة تعيين كلمة المرور الخاصة بك، ربما لكونك قد نسيتها, إن لم يكن الأمر كذلك، المرجو تجاهل هذه الرسالة.",
|
||||
"reset.text2": "لمواصلة طلب إعاة تعيين كلمة المرور، الرجاء تتبع هذا الرابط.",
|
||||
"reset.cta": "انقر هنا لإعادة تعيين كلمة السر الخاصة بك.",
|
||||
"reset.notify.subject": "تم تغيير كلمة المرور بنجاح",
|
||||
"reset.notify.text1": "نحيطك علما أن كلمة مرورك قد تم تغييرها في %1",
|
||||
"reset.notify.text2": "إن لم يكن لديك علم بهذا، المرجو إشعار مدبر النظام بأسرع مايمكن.",
|
||||
"digest.notifications": "لديك تنبيهات غير مقروءة من طرف %1:",
|
||||
"digest.latest_topics": "آخر المستجدات من %1",
|
||||
"digest.top-topics": "Top topics from %1",
|
||||
"digest.popular-topics": "Popular topics from %1",
|
||||
"digest.cta": "انقر هنا لمشاهدة %1",
|
||||
"digest.unsub.info": "تم إرسال هذا الإشعار بآخر المستجدات وفقا لخيارات تسجيلكم.",
|
||||
"digest.no_topics": "ليس هناك مواضيع نشيطة في %1 الماضي",
|
||||
"digest.day": "يوم",
|
||||
"digest.week": "أسبوع",
|
||||
"digest.month": "شهر",
|
||||
"digest.subject": "إستهلاك ل",
|
||||
"digest.title.day": "Your Daily Digest",
|
||||
"digest.title.week": "Your Weekly Digest",
|
||||
"digest.title.month": "Your Monthly Digest",
|
||||
"notif.chat.subject": "هناك محادثة جديدة من %1",
|
||||
"notif.chat.cta": "انقر هنا لمتابعة المحادثة",
|
||||
"notif.chat.unsub.info": "تم إرسال هذا الإشعار بوجودة محادثة جديدة وفقا لخيارات تسجيلك.",
|
||||
"notif.post.cta": "انقر هنا لقراءة الموضوع بأكمله",
|
||||
"notif.post.unsub.info": "تم إشعارك بهذه المشاركة بناءً على الخيارات التي سبق وأن حددتها.",
|
||||
"notif.post.unsub.one-click": "Alternatively, unsubscribe from future emails like this, by clicking",
|
||||
"notif.cta": "To the forum",
|
||||
"notif.cta-new-reply": "View Post",
|
||||
"notif.cta-new-chat": "View Chat",
|
||||
"notif.test.short": "Testing Notifications",
|
||||
"notif.test.long": "This is a test of the notifications email. Send help!",
|
||||
"notif.cta": "Click here to go to forum",
|
||||
"test.text1": "هذه رسالة تجريبية للتأكد من صحة إعدادت الرسائل الإلكترونية في منتدى NodeBB خاصتك.",
|
||||
"unsub.cta": "انقر هنا لتغيير تلك الإعدادات",
|
||||
"unsubscribe": "unsubscribe",
|
||||
"unsub.success": "You will no longer receive emails from the <strong>%1</strong> mailing list",
|
||||
"banned.subject": "You have been banned from %1",
|
||||
"banned.text1": "The user %1 has been banned from %2.",
|
||||
"banned.text2": "This ban will last until %1.",
|
||||
|
||||
@@ -9,12 +9,8 @@
|
||||
"invalid-tid": "موضوع غير متواجد",
|
||||
"invalid-pid": "رد غير موجود",
|
||||
"invalid-uid": "مستخدم غير موجود",
|
||||
"invalid-date": "A valid date must be provided",
|
||||
"invalid-username": "اسم المستخدم غير مقبول",
|
||||
"invalid-email": "البريد الاكتروني غير مقبول",
|
||||
"invalid-fullname": "Invalid Fullname",
|
||||
"invalid-location": "Invalid Location",
|
||||
"invalid-birthday": "Invalid Birthday",
|
||||
"invalid-title": "Invalid title",
|
||||
"invalid-user-data": "بيانات المستخدم غير صحيحة",
|
||||
"invalid-password": "كلمة السر غير مقبولة",
|
||||
@@ -27,19 +23,16 @@
|
||||
"invalid-pagination-value": "رقم الصفحة غير صحيح ، يجب أن يكون بين %1 و %2 .",
|
||||
"username-taken": "اسم المستخدم مأخوذ",
|
||||
"email-taken": "البريد الالكتروني مأخوذ",
|
||||
"email-invited": "Email was already invited",
|
||||
"email-not-confirmed": "You are unable to post until your email is confirmed, please click here to confirm your email.",
|
||||
"email-not-confirmed": "عنوان بريدك الإلكتروني غير مفعل بعد. انقر هنا لتفعيله من فضلك.",
|
||||
"email-not-confirmed-chat": "لا يمكنك الدردشة حتى تقوم بتأكيد بريدك الإلكتروني، الرجاء إضغط هنا لتأكيد بريدك اﻹلكتروني.",
|
||||
"email-not-confirmed-email-sent": "Your email has not been confirmed yet, please check your inbox for the confirmation email. You won't be able to post or chat until your email is confirmed.",
|
||||
"email-not-confirmed-email-sent": "بريدك الإلكتروني غير مفعل. رجاءًا تفحص صندوق البريد عن رسالة التفعيل.",
|
||||
"no-email-to-confirm": "هذا المنتدى يستلزم تفعيل بريدك الإلكتروني، انقر هنا من فضلك لإدخاله.",
|
||||
"email-confirm-failed": "لم نستطع تفعيل بريدك الإلكتروني، المرجو المحاولة لاحقًا.",
|
||||
"confirm-email-already-sent": "لقد تم ارسال بريد التأكيد، الرجاء اﻹنتظار 1% دقائق لإعادة اﻹرسال",
|
||||
"sendmail-not-found": "The sendmail executable could not be found, please ensure it is installed and executable by the user running NodeBB.",
|
||||
"digest-not-enabled": "This user does not have digests enabled, or the system default is not configured to send digests",
|
||||
"username-too-short": "اسم المستخدم قصير.",
|
||||
"username-too-long": "اسم المستخدم طويل",
|
||||
"password-too-long": "كلمة السر طويلة ",
|
||||
"reset-rate-limited": "Too many password reset requests (rate limited)",
|
||||
"user-banned": "المستخدم محظور",
|
||||
"user-banned-reason": "Sorry, this account has been banned (Reason: %1)",
|
||||
"user-banned-reason-until": "Sorry, this account has been banned until %1 (Reason: %2)",
|
||||
@@ -85,7 +78,6 @@
|
||||
"still-uploading": "الرجاء الانتظار حتى يكتمل الرفع.",
|
||||
"file-too-big": "الحد الأقصى لرفع الملفات %1 كيلو بت. رجاءًا ارفع ملف أصغر",
|
||||
"guest-upload-disabled": "خاصية رفع الملفات غير مفعلة للزوار.",
|
||||
"cors-error": "Unable to upload image due to misconfigured CORS",
|
||||
"already-bookmarked": "You have already bookmarked this post",
|
||||
"already-unbookmarked": "You have already unbookmarked this post",
|
||||
"cant-ban-other-admins": "لايمكن حظر مدبر نظام آخر.",
|
||||
@@ -95,7 +87,6 @@
|
||||
"invalid-image-type": "نوع الصورة غير مدعوم. الأنواع المدعومة هي : %1",
|
||||
"invalid-image-extension": "امتداد الصورة غير مدعوم.",
|
||||
"invalid-file-type": "صيغة الملف غير مدعومة. الأنواع المدعومة هي: %1",
|
||||
"invalid-image-dimensions": "Image dimensions are too big",
|
||||
"group-name-too-short": "اسم المجموعة قصير",
|
||||
"group-name-too-long": "اسم المجموعة طويل.",
|
||||
"group-already-exists": "المجموعة موجودة مسبقا",
|
||||
@@ -105,8 +96,6 @@
|
||||
"group-needs-owner": "هذه المجموعة تتطلب مالك واحد على اﻷقل",
|
||||
"group-already-invited": "المستخدم سبق وأن تمت دعوته",
|
||||
"group-already-requested": "سبق وتم تسجيل طلب العضوية",
|
||||
"group-join-disabled": "You are not able to join this group at this time",
|
||||
"group-leave-disabled": "You are not able to leave this group at this time",
|
||||
"post-already-deleted": "سبق وتم حذف هذا الرد",
|
||||
"post-already-restored": "سبق وتم إلغاء حذف هذا الرد",
|
||||
"topic-already-deleted": "سبق وتم حذف هذا الموضوع",
|
||||
@@ -129,7 +118,7 @@
|
||||
"chat-edit-duration-expired": "You are only allowed to edit chat messages for %1 second(s) after posting",
|
||||
"chat-delete-duration-expired": "You are only allowed to delete chat messages for %1 second(s) after posting",
|
||||
"chat-deleted-already": "This chat message has already been deleted.",
|
||||
"chat-restored-already": "This chat message has already been restored.",
|
||||
"chat-restored'already": "This chat message has already been restored.",
|
||||
"already-voting-for-this-post": "لقد شاركت بالتصويت ، ألا تذكر؟",
|
||||
"reputation-system-disabled": "نظام السمعة معطل",
|
||||
"downvoting-disabled": "التصويتات السلبية معطلة",
|
||||
@@ -138,15 +127,8 @@
|
||||
"not-enough-reputation-min-rep-website": "You do not have enough reputation to add a website",
|
||||
"not-enough-reputation-min-rep-aboutme": "You do not have enough reputation to add an about me",
|
||||
"not-enough-reputation-min-rep-signature": "You do not have enough reputation to add a signature",
|
||||
"not-enough-reputation-min-rep-profile-picture": "You do not have enough reputation to add a profile picture",
|
||||
"not-enough-reputation-min-rep-cover-picture": "You do not have enough reputation to add a cover picture",
|
||||
"post-already-flagged": "You have already flagged this post",
|
||||
"user-already-flagged": "You have already flagged this user",
|
||||
"post-flagged-too-many-times": "This post has been flagged by others already",
|
||||
"user-flagged-too-many-times": "This user has been flagged by others already",
|
||||
"already-flagged": "لقد بلغت عن هذه المشاركة من قبل.",
|
||||
"self-vote": "You cannot vote on your own post",
|
||||
"too-many-downvotes-today": "You can only downvote %1 times a day",
|
||||
"too-many-downvotes-today-user": "You can only downvote a user %1 times a day",
|
||||
"reload-failed": "المنتدى واجه مشكلة أثناء إعادة التحميل: \"%1\". سيواصل المنتدى خدمة العملاء السابقين لكن يجب عليك إلغاء أي تغيير قمت به قبل إعادة التحميل.",
|
||||
"registration-error": "حدث خطأ أثناء التسجيل",
|
||||
"parse-error": "حدث خطأ ما أثناء تحليل استجابة الخادم",
|
||||
@@ -157,6 +139,7 @@
|
||||
"invite-maximum-met": "لقد قمت بدعوة الحد الأقصى من الأشخاص (%1 من %2)",
|
||||
"no-session-found": "لم دخول مسجل!",
|
||||
"not-in-room": "المستخدم غير موجود في الغرفة.",
|
||||
"no-users-in-room": "لا يوجد مستخدمين في الغرفة.",
|
||||
"cant-kick-self": "لا يمكنك طرد نفسك من المجموعة.",
|
||||
"no-users-selected": "لا يوجد مستخدم محدد.",
|
||||
"invalid-home-page-route": "Invalid home page route",
|
||||
@@ -164,12 +147,6 @@
|
||||
"invalid-session-text": "يبدو أن فترة التسجيل لم تعد قائمة او هي غير مطابقة مع الخادم. يرجى إعادة تحميل هذه الصفحة.",
|
||||
"no-topics-selected": "No topics selected!",
|
||||
"cant-move-to-same-topic": "Can't move post to same topic!",
|
||||
"cant-move-topic-to-same-category": "Can't move topic to the same category!",
|
||||
"cannot-block-self": "You cannot block yourself!",
|
||||
"cannot-block-privileged": "You cannot block administrators or global moderators",
|
||||
"cannot-block-guest": "Guest are not able to block other users",
|
||||
"already-blocked": "This user is already blocked",
|
||||
"already-unblocked": "This user is already unblocked",
|
||||
"no-connection": "There seems to be a problem with your internet connection",
|
||||
"plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP"
|
||||
"cannot-block-privileged": "You cannot block administrators or global moderators"
|
||||
}
|
||||