Merge branch 'develop' into single-server

# Conflicts:
#	android/app/build.gradle
#	android/gradle.properties
#	ios/Pods/Pods.xcodeproj/project.pbxproj
#	ios/RocketChatRN.xcodeproj/project.pbxproj
This commit is contained in:
Diego Mello 2020-12-15 15:38:04 -03:00
commit 3ddbe6cc8e
412 changed files with 31358 additions and 25625 deletions

View File

@ -8,6 +8,11 @@ macos: &macos
bash-env: &bash-env
BASH_ENV: "~/.nvm/nvm.sh"
android-env: &android-env
JAVA_OPTS: '-Xms512m -Xmx2g'
GRADLE_OPTS: '-Xmx3g -Dorg.gradle.daemon=false -Dorg.gradle.jvmargs="-Xmx2g -XX:+HeapDumpOnOutOfMemoryError"'
TERM: dumb
install-npm-modules: &install-npm-modules
name: Install NPM modules
command: yarn
@ -75,6 +80,231 @@ restore_cache: &restore-gradle-cache
name: Restore gradle cache
key: android-{{ checksum "android/build.gradle" }}-{{ checksum "android/app/build.gradle" }}
# COMMANDS
commands:
android-build:
description: "Build Android app"
steps:
- checkout
- run: *install-node
- restore_cache: *restore-npm-cache-linux
- run: *install-npm-modules
- restore_cache: *restore-gradle-cache
- run:
name: Configure Gradle
command: |
echo -e "" > ./gradle.properties
# echo -e "android.enableAapt2=false" >> ./gradle.properties
echo -e "android.useAndroidX=true" >> ./gradle.properties
echo -e "android.enableJetifier=true" >> ./gradle.properties
echo -e "FLIPPER_VERSION=0.51.0" >> ./gradle.properties
echo -e "VERSIONCODE=$CIRCLE_BUILD_NUM" >> ./gradle.properties
if [[ $CIRCLE_JOB == "android-build-official" ]]; then
echo -e "APPLICATION_ID=chat.rocket.android" >> ./gradle.properties
echo -e "BugsnagAPIKey=$BUGSNAG_KEY_OFFICIAL" >> ./gradle.properties
echo $CHAT_ROCKET_ANDROID_STORE_FILE_BASE64_JKS | base64 --decode > ./app/$KEYSTORE_OFFICIAL
echo -e "KEYSTORE=$KEYSTORE_OFFICIAL" >> ./gradle.properties
echo -e "KEYSTORE_PASSWORD=$CHAT_ROCKET_ANDROID_STORE_PASSWORD" >> ./gradle.properties
echo -e "KEY_ALIAS=$CHAT_ROCKET_ANDROID_KEY_ALIAS" >> ./gradle.properties
echo -e "KEY_PASSWORD=$CHAT_ROCKET_ANDROID_KEY_PASSWORD" >> ./gradle.properties
else
echo -e "APPLICATION_ID=chat.rocket.reactnative" >> ./gradle.properties
echo -e "BugsnagAPIKey=$BUGSNAG_KEY" >> ./gradle.properties
echo $KEYSTORE_BASE64 | base64 --decode > ./app/$KEYSTORE
echo -e "KEYSTORE=$KEYSTORE" >> ./gradle.properties
echo -e "KEYSTORE_PASSWORD=$KEYSTORE_PASSWORD" >> ./gradle.properties
echo -e "KEY_ALIAS=$KEY_ALIAS" >> ./gradle.properties
echo -e "KEY_PASSWORD=$KEYSTORE_PASSWORD" >> ./gradle.properties
fi
working_directory: android
- run:
name: Set Google Services
command: |
if [[ $KEYSTORE ]]; then
echo $GOOGLE_SERVICES_ANDROID | base64 --decode > google-services.json
fi
working_directory: android/app
- run:
name: Config variables
command: |
if [[ $CIRCLE_JOB == "android-build-official" ]]; then
echo -e "export default { BUGSNAG_API_KEY: '$BUGSNAG_KEY_OFFICIAL' };" > ./config.js
else
echo -e "export default { BUGSNAG_API_KEY: '$BUGSNAG_KEY' };" > ./config.js
fi
- run:
name: Build App
command: |
if [[ $CIRCLE_JOB == "android-build-official" ]]; then
./gradlew bundleOfficialPlayRelease
fi
if [[ $CIRCLE_JOB == "android-build-experimental" ]]; then
./gradlew bundleExperimentalPlayRelease
fi
if [[ ! $KEYSTORE ]]; then
./gradlew assembleExperimentalPlayDebug
fi
working_directory: android
- run:
name: Upload sourcemaps to Bugsnag
command: |
if [[ $CIRCLE_JOB == "android-build-official" ]]; then
yarn generate-source-maps-android upload \
--api-key=$BUGSNAG_KEY_OFFICIAL \
--app-version=$CIRCLE_BUILD_NUM \
--minifiedFile=android/app/build/generated/assets/react/officialPlay/release/app.bundle \
--source-map=android/app/build/generated/sourcemaps/react/officialPlay/release/app.bundle.map \
--minified-url=app.bundle \
--upload-sources
fi
if [[ $CIRCLE_JOB == "android-build-experimental" ]]; then
yarn generate-source-maps-android upload \
--api-key=$BUGSNAG_KEY \
--app-version=$CIRCLE_BUILD_NUM \
--minifiedFile=android/app/build/generated/assets/react/experimentalPlay/release/app.bundle \
--source-map=android/app/build/generated/sourcemaps/react/experimentalPlay/release/app.bundle.map \
--minified-url=app.bundle \
--upload-sources
fi
- store_artifacts:
path: android/app/build/outputs
- save_cache: *save-npm-cache-linux
- save_cache: *save-gradle-cache
- persist_to_workspace:
root: .
paths:
- android/app/build/outputs
ios-build:
description: "Build iOS app"
steps:
- checkout
- restore_cache: *restore-gems-cache
- restore_cache: *restore-npm-cache-mac
- run: *install-node
- run: *install-npm-modules
- run: *update-fastlane-ios
- run:
name: Set Google Services
command: |
if [[ $KEYSTORE ]]; then
echo $GOOGLE_SERVICES_IOS | base64 --decode > GoogleService-Info.plist
fi
working_directory: ios
- run:
name: Upload sourcemaps to Bugsnag
command: |
if [[ $CIRCLE_JOB == "ios-build-official" ]]; then
yarn generate-source-maps-ios
curl https://upload.bugsnag.com/react-native-source-map \
-F apiKey=$BUGSNAG_KEY_OFFICIAL \
-F appBundleVersion=$CIRCLE_BUILD_NUM \
-F dev=false \
-F platform=ios \
-F sourceMap=@ios-release.bundle.map \
-F bundle=@ios-release.bundle
fi
if [[ $CIRCLE_JOB == "ios-build-experimental" ]]; then
yarn generate-source-maps-ios
curl https://upload.bugsnag.com/react-native-source-map \
-F apiKey=$BUGSNAG_KEY \
-F appBundleVersion=$CIRCLE_BUILD_NUM \
-F dev=false \
-F platform=ios \
-F sourceMap=@ios-release.bundle.map \
-F bundle=@ios-release.bundle
fi
- run:
name: Fastlane Build
no_output_timeout: 1200
command: |
agvtool new-version -all $CIRCLE_BUILD_NUM
if [[ $CIRCLE_JOB == "ios-build-official" ]]; then
/usr/libexec/PlistBuddy -c "Set BugsnagAPIKey $BUGSNAG_KEY_OFFICIAL" ./RocketChatRN/Info.plist
/usr/libexec/PlistBuddy -c "Set IS_OFFICIAL YES" ./RocketChatRN/Info.plist
else
/usr/libexec/PlistBuddy -c "Set BugsnagAPIKey $BUGSNAG_KEY" ./RocketChatRN/Info.plist
/usr/libexec/PlistBuddy -c "Set IS_OFFICIAL NO" ./RocketChatRN/Info.plist
fi
if [[ $APP_STORE_CONNECT_API_KEY ]]; then
echo $APP_STORE_CONNECT_API_KEY | base64 --decode > ./fastlane/app_store_connect_api_key.p8
if [[ $CIRCLE_JOB == "ios-build-official" ]]; then
bundle exec fastlane ios build_official
else
if [[ $KEYSTORE ]]; then
bundle exec fastlane ios release # TODO: rename
else
bundle exec fastlane ios build_fork
fi
fi
fi
working_directory: ios
- save_cache: *save-npm-cache-mac
- save_cache: *save-gems-cache
- store_artifacts:
path: ios/Rocket.Chat.ipa
- store_artifacts:
path: ios/Rocket.Chat.app.dSYM.zip
- persist_to_workspace:
root: .
paths:
- ios/*.ipa
- ios/*.zip
upload-to-google-play-beta:
description: "Upload to Google Play beta"
parameters:
official:
type: boolean
steps:
- checkout
- attach_workspace:
at: android
- run:
name: Store the google service account key
command: echo "$FASTLANE_GOOGLE_SERVICE_ACCOUNT" | base64 --decode > service_account.json
working_directory: android
- run: *update-fastlane-android
- run:
name: Fastlane Play Store Upload
command: bundle exec fastlane android beta official:<< parameters.official >>
working_directory: android
upload-to-testflight:
description: "Upload to TestFlight"
parameters:
official:
type: boolean
steps:
- checkout
- attach_workspace:
at: ios
- restore_cache: *restore-gems-cache
- run: *update-fastlane-ios
- run:
name: Fastlane Tesflight Upload
command: |
echo $APP_STORE_CONNECT_API_KEY | base64 --decode > ./fastlane/app_store_connect_api_key.p8
bundle exec fastlane ios beta official:<< parameters.official >>
working_directory: ios
- save_cache: *save-gems-cache
version: 2.1
# EXECUTORS
@ -119,303 +349,124 @@ jobs:
- save_cache: *save-npm-cache-linux
# Android builds
android-play-build:
android-build-experimental:
<<: *defaults
docker:
- image: circleci/android:api-28-node
environment:
JAVA_OPTS: '-Xms512m -Xmx2g'
GRADLE_OPTS: '-Xmx3g -Dorg.gradle.daemon=false -Dorg.gradle.jvmargs="-Xmx2g -XX:+HeapDumpOnOutOfMemoryError"'
TERM: dumb
<<: *android-env
<<: *bash-env
steps:
- checkout
- android-build
- run: *install-node
- restore_cache: *restore-npm-cache-linux
- run: *install-npm-modules
- run: *update-fastlane-android
- restore_cache: *restore-gradle-cache
- run:
name: Configure Gradle
command: |
echo -e "" > ./gradle.properties
# echo -e "android.enableAapt2=false" >> ./gradle.properties
echo -e "android.useAndroidX=true" >> ./gradle.properties
echo -e "android.enableJetifier=true" >> ./gradle.properties
echo -e "FLIPPER_VERSION=0.51.0" >> ./gradle.properties
if [[ $KEYSTORE ]]; then
echo $KEYSTORE_BASE64 | base64 --decode > ./app/$KEYSTORE
echo -e "KEYSTORE=$KEYSTORE" >> ./gradle.properties
echo -e "KEYSTORE_PASSWORD=$KEYSTORE_PASSWORD" >> ./gradle.properties
echo -e "KEY_ALIAS=$KEY_ALIAS" >> ./gradle.properties
echo -e "KEY_PASSWORD=$KEYSTORE_PASSWORD" >> ./gradle.properties
fi
echo -e "VERSIONCODE=$CIRCLE_BUILD_NUM" >> ./gradle.properties
echo -e "BugsnagAPIKey=$BUGSNAG_KEY" >> ./gradle.properties
working_directory: android
- run:
name: Set Google Services
command: |
if [[ $KEYSTORE ]]; then
echo $GOOGLE_SERVICES_ANDROID | base64 --decode > google-services.json
fi
working_directory: android/app/src/play
- run:
name: Config variables
command: |
echo -e "export default { BUGSNAG_API_KEY: '$BUGSNAG_KEY' };" > ./config.js
- run:
name: Build Android Play App
command: |
if [[ $KEYSTORE ]]; then
bundle exec fastlane android playRelease
else
bundle exec fastlane android playBuild
fi
working_directory: android
- run:
name: Upload sourcemaps to Bugsnag
command: |
if [[ $BUGSNAG_KEY ]]; then
yarn generate-source-maps-android upload \
--api-key=$BUGSNAG_KEY \
--app-version=$CIRCLE_BUILD_NUM \
--minifiedFile=android/app/build/generated/assets/react/play/release/app.bundle \
--source-map=android/app/build/generated/sourcemaps/react/play/release/app.bundle.map \
--minified-url=app.bundle \
--upload-sources
fi
- store_artifacts:
path: android/app/build/outputs
- save_cache: *save-npm-cache-linux
- save_cache: *save-gradle-cache
- persist_to_workspace:
root: .
paths:
- android/fastlane/report.xml
- android/app/build/outputs
android-foss-build:
android-build-official:
<<: *defaults
docker:
- image: circleci/android:api-28-node
environment:
JAVA_OPTS: '-Xms512m -Xmx2g'
GRADLE_OPTS: '-Xmx3g -Dorg.gradle.daemon=false -Dorg.gradle.jvmargs="-Xmx2g -XX:+HeapDumpOnOutOfMemoryError"'
TERM: dumb
<<: *android-env
<<: *bash-env
steps:
- checkout
- android-build
- run: *install-node
- restore_cache: *restore-npm-cache-linux
- run: *install-npm-modules
- run: *update-fastlane-android
- restore_cache: *restore-gradle-cache
- run:
name: Configure Gradle
command: |
echo -e "" > ./gradle.properties
# echo -e "android.enableAapt2=false" >> ./gradle.properties
echo -e "android.useAndroidX=true" >> ./gradle.properties
echo -e "android.enableJetifier=true" >> ./gradle.properties
echo -e "FLIPPER_VERSION=0.51.0" >> ./gradle.properties
echo -e "VERSIONCODE=$CIRCLE_BUILD_NUM" >> ./gradle.properties
if [[ $KEYSTORE ]]; then
echo $KEYSTORE_BASE64 | base64 --decode > ./app/$KEYSTORE
echo -e "KEYSTORE=$KEYSTORE" >> ./gradle.properties
echo -e "KEYSTORE_PASSWORD=$KEYSTORE_PASSWORD" >> ./gradle.properties
echo -e "KEY_ALIAS=$KEY_ALIAS" >> ./gradle.properties
echo -e "KEY_PASSWORD=$KEYSTORE_PASSWORD" >> ./gradle.properties
fi
working_directory: android
- run:
name: Build Android Foss App
command: bundle exec fastlane android fossRelease
working_directory: android
- store_artifacts:
path: android/app/build/outputs
- save_cache: *save-npm-cache-linux
- save_cache: *save-gradle-cache
- persist_to_workspace:
root: .
paths:
- android/fastlane/report.xml
- android/app/build/outputs
android-google-play-beta:
android-google-play-beta-experimental:
<<: *defaults
docker:
- image: circleci/android:api-28-node
steps:
- checkout
- upload-to-google-play-beta:
official: false
- attach_workspace:
at: android
android-google-play-beta-official:
<<: *defaults
docker:
- image: circleci/android:api-28-node
- run:
name: Store the google service account key
command: echo "$FASTLANE_GOOGLE_SERVICE_ACCOUNT" | base64 --decode > service_account.json
working_directory: android
- run: *update-fastlane-android
- run:
name: Fastlane Play Store Upload
command: bundle exec fastlane android beta
working_directory: android
steps:
- upload-to-google-play-beta:
official: true
# iOS builds
ios-build:
ios-build-experimental:
executor: mac-env
steps:
- checkout
- ios-build
- restore_cache: *restore-gems-cache
- restore_cache: *restore-npm-cache-mac
- run: *install-node
- run: *install-npm-modules
- run: *update-fastlane-ios
- run:
name: Set Google Services
command: |
if [[ $KEYSTORE ]]; then
echo $GOOGLE_SERVICES_REACTNATIVE | base64 --decode > GoogleService-Info.plist
fi
working_directory: ios
- run:
name: Upload sourcemaps to Bugsnag
command: |
if [[ $BUGSNAG_KEY ]]; then
yarn generate-source-maps-ios
curl https://upload.bugsnag.com/react-native-source-map \
-F apiKey=$BUGSNAG_KEY \
-F appBundleVersion=$CIRCLE_BUILD_NUM \
-F dev=false \
-F platform=ios \
-F sourceMap=@ios-release.bundle.map \
-F bundle=@ios-release.bundle
fi
- run:
name: Fastlane Build
no_output_timeout: 1200
command: |
agvtool new-version -all $CIRCLE_BUILD_NUM
/usr/libexec/PlistBuddy -c "Set BugsnagAPIKey $BUGSNAG_KEY" ./RocketChatRN/Info.plist
if [[ $APP_STORE_CONNECT_API_KEY ]]; then
echo $APP_STORE_CONNECT_API_KEY | base64 --decode > ./fastlane/app_store_connect_api_key.p8
bundle exec fastlane ios release
else
bundle exec fastlane ios build_fork
fi
working_directory: ios
- save_cache: *save-npm-cache-mac
- save_cache: *save-gems-cache
- store_artifacts:
path: ios/RocketChatRN.ipa
- persist_to_workspace:
root: .
paths:
- ios/*.ipa
- ios/fastlane/report.xml
ios-testflight:
ios-build-official:
executor: mac-env
steps:
- checkout
- ios-build
- attach_workspace:
at: ios
ios-testflight-experimental:
executor: mac-env
steps:
- upload-to-testflight:
official: false
- restore_cache: *restore-gems-cache
- run: *update-fastlane-ios
- run:
name: Fastlane Tesflight Upload
command: |
echo $APP_STORE_CONNECT_API_KEY | base64 --decode > ./fastlane/app_store_connect_api_key.p8
bundle exec fastlane ios beta
working_directory: ios
- save_cache: *save-gems-cache
ios-testflight-official:
executor: mac-env
steps:
- upload-to-testflight:
official: true
workflows:
build-and-test:
jobs:
- lint-testunit
- ios-build:
# iOS Experimental
- ios-build-experimental:
requires:
- lint-testunit
- ios-hold-testflight:
- ios-hold-testflight-experimental:
type: approval
requires:
- ios-build
- ios-testflight:
- ios-build-experimental
- ios-testflight-experimental:
requires:
- ios-hold-testflight
- ios-hold-testflight-experimental
- android-play-build:
requires:
- lint-testunit
- android-hold-google-play-beta:
type: approval
requires:
- android-play-build
- android-google-play-beta:
requires:
- android-hold-google-play-beta
- android-hold-foss-build:
# iOS Official
- ios-hold-build-official:
type: approval
requires:
- lint-testunit
- android-foss-build:
- ios-build-official:
requires:
- android-hold-foss-build
- ios-hold-build-official
- ios-hold-testflight-official:
type: approval
requires:
- ios-build-official
- ios-testflight-official:
requires:
- ios-hold-testflight-official
# Android Experimental
- android-build-experimental:
requires:
- lint-testunit
- android-hold-google-play-beta-experimental:
type: approval
requires:
- android-build-experimental
- android-google-play-beta-experimental:
requires:
- android-hold-google-play-beta-experimental
# Android Official
- android-hold-build-official:
type: approval
requires:
- lint-testunit
- android-build-official:
requires:
- android-hold-build-official
- android-hold-google-play-beta-official:
type: approval
requires:
- android-build-official
- android-google-play-beta-official:
requires:
- android-hold-google-play-beta-official

1
.gitignore vendored
View File

@ -21,6 +21,7 @@ DerivedData
*.ipa
*.xcuserstate
project.xcworkspace
*.mobileprovision
# Android/IntelliJ
#

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,10 @@
def taskRequests = getGradle().getStartParameter().getTaskRequests().toString().toLowerCase()
def isPlay = !taskRequests.contains("foss")
def isFoss = taskRequests.contains("foss")
apply plugin: "com.android.application"
apply plugin: 'kotlin-android'
if (isPlay) {
if (!isFoss) {
apply plugin: 'com.google.firebase.crashlytics'
apply plugin: 'com.bugsnag.android.gradle'
}
@ -140,13 +140,13 @@ android {
}
defaultConfig {
applicationId APPLICATIONID as String
applicationId APPLICATION_ID
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode VERSIONCODE as Integer
versionName VERSIONNAME as String
versionCode VERSIONCODE
versionName "4.13.0"
vectorDrawables.useSupportLibrary = true
if (isPlay) {
if (!isFoss) {
manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String]
missingDimensionStrategy "RNNotifications.reactNativeVersion", "reactNative60" // See note below!
}
@ -176,7 +176,7 @@ android {
minifyEnabled enableProguardInReleaseBuilds
setProguardFiles([getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'])
signingConfig signingConfigs.release
if (isPlay) {
if (!isFoss) {
firebaseCrashlytics {
nativeSymbolUploadEnabled true
}
@ -193,10 +193,17 @@ android {
// applicationVariants are e.g. debug, release
flavorDimensions "type"
flavorDimensions "app", "type"
productFlavors {
official {
dimension = "app"
buildConfigField "boolean", "IS_OFFICIAL", "true"
}
experimental {
dimension = "app"
buildConfigField "boolean", "IS_OFFICIAL", "false"
}
foss {
applicationId "chat.rocket.android"
dimension = "type"
buildConfigField "boolean", "FDROID_BUILD", "true"
}
@ -206,11 +213,20 @@ android {
}
}
sourceSets {
playDebug {
// TODO: refactor making sure notifications are working properly both on debug and release
experimentalPlayDebug {
java.srcDirs = ['src/main/java', 'src/play/java']
manifest.srcFile 'src/play/AndroidManifest.xml'
}
playRelease {
experimentalPlayRelease {
java.srcDirs = ['src/main/java', 'src/play/java']
manifest.srcFile 'src/play/AndroidManifest.xml'
}
officialPlayDebug {
java.srcDirs = ['src/main/java', 'src/play/java']
manifest.srcFile 'src/play/AndroidManifest.xml'
}
officialPlayRelease {
java.srcDirs = ['src/main/java', 'src/play/java']
manifest.srcFile 'src/play/AndroidManifest.xml'
}
@ -245,7 +261,6 @@ android {
dependencies {
addUnimodulesDependencies()
implementation project(':watermelondb')
implementation project(":reactnativekeyboardinput")
implementation project(':@react-native-community_viewpager')
playImplementation project(':reactnativenotifications')
playImplementation project(':@react-native-firebase_app')
@ -288,6 +303,6 @@ task copyDownloadableDepsToLibs(type: Copy) {
}
apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
if (isPlay) {
if (!isFoss) {
apply plugin: 'com.google.gms.google-services'
}

View File

@ -0,0 +1,83 @@
{
"project_info": {
"project_number": "115198584049",
"firebase_url": "https://rocketchat-reactnative-test.firebaseio.com",
"project_id": "rocketchat-reactnative-test",
"storage_bucket": "rocketchat-reactnative-test.appspot.com"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:115198584049:android:a79216ae48935d2c9ab550",
"android_client_info": {
"package_name": "chat.rocket.android"
}
},
"oauth_client": [
{
"client_id": "115198584049-ack609b1338b827fta26s9rd2ab1aad5.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyAWwowhAfACHBw3YxmDOXY3QyakgjhJLqc"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "115198584049-ack609b1338b827fta26s9rd2ab1aad5.apps.googleusercontent.com",
"client_type": 3
},
{
"client_id": "115198584049-0efgfvm0oh9ap55g7epmqnjm27mq3j4e.apps.googleusercontent.com",
"client_type": 2,
"ios_info": {
"bundle_id": "chat.rocket.reactnative"
}
}
]
}
}
},
{
"client_info": {
"mobilesdk_app_id": "1:115198584049:android:8be27b1f7c42a2ed",
"android_client_info": {
"package_name": "chat.rocket.reactnative"
}
},
"oauth_client": [
{
"client_id": "115198584049-ack609b1338b827fta26s9rd2ab1aad5.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyAWwowhAfACHBw3YxmDOXY3QyakgjhJLqc"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "115198584049-ack609b1338b827fta26s9rd2ab1aad5.apps.googleusercontent.com",
"client_type": 3
},
{
"client_id": "115198584049-0efgfvm0oh9ap55g7epmqnjm27mq3j4e.apps.googleusercontent.com",
"client_type": 2,
"ios_info": {
"bundle_id": "chat.rocket.reactnative"
}
}
]
}
}
}
],
"configuration_version": "1"
}

View File

@ -1,10 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="chat.rocket.reactnative">
xmlns:tools="http://schemas.android.com/tools">
<application
android:name=".MainDebugApplication"
android:name="chat.rocket.reactnative.MainDebugApplication"
tools:ignore="GoogleAppIndexingWarning"
tools:replace="android:name"
tools:targetApi="28"

View File

@ -21,6 +21,7 @@ import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPl
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.modules.network.NetworkingModule;
import com.facebook.react.modules.network.CustomClientBuilder;
import okhttp3.OkHttpClient;
public class ReactNativeFlipper {
public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) {
@ -33,7 +34,7 @@ public class ReactNativeFlipper {
client.addPlugin(CrashReporterPlugin.getInstance());
NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin();
NetworkingModule.setCustomClientBuilder(
new NetworkingModule.CustomClientBuilder() {
new CustomClientBuilder() {
@Override
public void apply(OkHttpClient.Builder builder) {
builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin));

View File

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

View File

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="splashBackground" type="color">#000000</item>
</resources>

View File

@ -1,28 +0,0 @@
<resources>
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:colorEdgeEffect">#aaaaaa</item>
<item name="colorPrimaryDark">@color/splashBackground</item>
<item name="android:navigationBarColor">@color/splashBackground</item>
</style>
<style name="Share.Window" parent="android:Theme">
<item name="android:windowEnterAnimation">@null</item>
<item name="android:windowExitAnimation">@null</item>
</style>
<style name="Theme.Share.Transparent" parent="android:Theme">
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowBackground">@color/primary_dark</item>
<item name="android:windowContentOverlay">@null</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowIsFloating">true</item>
<item name="android:backgroundDimEnabled">true</item>
<item name="android:windowAnimationStyle">@style/Share.Window</item>
</style>
<style name="BootTheme" parent="AppTheme">
<item name="android:background">@drawable/launch_screen</item>
<item name="colorPrimaryDark">@color/splashBackground</item>
<item name="android:navigationBarColor">@color/splashBackground</item>
</style>
</resources>

View File

@ -7,14 +7,15 @@
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application
android:name=".MainApplication"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:theme="@style/AppTheme"
android:networkSecurityConfig="@xml/network_security_config"
android:name="chat.rocket.reactnative.MainApplication"
android:allowBackup="false"
tools:replace="android:allowBackup"
>
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
android:requestLegacyExternalStorage="true"
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:replace="android:allowBackup">
<activity
android:name="com.zoontek.rnbootsplash.RNBootSplashActivity"
android:theme="@style/BootTheme">
@ -24,37 +25,50 @@
</intent-filter>
</activity>
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:name="chat.rocket.reactnative.MainActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
android:exported="true"
android:label="@string/app_name"
android:launchMode="singleTask"
android:windowSoftInputMode="adjustResize"
android:exported="true">
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.DOWNLOAD_COMPLETE" />
</intent-filter>
<intent-filter android:label="@string/app_name">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" android:host="go.rocket.chat" />
<data android:scheme="https" android:host="jitsi.rocket.chat" />
<data android:scheme="rocketchat" android:host="room" />
<data android:scheme="rocketchat" android:host="auth" />
<data android:scheme="rocketchat" android:host="jitsi.rocket.chat" />
<data
android:host="go.rocket.chat"
android:scheme="https" />
<data
android:host="jitsi.rocket.chat"
android:scheme="https" />
<data
android:host="room"
android:scheme="rocketchat" />
<data
android:host="auth"
android:scheme="rocketchat" />
<data
android:host="jitsi.rocket.chat"
android:scheme="rocketchat" />
</intent-filter>
</activity>
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
<activity
android:noHistory="true"
android:name=".share.ShareActivity"
android:name="chat.rocket.reactnative.share.ShareActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
android:label="@string/share_extension_name"
android:noHistory="true"
android:screenOrientation="portrait"
android:theme="@style/AppTheme">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" />
</intent-filter>

View File

@ -11,7 +11,6 @@ import com.facebook.react.ReactPackage;
import com.facebook.soloader.SoLoader;
import com.nozbe.watermelondb.WatermelonDBPackage;
import com.reactnativecommunity.viewpager.RNCViewPagerPackage;
import com.wix.reactnativekeyboardinput.KeyboardInputPackage;
import org.unimodules.adapters.react.ModuleRegistryAdapter;
import org.unimodules.adapters.react.ReactModuleRegistryProvider;
@ -20,6 +19,7 @@ import java.util.Arrays;
import java.util.List;
import chat.rocket.reactnative.generated.BasePackageList;
import chat.rocket.reactnative.networking.SSLPinningPackage;
public class MainApplication extends Application implements ReactApplication {
@ -35,9 +35,9 @@ public class MainApplication extends Application implements ReactApplication {
protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
packages.add(new KeyboardInputPackage(MainApplication.this));
packages.add(new WatermelonDBPackage());
packages.add(new RNCViewPagerPackage());
packages.add(new SSLPinningPackage());
List<ReactPackage> unimodules = Arrays.<ReactPackage>asList(
new ModuleRegistryAdapter(mModuleRegistryProvider)
);

View File

@ -0,0 +1,193 @@
package chat.rocket.reactnative.networking;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.modules.network.NetworkingModule;
import com.facebook.react.modules.network.CustomClientBuilder;
import com.facebook.react.modules.network.ReactCookieJarContainer;
import com.facebook.react.modules.websocket.WebSocketModule;
import com.facebook.react.modules.fresco.ReactOkHttpNetworkFetcher;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.Promise;
import java.net.Socket;
import java.security.Principal;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.X509ExtendedKeyManager;
import java.security.PrivateKey;
import javax.net.ssl.SSLContext;
import javax.net.ssl.X509TrustManager;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import okhttp3.OkHttpClient;
import java.lang.InterruptedException;
import android.app.Activity;
import javax.net.ssl.KeyManager;
import android.security.KeyChain;
import android.security.KeyChainAliasCallback;
import java.util.concurrent.TimeUnit;
import com.RNFetchBlob.RNFetchBlob;
import com.reactnativecommunity.webview.RNCWebViewManager;
import com.dylanvann.fastimage.FastImageOkHttpUrlLoader;
import expo.modules.av.player.datasource.SharedCookiesDataSourceFactory;
public class SSLPinningModule extends ReactContextBaseJavaModule implements KeyChainAliasCallback {
private Promise promise;
private static String alias;
private static ReactApplicationContext reactContext;
public SSLPinningModule(ReactApplicationContext reactContext) {
super(reactContext);
this.reactContext = reactContext;
}
public class CustomClient implements CustomClientBuilder {
@Override
public void apply(OkHttpClient.Builder builder) {
if (alias != null) {
SSLSocketFactory sslSocketFactory = getSSLFactory(alias);
if (sslSocketFactory != null) {
builder.sslSocketFactory(sslSocketFactory);
}
}
}
}
protected OkHttpClient getOkHttpClient() {
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.connectTimeout(0, TimeUnit.MILLISECONDS)
.readTimeout(0, TimeUnit.MILLISECONDS)
.writeTimeout(0, TimeUnit.MILLISECONDS)
.cookieJar(new ReactCookieJarContainer());
if (alias != null) {
SSLSocketFactory sslSocketFactory = getSSLFactory(alias);
if (sslSocketFactory != null) {
builder.sslSocketFactory(sslSocketFactory);
}
}
return builder.build();
}
@Override
public String getName() {
return "SSLPinning";
}
@Override
public void alias(String alias) {
this.alias = alias;
this.promise.resolve(alias);
}
@ReactMethod
public void setCertificate(String data, Promise promise) {
this.alias = data;
// HTTP Fetch react-native layer
NetworkingModule.setCustomClientBuilder(new CustomClient());
// Websocket react-native layer
WebSocketModule.setCustomClientBuilder(new CustomClient());
// Image networking react-native layer
ReactOkHttpNetworkFetcher.setOkHttpClient(getOkHttpClient());
// RNFetchBlob networking layer
RNFetchBlob.applyCustomOkHttpClient(getOkHttpClient());
// RNCWebView onReceivedClientCertRequest
RNCWebViewManager.setCertificateAlias(data);
// FastImage Glide network layer
FastImageOkHttpUrlLoader.setOkHttpClient(getOkHttpClient());
// Expo AV network layer
SharedCookiesDataSourceFactory.setOkHttpClient(getOkHttpClient());
promise.resolve(null);
}
@ReactMethod
public void pickCertificate(Promise promise) {
Activity activity = getCurrentActivity();
this.promise = promise;
KeyChain.choosePrivateKeyAlias(activity,
this, // Callback
null, // Any key types.
null, // Any issuers.
null, // Any host
-1, // Any port
"RocketChat");
}
public static SSLSocketFactory getSSLFactory(final String alias) {
try {
final PrivateKey privKey = KeyChain.getPrivateKey(reactContext, alias);
final X509Certificate[] certChain = KeyChain.getCertificateChain(reactContext, alias);
final X509ExtendedKeyManager keyManager = new X509ExtendedKeyManager() {
@Override
public String chooseClientAlias(String[] strings, Principal[] principals, Socket socket) {
return alias;
}
@Override
public String chooseServerAlias(String s, Principal[] principals, Socket socket) {
return alias;
}
@Override
public X509Certificate[] getCertificateChain(String s) {
return certChain;
}
@Override
public String[] getClientAliases(String s, Principal[] principals) {
return new String[]{alias};
}
@Override
public String[] getServerAliases(String s, Principal[] principals) {
return new String[]{alias};
}
@Override
public PrivateKey getPrivateKey(String s) {
return privKey;
}
};
final TrustManager[] trustAllCerts = new TrustManager[] {
new X509TrustManager() {
@Override
public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return certChain;
}
}
};
final SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(new KeyManager[]{keyManager}, trustAllCerts, new java.security.SecureRandom());
SSLContext.setDefault(sslContext);
final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
return sslSocketFactory;
} catch (Exception e) {
return null;
}
}
}

View File

@ -0,0 +1,23 @@
package chat.rocket.reactnative.networking;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import com.facebook.react.bridge.JavaScriptModule;
public class SSLPinningPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return Arrays.<NativeModule>asList(new SSLPinningModule(reactContext));
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}

View File

@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="splashBackground" type="color">#000000</item>
<item name="notification_text" type="color">#1D74F5</item>
</resources>

View File

@ -3,6 +3,7 @@
<item name="android:colorEdgeEffect">#aaaaaa</item>
<item name="colorPrimaryDark">@color/splashBackground</item>
<item name="android:navigationBarColor">@color/splashBackground</item>
<item name="android:forceDarkAllowed">false</item>
</style>
<style name="Share.Window" parent="android:Theme">

View File

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

View File

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -1,10 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="chat.rocket.reactnative">
xmlns:tools="http://schemas.android.com/tools">
<application
android:name=".MainPlayApplication"
android:name="chat.rocket.reactnative.MainPlayApplication"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:theme="@style/AppTheme"
@ -12,11 +11,11 @@
tools:replace="android:name"
>
<receiver
android:name=".ReplyBroadcast"
android:name="chat.rocket.reactnative.ReplyBroadcast"
android:enabled="true"
android:exported="false" />
<receiver
android:name=".DismissNotification"
android:name="chat.rocket.reactnative.DismissNotification"
android:enabled="true"
android:exported="false" >
</receiver>

View File

@ -1,28 +1,22 @@
package chat.rocket.reactnative;
import android.util.Log;
import android.util.Base64;
import android.database.Cursor;
import android.util.Base64;
import android.util.Log;
import com.pedrouid.crypto.RSA;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.WritableMap;
import com.google.gson.Gson;
import com.nozbe.watermelondb.Database;
import com.pedrouid.crypto.RCTAes;
import com.pedrouid.crypto.RCTRsaUtils;
import com.pedrouid.crypto.RSA;
import com.pedrouid.crypto.Util;
import com.google.gson.Gson;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.nozbe.watermelondb.Database;
import java.util.Arrays;
import java.lang.reflect.Field;
import java.security.SecureRandom;
import java.util.Arrays;
class Message {
String _id;
@ -69,8 +63,24 @@ class Encryption {
public static Encryption shared = new Encryption();
private ReactApplicationContext reactContext;
public Room readRoom(final Ejson ejson) {
Database database = new Database(ejson.serverURL().replace("https://", "") + "-experimental.db", reactContext);
public Room readRoom(final Ejson ejson) throws NoSuchFieldException {
int resId = reactContext.getResources().getIdentifier("rn_config_reader_custom_package", "string", reactContext.getPackageName());
String className = reactContext.getString(resId);
Class clazz = null;
Boolean isOfficial = false;
try {
clazz = Class.forName(className + ".BuildConfig");
Field IS_OFFICIAL = clazz.getField("IS_OFFICIAL");
isOfficial = (Boolean) IS_OFFICIAL.get(null);
} catch (ClassNotFoundException | IllegalAccessException e) {
e.printStackTrace();
}
String dbName = ejson.serverURL().replace("https://", "");
if (!isOfficial) {
dbName += "-experimental";
}
dbName += ".db";
Database database = new Database(dbName, reactContext);
String[] query = {ejson.rid};
Cursor cursor = database.rawQuery("select * from subscriptions where id == ? limit 1", query);
@ -152,7 +162,7 @@ class Encryption {
return m.text;
} catch (Exception e) {
Log.d("[ROCKETCHAT][ENCRYPTION]", Log.getStackTraceString(e));
Log.d("[ROCKETCHAT][E2E]", Log.getStackTraceString(e));
}
return null;
@ -182,7 +192,7 @@ class Encryption {
return keyId + Base64.encodeToString(concat(bytes, data), Base64.NO_WRAP);
} catch (Exception e) {
Log.d("[ROCKETCHAT][ENCRYPTION]", Log.getStackTraceString(e));
Log.d("[ROCKETCHAT][E2E]", Log.getStackTraceString(e));
}
return message;

View File

@ -12,12 +12,12 @@ buildscript {
minSdkVersion = 23
compileSdkVersion = 29
targetSdkVersion = 29
glideVersion = "4.9.0"
glideVersion = "4.11.0"
kotlin_version = "1.3.50"
supportLibVersion = "28.0.0"
libre_build = !(isPlay.toBoolean())
jitsi_url = isPlay ? "https://github.com/RocketChat/jitsi-maven-repository/raw/master/releases" : "https://github.com/RocketChat/jitsi-maven-repository/raw/libre/releases"
jitsi_version = isPlay ? "2.10.2-rc" : "2.10.0-libre"
jitsi_version = isPlay ? "2.10.2" : "2.10.0-libre"
}
repositories {
@ -51,13 +51,13 @@ allprojects {
// Android JSC is installed from npm
url("$rootDir/../node_modules/jsc-android/dist")
}
maven {
url safeExtGet("jitsi_url", "https://github.com/RocketChat/jitsi-maven-repository/raw/master/releases")
url jitsi_url
}
google()
jcenter()
maven { url 'https://maven.google.com' }
maven { url 'https://www.jitpack.io' }
}

View File

@ -16,26 +16,20 @@
default_platform(:android)
platform :android do
desc "Play build for development"
lane :playBuild do
gradle(task: "assemblePlayDebug")
end
desc "Foss build for release"
lane :fossRelease do
gradle(task: "assembleFossRelease")
end
desc "Play build for release"
lane :playRelease do
gradle(task: "bundlePlayRelease")
end
desc "Upload App to Play Store Internal"
lane :beta do
lane :beta do |options|
if options[:official]
upload_to_play_store(
package_name: 'chat.rocket.android',
track: 'internal',
aab: 'android/app/build/outputs/bundle/playRelease/app-play-release.aab'
aab: 'android/app/build/outputs/bundle/officialPlayRelease/app-official-play-release.aab'
)
else
upload_to_play_store(
package_name: 'chat.rocket.reactnative',
track: 'internal',
aab: 'android/app/build/outputs/bundle/experimentalPlayRelease/app-experimental-play-release.aab'
)
end
end
end

View File

@ -26,7 +26,6 @@ android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true
APPLICATIONID=chat.rocket.reactnative
VERSIONNAME=4.12.1
VERSIONCODE=1
BugsnagAPIKey=
KEYSTORE=my-upload-key.keystore

View File

@ -6,8 +6,6 @@ include ':watermelondb'
project(':watermelondb').projectDir = new File(rootProject.projectDir, '../node_modules/@nozbe/watermelondb/native/android')
include ':reactnativenotifications'
project(':reactnativenotifications').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-notifications/android/app')
include ':reactnativekeyboardinput'
project(':reactnativekeyboardinput').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-keyboard-input/lib/android')
include ':@react-native-community_viewpager'
project(':@react-native-community_viewpager').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-community/viewpager/android')
include ':@react-native-firebase_app'

View File

@ -23,11 +23,10 @@ export function selectServerFailure() {
};
}
export function serverRequest(server, certificate = null, username = null, fromServerHistory = false) {
export function serverRequest(server, username = null, fromServerHistory = false) {
return {
type: SERVER.REQUEST,
server,
certificate,
username,
fromServerHistory
};

View File

@ -1,3 +1,5 @@
import RNConfigReader from 'react-native-config-reader';
export const isFDroidBuild = RNConfigReader.FDROID_BUILD;
export const isOfficial = RNConfigReader.IS_OFFICIAL;

View File

@ -56,7 +56,7 @@ export default StyleSheet.create({
},
text: {
fontSize: 16,
textAlign: 'center',
...sharedStyles.textMedium
...sharedStyles.textMedium,
...sharedStyles.textAlignCenter
}
});

View File

@ -17,8 +17,8 @@ const styles = StyleSheet.create({
},
text: {
fontSize: 16,
textAlign: 'center',
...sharedStyles.textMedium
...sharedStyles.textMedium,
...sharedStyles.textAlignCenter
},
disabled: {
opacity: 0.3

View File

@ -1,5 +1,7 @@
import { StyleSheet } from 'react-native';
import sharedStyles from '../../views/Styles';
export default StyleSheet.create({
container: {
flex: 1
@ -46,8 +48,8 @@ export default StyleSheet.create({
flex: 1
},
categoryEmoji: {
...sharedStyles.textAlignCenter,
backgroundColor: 'transparent',
textAlign: 'center',
color: '#ffffff'
},
customCategoryEmoji: {

View File

@ -13,8 +13,13 @@ const styles = StyleSheet.create({
}
});
const ListIcon = React.memo(({ theme, name, color }) => (
<View style={styles.icon}>
const ListIcon = React.memo(({
theme,
name,
color,
style
}) => (
<View style={[styles.icon, style]}>
<CustomIcon
name={name}
color={color ?? themes[theme].auxiliaryText}
@ -26,7 +31,8 @@ const ListIcon = React.memo(({ theme, name, color }) => (
ListIcon.propTypes = {
theme: PropTypes.string,
name: PropTypes.string,
color: PropTypes.string
color: PropTypes.string,
style: PropTypes.object
};
ListIcon.displayName = 'List.Icon';

View File

@ -1,5 +1,10 @@
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import {
View,
Text,
StyleSheet,
I18nManager
} from 'react-native';
import PropTypes from 'prop-types';
import Touch from '../../utils/touch';
@ -39,6 +44,11 @@ const styles = StyleSheet.create({
subtitle: {
fontSize: 14,
...sharedStyles.textRegular
},
actionIndicator: {
...I18nManager.isRTL
? { transform: [{ rotate: '180deg' }] }
: {}
}
});
@ -64,7 +74,7 @@ const Content = React.memo(({
? (
<View style={styles.rightContainer}>
{right ? right() : null}
{showActionIndicator ? <Icon name='chevron-right' /> : null}
{showActionIndicator ? <Icon name='chevron-right' style={styles.actionIndicator} /> : null}
</View>
)
: null}

View File

@ -12,6 +12,7 @@ import CustomEmoji from '../EmojiPicker/CustomEmoji';
import database from '../../lib/database';
import { Button } from '../ActionSheet';
import { useDimensions } from '../../dimensions';
import sharedStyles from '../../views/Styles';
export const HEADER_HEIGHT = 36;
const ITEM_SIZE = 36;
@ -32,7 +33,7 @@ const styles = StyleSheet.create({
alignItems: 'center'
},
headerIcon: {
textAlign: 'center',
...sharedStyles.textAlignCenter,
fontSize: 20,
color: '#fff'
},

View File

@ -1,6 +1,6 @@
import React from 'react';
import { View } from 'react-native';
import { KeyboardRegistry } from 'react-native-keyboard-input';
import { KeyboardRegistry } from 'react-native-ui-lib/keyboard';
import PropTypes from 'prop-types';
import store from '../../lib/createStore';

View File

@ -1,10 +1,10 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import {
View, Alert, Keyboard, NativeModules, Text
View, Alert, Keyboard, NativeModules, Text, InteractionManager
} from 'react-native';
import { connect } from 'react-redux';
import { KeyboardAccessoryView } from 'react-native-keyboard-input';
import { KeyboardAccessoryView } from 'react-native-ui-lib/keyboard';
import ImagePicker from 'react-native-image-crop-picker';
import equal from 'deep-equal';
import DocumentPicker from 'react-native-document-picker';
@ -50,6 +50,10 @@ import { withActionSheet } from '../ActionSheet';
import { sanitizeLikeString } from '../../lib/database/utils';
import { CustomIcon } from '../../lib/Icons';
if (isAndroid) {
require('./EmojiKeyboard');
}
const imagePickerConfig = {
cropping: true,
compressImageQuality: 0.8,
@ -108,7 +112,7 @@ class MessageBox extends Component {
id: ''
},
sharing: false,
iOSScrollBehavior: NativeModules.KeyboardTrackingViewManager?.KeyboardTrackingScrollBehaviorFixedOffset,
iOSScrollBehavior: NativeModules.KeyboardTrackingViewTempManager?.KeyboardTrackingScrollBehaviorFixedOffset,
isActionsEnabled: true,
getCustomEmoji: () => {}
}
@ -213,19 +217,20 @@ class MessageBox extends Component {
this.setShowSend(true);
}
if (isAndroid) {
require('./EmojiKeyboard');
}
if (isTablet) {
EventEmiter.addEventListener(KEY_COMMAND, this.handleCommands);
}
this.unsubscribeFocus = navigation.addListener('focus', () => {
// didFocus
// We should wait pushed views be dismissed
InteractionManager.runAfterInteractions(() => {
if (this.tracking && this.tracking.resetTracking) {
// Reset messageBox keyboard tracking
this.tracking.resetTracking();
}
});
});
this.unsubscribeBlur = navigation.addListener('blur', () => {
this.component?.blur();
});

View File

@ -55,13 +55,13 @@ export default StyleSheet.create({
width: 46,
height: 36,
fontSize: isIOS ? 30 : 25,
textAlign: 'center'
...sharedStyles.textAlignCenter
},
fixedMentionAvatar: {
textAlign: 'center',
width: 46,
fontSize: 14,
...sharedStyles.textBold
...sharedStyles.textBold,
...sharedStyles.textAlignCenter
},
mentionText: {
fontSize: 14,

View File

@ -23,9 +23,9 @@ const styles = StyleSheet.create({
paddingVertical: 10
},
title: {
textAlign: 'center',
fontSize: 16,
...sharedStyles.textSemibold
...sharedStyles.textSemibold,
...sharedStyles.textAlignCenter
},
reactCount: {
fontSize: 13,

View File

@ -11,7 +11,7 @@ import ActivityIndicator from './ActivityIndicator';
const styles = StyleSheet.create({
error: {
textAlign: 'center',
...sharedStyles.textAlignCenter,
paddingTop: 5
},
inputContainer: {
@ -26,8 +26,7 @@ const styles = StyleSheet.create({
...sharedStyles.textRegular,
height: 48,
fontSize: 16,
paddingLeft: 14,
paddingRight: 14,
paddingHorizontal: 14,
borderWidth: StyleSheet.hairlineWidth,
borderRadius: 2
},

View File

@ -14,9 +14,9 @@ const styles = StyleSheet.create({
padding: 10
},
text: {
...sharedStyles.textRegular,
fontSize: 14,
textAlign: 'center'
...sharedStyles.textRegular,
...sharedStyles.textAlignCenter
}
});

View File

@ -211,7 +211,7 @@ class Markdown extends PureComponent {
return null;
}
return (
<Text style={[style, { color: themes[theme].bodyText }]} numberOfLines={numberOfLines}>
<Text style={[styles.text, style, { color: themes[theme].bodyText }]} numberOfLines={numberOfLines}>
{children}
</Text>
);

View File

@ -6,7 +6,8 @@ import equal from 'deep-equal';
import I18n from '../../i18n';
import styles from './styles';
import Markdown from '../markdown';
import { getInfoMessage } from './utils';
import User from './User';
import { getInfoMessage, SYSTEM_MESSAGE_TYPES_WITH_AUTHOR_NAME } from './utils';
import { themes } from '../../constants/colors';
import MessageContext from './Context';
import Encrypted from './Encrypted';
@ -15,13 +16,25 @@ import { E2E_MESSAGE_TYPE } from '../../lib/encryption/constants';
const Content = React.memo((props) => {
if (props.isInfo) {
const infoMessage = getInfoMessage({ ...props });
return (
const renderMessageContent = (
<Text
style={[styles.textInfo, { color: themes[props.theme].auxiliaryText }]}
accessibilityLabel={infoMessage}
>{infoMessage}
>
{infoMessage}
</Text>
);
if (SYSTEM_MESSAGE_TYPES_WITH_AUTHOR_NAME.includes(props.type)) {
return (
<Text>
<User {...props} /> {renderMessageContent}
</Text>
);
}
return renderMessageContent;
}
const isPreview = props.tmid && !props.isThreadRoom;
@ -67,6 +80,10 @@ const Content = React.memo((props) => {
);
}
if (props.isIgnored) {
content = <Text style={[styles.textInfo, { color: themes[props.theme].auxiliaryText }]}>{I18n.t('Message_Ignored')}</Text>;
}
return (
<View style={props.isTemp && styles.temp}>
{content}
@ -88,6 +105,9 @@ const Content = React.memo((props) => {
if (prevProps.isEncrypted !== nextProps.isEncrypted) {
return false;
}
if (prevProps.isIgnored !== nextProps.isIgnored) {
return false;
}
if (!equal(prevProps.mentions, nextProps.mentions)) {
return false;
}
@ -111,6 +131,7 @@ Content.propTypes = {
mentions: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
navToRoomInfo: PropTypes.func,
useRealName: PropTypes.bool,
isIgnored: PropTypes.bool,
type: PropTypes.string
};
Content.displayName = 'MessageContent';

View File

@ -63,12 +63,12 @@ const MessageInner = React.memo((props) => {
MessageInner.displayName = 'MessageInner';
const Message = React.memo((props) => {
if (props.isThreadReply || props.isThreadSequential || props.isInfo) {
if (props.isThreadReply || props.isThreadSequential || props.isInfo || props.isIgnored) {
const thread = props.isThreadReply ? <RepliedThread {...props} /> : null;
return (
<View style={[styles.container, props.style]}>
{thread}
<View style={[styles.flex, styles.center]}>
<View style={styles.flex}>
<MessageAvatar small {...props} />
<View
style={[
@ -82,6 +82,7 @@ const Message = React.memo((props) => {
</View>
);
}
return (
<View style={[styles.container, props.style]}>
<View style={styles.flex}>
@ -146,7 +147,8 @@ Message.propTypes = {
onLongPress: PropTypes.func,
isReadReceiptEnabled: PropTypes.bool,
unread: PropTypes.bool,
theme: PropTypes.string
theme: PropTypes.string,
isIgnored: PropTypes.bool
};
MessageInner.propTypes = {

View File

@ -12,6 +12,7 @@ import MessageError from './MessageError';
import sharedStyles from '../../views/Styles';
import messageStyles from './styles';
import MessageContext from './Context';
import { SYSTEM_MESSAGE_TYPES_WITH_AUTHOR_NAME } from './utils';
const styles = StyleSheet.create({
container: {
@ -24,6 +25,10 @@ const styles = StyleSheet.create({
lineHeight: 22,
...sharedStyles.textMedium
},
usernameInfoMessage: {
fontSize: 16,
...sharedStyles.textMedium
},
titleContainer: {
flex: 1,
flexDirection: 'row',
@ -36,7 +41,7 @@ const styles = StyleSheet.create({
});
const User = React.memo(({
isHeader, useRealName, author, alias, ts, timeFormat, hasError, theme, navToRoomInfo, ...props
isHeader, useRealName, author, alias, ts, timeFormat, hasError, theme, navToRoomInfo, type, ...props
}) => {
if (isHeader || hasError) {
const navParam = {
@ -47,17 +52,37 @@ const User = React.memo(({
const username = (useRealName && author.name) || author.username;
const aliasUsername = alias ? (<Text style={[styles.alias, { color: themes[theme].auxiliaryText }]}> @{username}</Text>) : null;
const time = moment(ts).format(timeFormat);
const onUserPress = () => navToRoomInfo(navParam);
const isDisabled = author._id === user.id;
const textContent = (
<>
{alias || username}
{aliasUsername}
</>
);
if (SYSTEM_MESSAGE_TYPES_WITH_AUTHOR_NAME.includes(type)) {
return (
<Text
style={[styles.usernameInfoMessage, { color: themes[theme].titleText }]}
onPress={onUserPress}
disabled={isDisabled}
>
{textContent}
</Text>
);
}
return (
<View style={styles.container}>
<TouchableOpacity
style={styles.titleContainer}
onPress={() => navToRoomInfo(navParam)}
disabled={author._id === user.id}
onPress={onUserPress}
disabled={isDisabled}
>
<Text style={[styles.username, { color: themes[theme].titleText }]} numberOfLines={1}>
{alias || username}
{aliasUsername}
{textContent}
</Text>
</TouchableOpacity>
<Text style={[messageStyles.time, { color: themes[theme].auxiliaryText }]}>{time}</Text>
@ -77,7 +102,8 @@ User.propTypes = {
ts: PropTypes.instanceOf(Date),
timeFormat: PropTypes.string,
theme: PropTypes.string,
navToRoomInfo: PropTypes.func
navToRoomInfo: PropTypes.func,
type: PropTypes.string
};
User.displayName = 'MessageUser';

View File

@ -1,6 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import { KeyboardUtils } from 'react-native-keyboard-input';
import { Keyboard } from 'react-native';
import Message from './Message';
import MessageContext from './Context';
@ -32,6 +32,7 @@ class MessageContainer extends React.Component {
autoTranslateRoom: PropTypes.bool,
autoTranslateLanguage: PropTypes.string,
status: PropTypes.number,
isIgnored: PropTypes.bool,
getCustomEmoji: PropTypes.func,
onLongPress: PropTypes.func,
onReactionPress: PropTypes.func,
@ -70,9 +71,12 @@ class MessageContainer extends React.Component {
blockAction: () => {},
archived: false,
broadcast: false,
isIgnored: false,
theme: 'light'
}
state = { isManualUnignored: false };
componentDidMount() {
const { item } = this.props;
if (item && item.observe) {
@ -83,14 +87,21 @@ class MessageContainer extends React.Component {
}
}
shouldComponentUpdate(nextProps) {
const { theme, threadBadgeColor } = this.props;
shouldComponentUpdate(nextProps, nextState) {
const { isManualUnignored } = this.state;
const { theme, threadBadgeColor, isIgnored } = this.props;
if (nextProps.theme !== theme) {
return true;
}
if (nextProps.threadBadgeColor !== threadBadgeColor) {
return true;
}
if (nextProps.isIgnored !== isIgnored) {
return true;
}
if (nextState.isManualUnignored !== isManualUnignored) {
return true;
}
return false;
}
@ -101,8 +112,12 @@ class MessageContainer extends React.Component {
}
onPress = debounce(() => {
if (this.isIgnored) {
return this.onIgnoredMessagePress();
}
const { item, isThreadRoom } = this.props;
KeyboardUtils.dismiss();
Keyboard.dismiss();
if (((item.tlm || item.tmid) && !isThreadRoom)) {
this.onThreadPress();
@ -161,6 +176,10 @@ class MessageContainer extends React.Component {
}
}
onIgnoredMessagePress = () => {
this.setState({ isManualUnignored: true });
}
get isHeader() {
const {
item, previousItem, broadcast, Message_GroupingPeriod
@ -198,16 +217,11 @@ class MessageContainer extends React.Component {
}
get isThreadSequential() {
const {
item, previousItem, isThreadRoom
} = this.props;
const { item, isThreadRoom } = this.props;
if (isThreadRoom) {
return false;
}
if (previousItem && item.tmid && ((previousItem.tmid === item.tmid) || (previousItem.id === item.tmid))) {
return true;
}
return false;
return item.tmid;
}
get isEncrypted() {
@ -226,6 +240,12 @@ class MessageContainer extends React.Component {
return item.status === messagesStatus.TEMP || item.status === messagesStatus.ERROR;
}
get isIgnored() {
const { isManualUnignored } = this.state;
const { isIgnored } = this.props;
return isManualUnignored ? false : isIgnored;
}
get hasError() {
const { item } = this.props;
return item.status === messagesStatus.ERROR;
@ -311,6 +331,7 @@ class MessageContainer extends React.Component {
fetchThreadName={fetchThreadName}
mentions={mentions}
channels={channels}
isIgnored={this.isIgnored}
isEdited={editedBy && !!editedBy.username}
isHeader={this.isHeader}
isThreadReply={this.isThreadReply}

View File

@ -26,9 +26,6 @@ export default StyleSheet.create({
messageContentWithError: {
marginLeft: 0
},
center: {
alignItems: 'center'
},
flex: {
flexDirection: 'row'
// flex: 1
@ -140,7 +137,7 @@ export default StyleSheet.create({
},
time: {
fontSize: 12,
paddingLeft: 8,
marginLeft: 8,
...sharedStyles.textRegular
},
repliedThread: {

View File

@ -40,6 +40,24 @@ export const SYSTEM_MESSAGES = [
'thread-created'
];
export const SYSTEM_MESSAGE_TYPES = {
MESSAGE_REMOVED: 'rm',
MESSAGE_PINNED: 'message_pinned',
MESSAGE_SNIPPETED: 'message_snippeted',
USER_JOINED_CHANNEL: 'uj',
USER_JOINED_DISCUSSION: 'ut',
USER_LEFT_CHANNEL: 'ul'
};
export const SYSTEM_MESSAGE_TYPES_WITH_AUTHOR_NAME = [
SYSTEM_MESSAGE_TYPES.MESSAGE_REMOVED,
SYSTEM_MESSAGE_TYPES.MESSAGE_PINNED,
SYSTEM_MESSAGE_TYPES.MESSAGE_SNIPPETED,
SYSTEM_MESSAGE_TYPES.USER_JOINED_CHANNEL,
SYSTEM_MESSAGE_TYPES.USER_JOINED_DISCUSSION,
SYSTEM_MESSAGE_TYPES.USER_LEFT_CHANNEL
];
export const getInfoMessage = ({
type, role, msg, author
}) => {

View File

@ -2,6 +2,8 @@ import i18n from 'i18n-js';
import { I18nManager } from 'react-native';
import * as RNLocalize from 'react-native-localize';
export * from './isRTL';
export const LANGUAGES = [
{
label: 'English',
@ -51,6 +53,10 @@ export const LANGUAGES = [
label: '日本語',
value: 'ja',
file: require('./locales/ja').default
}, {
label: 'العربية',
value: 'ar',
file: require('./locales/ar').default
}
];
@ -67,6 +73,8 @@ const availableLanguages = Object.keys(i18n.translations);
const { languageTag, isRTL } = RNLocalize.findBestAvailableLanguage(availableLanguages) || defaultLanguage;
I18nManager.forceRTL(isRTL);
I18nManager.swapLeftAndRightInRTL(isRTL);
i18n.locale = languageTag;
i18n.isRTL = I18nManager.isRTL;
export default i18n;

16
app/i18n/isRTL.js Normal file
View File

@ -0,0 +1,16 @@
// https://github.com/zoontek/react-native-localize/blob/master/src/constants.ts#L5
const USES_RTL_LAYOUT = [
'ar',
'ckb',
'fa',
'he',
'ks',
'lrc',
'mzn',
'ps',
'ug',
'ur',
'yi'
];
export const isRTL = locale => USES_RTL_LAYOUT.includes(locale);

663
app/i18n/locales/ar.js Normal file
View File

@ -0,0 +1,663 @@
export default {
'1_person_reacted': 'تفاعل شخص 1',
'1_user': 'مستخدم 1',
'error-action-not-allowed': '{{action}} غير مسموح',
'error-application-not-found': 'لم يتم العثور على البرنامج',
'error-archived-duplicate-name': 'هناك قناة مؤرشفة باسم {{room_name}}',
'error-avatar-invalid-url': 'عنوان الصورة الرمزية غير صحيح: {{url}}',
'error-avatar-url-handling': 'خطأ في معالجة الصورة الرمزية ({{url}}) للمستخدم {{username}}',
'error-cant-invite-for-direct-room': 'لا يمكن دعوة المستخدم في الغرفة المباشرة',
'error-could-not-change-email': 'تعذر تغيير البريد الإلكتروني',
'error-could-not-change-name': 'تعذر تغيير الاسم',
'error-could-not-change-username': 'تعذر تغيير اسم المستخدم',
'error-could-not-change-status': 'تعذر تغيير الحالة',
'error-delete-protected-role': 'لا يمكن حذف دور محمي',
'error-department-not-found': 'القسم غير موجود',
'error-direct-message-file-upload-not-allowed': 'مشاركة الملفات غير مسموح في الرسالة المباشرة',
'error-duplicate-channel-name': 'القناة {{channel_name}} موجودة مسبقاً',
'error-email-domain-blacklisted': 'عنوان اﻹيميل محظور',
'error-email-send-failed': 'خطأ في إرسال البريد اﻹلكتروني: {{message}}',
'error-save-image': 'خطأ عند حفظ الصورة',
'error-save-video': 'خطأ عند حفظ الفيديو',
'error-field-unavailable': '{{field}} مستخدم بالفعل :(',
'error-file-too-large': 'حجم الملف كبير جداً',
'error-importer-not-defined': 'المستورِد معرف بطريقة غير صحيحة، يجب تحديد نوع الإستيراد',
'error-input-is-not-a-valid-field': '{{input}} غير صالح {{field}}',
'error-invalid-actionlink': 'رابط الإجراء غير صالح',
'error-invalid-arguments': 'وسائط غير صحيحة',
'error-invalid-asset': 'ممتلكات غير صحيحة',
'error-invalid-channel': 'قناة غير صحيحة',
'error-invalid-channel-start-with-chars': 'قناة غير صحيحة. تبدأ القناة بحرف @ أو #',
'error-invalid-custom-field': 'حقل مخصص غير صالح',
'error-invalid-custom-field-name': 'اسم الحقل المخصص غير صالح. استخدم الحروف والأرقام والواصلات والشرطات السفلية فقط',
'error-invalid-date': 'التاريخ غير صالح',
'error-invalid-description': 'الوصف غير صالح',
'error-invalid-domain': 'عنوان الموقع غير صالح',
'error-invalid-email': 'عنوان البريد اﻹلكتروني غير صالح {{emai}}',
'error-invalid-email-address': 'عنوان البريد اﻹلكتروني غير صالح',
'error-invalid-file-height': 'ارتفاع الملف غير صالح',
'error-invalid-file-type': 'نوع الملف غير صالح',
'error-invalid-file-width': 'عرض الملف غير صالح',
'error-invalid-from-address': 'عنوان غير صالح في خانة (من)',
'error-invalid-integration': 'تكامل غير صالح',
'error-invalid-message': 'رسالة غير صالحة',
'error-invalid-method': 'طريقة غير صالحة',
'error-invalid-name': 'اسم غير صالح',
'error-invalid-password': 'كلمة مرور خاطئة',
'error-invalid-redirectUri': 'رابط إعادة توجيه غير صحيح',
'error-invalid-role': 'دور غير صالح',
'error-invalid-room': 'غرفة غير صالحة',
'error-invalid-room-name': '{{room_name}} اسم الغرفة غير صالح',
'error-invalid-room-type': '{{type}} نوع الغرفة غير صالح',
'error-invalid-settings': 'الإعدادات المعطاة غير صالحة',
'error-invalid-subscription': 'اشتراك غير صالح',
'error-invalid-token': 'الرمز غير صالح',
'error-invalid-triggerWords': 'كلمات محفزة غير صالحة',
'error-invalid-urls': 'عناوين غير صالحة',
'error-invalid-user': 'مستخدم غير صالح',
'error-invalid-username': 'اسم المستخدم غير صالح',
'error-invalid-webhook-response': 'الرد التلقائي من العنوان استجاب برمز مغاير عن 200',
'error-message-deleting-blocked': 'حذف الرسالة محظور',
'error-message-editing-blocked': 'تعديل الرسالة محظور',
'error-message-size-exceeded': 'حجم الرسالة تجاوز الحد المسموح به (Message_MaxAllowedSize)',
'error-missing-unsubscribe-link': 'يجب عليك تقديم رابط [unsubscribe]',
'error-no-tokens-for-this-user': 'لا توجد رموز لهذا المستخدم',
'error-not-allowed': 'غير مسموح',
'error-not-authorized': 'غير مصرح',
'error-push-disabled': 'إرسال الإشعارات معطل',
'error-remove-last-owner': 'هذا هو المالك الأخير. يرجى تعيين مالك جديد قبل إزالة هذا المالك',
'error-role-in-use': 'لا يمكن حذف الدور لأنه قيد الاستخدام',
'error-role-name-required': 'اسم الدور مطلوب',
'error-the-field-is-required': 'هذا الحقل {{field}} مطلوب',
'error-too-many-requests': 'خطأ، تلقينا الكثير من الطلبات. من فضلك خفف السرعة، يجب الانتظار لمدة {{seconds}} ثانية قبل المحاولة مرة أخرى',
'error-user-is-not-activated': 'المستخدم غير منشط',
'error-user-has-no-roles': 'ليس للمستخدم أدوار',
'error-user-limit-exceeded': 'يتجاوز عدد المستخدمين الذين تحاول دعوتهم إلى #channel_name الحد الذي حدده المشرف',
'error-user-not-in-room': 'المستخدم ليس في هذه الغرفة',
'error-user-registration-custom-field': 'error-user-registration-custom-field',
'error-user-registration-disabled': 'التسجيل معطل',
'error-user-registration-secret': 'التسجيل مسموح به عبر عنوان الويب السري فقط',
'error-you-are-last-owner': 'أنت المالك الأخير. يرجى تعيين مالك جديد قبل مغادرة الغرفة',
Actions: 'الإجراءات',
activity: 'نشاط',
Activity: 'النشاط',
Add_Reaction: 'إضافة تفاعل',
Add_Server: 'إضافة خادم',
Add_users: 'إضافة مستخدمين',
Admin_Panel: 'لوحة الإدارة',
Agent: 'المندوب',
Alert: 'إنذار',
alert: 'إنذار',
alerts: 'الإنذارات',
All_users_in_the_channel_can_write_new_messages: 'يمكن لجميع المستخدمين في القناة كتابة رسائل جديدة',
A_meaningful_name_for_the_discussion_room: 'اسم معبر لغرفة النقاش',
All: 'الكل',
All_Messages: 'كل الرسائل',
Allow_Reactions: 'السماح للتفاعلات',
Alphabetical: 'أبجدي',
and_more: 'وأكثر',
and: 'و',
announcement: 'إعلان',
Announcement: 'إعلان',
Apply_Your_Certificate: 'طبق شهادتك',
Applying_a_theme_will_change_how_the_app_looks: 'سيؤدي تطبيق السمة إلى تغيير شكل التطبيق',
ARCHIVE: 'أرشفة',
archive: 'أرشيف',
are_typing: 'يكتب',
Are_you_sure_question_mark: 'هل أنت متأكد؟',
Are_you_sure_you_want_to_leave_the_room: 'متأكد من مغادرة الغرفة {{room}}؟',
Audio: 'صوت',
Authenticating: 'تتم المصادقة',
Automatic: 'تلقائي',
Auto_Translate: 'ترجمة تلقائية',
Avatar_changed_successfully: 'تم تغيير الصورة الرمزية بنجاح!',
Avatar_Url: 'عنوان ويب الصورة الرمزية',
Away: 'غير متواجد',
Back: 'عودة',
Black: 'أسود',
Block_user: 'حظر المستخدم',
Browser: 'المتصفح',
Broadcast_channel_Description: 'يمكن فقط للمستخدمين المصرح لهم كتابة رسائل جديدة، ولكن سيتمكن المستخدمون الآخرون من الرد',
Broadcast_Channel: 'قناة البث',
Busy: 'مشغول',
By_proceeding_you_are_agreeing: 'من خلال المتابعة، أنت توافق على',
Cancel_editing: 'إلغاء التعديل',
Cancel_recording: 'إلغاء التسجيل الصوتي',
Cancel: 'إلغاء',
changing_avatar: 'تغيير الصورة الرمزية',
creating_channel: 'إنشاء قناة',
creating_invite: 'إنشاء دعوة',
Channel_Name: 'اسم القناة',
Channels: 'قنوات',
Chats: 'محادثات',
Call_already_ended: 'انتهت المكالمة بالفعل!',
Clear_cookies_alert: 'هل تريد حذف جميع ملفات تعريف الإرتباط؟',
Clear_cookies_desc: 'هذا الإجراء سيحذف ملفات تعريف الإرتباط الخاصة بتسجيل الدخول مما يسمح بتسجيل الدخول لحسابات أخرى',
Clear_cookies_yes: 'نعم، مسح ملفات الإرتباط',
Clear_cookies_no: 'لا، احتفظ بملفات تعريف الإرتباط',
Click_to_join: 'انقر للانضمام!',
Close: 'إغلاق',
Close_emoji_selector: 'إغلاق محدد الرموز التعبيرية',
Closing_chat: 'إغلاق المحادثة',
Change_language_loading: 'تغيير اللغة',
Chat_closed_by_agent: 'المندوب أغلق المحادثة',
Choose: 'اختر',
Choose_from_library: 'اختر من المكتبة',
Choose_file: 'اختر ملف',
Choose_where_you_want_links_be_opened: 'اختر المكان الذي تريد فتح الروابط فيه',
Code: 'الرمز',
Code_or_password_invalid: 'الرمز أو كلمة المرور خاطئة',
Collaborative: 'تعاونية',
Confirm: 'تأكيد',
Connect: 'اتصال',
Connected: 'متصل',
connecting_server: 'يتم الاتصال بالخادم',
Connecting: 'جار الاتصال...',
Contact_us: 'تواصل معنا',
Contact_your_server_admin: 'اتصل بمسؤول الخادم',
Continue_with: 'متابعة بـ',
Copied_to_clipboard: 'تم النسخ للحافظة!',
Copy: 'نسخ',
Conversation: 'محادثة',
Permalink: 'رابط ثابت',
Certificate_password: 'الرقم السري للشهادة',
Clear_cache: 'امسح ذاكرة التخزين المؤقتة للخادم',
Clear_cache_loading: 'يتم مسح ذاكرة التخزين',
Whats_the_password_for_your_certificate: 'ماهي كلمة المرور للشهادة؟',
Create_account: 'إنشاء حساب',
Create_Channel: 'إنشاء قناة',
Create_Direct_Messages: 'إنشاء رسالة مباشرة',
Create_Discussion: 'إنشاء نقاش',
Created_snippet: 'إنشاء مقتطف',
Create_a_new_workspace: 'إنشاء مساحة عمل جديدة',
Create: 'إنشاء',
Custom_Status: 'حالة مخصصة',
Dark: 'داكن',
Dark_level: 'مستوى السمة الداكنة',
Default: 'افتراضي',
Default_browser: 'المتصفح الأساسي',
Delete_Room_Warning: 'سيؤدي حذف الغرفة إلى حذف جميع الرسائل المنشورة. لا يمكن التراجع بعد الحذف',
Department: 'القسم',
delete: 'حذف',
Delete: 'حذف',
DELETE: 'حذف',
deleting_room: 'حذف الغرفة',
description: 'وصف',
Description: 'وصف',
DESKTOP_OPTIONS: 'خيارات سطح المكتب',
DESKTOP_NOTIFICATIONS: 'إشعارات سطح المكتب',
Desktop_Alert_info: 'هذه الإشعارات ترسل لسطح المكتب',
Directory: 'مجلد',
Direct_Messages: 'رسالة مباشرة',
Disable_notifications: 'أوقف الإشعارات',
Discussions: 'مناقشات',
Discussion_Desc: 'ساهم بإعطاء نظرة عامة لما يجري. عند إنشاء مناقشة يتم إنشاء قناة فرعية وربطها بالقناة المحددة',
Discussion_name: 'اسم النقاش',
Done: 'تم',
Dont_Have_An_Account: 'ليس لديك حساب؟',
Do_you_have_an_account: 'هل لديك حساب؟',
Do_you_have_a_certificate: 'هل لديك شهادة؟',
Do_you_really_want_to_key_this_room_question_mark: 'هل تريد حقاً {{key}} هذه الغرفة؟',
E2E_How_It_Works_info1: 'يمكنك الآن إنشاء مجموعات خاصة ورسائل مباشرة مشفرة. بإمكانك أيضاً تشفير المجموعات الخاصة والرسائل المباشرة الموجودة مسبقاً',
E2E_How_It_Works_info2: 'التشفير يتم بين الطرفيات بمعنى أن المفتاح سيستخدم لتشفير وفك تشفير رسائلك ولن يتم حفظه في الخادم. لذلك يترتب عليك حفظ كلمة المرور هذه في مكان آمن',
E2E_How_It_Works_info3: 'حين الاستمرار سيتم إنشاء كلمة المرور بين الطرفيات',
E2E_How_It_Works_info4: 'بإمكانك أيضاً إنشاء كلمة مرور جديدة لمفتاح التشفير في أي وقت عند دخولك بكلمة المرور الحالية لمفتاح التشفير',
edit: 'تعديل',
edited: 'معدل',
Edit: 'تعديل',
Edit_Status: 'تعديل الحالة',
Edit_Invite: 'تعديل الدعوة',
End_to_end_encrypted_room: 'غرفة مشفرة بين الطرفيات',
end_to_end_encryption: 'تشفير بين الطرفيات',
Email_Notification_Mode_All: 'لكل إشارة أو رسالة مباشرة',
Email_Notification_Mode_Disabled: 'معطل',
Email_or_password_field_is_empty: 'حقل البريد الإلكتروني أو كلمة المرور فارغ',
Email: 'البريد الإلكتروني',
EMAIL: 'البريد الإلكتروني',
email: 'البريد الإلكتروني',
Empty_title: 'عنوان فارغ',
Enable_Auto_Translate: 'تمكين الترجمة التلقائية',
Enable_notifications: 'تفعيل الإشعارات',
Encrypted: 'مشفر',
Encrypted_message: 'رسالة مشفرة',
Enter_Your_E2E_Password: 'أدخل كلمة المرور بين الطرفيات',
Enter_Your_Encryption_Password_desc1: 'سيمكنك هذا من الوصول لرسائلك المباشرة والمجموعات الخاصة',
Enter_Your_Encryption_Password_desc2: 'يجب إدخال كلمة المرور لتشفير وفك تشفير الرسائل المرسلة',
Encryption_error_title: 'كلمة المرور المشفرة خاطئة',
Encryption_error_desc: 'تعذر قراءة مفتاح التشفير أثناء الاستيراد',
Everyone_can_access_this_channel: 'يمكن للجميع الوصول إلى هذه القناة',
Error_uploading: 'خطأ في الرفع',
Expiration_Days: 'انتهاء (أيام)',
Favorite: 'مفضل',
Favorites: 'مفضلات',
Files: 'ملفات',
File_description: 'وصف الملف',
File_name: 'اسم الملف',
Finish_recording: 'إنهاء التسجيل',
Following_thread: 'متابعة الموضوع',
For_your_security_you_must_enter_your_current_password_to_continue: 'من أجل حمايتك، يجب عليك إدخال كلمة المرور الحالية للمتابعة',
Forgot_password_If_this_email_is_registered: 'إن كان البريد الإلكتروني مسجلاً، فسنرسل تعليمات إعادة تعيين كلمة المرور الخاصة بك. إذا لم تتلق بريداً إلكترونياً قريباً، فيرجى العودة والمحاولة مرة أخرى',
Forgot_password: 'هل نسيت كلمة المرور؟',
Forgot_Password: 'نسيت كلمة المرور',
Forward: 'إعادة توجيه',
Forward_Chat: 'إعادة توجيه المحادثة',
Forward_to_department: 'إعادة توجيه للقسم',
Forward_to_user: 'إعادة توجيه لمستخدم',
Full_table: 'انقر لرؤية الجدول كاملاً',
Generate_New_Link: 'إنشاء رابط جديد',
Group_by_favorites: 'جمع حسب المفضلة',
Group_by_type: 'جمع حسب النوع',
Hide: 'إخفاء',
Has_joined_the_channel: 'انضم إلى القناة',
Has_joined_the_conversation: 'انضم إلى المحادثة',
Has_left_the_channel: 'غادر القناة',
Hide_System_Messages: 'إخفاء رسائل النظام',
Hide_type_messages: 'إخفاء رسائل "{{type}}"',
How_It_Works: 'طريقة العمل',
Message_HideType_uj: 'مستخدم انضم',
Message_HideType_ul: 'مستخدم غادر',
Message_HideType_ru: 'مستخدم حُذف',
Message_HideType_au: 'مستخدم أضيف',
Message_HideType_mute_unmute: 'صوت المستخدم كتم / فتح',
Message_HideType_r: 'اسم الغرفة تغير',
Message_HideType_ut: 'مستخدم انضم للمحادثة',
Message_HideType_wm: 'ترحيب',
Message_HideType_rm: 'رسالة حُذفت',
Message_HideType_subscription_role_added: 'تعيين دور جديد',
Message_HideType_subscription_role_removed: 'دور أصبح غير معرف',
Message_HideType_room_archived: 'غرفة أرشفت',
Message_HideType_room_unarchived: 'غرفة ألغيت أرشفتها',
I_Saved_My_E2E_Password: 'قمت بحفظ كلمة المرور الطرفية',
IP: ' عنوان بروتوكول الإنترنت (الآيبي)',
In_app: 'في التطبيق',
IN_APP_AND_DESKTOP: 'داخل التطبيق وسطح المكتب',
In_App_and_Desktop_Alert_info: 'يعرض شعاراً أعلى الشاشة عندما يكون التطبيق مفتوحًا، ويعرض إشعاراً على سطح المكتب',
Invisible: 'غير مرئي',
Invite: 'دعوة',
is_a_valid_RocketChat_instance: 'خادم Rocket.Chat صالح',
is_not_a_valid_RocketChat_instance: 'خادم Rocket.Chat غير صالح',
is_typing: 'يكتب',
Invalid_or_expired_invite_token: 'رمز الدعوة غير صالح أو منتهي الصلاحية',
Invalid_server_version: 'الخادم الذي تحاول الاتصال به يستخدم إصدارا لم يعد مدعوماً: {{currentVersion}}.\n\n النسخ المدعومة تبدأ من {{minVersion}}',
Invite_Link: 'رابط الدعوة',
Invite_users: 'دعوة المستخدمين',
Join: 'انضم',
Join_our_open_workspace: 'انضم لمساحة عملنا المفتوحة',
Join_your_workspace: 'انضم لمساحة عملك',
Just_invited_people_can_access_this_channel: 'يمكن للأشخاص المدعوين فقط الوصول إلى هذه القناة',
Language: 'اللغة',
last_message: 'الرسالة الأخيرة',
Leave_channel: 'مغادرة القناة',
leaving_room: 'مغادرة الغرفة',
leave: 'مغادرة',
Legal: 'قانوني',
Light: 'ساطع',
License: 'رخصة',
Livechat: 'محادثة مباشرة',
Livechat_edit: 'تعديل المحادثة المباشرة',
Login: 'تسجيل الدخول',
Login_error: 'تم رفض تسجيل الدخول الرجاء المحاولة مرة أخرى',
Login_with: 'تسجيل الدخول بـ',
Logging_out: 'تسجيل الخروج',
Logout: 'تسجيل الخروج',
Max_number_of_uses: 'أقصى عدد للاستخدام',
Max_number_of_users_allowed_is_number: 'أقصى عدد للمستخدمين {{maxUsers}}',
members: 'أفراد',
Members: 'أفراد',
Mentioned_Messages: 'الرسائل المذكورة',
mentioned: 'مشار',
Mentions: 'الإشارات',
Message_accessibility: 'الرسالة من {{user}} في {{time}}: {{message}}',
Message_actions: 'إجراءات الرسالة',
Message_pinned: 'الرسالة مثبتة',
Message_removed: 'الرسالة حذفت',
Message_starred: 'الرسالة مميزة',
Message_unstarred: 'الرسالة غير مميزة',
message: 'رسالة',
messages: 'رسائل',
Message: 'الرسالة',
Messages: 'الرسائل',
Message_Reported: 'تم التبليغ على الرسالة',
Microphone_Permission_Message: 'يحتاج Rocket.Chat للوصول إلى الميكروفون الخاص بك حتى تتمكن من إرسال رسالة صوتية',
Microphone_Permission: 'إذن الميكروفون',
Mute: 'كتم',
muted: 'مكتوم',
My_servers: 'الخوادم',
N_people_reacted: '{{n}} تفاعل الناس',
N_users: '{{n}} مستخدمين',
name: 'اسم',
Name: 'اسم',
Navigation_history: 'تاريخ التصفح',
Never: 'أبداً',
New_Message: 'رسالة جديدة',
New_Password: 'كلمة مرور جديدة',
New_Server: 'خادم جديد',
Next: 'التالي',
No_files: 'لا ملفات',
No_limit: 'لا حدود',
No_mentioned_messages: 'لا رسائل مشارة',
No_pinned_messages: 'لا رسائل مثبتة',
No_results_found: 'لا نتائج',
No_starred_messages: 'لا رسائل مميزة',
No_thread_messages: 'لا رسائل للموضوع',
No_label_provided: 'لا {{label}} معطى',
No_Message: 'لا رسائل',
No_messages_yet: 'لا رسائل حتى اﻵن',
No_Reactions: 'لا تفاعل',
No_Read_Receipts: 'لا إيصالات قراءة',
Not_logged: 'غير مسجل الدخول',
Not_RC_Server: 'هذا ليس بخادم Rocket.Chat.\n{{contact}}',
Nothing: 'لا شيء',
Nothing_to_save: 'لا شيء للحفظ!',
Notify_active_in_this_room: 'أبلغ المستخدمين النشطين في هذه الغرفة',
Notify_all_in_this_room: 'أبلغ الجميع في الغرفة',
Notifications: 'الإشعارات',
Notification_Duration: 'زمن الإشعار',
Notification_Preferences: 'تفضيلات الإشعار',
No_available_agents_to_transfer: 'المندوبين غير متوفرين حالياً',
Offline: 'غير متصل',
Oops: 'عفوًا!',
Omnichannel: 'القنوات الموحدة',
Open_Livechats: 'محادثات مباشرة جارية',
Omnichannel_enable_alert: 'أنت غير متاح ',
Onboarding_description: 'مساحة عمل هي مساحة لفريقك ومنظمتك للتعاون. اطلب من المشرف العنوان للانضمام أو أنشئ لفريقك',
Onboarding_join_workspace: 'انضم لمساحة عمل',
Onboarding_subtitle: 'ما بعد بيئة فريق تعاونية',
Onboarding_title: 'مرحبا بك في Rocket.Chat',
Onboarding_join_open_description: 'انضم لمساحة عملنا للتواصل مع فريق Rocket.Chat ومع المجتمع',
Onboarding_agree_terms: 'بالمواصلة أنت توافق على Rocket.Chat',
Onboarding_less_options: 'خيارات أقل',
Onboarding_more_options: 'خيارات أكثر',
Online: 'متصل',
Only_authorized_users_can_write_new_messages: 'يمكن للمستخدمين المصرح لهم فقط كتابة رسائل جديدة',
Open_emoji_selector: 'افتح محدد الرموز التعبيرية',
Open_Source_Communication: 'تواصل مفتوح المصدر',
Open_your_authentication_app_and_enter_the_code: 'افتح تطبيق المصادقة الخاص بك وأدخل الرمز',
OR: 'أو',
OS: 'نظام التشغيل',
Overwrites_the_server_configuration_and_use_room_config: 'يمكن للمستخدمين المصرح لهم فقط كتابة رسائل جديدة',
Password: 'كلمة المرور',
Parent_channel_or_group: 'القناة أو المجموعة الأب',
Permalink_copied_to_clipboard: 'تم نسخ الرابط الثابت إلى الحافظة!',
Phone: 'الهاتف',
Pin: 'ثبت',
Pinned_Messages: 'رسائل مثبتة',
pinned: 'مثبت',
Pinned: 'مثبت',
Please_add_a_comment: 'الرجاء إضافة تعليق',
Please_enter_your_password: 'الرجاء إدخال كلمة المرور',
Please_wait: 'الرجاء الإنتظار',
Preferences: 'التفضيلات',
Preferences_saved: 'تم حفظ التفضيلات',
Privacy_Policy: 'سياسة الخصوصية',
Private_Channel: 'قناة خاصة',
Private_Groups: 'مجموعات خاصة',
Private: 'خاص',
Processing: 'جار معالجة...',
Profile_saved_successfully: 'تم حفظ الملف الشخصي بنجاح!',
Profile: 'الملف الشخصي',
Public_Channel: 'قناة عامة',
Public: 'عام',
PUSH_NOTIFICATIONS: 'الإشعارات',
Push_Notifications_Alert_Info: 'يتم إرسال هذه الإشعارات إليك عندما لا يكون التطبيق مفتوحاً',
Quote: 'اقتباس',
Reactions_are_disabled: 'التفاعل معطل',
Reactions_are_enabled: 'التفاعل مفعل',
Reactions: 'التفاعلات',
Read: 'قراءة',
Read_External_Permission_Message: 'يحتاج Rocket.chat للوصول إلى الصور والملفات الموجودة على الجهاز',
Read_External_Permission: 'صلاحية قراءة الوسائط',
Read_Only_Channel: 'قناة للقراءة فقط',
Read_Only: 'قراءة فقط',
Read_Receipt: 'قراءة المستلم',
Receive_Group_Mentions: 'تلقي إشارات المجموعة',
Receive_Group_Mentions_Info: 'تلقي @all و @here للإشعارات',
Register: 'تسجيل',
Repeat_Password: 'أعد كلمة المرور',
Replied_on: 'تم الرد على:',
replies: 'ردود',
reply: 'رد',
Reply: 'رد',
Report: 'بلاغ',
Receive_Notification: 'استلام الإشعار',
Receive_notifications_from: 'استلام الإشعارات من {{name}}',
Resend: 'أعد الإرسال',
Reset_password: 'إعادة تعيين كلمة المرور',
resetting_password: 'إعادة تعيين كلمة المرور',
RESET: 'إعادة',
Return: 'العودة',
Review_app_title: 'هل أنت مستمتع بهذا التطبيق؟',
Review_app_desc: 'قم بإعطائنا 5 نجوم {{store}}',
Review_app_yes: 'أكيد!',
Review_app_no: 'لا',
Review_app_later: 'ربما لاحقاً',
Review_app_unable_store: 'لم يتمكن من فتح {{store}}',
Review_this_app: 'تقييم هذا التطبيق',
Remove: 'حذف',
Roles: 'أدوار',
Room_actions: 'إجراءات الغرفة',
Room_changed_announcement: 'تم تغيير إعلان الغرفة إلى: {{announcement}} من قبل {{userBy}}',
Room_changed_description: 'تم تغيير وصف الغرفة إلى: {{description}} من قبل {{userBy}}',
Room_changed_privacy: 'تم تغيير نوع الغرفة إلى: {{type}} من قبل {{userBy}}',
Room_changed_topic: 'تم تغيير موضوع الغرفة إلى: {{topic}} من قبل {{userBy}}',
Room_Files: 'ملفات الغرفة',
Room_Info_Edit: 'تعديل معلومات الغرفة',
Room_Info: 'معلومات الغرفة',
Room_Members: 'أعضاء الغرفة',
Room_name_changed: 'تم تغيير اسم الغرفة إلى: {{name}} من قبل {{userBy}}',
SAVE: 'حفظ',
Saved: 'تم الحفظ',
Save_Changes: 'حفظ التغيرات',
Save: 'حفظ',
saving_preferences: 'حفظ التفضيلات',
saving_profile: 'حفظ الملف الشخصي',
saving_settings: 'حفظ الإعدادات',
saved_to_gallery: 'تم الحفظ في المعرض',
Save_Your_E2E_Password: 'حفظ كلمة المرور بين الطرفين',
Save_Your_Encryption_Password: 'حفظ كلمة المرور المشفرة',
Save_Your_Encryption_Password_warning: 'لا نقوم بتخزين كلمة المرور، الرجاء حفظها لديك في مكان آخر',
Save_Your_Encryption_Password_info: 'لا يمكن إستعادة كلمة المرور في حال فقدانها ولن تستطيع الوصول لرسائلك',
Search_Messages: 'بحث الرسائل',
Search: 'بحث',
Search_by: 'بحث حسب',
Search_global_users: 'بحث عام عن المستخدمين',
Search_global_users_description: 'إذا قمت بالتفعيل، فسيمكنك البحث عن أي مستخدم في شركات أو خوادم أخرى',
Seconds: '{{second}} ثواني',
Select_Avatar: 'حدد الصورة الرمزية',
Select_Server: 'حدد خادم',
Select_Users: 'حدد مستخدمين',
Select_a_Channel: 'حدد قناة',
Select_a_Department: 'حدد قسم',
Select_an_option: 'حدد خيار',
Select_a_User: 'حدد مستخدم',
Send: 'إرسال',
Send_audio_message: 'إرسال رسالة صوتية',
Send_crash_report: 'إرسال تقرير الأعطال',
Send_message: 'إرسال الرسالة',
Send_me_the_code_again: 'أرسل الرمز مرة أخرى',
Send_to: 'إرسال إلى...',
Sending_to: 'يتم الإرسال إلى',
Sent_an_attachment: 'تم إرسال المرفق',
Server: 'خادم',
Servers: 'خوادم',
Server_version: 'نسخة الخادم: {{version}}',
Set_username_subtitle: 'يتم استخدام اسم المستخدم للسماح للآخرين بذكرك في الرسائل',
Set_custom_status: 'حدد حالة خاصة',
Set_status: 'حدد حالة',
Status_saved_successfully: 'تم حفظ الحالة بنجاح!',
Settings: 'الإعدادات',
Settings_succesfully_changed: 'تم تعديل الإعدادات بنجاح!',
Share: 'مشاركة',
Share_Link: 'مشاركة رابط',
Share_this_app: 'مشاركة هذا البرنامج',
Show_more: 'إظهار أكثر..',
Show_Unread_Counter: 'عرض عدد الرسائل غير المقروءة',
Show_Unread_Counter_Info: 'يتم عرض العدد غير المقروء كشارة على يمين القناة في القائمة',
Sign_in_your_server: 'تسجيل الدخول إلى الخادم الخاص بك',
Sign_Up: 'تسجيل جديد',
Some_field_is_invalid_or_empty: 'بعض الحقول غير صالحة أو فارغة',
Sorting_by: 'فرز حسب {{key}}',
Sound: 'الصوت',
Star_room: 'تمييز الغرفة',
Star: 'تمييز',
Starred_Messages: 'رسائل مميزة',
starred: 'مميزة',
Starred: 'مميزة',
Start_of_conversation: 'بداية المحادثة',
Start_a_Discussion: 'ابدأ نقاش',
Started_discussion: 'بدأ النقاش',
Started_call: 'أجرى الاتصال {{userBy}}',
Submit: 'إرسال',
Table: 'جدول',
Tags: 'العلامات',
Take_a_photo: 'التقاط صورة',
Take_a_video: 'التقاط فيديو',
Take_it: 'التقط!',
tap_to_change_status: 'انقر لتغيير الحالة',
Tap_to_view_servers_list: 'انقر لعرض قائمة الخوادم',
Terms_of_Service: ' شروط الخدمة ',
Theme: 'سمة',
The_user_wont_be_able_to_type_in_roomName: 'المستخدم لن يتمكن من الكتابة في {{roomName}}',
The_user_will_be_able_to_type_in_roomName: 'المستخدم سيتمكن من الكتابة في {{roomName}}',
There_was_an_error_while_action: 'حدث خطأ أثناء {{action}}!',
This_room_is_blocked: 'هذه الغرفة محظورة',
This_room_is_read_only: 'هذه الغرفة للقراءة فقط',
Thread: 'موضوع',
Threads: 'مواضيع',
Timezone: 'المنطقة الزمنية',
To: 'إلى',
topic: 'عنوان',
Topic: 'عنوان',
Translate: 'ترجمة',
Try_again: 'حاول مجدداً',
Two_Factor_Authentication: 'المصادقة الثنائية',
Type_the_channel_name_here: 'اكتب اسم القناة هنا',
unarchive: 'إلغاء الأرشفة',
UNARCHIVE: 'إلغاء الأرشفة',
Unblock_user: 'إلغاء حظر عن مستخدم',
Unfavorite: 'إزالة من المفضلة',
Unfollowed_thread: 'موضوع غير متابع',
Unmute: 'إلغاء كتم',
unmuted: 'إلغاء كتم',
Unpin: 'إلغاء التثبيت',
unread_messages: 'رسائل غير مقروءة',
Unread: 'غير مقروء',
Unread_on_top: 'غير مقروء في الأعلى',
Unstar: 'إلغاء التمييز',
Updating: 'جار التحديث...',
Uploading: 'جار الرفع',
Upload_file_question_mark: 'رفع الملف؟',
User: 'مستخدم',
Users: 'مستخدمين',
User_added_by: 'مستخدم {{userAdded}} أضيف من قبل {{userBy}}',
User_Info: 'معلومات المستخدم',
User_has_been_key: 'تمت {{key}} المستخدم!',
User_is_no_longer_role_by_: 'تم إزالة الدور {{role}} عن المستخدم {{user}} من قبل {{userBy}}',
User_muted_by: 'المستخدم {{userMuted}} كتم من قبل {{userBy}}',
User_removed_by: 'المستخدم {{userRemoved}} حذف من قبل {{userBy}}',
User_sent_an_attachment: '{{user}} أرسل مرفقًا',
User_unmuted_by: 'ألغى {{userBy}} الكتم عن {{userUnmuted}}',
User_was_set_role_by_: 'أضيف دور {{role}} للمستخدم {{user}} من قبل {{userBy}}',
Username_is_empty: 'اسم المستخدم فارغ',
Username: 'اسم المستخدم',
Username_or_email: 'اسم المستخدم أو البريد الالكتروني',
Uses_server_configuration: 'يستخدم إعداد الخادم',
Usually_a_discussion_starts_with_a_question_like_How_do_I_upload_a_picture: 'عادةً، النقاش يبدأ بسؤال، على سبيل المثال: كيف أرفع صورة؟',
Validating: 'يتم التحقق',
Registration_Succeeded: 'تم التسجيل بنجاح',
Verify: 'تحقق',
Verify_email_title: 'تم التسجيل!',
Verify_email_desc: 'لقد أرسلنا إليك بريداً إلكترونياً لتأكيد تسجيلك. إذا لم تتلق البريد الإلكتروني قريباً، فيرجى العودة والمحاولة مرة أخرى',
Verify_your_email_for_the_code_we_sent: 'يرجى تأكيد البريد الإلكتروني عبر الرمز المرسل',
Video_call: 'مكالمة فيديو',
View_Original: 'عرض المحتوى الأصلي',
Voice_call: 'مكالمة صوتية',
Waiting_for_network: 'بانتظار توفر شبكة...',
Websocket_disabled: 'تم تعطيل Websocket لهذا الخادم.\n{{contact}}',
Welcome: 'مرحبا',
What_are_you_doing_right_now: 'ما الذي تفعله حالياً؟',
Whats_your_2fa: 'ما هو رمز التحقق الثنائي؟',
Without_Servers: 'بدون خوادم',
Workspaces: 'مساحات العمل',
Would_you_like_to_return_the_inquiry: 'هل ترغب بالرد على السؤال؟',
Write_External_Permission_Message: 'يحتاج Rocket.Chat للوصول إلى معرض الصور الخاص بك حتى تتمكن من حفظ الصور',
Write_External_Permission: 'إذن معرض',
Yes: 'نعم',
Yes_action_it: 'نعم، {{action}}!',
Yesterday: 'أمس',
You_are_in_preview_mode: 'أنت في وضع المعاينة',
You_are_offline: 'أنت غير متصل',
You_can_search_using_RegExp_eg: 'يمكنك استخدام RegExp. مثال: `/^text$/i`',
You_colon: 'أنت: ',
you_were_mentioned: 'تمت الإشارة إليك',
You_were_removed_from_channel: 'تمت إزالتك من {{channel}}',
you: 'أنت',
You: 'أنت',
Logged_out_by_server: 'لقد تم تسجيل خروجك من قبل الخادم. الرجاد الدخول من جديد',
You_need_to_access_at_least_one_RocketChat_server_to_share_something: 'تحتاج إلى الوصول إلى خادم Rocket.Chat واحد على الأقل لمشاركة شيء ما',
You_need_to_verifiy_your_email_address_to_get_notications: 'يجب تأكيد البريد الإلكتروني حتى تصلك الإشعارات',
Your_certificate: 'شهادتك',
Your_message: 'رسالتك',
Your_invite_link_will_expire_after__usesLeft__uses: 'سوف تنتهي صلاحية رابط الدعوة الخاص بك بعد {{usesLeft}} استخدامات',
Your_invite_link_will_expire_on__date__or_after__usesLeft__uses: 'ستنتهي صلاحية رابط الدعوة الخاص بك في {{date}} أو بعد {{usesLeft}} استخدامات',
Your_invite_link_will_expire_on__date__: 'ستنتهي صلاحية رابط الدعوة الخاص بك في {{date}}',
Your_invite_link_will_never_expire: 'لن تنتهي صلاحية رابط الدعوة الخاص بك',
Your_workspace: 'مساحة عملك',
Your_password_is: 'كلمة المرور الخاصة بك هي',
Version_no: 'النسخة: {{version}}',
You_will_not_be_able_to_recover_this_message: 'لن تتمكن من استعادة هذه الرسالة!',
You_will_unset_a_certificate_for_this_server: 'ستلغي شهادة هذا الخادم',
Change_Language: 'تغيير اللغة',
Crash_report_disclaimer: 'نحن لا نتتبع محتوى محادثاتك أبداً. يحتوي تقرير الأعطال فقط على المعلومات ذات الصلة لنا من أجل تحديد المشاكل وإصلاحها',
Type_message: 'اكتب رسالة',
Room_search: 'البحث عن الغرف',
Room_selection: 'اختيار الغرفة 1...9',
Next_room: 'الغرفة المجاورة',
Previous_room: 'الغرفة السابقة',
New_room: 'غرفة جديدة',
Upload_room: 'تحميل إلى الغرفة',
Search_messages: 'رسائل البحث',
Scroll_messages: 'تمرير الرسائل',
Reply_latest: 'الرد على الأحدث',
Reply_in_Thread: 'الرد في موضوع',
Server_selection: 'اختيار الخادم',
Server_selection_numbers: 'اختيار الخادم 1...9',
Add_server: 'إضافة خادم',
New_line: 'سطر جديد',
You_will_be_logged_out_of_this_application: 'سيتم تسجيل خروجك من هذا التطبيق.',
Clear: 'مسح',
This_will_clear_all_your_offline_data: 'سيؤدي هذا إلى محو جميع بياناتك في وضع عدم الاتصال.',
This_will_remove_all_data_from_this_server: 'هذا الإجراء يحذف جميع البيانات من هذا السيرفر',
Mark_unread: 'علامة غير مقروء',
Wait_activation_warning: 'يحب تفعيل حسابك من المشرف قبل تسجيل الدخول',
Screen_lock: 'قفل الشاشة',
Local_authentication_biometry_title: 'صادق',
Local_authentication_biometry_fallback: 'استخدم كلمة المرور',
Local_authentication_unlock_option: 'افتح القفل بكلمة المرور',
Local_authentication_change_passcode: 'تغيير كلمة المرور',
Local_authentication_info: 'تنويه: يجب حذف التطبيق عند نسيان كلمة المرور',
Local_authentication_facial_recognition: 'التعرف على الوجه',
Local_authentication_fingerprint: 'البصمة',
Local_authentication_unlock_with_label: 'فتح القفل بـ {{label}}',
Local_authentication_auto_lock_60: 'بعد دقيقة',
Local_authentication_auto_lock_300: 'بعد 5 دقائق',
Local_authentication_auto_lock_900: 'بعد 15 دقيقة',
Local_authentication_auto_lock_1800: 'بعد 30 دقيقة',
Local_authentication_auto_lock_3600: 'بعد ساعة',
Passcode_enter_title: 'أدخل كلمة المرور',
Passcode_choose_title: 'اختر كلمة المرور الجديدة',
Passcode_choose_confirm_title: 'تأكيد كلمة المرور الجديدة',
Passcode_choose_error: 'كلمة المرور غير متطابقة. حاول مجدداً',
Passcode_choose_force_set: 'كلمة المرور مطلوبة من المشرف',
Passcode_app_locked_title: 'التطبيق مقفل',
Passcode_app_locked_subtitle: 'حاول مجدداً بعد {{timeLeft}} ثوان',
After_seconds_set_by_admin: 'بعد {{seconds}} ثوان (حددها المدير)',
Dont_activate: 'لا تقم بالتفعيل الآن',
Queued_chats: 'محادثات في قائمى الانتظار',
Queue_is_empty: 'قائمة الانتظار فارغة',
Logout_from_other_logged_in_locations: 'تسجيل الخروج من الأماكن الأخرى',
You_will_be_logged_out_from_other_locations: 'سيتم تسجيل خروج من الأماكن الأخرى',
Logged_out_of_other_clients_successfully: 'تم تسجيل الخروج من الأماكن الأخرى بنجاح',
Logout_failed: 'فشل تسجيل الخروج!',
Log_analytics_events: 'تحليلات سجل الأحداث'
};

View File

@ -552,7 +552,7 @@ export default {
Users: 'Benutzer',
User_added_by: 'Benutzer {{userAdded}} hinzugefügt von {{userBy}}',
User_Info: 'Benutzerinfo',
User_has_been_key: 'Benutzer wurde {{key}}!',
User_has_been_key: 'Benutzer wurde {{key}}',
User_is_no_longer_role_by_: '{{user}} ist nicht länger {{role}} von {{userBy}}',
User_muted_by: 'Benutzer {{userMuted}} von {{userBy}} stummgeschaltet',
User_removed_by: 'Benutzer {{userRemoved}} wurde von {{userBy}} entfernt',

View File

@ -167,7 +167,7 @@ export default {
Create_Channel: 'Create Channel',
Create_Direct_Messages: 'Create Direct Messages',
Create_Discussion: 'Create Discussion',
Created_snippet: 'Created a snippet',
Created_snippet: 'created a snippet',
Create_a_new_workspace: 'Create a new workspace',
Create: 'Create',
Custom_Status: 'Custom Status',
@ -247,9 +247,9 @@ export default {
Group_by_favorites: 'Group favorites',
Group_by_type: 'Group by type',
Hide: 'Hide',
Has_joined_the_channel: 'Has joined the channel',
Has_joined_the_conversation: 'Has joined the conversation',
Has_left_the_channel: 'Has left the channel',
Has_joined_the_channel: 'has joined the channel',
Has_joined_the_conversation: 'has joined the conversation',
Has_left_the_channel: 'has left the channel',
Hide_System_Messages: 'Hide System Messages',
Hide_type_messages: 'Hide "{{type}}" messages',
How_It_Works: 'How It Works',
@ -281,6 +281,8 @@ export default {
Invite_Link: 'Invite Link',
Invite_users: 'Invite users',
Join: 'Join',
Join_Code: 'Join Code',
Insert_Join_Code: 'Insert Join Code',
Join_our_open_workspace: 'Join our open workspace',
Join_your_workspace: 'Join your workspace',
Just_invited_people_can_access_this_channel: 'Just invited people can access this channel',
@ -552,7 +554,7 @@ export default {
Users: 'Users',
User_added_by: 'User {{userAdded}} added by {{userBy}}',
User_Info: 'User Info',
User_has_been_key: 'User has been {{key}}!',
User_has_been_key: 'User has been {{key}}',
User_is_no_longer_role_by_: '{{user}} is no longer {{role}} by {{userBy}}',
User_muted_by: 'User {{userMuted}} muted by {{userBy}}',
User_removed_by: 'User {{userRemoved}} removed by {{userBy}}',
@ -680,5 +682,27 @@ export default {
No_threads: 'There are no threads',
No_threads_following: 'You are not following any threads',
No_threads_unread: 'There are no unread threads',
Messagebox_Send_to_channel: 'Send to channel'
Messagebox_Send_to_channel: 'Send to channel',
Set_as_leader: 'Set as leader',
Set_as_moderator: 'Set as moderator',
Set_as_owner: 'Set as owner',
Remove_as_leader: 'Remove as leader',
Remove_as_moderator: 'Remove as moderator',
Remove_as_owner: 'Remove as owner',
Remove_from_room: 'Remove from room',
Ignore: 'Ignore',
Unignore: 'Unignore',
User_has_been_ignored: 'User has been ignored',
User_has_been_unignored: 'User is no longer ignored',
User_has_been_removed_from_s: 'User has been removed from {{s}}',
User__username__is_now_a_leader_of__room_name_: 'User {{username}} is now a leader of {{room_name}}',
User__username__is_now_a_moderator_of__room_name_: 'User {{username}} is now a moderator of {{room_name}}',
User__username__is_now_a_owner_of__room_name_: 'User {{username}} is now a owner of {{room_name}}',
User__username__removed_from__room_name__leaders: 'User {{username}} removed from {{room_name}} leaders',
User__username__removed_from__room_name__moderators: 'User {{username}} removed from {{room_name}} moderators',
User__username__removed_from__room_name__owners: 'User {{username}} removed from {{room_name}} owners',
The_user_will_be_removed_from_s: 'The user will be removed from {{s}}',
Yes_remove_user: 'Yes, remove user!',
Direct_message: 'Direct message',
Message_Ignored: 'Message ignored. Tap to display it.'
};

View File

@ -146,7 +146,7 @@ export default {
Whats_the_password_for_your_certificate: '¿Cuál es la contraseña de tu cerficiado?',
Create_account: 'Crear una cuenta',
Create_Channel: 'Crear Sala',
Created_snippet: 'Crear snippet',
Created_snippet: 'crear snippet',
Create_a_new_workspace: 'Crear un Workspace',
Create: 'Crear',
Dark: 'Óscuro',
@ -192,9 +192,9 @@ export default {
Group_by_favorites: 'Agrupar por favoritos',
Group_by_type: 'Agrupar por tipo',
Hide: 'Ocultar',
Has_joined_the_channel: 'Se ha unido al canal',
Has_joined_the_conversation: 'Se ha unido a la conversación',
Has_left_the_channel: 'Ha dejado el canal',
Has_joined_the_channel: 'se ha unido al canal',
Has_joined_the_conversation: 'se ha unido a la conversación',
Has_left_the_channel: 'ha dejado el canal',
In_App_And_Desktop: 'In-app and Desktop',
In_App_and_Desktop_Alert_info: 'Muestra un banner en la parte superior de la pantalla cuando la aplicación está abierta y muestra una notificación en el escritorio',
Invisible: 'Invisible',
@ -406,7 +406,7 @@ export default {
Upload_file_question_mark: 'Subir fichero?',
Users: 'Usuarios',
User_added_by: 'Usuario {{userAdded}} añadido por {{userBy}}',
User_has_been_key: 'El usuario ha sido {{key}}!',
User_has_been_key: 'El usuario ha sido {{key}}',
User_is_no_longer_role_by_: '{{user}} ha dejado de ser {{role}} por {{userBy}}',
User_muted_by: 'Usuario {{userMuted}} muteado por {{userBy}}',
User_removed_by: 'Usuario {{userRemoved}} eliminado por {{userBy}}',

View File

@ -163,7 +163,7 @@ export default {
Create_Channel: 'Créer un canal',
Create_Direct_Messages: 'Créer un message direct',
Create_Discussion: 'Créer une Discussion',
Created_snippet: 'Créé un extrait',
Created_snippet: 'créé un extrait',
Create_a_new_workspace: 'Créer un nouvel espace de travail',
Create: 'Créer',
Custom_Status: 'Statut Personnalisé',
@ -173,9 +173,9 @@ export default {
Default_browser: 'Navigateur par défaut',
Delete_Room_Warning: 'Supprimer une salle supprimera tous les messages postés dans la salle. Ça ne peut pas être annulé.',
Department: 'Département',
delete: 'supprimez',
Delete: 'Supprimez',
DELETE: 'SUPPRIMEZ',
delete: 'supprimer',
Delete: 'Supprimer',
DELETE: 'SUPPRIMER',
deleting_room: 'effacement de la salle',
description: 'la description',
Description: 'La description',
@ -225,9 +225,9 @@ export default {
Group_by_favorites: 'Grouper par favoris',
Group_by_type: 'Grouper par type',
Hide: 'Cacher',
Has_joined_the_channel: 'A rejoint le canal',
Has_joined_the_conversation: 'A rejoint la conversation',
Has_left_the_channel: 'A quitté la chaîne',
Has_joined_the_channel: 'a rejoint le canal',
Has_joined_the_conversation: 'a rejoint la conversation',
Has_left_the_channel: 'a quitté la chaîne',
Hide_System_Messages: 'Masquer les messages système',
Hide_type_messages: 'Masquer les messages "{{type}}"',
Message_HideType_uj: 'L\'utilisateur a rejoint',
@ -391,7 +391,7 @@ export default {
replies: 'réponses',
reply: 'répondre',
Reply: 'Répondre',
Report: 'Reporter',
Report: 'Signaler',
Receive_Notification: 'Recevoir une notification',
Receive_notifications_from: 'Recevoir les notifications de {{name}}',
Resend: 'Renvoyer',
@ -520,7 +520,7 @@ export default {
Users: 'Utilisateurs',
User_added_by: 'L\'utilisateur {{userAdded}} a été ajouté par {{userBy}}',
User_Info: 'Info d\'utilisateur',
User_has_been_key: 'L\'utilisateur a été {{key}}!',
User_has_been_key: 'L\'utilisateur a été {{key}}',
User_is_no_longer_role_by_: '{{user}} n\'est plus {{role}} par {{userBy}}',
User_muted_by: 'L\'utilisateur {{userMuted}} a été rendu muet par {{userBy}}',
User_removed_by: 'L\'utilisateur {{userRemoved}} a été retiré par {{userBy}}',

View File

@ -549,7 +549,7 @@ export default {
Users: 'Utenti',
User_added_by: 'Utente {{userAdded}} aggiunto da {{userBy}}',
User_Info: 'Informazioni utente',
User_has_been_key: 'Utente {{key}}!',
User_has_been_key: 'Utente {{key}}',
User_is_no_longer_role_by_: '{{user}} non è più {{role}} da {{userBy}}',
User_muted_by: 'Utente {{userMuted}} silenziato da {{userBy}}',
User_removed_by: 'Utente {{userRemoved}} rimosso da {{userBy}}',

View File

@ -464,7 +464,7 @@ export default {
Users: 'ユーザー',
User_added_by: '{{userBy}} が {{userAdded}} を追加しました',
User_Info: 'ユーザー情報',
User_has_been_key: 'ユーザーは{{ key }}',
User_has_been_key: 'ユーザーは{{ key }}',
User_is_no_longer_role_by_: '{{userBy}} は {{user}} のロール {{role}} を削除しました。',
User_muted_by: '{{userBy}} は {{userMuted}} をミュートしました。',
User_removed_by: '{{userBy}} は {{userRemoved}} を退出させました。',

View File

@ -148,7 +148,7 @@ export default {
Whats_the_password_for_your_certificate: 'Wat is het wachtwoord voor je certificate?',
Create_account: 'Maak een account',
Create_Channel: 'Maak een kanaal',
Created_snippet: 'Snippet gemaakt',
Created_snippet: 'snippet gemaakt',
Create_a_new_workspace: 'Een nieuwe workspace maken',
Create: 'Maken',
Dark: 'Donker',
@ -197,9 +197,9 @@ export default {
Group_by_favorites: 'Sorteer op favorieten',
Group_by_type: 'Sorteer op type',
Hide: 'Verberg',
Has_joined_the_channel: 'Is bij het kanaal gekomen',
Has_joined_the_conversation: 'Neemt deel aan het gesprek',
Has_left_the_channel: 'Heeft het kanaal verlaten',
Has_joined_the_channel: 'is bij het kanaal gekomen',
Has_joined_the_conversation: 'neemt deel aan het gesprek',
Has_left_the_channel: 'heeft het kanaal verlaten',
In_App_And_Desktop: 'In-app en Desktop',
In_App_and_Desktop_Alert_info: 'Laat een banner bovenaan het scherm zien als de app open is en geeft een notificatie op de desktop',
Invisible: 'Onzichtbaar',
@ -427,7 +427,7 @@ export default {
Users: 'Gebruikers',
User_added_by: 'Gebruiker {{userAdded}} toegevoegd door {{userBy}}',
User_Info: 'Gebruiker Info',
User_has_been_key: 'Gebruiker is {{key}}!',
User_has_been_key: 'Gebruiker is {{key}}',
User_is_no_longer_role_by_: '{{user}} is geen {{role}} meer door {{userBy}}',
User_muted_by: 'Gebruiker {{userMuted}} gedempt door {{userBy}}',
User_removed_by: 'Gebruiker {{userRemoved}} verwijderd door {{userBy}}',

View File

@ -167,7 +167,7 @@ export default {
Create_Channel: 'Criar Canal',
Create_Direct_Messages: 'Criar Mensagens Diretas',
Create_Discussion: 'Criar Discussão',
Created_snippet: 'Criou um snippet',
Created_snippet: 'criou um snippet',
Create_a_new_workspace: 'Criar nova área de trabalho',
Create: 'Criar',
Dark: 'Escuro',
@ -241,9 +241,9 @@ export default {
Generate_New_Link: 'Gerar novo convite',
Group_by_favorites: 'Agrupar favoritos',
Group_by_type: 'Agrupar por tipo',
Has_joined_the_channel: 'Entrou no canal',
Has_joined_the_conversation: 'Entrou na conversa',
Has_left_the_channel: 'Saiu da conversa',
Has_joined_the_channel: 'entrou no canal',
Has_joined_the_conversation: 'entrou na conversa',
Has_left_the_channel: 'saiu da conversa',
Hide_System_Messages: 'Esconder mensagens do sistema',
Hide_type_messages: 'Esconder mensagens de "{{type}}"',
Message_HideType_uj: 'Utilizador Entrou',
@ -270,6 +270,8 @@ export default {
Invite_Link: 'Link de Convite',
Invite_users: 'Convidar usuários',
Join: 'Entrar',
Join_Code: 'Insira o Código da Sala',
Insert_Join_Code: 'Insira o código para entrar na sala',
Join_our_open_workspace: 'Entra na nossa workspace pública',
Join_your_workspace: 'Entre na sua workspace',
Just_invited_people_can_access_this_channel: 'Apenas as pessoas convidadas podem acessar este canal',
@ -509,7 +511,7 @@ export default {
User: 'Usuário',
Users: 'Usuários',
User_added_by: 'Usuário {{userAdded}} adicionado por {{userBy}}',
User_has_been_key: 'Usuário foi {{key}}!',
User_has_been_key: 'Usuário foi {{key}}',
User_is_no_longer_role_by_: '{{user}} não pertence mais à {{role}} por {{userBy}}',
User_muted_by: 'User {{userMuted}} muted por {{userBy}}',
User_removed_by: 'Usuário {{userRemoved}} removido por {{userBy}}',
@ -627,5 +629,27 @@ export default {
No_threads: 'Não há tópicos',
No_threads_following: 'Você não está seguindo tópicos',
No_threads_unread: 'Não há tópicos não lidos',
Messagebox_Send_to_channel: 'Mostrar no canal'
Messagebox_Send_to_channel: 'Mostrar no canal',
Set_as_leader: 'Definir como líder',
Set_as_moderator: 'Definir como moderador',
Set_as_owner: 'Definir como proprietário',
Remove_as_leader: 'Remover como líder',
Remove_as_moderator: 'Remover como moderador',
Remove_as_owner: 'Remover como owner',
Remove_from_room: 'Remover do canal',
Ignore: 'Ignorar',
Unignore: 'Deixar de ignorar',
User_has_been_ignored: 'Usuário foi ignorado',
User_has_been_unignored: 'O usuário não é mais ignorado',
User_has_been_removed_from_s: 'Usuário foi removido de {{s}}',
User__username__is_now_a_leader_of__room_name_: 'O usuário {{username}} agora é líder de {{room_name}}',
User__username__is_now_a_moderator_of__room_name_: 'O usuário {{username}} agora é moderador de {{room_name}}',
User__username__is_now_a_owner_of__room_name_: 'O usuário {{username}} agora é proprietário de {{room_name}}',
User__username__removed_from__room_name__leaders: 'O usuário {{username}} foi removido dos líderes de {{room_name}}',
User__username__removed_from__room_name__moderators: 'O usuário {{username}} foi removido dos moderadores de {{room_name}}',
User__username__removed_from__room_name__owners: 'O usuário {{username}} foi removido dos proprietários de {{room_name}}',
The_user_will_be_removed_from_s: 'O usuário será removido de {{s}}',
Yes_remove_user: 'Sim, remover usuário!',
Direct_message: 'Mensagem direta',
Message_Ignored: 'Mensagem ignorada. Toque para mostrar.'
};

View File

@ -130,7 +130,7 @@ export default {
Permalink: 'Link permanente',
Create_account: 'Criar uma conta',
Create_Channel: 'Criar Canal',
Created_snippet: 'Criado um extracto',
Created_snippet: 'criado um extracto',
Create_a_new_workspace: 'Criar um novo espaço de trabalho',
Create: 'Criar',
Delete_Room_Warning: 'Apagar uma sala irá remover todas as mensagens contidas nela. Isto não pode ser desfeito.',
@ -163,9 +163,9 @@ export default {
Forgot_Password: 'Esquecer Palavra-passe',
Group_by_favorites: 'Agrupar por favoritos',
Group_by_type: 'Agrupar por tipo',
Has_joined_the_channel: 'Entrou no canal',
Has_joined_the_conversation: 'Entrou na conversa',
Has_left_the_channel: 'Saiu do canal',
Has_joined_the_channel: 'entrou no canal',
Has_joined_the_conversation: 'entrou na conversa',
Has_left_the_channel: 'saiu do canal',
Invisible: 'Invisível',
Invite: 'Convidar',
is_a_valid_RocketChat_instance: 'é uma instância válida do Rocket.Chat',
@ -324,7 +324,7 @@ export default {
Uploading: 'A enviar',
Upload_file_question_mark: 'Enviar ficheiro?',
User_added_by: 'Utilizador {{userAdded}} adicionado por {{userBy}}',
User_has_been_key: 'Utilizador foi {{key}}!',
User_has_been_key: 'Utilizador foi {{key}}',
User_is_no_longer_role_by_: '{{userBy}} removeu o estatuto de {{role}} de {{user}}',
User_muted_by: 'Utilizador {{userMuted}} foi silenciado por {{userBy}}',
User_removed_by: 'Utilizador {{userRemoved}} removido por {{userBy}}',

View File

@ -167,7 +167,7 @@ export default {
Create_Channel: 'Создать канал',
Create_Direct_Messages: 'Создать личное сообщение',
Create_Discussion: 'Создать обсуждение',
Created_snippet: 'Создать сниппет',
Created_snippet: 'создать сниппет',
Create_a_new_workspace: 'Новое рабочее пространство',
Create: 'Создать',
Custom_Status: 'Персонализированный Статус',
@ -247,9 +247,9 @@ export default {
Group_by_favorites: 'По избранным',
Group_by_type: 'По типу',
Hide: 'Скрыть',
Has_joined_the_channel: 'Присоединился к каналу',
Has_joined_the_conversation: 'Присоединился к беседе',
Has_left_the_channel: 'Покинул канал',
Has_joined_the_channel: 'присоединился к каналу',
Has_joined_the_conversation: 'присоединился к беседе',
Has_left_the_channel: 'покинул канал',
Hide_System_Messages: 'Скрыть Системные Сообщения',
Hide_type_messages: 'Скрыть "{{type}}" сообщения',
How_It_Works: 'Как Это Работает',
@ -552,7 +552,7 @@ export default {
Users: 'Пользователи',
User_added_by: 'Пользователь {{userAdded}} добавлен по решению {{userBy}}',
User_Info: 'Информация о пользователе',
User_has_been_key: 'Пользователь был {{key}}!',
User_has_been_key: 'Пользователь был {{key}}',
User_is_no_longer_role_by_: '{{user}} больше не {{role}} по решению {{userBy}}',
User_muted_by: 'Пользователь {{userMuted}} заглушен по решению {{userBy}}',
User_removed_by: 'Пользователь {{userRemoved}} удален по решению {{userBy}}',

View File

@ -89,12 +89,12 @@ export default {
Alert: '警报',
alert: '警报',
alerts: '警报',
All_users_in_the_channel_can_write_new_messages: '频道中的所有用户都可以发送新息',
All_users_in_the_channel_can_write_new_messages: '频道中的所有用户都可以发送新息',
A_meaningful_name_for_the_discussion_room: '取一个有意义的讨论区的名称',
All: '所有',
All_Messages: '全部信息',
Allow_Reactions: '允许表情贴',
Alphabetical: '按名称排列',
Alphabetical: '以名称排序',
and_more: '和更多的',
and: '和',
announcement: '公告',
@ -109,12 +109,12 @@ export default {
Authenticating: '正在验证身份',
Automatic: '自动',
Auto_Translate: '自动翻译',
Avatar_changed_successfully: '头像更新成功!',
Avatar_changed_successfully: '头像更新成功',
Avatar_Url: '头像地址',
Away: '离开',
Back: '返回',
Black: '黑色',
Block_user: '封锁用户',
Block_user: '屏蔽此用户',
Browser: '浏览器',
Broadcast_channel_Description: '只有经过授权的用户才能写新信息,但其他用户可以回复',
Broadcast_Channel: '广播频道',
@ -129,7 +129,7 @@ export default {
Channel_Name: '频道名',
Channels: '频道',
Chats: '聊天',
Call_already_ended: '通话已经结束!',
Call_already_ended: '通话已经结束',
Clear_cookies_alert: '是否清除所有 cookies?',
Clear_cookies_desc: '本操作将清除所有登入 cookies以登入其他帐号',
Clear_cookies_yes: '是,清除 cookies',
@ -141,7 +141,7 @@ export default {
Change_language_loading: '切换语言',
Chat_closed_by_agent: '聊天已被客服关闭',
Choose: '选择',
Choose_from_library: '选择',
Choose_from_library: '从媒体库选择',
Choose_file: '选择文件',
Choose_where_you_want_links_be_opened: '请选择您要将链接开启在',
Code: '代码',
@ -166,7 +166,7 @@ export default {
Create_account: '创建账户',
Create_Channel: '创建频道',
Create_Direct_Messages: '新增私人讯息',
Create_Discussion: '新增论坛',
Create_Discussion: '新增讨论区',
Created_snippet: '新增程式码片段',
Create_a_new_workspace: '创建一个新的工作区',
Create: '创建',
@ -189,30 +189,30 @@ export default {
Directory: '目录',
Direct_Messages: '私訊',
Disable_notifications: '禁用信息通知',
Discussions: '论坛',
Discussion_Desc: '帮助保持事态更新! 通过创建讨论,一个和所选频道双向关联的子频道将会被创建。',
Discussion_name: '论坛名称',
Done: '完',
Discussions: '讨论区',
Discussion_Desc: '帮助保持事态更新 通过创建讨论,一个和所选频道双向关联的子频道将会被创建。',
Discussion_name: '讨论区名称',
Done: '完',
Dont_Have_An_Account: '还没有账号?',
Do_you_have_an_account: '是否拥有帐号?',
Do_you_have_a_certificate: '是否拥有凭证?',
Do_you_have_a_certificate: '是否拥有凭证',
Do_you_really_want_to_key_this_room_question_mark: '您真的想要{{key}}这个聊天室吗?',
E2E_Encryption: 'E2E 加密',
E2E_How_It_Works_info1: '您现在可以创建加密的专用组和直接消息。您也可以将现有的私人组或直接信息加密。',
E2E_How_It_Works_info2: '这是端到端加密,因此编码/解码邮件的密钥不会保存在服务器上。因此,您需要将密码存储在安全的地方。您需要在希望使用端到端加密的其他设备上输入。',
E2E_How_It_Works_info2: '这是點對點加密,因此编码/解码邮件的密钥不会保存在服务器上。因此,您需要将密码存储在安全的地方。您需要在希望使用點對點加密的其他设备上输入。',
E2E_How_It_Works_info3: '如果继续,将自动生成一组 E2E 密码',
E2E_How_It_Works_info4: '这是系统自动生成的密码,您可以为您的密钥设置一个新密码(您可以随时从任何浏览器输入现有密码)。',
E2E_How_It_Works_info4: '您也可以随时从已输入既有密码的任何浏览器设定新密码给您的加密金钥。',
edit: '编辑',
edited: '已编辑',
Edit: '编辑',
Edit_Status: '编辑状态',
Edit_Invite: '编辑邀请',
End_to_end_encrypted_room: '端到端加密聊天室',
end_to_end_encryption: '端到端加密',
Email_Notification_Mode_All: '每次被提及或私讯',
End_to_end_encrypted_room: 'E2E 加密聊天室',
end_to_end_encryption: 'E2E 加密',
Email_Notification_Mode_All: '每次被标记或私讯',
Email_Notification_Mode_Disabled: '禁用',
Email_or_password_field_is_empty: '邮件或密码字段为空',
Email: '邮箱',
EMAIL: '邮箱通知',
email: '邮箱',
Empty_title: '空白标题',
Enable_Auto_Translate: '开启自动翻译',
@ -245,7 +245,7 @@ export default {
Full_table: '点击以查看完整表格',
Generate_New_Link: '产生新的链接',
Group_by_favorites: '收藏优先',
Group_by_type: '类型分组',
Group_by_type: '类型分组',
Hide: '隐藏',
Has_joined_the_channel: '已加入频道',
Has_joined_the_conversation: '已经加入此对话',
@ -270,13 +270,14 @@ export default {
IP: 'IP',
In_app: 'App 内',
In_App_And_Desktop: 'App 内及桌面',
In_App_and_Desktop_Alert_info: '打开应用程序时,在屏幕顶部显示横幅,并在桌面上显示通知',
In_App_and_Desktop_Alert_info: '打开 app 时,會在屏幕上方显示横幅;显示桌面通知',
Invisible: '隐身',
Invite: '邀请',
is_a_valid_RocketChat_instance: '是一个有效的 Rocket.Chat 实例',
is_not_a_valid_RocketChat_instance: '不是有效的 Rocket.Chat 实例',
is_typing: '正在输入',
Invalid_or_expired_invite_token: '无效或到期的邀请 token',
Invalid_server_version: '此 App 版本已不支援您正在连线之服务器版本。当前版本: {{currentVersion}}.\\n\\n最低版本要求: {{minVersion}}',
Join_your_workspace: '加入您的工作区',
Invite_Link: '邀请链接',
Invite_users: '邀请用戶',
@ -294,7 +295,7 @@ export default {
Livechat: '即时聊天',
Livechat_edit: '即时聊天编辑',
Login: '登陆',
Login_error: '你的凭证被拒绝了! 请再试一次',
Login_error: '认证失败!请再试一次',
Login_with: '登陆为',
Logging_out: '正在登出',
Logout: '注销',
@ -333,7 +334,7 @@ export default {
Next: '下一步',
No_files: '没有文件',
No_limit: '没有限制',
No_mentioned_messages: '没有提到的信息',
No_mentioned_messages: '没有被提及的信息',
No_pinned_messages: '没有钉选的消息',
No_results_found: '没有搜寻结果',
No_starred_messages: '没有加星标的消息',
@ -345,7 +346,7 @@ export default {
No_Read_Receipts: '没有已读人员',
Not_logged: '没有记录',
Not_RC_Server: '这不是一个 Rocket.Chat server.\\n{{contact}}',
Nothing: '没有东西',
Nothing: '',
Nothing_to_save: '什么都没有保存!',
Notify_active_in_this_room: '通知这个聊天室的活跃用户',
Notify_all_in_this_room: '通知这个聊天室的所有人',
@ -354,11 +355,11 @@ export default {
Notification_Preferences: '通知偏好设置',
No_available_agents_to_transfer: '没有可用的代理进行传输',
Offline: '离线',
Oops: '哎呀!',
Oops: '哎呀',
Omnichannel: 'Omnichannel',
Open_Livechats: '打开即时聊天',
Omnichannel_enable_alert: '您尚未启用 Omnichannel是否想要启用',
Onboarding_description: '工作區是團隊或組織協作的空間。向工作区管理员询问要加入的地址或为您的团队创建一个。',
Onboarding_description: '工作区是团队或组织协作的空间。向工作区管理员询问要加入的地址或为您的团队创建一个。',
Onboarding_join_workspace: '加入一个工作区',
Onboarding_subtitle: '超越团队合作',
Onboarding_title: '欢迎来到 Rocket.Chat',
@ -382,7 +383,7 @@ export default {
Pinned_Messages: '钉选信息',
pinned: '已被钉选',
Pinned: '被钉选',
Please_add_a_comment: '请评论',
Please_add_a_comment: '请增评论',
Please_enter_your_password: '请输入密码',
Please_wait: '请稍候',
Preferences: '偏好设置',
@ -435,6 +436,7 @@ export default {
Roles: '角色',
Room_actions: '聊天室操作',
Room_changed_announcement: '{{userBy}}将聊天室通知改为:{{announcement}}',
Room_changed_avatar: 'Room avatar changed by {{userBy}}',
Room_changed_description: '{{userBy}}将聊天室说明改为:{{description}}',
Room_changed_privacy: '{{userBy}}将聊天室类型改为:{{type}}',
Room_changed_topic: '{{userBy}}将聊天室主题改为:{{topic}}',
@ -451,7 +453,7 @@ export default {
saving_profile: '保存配置文件',
saving_settings: '保存设置',
saved_to_gallery: '储存至图片库',
Save_Your_E2E_Password: '储存您的端对端密码',
Save_Your_E2E_Password: '储存您的 E2E 密码',
Save_Your_Encryption_Password: '储存您的加密密码',
Save_Your_Encryption_Password_warning: '此密码未被储存在任何地方,为此您必须安全存放您的密码',
Save_Your_Encryption_Password_info: '请记住,如果你遗失了您的密码,您将无法存取您的信息并不可恢复',
@ -461,8 +463,9 @@ export default {
Search_global_users: '搜寻全域用户',
Search_global_users_description: '如果启用,您将可以搜寻其他公司、服务器上的任何用戶',
Seconds: '{{second}} 秒',
Security_and_privacy: '安全与隐私',
Select_Avatar: '选择头像',
Select_Server: '选择服器',
Select_Server: '选择器',
Select_Users: '选择用户',
Select_a_Channel: '选择一个频道',
Select_a_Department: '选择一个部门',
@ -478,13 +481,13 @@ export default {
Sent_an_attachment: '发送附件',
Server: '服务器',
Servers: '服务器',
Server_version: '服务器版本',
Server_version: '服务器版本: {{version}}',
Set_username_subtitle: '用户名是用來让其他人在信息中提到您',
Set_custom_status: '设定自订状态',
Set_status: '设定状态',
Status_saved_successfully: '状态储存成功',
Settings: '设置',
Settings_succesfully_changed: '更改设置成功!',
Settings_succesfully_changed: '设置更改成功!',
Share: '分享',
Share_Link: '分享链接',
Share_this_app: '分享此 app',
@ -502,8 +505,8 @@ export default {
starred: '被标记',
Starred: '已标记',
Start_of_conversation: '开始对话',
Start_a_Discussion: '开始',
Started_discussion: '已开始的',
Start_a_Discussion: '开始一个讨论',
Started_discussion: '已开始的论',
Started_call: '{{userBy}} 开始的通话',
Submit: '提交',
Table: '表格',
@ -532,7 +535,7 @@ export default {
Type_the_channel_name_here: '在这里输入频道名称',
unarchive: '取消封存',
UNARCHIVE: '取消封存',
Unblock_user: '解锁用户',
Unblock_user: '解除屏蔽',
Unfavorite: '取消收藏',
Unfollowed_thread: '取消追踪讨论',
Unmute: '取消静音',
@ -547,9 +550,9 @@ export default {
Upload_file_question_mark: '上传文件?',
User: '用戶',
Users: '用戶',
User_added_by: '由{{userBy}}添加的用户 {{useradd}}',
User_added_by: '由{{userBy}}添加的用户 {{userAdded}}',
User_Info: '用戶资讯',
User_has_been_key: '用户已被{{key}}!',
User_has_been_key: '用户已被{{key}}',
User_is_no_longer_role_by_: '{{userBy}}将角色 {{role}} 从用户 {{user}} 身上移除',
User_muted_by: '用户 {{userMuted}} 被 {{userBy}} 静音',
User_removed_by: '用户 {{userRemoved}} 被 {{userBy}} 移除',
@ -585,7 +588,7 @@ export default {
Yesterday: '昨天',
You_are_in_preview_mode: '您处于预览模式',
You_are_offline: '您处于离线状态',
You_can_search_using_RegExp_eg: '您可以使用RegExp进行搜索。 例如`/^text$/i`',
You_can_search_using_RegExp_eg: '您可 RegExp 进行搜索。 例如`/^text$/i`',
You_colon: '你:',
you_were_mentioned: '你被提到了',
You_were_removed_from_channel: '您已从 {{channel}} 中被踢除',
@ -617,11 +620,11 @@ export default {
Search_messages: '搜索信息',
Scroll_messages: '信息滚动',
Reply_latest: '回覆最新信息',
Reply_in_Thread: '回覆讨论',
Reply_in_Thread: '讨论串回覆',
Server_selection: '选择服务器',
Server_selection_numbers: '选择服务器(输入 1...9)',
Add_server: '創建服务器',
New_line: '新行',
New_line: '新的一行',
You_will_be_logged_out_of_this_application: '您即将登出',
Clear: '清除',
This_will_clear_all_your_offline_data: '这将清除您的所有离线资料。',
@ -655,7 +658,27 @@ export default {
Queue_is_empty: '队列是空的',
Logout_from_other_logged_in_locations: '注销其他已登陆的设备',
You_will_be_logged_out_from_other_locations: '您将于其他设备上注销',
Logged_out_of_other_clients_successfully: '成功登出其他户端',
Logged_out_of_other_clients_successfully: '成功登出其他户端',
Logout_failed: '注销失败',
Log_analytics_events: '日志分析事件'
Log_analytics_events: '日志分析事件',
E2E_encryption_change_password_title: '变更加密密码',
E2E_encryption_change_password_description: '现在您可以建立加密私人群组和私讯。您也可以变更已存在的私人群组或是私讯来加密。 \\n\\n这是点对点的加密所以用来加密/解密的金钥将不会储存到伺服器上。为此,您必须安全存放您的密码。如果您希望在其他装置上使用对点加密时,将会需要输入此密码。',
E2E_encryption_change_password_error: '变更 E2E 密码时发生错误!',
E2E_encryption_change_password_success: 'E2E 密码已成功变更!',
E2E_encryption_change_password_message: '请确定您已将其安全存放至别处',
E2E_encryption_change_password_confirmation: '是,我要变更',
E2E_encryption_reset_title: '重设 E2E 金钥',
E2E_encryption_reset_description: '此选项将撤销您目前的*E2E 金钥*并将您登出。 \\n当您再次登入时Rocket.Chat 将产生新的一组金钥并恢复您对任一有在线成员的加密聊天室之存取权限。 \\n由于 E2E 加密的特性Rocket.Chat 无法恢复无在线成员之聊天室之存取权限。',
E2E_encryption_reset_button: '重设',
E2E_encryption_reset_error: '重设 E2E 金钥时发生错误!',
E2E_encryption_reset_message: '您将被登出',
E2E_encryption_reset_confirmation: '是,我要重设',
Following: '正在追踪',
Threads_displaying_all: '显示全部',
Threads_displaying_following: '显示追踪中',
Threads_displaying_unread: '显示未读',
No_threads: '当前没有讨论串',
No_threads_following: '当前没有正在追踪的讨论',
No_threads_unread: '当前没有未读的讨论',
Messagebox_Send_to_channel: '发送至频道'
};

View File

@ -80,7 +80,7 @@ export default {
'error-you-are-last-owner': '您是最後的擁有者。請刪除此人之前設置一個新的擁有者。',
Actions: '操作',
activity: '活動時間',
Activity: '按活動時間排列',
Activity: '以活動時間排序',
Add_Reaction: '增加表情貼',
Add_Server: '新增伺服器',
Add_users: '新增使用者',
@ -94,7 +94,7 @@ export default {
All: '所有',
All_Messages: '全部訊息',
Allow_Reactions: '允許表情貼',
Alphabetical: '按名稱排列',
Alphabetical: '以名稱排序',
and_more: '和更多的',
and: '和',
announcement: '公告',
@ -109,12 +109,12 @@ export default {
Authenticating: '正在驗證身份',
Automatic: '自動',
Auto_Translate: '自動翻譯',
Avatar_changed_successfully: '大頭貼更新成功!',
Avatar_changed_successfully: '大頭貼更新成功',
Avatar_Url: '大頭貼地址',
Away: '離開',
Back: '返回',
Black: '黑色',
Block_user: '封鎖使用者',
Block_user: '封鎖此用戶',
Browser: '瀏覽器',
Broadcast_channel_Description: '只有經過授權的使用者才能發送新訊息,但其他使用者可以回覆',
Broadcast_Channel: '廣播頻道',
@ -129,7 +129,7 @@ export default {
Channel_Name: '頻道名稱',
Channels: '頻道',
Chats: '聊天',
Call_already_ended: '通話已經結束!',
Call_already_ended: '通話已經結束',
Clear_cookies_alert: '是否清除所有 cookies?',
Clear_cookies_desc: '本操作將清除所有登入 cookies以登入其他帳號',
Clear_cookies_yes: '是,清除 cookies',
@ -141,7 +141,7 @@ export default {
Change_language_loading: '切換語言',
Chat_closed_by_agent: '聊天已被客服關閉',
Choose: '選擇',
Choose_from_library: '選擇',
Choose_from_library: '從媒體庫選擇',
Choose_file: '選擇檔案',
Choose_where_you_want_links_be_opened: '請選擇您要將連結開啟在',
Code: '程式碼',
@ -166,7 +166,7 @@ export default {
Create_account: '新建帳戶',
Create_Channel: '新建頻道',
Create_Direct_Messages: '新增私人訊息',
Create_Discussion: '新增論壇',
Create_Discussion: '新增討論區',
Created_snippet: '新增程式碼片段',
Create_a_new_workspace: '建立一個新的工作區',
Create: '建立',
@ -189,30 +189,30 @@ export default {
Directory: '目錄',
Direct_Messages: '私訊',
Disable_notifications: '禁用訊息通知',
Discussions: '論壇',
Discussion_Desc: '幫助保持對正在發生的事情的概述!通過建立討論,將建立您選擇的子通道,並且兩者都是連接的。',
Discussion_name: '論壇名稱',
Done: '完',
Discussions: '討論區',
Discussion_Desc: '幫助保持事態更新!透過建立討論,一個和所選頻道雙向關聯的子頻道將會被建立。',
Discussion_name: '討論區名稱',
Done: '完',
Dont_Have_An_Account: '還未擁有帳號?',
Do_you_have_an_account: '是否擁有帳號?',
Do_you_have_a_certificate: '是否擁有憑證?',
Do_you_have_a_certificate: '是否擁有憑證',
Do_you_really_want_to_key_this_room_question_mark: '您真的想要{{key}}這個聊天室嗎?',
E2E_How_It_Works_info1: '您現在可以建立加密私人群組和私人訊息。您也可以變更已存在的私人群組或私訊來加密',
E2E_How_It_Works_info2: '這是點對點的加密,所以金鑰是用來加密/解密,您的訊息也不會儲存到伺服器上。為了這個原因您必須安全存放您的密碼。您會希望使用 E2E 加密輸入到其他裝置。',
E2E_Encryption: 'E2E 加密',
E2E_How_It_Works_info1: '現在您可以建立加密私人群組和私訊。您也可以變更已存在的私人群組或是私訊來加密。',
E2E_How_It_Works_info2: '這是*點對點的加密*,所以用來加密/解密的金鑰將不會儲存到伺服器上。為此,*您必須安全存放您的密碼*。如果您希望在其他裝置上使用對點加密時,會需要輸入此密碼。',
E2E_How_It_Works_info3: '如果繼續,將自動產生一組 E2E 密碼',
E2E_How_It_Works_info4: '這是自動產生的密碼,在任何時間從任何瀏覽器您可以設定新的密碼給您的加密金鑰您可以輸入已存在的密碼。',
E2E_How_It_Works_info4: '您也可以隨時從已輸入既有密碼的任何瀏覽器設定新密碼給您的加密金鑰。',
edit: '編輯',
edited: '已編輯',
Edit: '編輯',
Edit_Status: '編輯狀態',
Edit_Invite: '編輯邀請',
End_to_end_encrypted_room: '點對點加密聊天室',
end_to_end_encryption: '點對點加密',
End_to_end_encrypted_room: 'E2E 加密聊天室',
end_to_end_encryption: 'E2E 加密',
Email_Notification_Mode_All: '每次被標記或私訊',
Email_Notification_Mode_Disabled: '禁用',
Email_or_password_field_is_empty: '電子郵件或密碼欄位為空',
Email: '電子郵件',
EMAIL: '電子郵件通知',
email: '電子郵件',
Empty_title: '空白標題',
Enable_Auto_Translate: '開啟自動翻譯',
@ -245,13 +245,13 @@ export default {
Full_table: '點擊以查看完整表格',
Generate_New_Link: '產生新的連結',
Group_by_favorites: '我的最愛優先',
Group_by_type: '類型分組',
Group_by_type: '類型分組',
Hide: '隱藏',
Has_joined_the_channel: '已加入頻道',
Has_joined_the_conversation: '已經加入此對話',
Has_left_the_channel: '已離開頻道',
Hide_System_Messages: '隱藏系統訊息',
Hide_type_messages: '隱藏 "{{type}}" 訊息',
Hide_type_messages: '隱藏 \'{{type}}\' 訊息',
How_It_Works: '運作方式',
Message_HideType_uj: '隱藏“使用者加入”訊息',
Message_HideType_ul: '隱藏“使用者離開”訊息',
@ -270,13 +270,14 @@ export default {
IP: 'IP',
In_app: 'App 內',
In_App_And_Desktop: 'App 內及桌面',
In_App_and_Desktop_Alert_info: '當在應用程序打開時,螢幕頂端顯示橫幅,並在桌面上顯示通知',
In_App_and_Desktop_Alert_info: '若 app 開啟時,會在螢幕上方顯示橫幅;顯示桌面通知',
Invisible: '隱身',
Invite: '邀請',
is_a_valid_RocketChat_instance: '是一個有效的 Rocket.Chat 實例',
is_not_a_valid_RocketChat_instance: '不是有效的 Rocket.Chat 實例',
is_typing: '正在輸入',
Invalid_or_expired_invite_token: '無效或到期的邀請 token',
Invalid_server_version: '此 App 版本已不支援您正在連線之伺服器版本。當前版本: {{currentVersion}}.\\n\\n最低版本要求: {{minVersion}}',
Join_your_workspace: '加入您的工作區',
Invite_Link: '邀請連結',
Invite_users: '邀請使用者',
@ -294,7 +295,7 @@ export default {
Livechat: '即時聊天',
Livechat_edit: '即時聊天編輯',
Login: '登入',
Login_error: '你的憑證被拒絕了! 請再試一次',
Login_error: '認證失敗!請再試一次',
Login_with: '登入為',
Logging_out: '正在登出',
Logout: '登出',
@ -333,10 +334,10 @@ export default {
Next: '下一步',
No_files: '沒有檔案',
No_limit: '沒有限制',
No_mentioned_messages: '沒有提及的訊息',
No_mentioned_messages: '沒有提及的訊息',
No_pinned_messages: '沒有釘選的訊息',
No_results_found: '沒有搜尋結果',
No_starred_messages: '沒有標記的訊息',
No_starred_messages: '沒有標記的訊息',
No_thread_messages: '沒有討論串訊息',
No_label_provided: '沒有提供 {{label}}',
No_Message: '沒有訊息',
@ -345,7 +346,7 @@ export default {
No_Read_Receipts: '沒有已讀人員',
Not_logged: '沒有記錄',
Not_RC_Server: '這不是一個 Rocket.Chat server.\\n{{contact}}',
Nothing: '沒有東西',
Nothing: '',
Nothing_to_save: '沒有東西可儲存!',
Notify_active_in_this_room: '通知這個聊天室的活躍使用者',
Notify_all_in_this_room: '通知這個聊天室的所有人',
@ -354,7 +355,7 @@ export default {
Notification_Preferences: '通知偏好設定',
No_available_agents_to_transfer: '沒有可用的代理進行傳輸',
Offline: '離線',
Oops: '哎呀!',
Oops: '哎呀',
Omnichannel: 'Omnichannel',
Open_Livechats: '打開即時聊天',
Omnichannel_enable_alert: '您尚未啟用 Omnichannel是否想要啟用',
@ -382,7 +383,7 @@ export default {
Pinned_Messages: '釘選訊息',
pinned: '已被釘選',
Pinned: '被釘選',
Please_add_a_comment: '請評論',
Please_add_a_comment: '請增評論',
Please_enter_your_password: '請輸入密碼',
Please_wait: '請稍候',
Preferences: '偏好設定',
@ -435,6 +436,7 @@ export default {
Roles: '角色',
Room_actions: '聊天室操作',
Room_changed_announcement: '{{userBy}}將聊天室通知改為:{{announcement}}',
Room_changed_avatar: 'Room avatar changed by {{userBy}}',
Room_changed_description: '{{userBy}}將聊天室說明改為:{{description}}',
Room_changed_privacy: '{{userBy}}將聊天室類型改為:{{type}}',
Room_changed_topic: '{{userBy}}將聊天室主題改為:{{topic}}',
@ -451,7 +453,7 @@ export default {
saving_profile: '儲存配置文件',
saving_settings: '儲存設定',
saved_to_gallery: '儲存至圖片庫',
Save_Your_E2E_Password: '儲存您的點對點密碼',
Save_Your_E2E_Password: '儲存您的 E2E 密碼',
Save_Your_Encryption_Password: '儲存您的加密密碼',
Save_Your_Encryption_Password_warning: '此密碼未被儲存在任何地方,為此您必須安全存放您的密碼',
Save_Your_Encryption_Password_info: '請記住,如果你遺失了您的密碼,您將無法存取您的訊息並不可恢復',
@ -461,6 +463,7 @@ export default {
Search_global_users: '搜尋全域使用者',
Search_global_users_description: '如果啟用,您將可以搜尋其他公司、伺服器上的任何使用者',
Seconds: '{{second}} 秒',
Security_and_privacy: '安全與隱私',
Select_Avatar: '選擇大頭貼',
Select_Server: '選擇伺服器',
Select_Users: '選擇使用者',
@ -478,13 +481,13 @@ export default {
Sent_an_attachment: '發送附件',
Server: '伺服器',
Servers: '伺服器',
Server_version: '伺服器版本',
Set_username_subtitle: '使用者名稱是用來讓其他使用者在訊息中提到您',
Server_version: '伺服器版本: {{version}}',
Set_username_subtitle: '使用者名稱是用來讓其他使用者在訊息中提到您',
Set_custom_status: '設定自訂狀態',
Set_status: '設定狀態',
Status_saved_successfully: '狀態儲存成功',
Settings: '設定',
Settings_succesfully_changed: '更改設定成功!',
Settings_succesfully_changed: '設定更改成功!',
Share: '分享',
Share_Link: '分享連結',
Share_this_app: '分享此 app',
@ -502,8 +505,8 @@ export default {
starred: '被標記',
Starred: '已標記',
Start_of_conversation: '開始對話',
Start_a_Discussion: '開始',
Started_discussion: '已開始的',
Start_a_Discussion: '開始一個討論',
Started_discussion: '已開始的論',
Started_call: '{{userBy}} 開始的通話',
Submit: '送出',
Table: '表格',
@ -532,8 +535,8 @@ export default {
Type_the_channel_name_here: '在這裡輸入頻道名稱',
unarchive: '取消封存',
UNARCHIVE: '取消封存',
Unblock_user: '取消封鎖使用者',
Unfavorite: '取消我的最愛',
Unblock_user: '解除封鎖',
Unfavorite: '取消最愛',
Unfollowed_thread: '取消追蹤討論',
Unmute: '取消靜音',
unmuted: '靜音狀態',
@ -547,9 +550,9 @@ export default {
Upload_file_question_mark: '上傳文件?',
User: '使用者',
Users: '使用者',
User_added_by: '由{{userBy}}添加的使用者 {{useradd}}',
User_added_by: '由{{userBy}}添加的使用者 {{userAdded}}',
User_Info: '使用者資訊',
User_has_been_key: '使用者已被{{key}}!',
User_has_been_key: '使用者已被{{key}}',
User_is_no_longer_role_by_: '{{userBy}}將角色 {{role}} 從使用者 {{user}} 身上移除',
User_muted_by: '使用者 {{userMuted}} 被 {{userBy}} 靜音',
User_removed_by: '使用者 {{userRemoved}} 被 {{userBy}} 移除',
@ -560,7 +563,7 @@ export default {
Username: '使用者名稱',
Username_or_email: '使用者名稱或電子郵件',
Uses_server_configuration: '使用伺服器設定',
Usually_a_discussion_starts_with_a_question_like_How_do_I_upload_a_picture: '通常,討論會由一個問題開始,像是 \\"如何上傳一個圖片?\\"',
Usually_a_discussion_starts_with_a_question_like_How_do_I_upload_a_picture: '通常,討論會由一個問題開始,像是 \\\'如何上傳一個圖片?\\\'',
Validating: '正在驗證',
Registration_Succeeded: '註冊成功',
Verify: '驗證',
@ -585,7 +588,7 @@ export default {
Yesterday: '昨天',
You_are_in_preview_mode: '您處於預覽模式',
You_are_offline: '您處於離線狀態',
You_can_search_using_RegExp_eg: '您可以使用RegExp進行搜索。例如`/^text$/i`',
You_can_search_using_RegExp_eg: '您可用 RegExp 進行搜尋。例如`/^text$/i`',
You_colon: '你:',
you_were_mentioned: '你被提到了',
You_were_removed_from_channel: '您已從 {{channel}} 中被踢除',
@ -617,11 +620,11 @@ export default {
Search_messages: '搜尋訊息',
Scroll_messages: '訊息滾動',
Reply_latest: '回覆最新訊息',
Reply_in_Thread: '回覆討論',
Reply_in_Thread: '討論串回覆',
Server_selection: '選擇伺服器',
Server_selection_numbers: '選擇伺服器(輸入 1...9)',
Add_server: '新增伺服器',
New_line: '新行',
New_line: '新的一行',
You_will_be_logged_out_of_this_application: '您即將登出',
Clear: '清除',
This_will_clear_all_your_offline_data: '這將清除您的所有離線資料。',
@ -655,7 +658,27 @@ export default {
Queue_is_empty: '佇列是空的',
Logout_from_other_logged_in_locations: '登出其他已登入的設備',
You_will_be_logged_out_from_other_locations: '您將於其他設備上登出',
Logged_out_of_other_clients_successfully: '成功登出其他戶端',
Logged_out_of_other_clients_successfully: '成功登出其他戶端',
Logout_failed: '登出失敗',
Log_analytics_events: '日誌分析事件'
Log_analytics_events: '日誌分析事件',
E2E_encryption_change_password_title: '變更加密密碼',
E2E_encryption_change_password_description: '現在您可以建立加密私人群組和私訊。您也可以變更已存在的私人群組或是私訊來加密。\\n\\n這是點對點的加密所以用來加密/解密的金鑰將不會儲存到伺服器上。為此,您必須安全存放您的密碼。如果您希望在其他裝置上使用對點加密時,將會需要輸入此密碼。',
E2E_encryption_change_password_error: '變更 E2E 密碼時發生錯誤!',
E2E_encryption_change_password_success: 'E2E 密碼已成功變更!',
E2E_encryption_change_password_message: '請確定您已將其安全存放至別處',
E2E_encryption_change_password_confirmation: '是,我要變更',
E2E_encryption_reset_title: '重設 E2E 金鑰',
E2E_encryption_reset_description: '此選項將撤銷您目前的*E2E 金鑰*並將您登出。\\n當您再次登入時Rocket.Chat 將產生新的一組金鑰並恢復您對任一有在線成員的加密聊天室之存取權限。\\n由於 E2E 加密的特性Rocket.Chat 無法恢復無在線成員之聊天室之存取權限。',
E2E_encryption_reset_button: '重設',
E2E_encryption_reset_error: '重設 E2E 金鑰時發生錯誤!',
E2E_encryption_reset_message: '您將被登出',
E2E_encryption_reset_confirmation: '是,我要重設',
Following: '正在追蹤',
Threads_displaying_all: '顯示全部',
Threads_displaying_following: '顯示追蹤中',
Threads_displaying_unread: '顯示未讀',
No_threads: '當前沒有討論串',
No_threads_following: '當前沒有正在追蹤的討論',
No_threads_unread: '當前沒有未讀的討論',
Messagebox_Send_to_channel: '發送至頻道'
};

View File

@ -28,6 +28,7 @@ import serversMigrations from './model/servers/migrations';
import { isIOS } from '../../utils/deviceInfo';
import appGroup from '../../utils/appGroup';
import { isOfficial } from '../../constants/environment';
const appGroupPath = isIOS ? appGroup.path : '';
@ -35,9 +36,11 @@ if (__DEV__ && isIOS) {
console.log(appGroupPath);
}
const getDatabasePath = name => `${ appGroupPath }${ name }${ isOfficial ? '' : '-experimental' }.db`;
export const getDatabase = (database = '') => {
const path = database.replace(/(^\w+:|^)\/\//, '').replace(/\//g, '.');
const dbName = `${ appGroupPath }${ path }-experimental.db`;
const dbName = getDatabasePath(path);
const adapter = new SQLiteAdapter({
dbName,
@ -70,7 +73,7 @@ class DB {
databases = {
serversDB: new Database({
adapter: new SQLiteAdapter({
dbName: `${ appGroupPath }default-experimental.db`,
dbName: getDatabasePath('default'),
schema: serversSchema,
migrations: serversMigrations
}),
@ -97,7 +100,7 @@ class DB {
setShareDB(database = '') {
const path = database.replace(/(^\w+:|^)\/\//, '').replace(/\//g, '.');
const dbName = `${ appGroupPath }${ path }-experimental.db`;
const dbName = getDatabasePath(path);
const adapter = new SQLiteAdapter({
dbName,

View File

@ -76,6 +76,8 @@ export default class Subscription extends Model {
@json('muted', sanitizer) muted;
@json('ignored', sanitizer) ignored;
@field('broadcast') broadcast;
@field('prid') prid;

View File

@ -201,6 +201,17 @@ export default schemaMigrations({
]
})
]
},
{
toVersion: 12,
steps: [
addColumns({
table: 'subscriptions',
columns: [
{ name: 'ignored', type: 'string', isOptional: true }
]
})
]
}
]
});

View File

@ -1,7 +1,7 @@
import { appSchema, tableSchema } from '@nozbe/watermelondb';
export default appSchema({
version: 11,
version: 12,
tables: [
tableSchema({
name: 'subscriptions',
@ -37,6 +37,7 @@ export default appSchema({
{ name: 'archived', type: 'boolean' },
{ name: 'join_code_required', type: 'boolean', isOptional: true },
{ name: 'muted', type: 'string', isOptional: true },
{ name: 'ignored', type: 'string', isOptional: true },
{ name: 'broadcast', type: 'boolean', isOptional: true },
{ name: 'prid', type: 'string', isOptional: true },
{ name: 'draft_message', type: 'string', isOptional: true },

View File

@ -15,11 +15,9 @@ export const merge = (subscription, room) => {
if (room) {
if (room._updatedAt) {
subscription.lastMessage = normalizeMessage(room.lastMessage);
if (subscription.lastMessage) {
subscription.roomUpdatedAt = subscription.lastMessage.ts;
} else {
subscription.roomUpdatedAt = room._updatedAt;
}
const updatedAt = room?._updatedAt ? new Date(room._updatedAt) : null;
const lastMessageTs = subscription?.lastMessage?.ts ? new Date(subscription.lastMessage.ts) : null;
subscription.roomUpdatedAt = Math.max(updatedAt, lastMessageTs);
subscription.description = room.description;
subscription.topic = room.topic;
subscription.announcement = room.announcement;
@ -38,6 +36,9 @@ export const merge = (subscription, room) => {
if (!subscription.roles || !subscription.roles.length) {
subscription.roles = [];
}
if (!subscription.ignored?.length) {
subscription.ignored = [];
}
if (room.muted && room.muted.length) {
subscription.muted = room.muted.filter(muted => !!muted);
} else {

Some files were not shown because too many files have changed in this diff Show More