Browse Source

Merge branch 'develop' into renovate/minor-2

pull/7602/head
աɨռɢӄաօռɢ 9 months ago
parent
commit
0aa10c1a51
  1. 551
      docker-compose/setup-script/noco.sh
  2. BIN
      packages/nc-gui/assets/img/placeholder/api-tokens.png
  3. BIN
      packages/nc-gui/assets/img/placeholder/invite-team.png
  4. BIN
      packages/nc-gui/assets/img/placeholder/link-records.png
  5. BIN
      packages/nc-gui/assets/img/placeholder/multi-field-editor.png
  6. BIN
      packages/nc-gui/assets/img/placeholder/table.png
  7. BIN
      packages/nc-gui/assets/img/placeholder/webhooks.png
  8. 61
      packages/nc-gui/components/account/Token.vue
  9. 20
      packages/nc-gui/components/account/UserList.vue
  10. 6
      packages/nc-gui/components/cell/Currency.vue
  11. 8
      packages/nc-gui/components/dashboard/Sidebar/UserInfo.vue
  12. 2
      packages/nc-gui/components/general/JoinCloud.vue
  13. 2
      packages/nc-gui/components/general/ReleaseInfo.vue
  14. 6
      packages/nc-gui/components/general/SocialCard.vue
  15. 70
      packages/nc-gui/components/project/AllTables.vue
  16. 2
      packages/nc-gui/components/smartsheet/Cell.vue
  17. 4
      packages/nc-gui/components/smartsheet/Form.vue
  18. 2
      packages/nc-gui/components/smartsheet/details/Fields.vue
  19. 11
      packages/nc-gui/components/smartsheet/details/Webhooks.vue
  20. 12
      packages/nc-gui/components/smartsheet/expanded-form/Comments.vue
  21. 38
      packages/nc-gui/components/smartsheet/expanded-form/index.vue
  22. 53
      packages/nc-gui/components/smartsheet/grid/Table.vue
  23. 12
      packages/nc-gui/components/smartsheet/toolbar/RowHeight.vue
  24. 81
      packages/nc-gui/components/virtual-cell/components/ListChildItems.vue
  25. 3
      packages/nc-gui/components/virtual-cell/components/ListItem.vue
  26. 56
      packages/nc-gui/components/virtual-cell/components/ListItems.vue
  27. 9
      packages/nc-gui/components/workspace/CollaboratorsList.vue
  28. 18
      packages/nc-gui/lang/ar.json
  29. 18
      packages/nc-gui/lang/bn_IN.json
  30. 18
      packages/nc-gui/lang/cs.json
  31. 18
      packages/nc-gui/lang/da.json
  32. 18
      packages/nc-gui/lang/de.json
  33. 18
      packages/nc-gui/lang/en.json
  34. 18
      packages/nc-gui/lang/es.json
  35. 18
      packages/nc-gui/lang/eu.json
  36. 18
      packages/nc-gui/lang/fa.json
  37. 18
      packages/nc-gui/lang/fi.json
  38. 210
      packages/nc-gui/lang/fr.json
  39. 18
      packages/nc-gui/lang/he.json
  40. 18
      packages/nc-gui/lang/hi.json
  41. 18
      packages/nc-gui/lang/hr.json
  42. 18
      packages/nc-gui/lang/id.json
  43. 18
      packages/nc-gui/lang/it.json
  44. 18
      packages/nc-gui/lang/ja.json
  45. 18
      packages/nc-gui/lang/ko.json
  46. 18
      packages/nc-gui/lang/lv.json
  47. 18
      packages/nc-gui/lang/nl.json
  48. 18
      packages/nc-gui/lang/no.json
  49. 18
      packages/nc-gui/lang/pl.json
  50. 18
      packages/nc-gui/lang/pt.json
  51. 20
      packages/nc-gui/lang/pt_BR.json
  52. 38
      packages/nc-gui/lang/ru.json
  53. 18
      packages/nc-gui/lang/sk.json
  54. 18
      packages/nc-gui/lang/sl.json
  55. 62
      packages/nc-gui/lang/sv.json
  56. 18
      packages/nc-gui/lang/th.json
  57. 18
      packages/nc-gui/lang/tr.json
  58. 18
      packages/nc-gui/lang/uk.json
  59. 18
      packages/nc-gui/lang/vi.json
  60. 18
      packages/nc-gui/lang/zh-Hans.json
  61. 18
      packages/nc-gui/lang/zh-Hant.json
  62. 6
      packages/nc-gui/package.json
  63. 8
      packages/noco-docs/package-lock.json
  64. 2
      packages/noco-docs/package.json
  65. 626
      packages/nocodb/src/cache/CacheMgr.ts
  66. 3
      packages/nocodb/src/cache/NocoCache.ts
  67. 414
      packages/nocodb/src/cache/RedisCacheMgr.ts
  68. 420
      packages/nocodb/src/cache/RedisMockCacheMgr.ts
  69. 31
      packages/nocodb/src/db/BaseModelSqlv2.ts
  70. 6
      packages/nocodb/src/db/conditionV2.ts
  71. 2
      packages/nocodb/src/db/sortV2.ts
  72. 8
      packages/nocodb/src/meta/migrations/XcMigrationSourcev2.ts
  73. 88
      packages/nocodb/src/meta/migrations/v2/nc_039_sqlite_alter_column_types.ts
  74. 28
      packages/nocodb/src/meta/migrations/v2/nc_040_form_view_alter_column_types.ts
  75. 1
      packages/nocodb/src/models/ApiToken.ts
  76. 2
      packages/nocodb/src/models/Base.ts
  77. 14
      packages/nocodb/src/models/Column.ts
  78. 3
      packages/nocodb/src/models/Filter.ts
  79. 2
      packages/nocodb/src/models/Hook.ts
  80. 2
      packages/nocodb/src/models/HookFilter.ts
  81. 4
      packages/nocodb/src/models/Model.ts
  82. 1
      packages/nocodb/src/models/ModelRoleVisibility.ts
  83. 2
      packages/nocodb/src/models/Sort.ts
  84. 3
      packages/nocodb/src/models/Source.ts
  85. 2
      packages/nocodb/src/models/User.ts
  86. 3
      packages/nocodb/src/models/View.ts
  87. 8
      packages/nocodb/src/modules/jobs/jobs/at-import/at-import.processor.ts
  88. 5
      packages/nocodb/src/modules/jobs/jobs/at-import/helpers/readAndProcessData.ts
  89. 59
      packages/nocodb/src/schema/swagger-v2.json
  90. 59
      packages/nocodb/src/schema/swagger.json
  91. 2
      packages/nocodb/src/utils/globals.ts
  92. 30
      pnpm-lock.yaml
  93. 2
      tests/playwright/constants/index.ts
  94. 55
      tests/playwright/tests/db/features/keyboardShortcuts.spec.ts

551
docker-compose/setup-script/noco.sh

@ -0,0 +1,551 @@
#!/bin/bash
# set -x
# ******************************************************************************
# ***************** HELPER FUNCTIONS START *********************************
# Function to URL encode special characters in a string
urlencode() {
local string="$1"
local strlen=${#string}
local encoded=""
local pos c o
for (( pos=0 ; pos<strlen ; pos++ )); do
c=${string:$pos:1}
case "$c" in
[-_.~a-zA-Z0-9] ) o="$c" ;;
* ) printf -v o '%%%02X' "'$c"
esac
encoded+="$o"
done
echo "$encoded"
}
# function to print a message in a box
print_box_message() {
message=("$@") # Store all arguments in the array "message"
edge="======================================"
padding=" "
echo "$edge"
for element in "${message[@]}"; do
echo "${padding}${element}"
done
echo "$edge"
}
# check command exists
command_exists() {
command -v "$1" >/dev/null 2>&1
}
# install package based on platform
install_package() {
if command_exists yum; then
sudo yum install -y "$1"
elif command_exists apt; then
sudo apt install -y "$1"
elif command_exists brew; then
brew install "$1"
else
echo "Package manager not found. Please install $1 manually."
fi
}
# Function to check if sudo is required for Docker Compose command
check_for_docker_compose_sudo() {
if docker-compose ps >/dev/null 2>&1; then
echo "n"
else
echo "y"
fi
}
# ***************** HELPER FUNCTIONS END ***********************************
# ******************************************************************************
# ******************************************************************************
# ******************** SYSTEM REQUIREMENTS CHECK START *************************
# Check if the following requirements are met:
# a. docker, docker-compose, jq installed
# b. port mapping check : 80,443 are free or being used by nginx container
REQUIRED_PORTS=(80 443)
echo "** Performing nocodb system check and setup. This step may require sudo permissions"
# pre install wget if not found
if ! command_exists wget; then
echo "wget is not installed. Setting up for installation..."
install_package wget
fi
# d. Check if required tools are installed
echo " | Checking if required tools (docker, docker-compose, lsof) are installed..."
for tool in docker docker-compose lsof openssl; do
if ! command_exists "$tool"; then
echo "$tool is not installed. Setting up for installation..."
if [ "$tool" = "docker-compose" ]; then
sudo -E curl -L https://github.com/docker/compose/releases/download/1.29.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
elif [ "$tool" = "docker" ]; then
wget -qO- https://get.docker.com/ | sh
elif [ "$tool" = "lsof" ]; then
install_package lsof
fi
fi
done
# e. Check if NocoDB is already installed and its expected version
# echo "Checking if NocoDB is already installed and its expected version..."
# Replace the following command with the actual command to check NocoDB installation and version
# Example: nocodb_version=$(command_to_get_nocodb_version)
# echo "NocoDB version: $nocodb_install_version"
# f. Port mapping check
echo " | Checking port accessibility..."
for port in "${REQUIRED_PORTS[@]}"; do
if lsof -Pi :$port -sTCP:LISTEN -t >/dev/null; then
echo " | WARNING: Port $port is in use. Please make sure it is free." >&2
else
echo " | Port $port is free."
fi
done
echo "** System check completed successfully. **"
# Define an array to store the messages to be printed at the end
message_arr=()
# extract public ip address
PUBLIC_IP=$(dig +short myip.opendns.com @resolver1.opendns.com)
# Check if the public IP address is not empty, if empty then use the localhost
if [ -z "$PUBLIC_IP" ]; then
PUBLIC_IP="localhost"
fi
# generate a folder for the docker-compose file which is not existing and do the setup within the folder
# Define the folder name
FOLDER_NAME="nocodb_$(date +"%Y%m%d_%H%M%S")"
# prompt for custom folder name and if left empty skip
#echo "Enter a custom folder name or press Enter to use the default folder name ($FOLDER_NAME): "
#read CUSTOM_FOLDER_NAME
message_arr+=("Setup folder: $FOLDER_NAME")
if [ -n "$CUSTOM_FOLDER_NAME" ]; then
FOLDER_NAME="$CUSTOM_FOLDER_NAME"
fi
# Create the folder
mkdir -p "$FOLDER_NAME"
# Navigate into the folder
cd "$FOLDER_NAME" || exit
# ******************** SYSTEM REQUIREMENTS CHECK END **************************
# ******************************************************************************
# ******************** INPUTS FROM USER START ********************************
# ******************************************************************************
echo "Choose Community or Enterprise Edition [CE/EE] (default: CE): "
read EDITION
echo "Do you want to configure SSL [Y/N] (default: N): "
read SSL_ENABLED
if [ -n "$SSL_ENABLED" ] && { [ "$SSL_ENABLED" = "Y" ] || [ "$SSL_ENABLED" = "y" ]; }; then
SSL_ENABLED='y'
echo "Enter the domain name for the SSL certificate: "
read DOMAIN_NAME
if [ -z "$DOMAIN_NAME" ]; then
echo "Domain name is required for SSL configuration"
exit 1
fi
message_arr+=("Domain: $DOMAIN_NAME")
else
# prompt for ip address and if left empty use extracted public ip
echo "Enter the IP address or domain name for the NocoDB instance (default: $PUBLIC_IP): "
read DOMAIN_NAME
if [ -z "$DOMAIN_NAME" ]; then
DOMAIN_NAME="$PUBLIC_IP"
fi
fi
if [ -n "$EDITION" ] && { [ "$EDITION" = "EE" ] || [ "$EDITION" = "ee" ]; }; then
echo "Enter the NocoDB license key: "
read LICENSE_KEY
if [ -z "$LICENSE_KEY" ]; then
echo "License key is required for Enterprise Edition installation"
exit 1
fi
fi
echo "Do you want to enabled Redis for caching [Y/N] (default: Y): "
read REDIS_ENABLED
if [ -z "$REDIS_ENABLED" ] || { [ "$REDIS_ENABLED" != "N" ] && [ "$REDIS_ENABLED" != "n" ]; }; then
message_arr+=("Redis: Enabled")
else
message_arr+=("Redis: Disabled")
fi
echo "Do you want to enabled Watchtower for automatic updates [Y/N] (default: Y): "
read WATCHTOWER_ENABLED
if [ -z "$WATCHTOWER_ENABLED" ] || { [ "$WATCHTOWER_ENABLED" != "N" ] && [ "$WATCHTOWER_ENABLED" != "n" ]; }; then
message_arr+=("Watchtower: Enabled")
else
message_arr+=("Watchtower: Disabled")
fi
# ******************************************************************************
# *********************** INPUTS FROM USER END ********************************
# ******************************************************************************
# *************************** SETUP START *************************************
# Generate a strong random password for PostgreSQL
STRONG_PASSWORD=$(openssl rand -base64 48 | tr -dc 'a-zA-Z0-9!@#$%^&*()-_+=' | head -c 32)
REDIS_PASSWORD=$(openssl rand -base64 48 | tr -dc 'a-zA-Z0-9' | head -c 24)
# Encode special characters in the password for JDBC URL usage
ENCODED_PASSWORD=$(urlencode "$STRONG_PASSWORD")
IMAGE="nocodb/nocodb:latest";
# Determine the Docker image to use based on the edition
if [ -n "$EDITION" ] && { [ "$EDITION" = "EE" ] || [ "$EDITION" = "ee" ]; }; then
IMAGE="nocodb/nocodb-ee:latest"
DATABASE_URL="DATABASE_URL=postgres://postgres:${ENCODED_PASSWORD}@db:5432/nocodb"
else
# use NC_DB url until the issue with DATABASE_URL is resolved(encoding)
DATABASE_URL="NC_DB=pg://db:5432?d=nocodb&user=postgres&password=${ENCODED_PASSWORD}"
fi
message_arr+=("Docker image: $IMAGE")
DEPENDS_ON=""
# Add Redis service if enabled
if [ -z "$REDIS_ENABLED" ] || { [ "$REDIS_ENABLED" != "N" ] && [ "$REDIS_ENABLED" != "n" ]; }; then
DEPENDS_ON="- redis"
fi
# Write the Docker Compose file with the updated password
cat <<EOF > docker-compose.yml
version: '3'
services:
nocodb:
image: ${IMAGE}
env_file: docker.env
depends_on:
- db
${DEPENDS_ON}
restart: unless-stopped
volumes:
- ./nocodb:/usr/app/data
labels:
- "com.centurylinklabs.watchtower.enable=true"
networks:
- nocodb-network
db:
image: postgres:16.1
env_file: docker.env
volumes:
- ./postgres:/var/lib/postgresql/data
restart: unless-stopped
healthcheck:
interval: 10s
retries: 10
test: "pg_isready -U \"\$\$POSTGRES_USER\" -d \"\$\$POSTGRES_DB\""
timeout: 2s
networks:
- nocodb-network
nginx:
image: nginx:latest
volumes:
- ./nginx:/etc/nginx/conf.d
EOF
if [ "$SSL_ENABLED" = 'y' ] || [ "$SSL_ENABLED" = 'Y' ]; then
cat <<EOF >> docker-compose.yml
- webroot:/var/www/certbot
- ./letsencrypt:/etc/letsencrypt
- letsencrypt-lib:/var/lib/letsencrypt
EOF
fi
cat <<EOF >> docker-compose.yml
ports:
- "80:80"
- "443:443"
depends_on:
- nocodb
restart: unless-stopped
networks:
- nocodb-network
EOF
if [ "$SSL_ENABLED" = 'y' ] || [ "$SSL_ENABLED" = 'Y' ]; then
cat <<EOF >> docker-compose.yml
certbot:
image: certbot/certbot
volumes:
- ./letsencrypt:/etc/letsencrypt
- letsencrypt-lib:/var/lib/letsencrypt
- webroot:/var/www/certbot
entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait \$\${!}; done;'"
depends_on:
- nginx
restart: unless-stopped
networks:
- nocodb-network
EOF
fi
if [ -z "$REDIS_ENABLED" ] || { [ "$REDIS_ENABLED" != "N" ] && [ "$REDIS_ENABLED" != "n" ]; }; then
cat <<EOF >> docker-compose.yml
redis:
image: redis:latest
restart: unless-stopped
env_file: docker.env
command:
- /bin/sh
- -c
- redis-server --requirepass "\$\${REDIS_PASSWORD}"
volumes:
- redis:/data
healthcheck:
test: [ "CMD", "redis-cli", "-a", "\$\${REDIS_PASSWORD}", "--raw", "incr", "ping" ]
networks:
- nocodb-network
EOF
fi
if [ -z "$WATCHTOWER_ENABLED" ] || { [ "$WATCHTOWER_ENABLED" != "N" ] && [ "$WATCHTOWER_ENABLED" != "n" ]; }; then
cat <<EOF >> docker-compose.yml
watchtower:
image: containrrr/watchtower
volumes:
- /var/run/docker.sock:/var/run/docker.sock
command: --schedule "0 2 * * 6" --cleanup
restart: unless-stopped
networks:
- nocodb-network
EOF
fi
if [ "$SSL_ENABLED" = 'y' ] || [ "$SSL_ENABLED" = 'Y' ]; then
cat <<EOF >> docker-compose.yml
volumes:
letsencrypt-lib:
webroot:
EOF
fi
# add the cache volume
if [ -z "$REDIS_ENABLED" ] || { [ "$REDIS_ENABLED" != "N" ] && [ "$REDIS_ENABLED" != "n" ]; }; then
# check ssl enabled
if [ "$SSL_ENABLED" = 'y' ] || [ "$SSL_ENABLED" = 'Y' ]; then
cat <<EOF >> docker-compose.yml
redis:
EOF
else
cat <<EOF >> docker-compose.yml
volumes:
redis:
EOF
fi
fi
# Create the network
cat <<EOF >> docker-compose.yml
networks:
nocodb-network:
driver: bridge
EOF
# Write the docker.env file
cat <<EOF > docker.env
POSTGRES_DB=nocodb
POSTGRES_USER=postgres
POSTGRES_PASSWORD=${STRONG_PASSWORD}
$DATABASE_URL
NC_LICENSE_KEY=${LICENSE_KEY}
EOF
# add redis env if enabled
if [ -z "$REDIS_ENABLED" ] || { [ "$REDIS_ENABLED" != "N" ] && [ "$REDIS_ENABLED" != "n" ]; }; then
cat <<EOF >> docker.env
REDIS_PASSWORD=${REDIS_PASSWORD}
NC_REDIS_URL=redis://:${REDIS_PASSWORD}@redis:6379/0
EOF
fi
mkdir -p ./nginx
# Create nginx config with the provided domain name
cat > ./nginx/default.conf <<EOF
server {
listen 80;
EOF
if [ "$SSL_ENABLED" = 'y' ] || [ "$SSL_ENABLED" = 'Y' ]; then
cat >> ./nginx/default.conf <<EOF
server_name $DOMAIN_NAME;
EOF
fi
cat >> ./nginx/default.conf <<EOF
location / {
proxy_pass http://nocodb:8080;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
}
EOF
if [ "$SSL_ENABLED" = 'y' ] || [ "$SSL_ENABLED" = 'Y' ]; then
cat >> ./nginx/default.conf <<EOF
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
EOF
fi
cat >> ./nginx/default.conf <<EOF
}
EOF
if [ "$SSL_ENABLED" = 'y' ] || [ "$SSL_ENABLED" = 'Y' ]; then
mkdir -p ./nginx-post-config
# Create nginx config with the provided domain name
cat > ./nginx-post-config/default.conf <<EOF
server {
listen 80;
server_name $DOMAIN_NAME;
location / {
return 301 https://\$host\$request_uri;
}
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
}
server {
listen 443 ssl;
server_name $DOMAIN_NAME;
ssl_certificate /etc/letsencrypt/live/$DOMAIN_NAME/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/$DOMAIN_NAME/privkey.pem;
location / {
proxy_pass http://nocodb:8080;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
}
}
EOF
fi
IS_DOCKER_COMPOSE_REQUIRE_SUDO=$(check_for_docker_compose_sudo)
# Generate the update.sh file for upgrading images
if [ "$IS_DOCKER_COMPOSE_REQUIRE_SUDO" = "y" ]; then
cat > ./update.sh <<EOF
sudo docker-compose pull
sudo docker-compose up -d --force-recreate
sudo docker image prune -a -f
EOF
else
cat > ./update.sh <<EOF
docker-compose pull
docker-compose up -d --force-recreate
docker image prune -a -f
EOF
fi
message_arr+=("Update script: update.sh")
# Pull latest images and start the docker-compose setup
if [ "$IS_DOCKER_COMPOSE_REQUIRE_SUDO" = "y" ]; then
echo "Docker compose requires sudo. Running the docker-compose setup with sudo."
sudo docker-compose pull
sudo docker-compose up -d
else
docker-compose pull
docker-compose up -d
fi
echo 'Waiting for Nginx to start...';
sleep 5
if [ "$SSL_ENABLED" = 'y' ] || [ "$SSL_ENABLED" = 'Y' ]; then
echo 'Starting Letsencrypt certificate request...';
if [ "$IS_DOCKER_COMPOSE_REQUIRE_SUDO" = "y" ]; then
sudo docker-compose exec certbot certbot certonly --webroot --webroot-path=/var/www/certbot -d $DOMAIN_NAME --email contact@$DOMAIN_NAME --agree-tos --no-eff-email && echo "Certificate request successful" || echo "Certificate request failed"
else
docker-compose exec certbot certbot certonly --webroot --webroot-path=/var/www/certbot -d $DOMAIN_NAME --email contact@$DOMAIN_NAME --agree-tos --no-eff-email && echo "Certificate request successful" || echo "Certificate request failed"
fi
# Initial Let's Encrypt certificate request
# Update the nginx config to use the new certificates
rm -rf ./nginx/default.conf
mv ./nginx-post-config/default.conf ./nginx/
rm -r ./nginx-post-config
echo "Restarting nginx to apply the new certificates"
# Reload nginx to apply the new certificates
if [ "$IS_DOCKER_COMPOSE_REQUIRE_SUDO" = "y" ]; then
sudo docker-compose exec nginx nginx -s reload
else
docker-compose exec nginx nginx -s reload
fi
message_arr+=("NocoDB is now available at https://$DOMAIN_NAME")
elif [ -n "$DOMAIN_NAME" ]; then
message_arr+=("NocoDB is now available at http://$DOMAIN_NAME")
else
message_arr+=("NocoDB is now available at http://localhost")
fi
print_box_message "${mecdessage_arr[@]}"
# *************************** SETUP END *************************************
# ******************************************************************************

BIN
packages/nc-gui/assets/img/placeholder/api-tokens.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

BIN
packages/nc-gui/assets/img/placeholder/invite-team.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 743 KiB

BIN
packages/nc-gui/assets/img/placeholder/link-records.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

BIN
packages/nc-gui/assets/img/placeholder/multi-field-editor.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

BIN
packages/nc-gui/assets/img/placeholder/table.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

BIN
packages/nc-gui/assets/img/placeholder/webhooks.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

61
packages/nc-gui/components/account/Token.vue

@ -45,6 +45,8 @@ const pagination = reactive({
pageSize: 10, pageSize: 10,
}) })
const isLoadingAllTokens = ref(true)
const setDefaultTokenName = () => { const setDefaultTokenName = () => {
selectedTokenData.value.description = extractNextDefaultName( selectedTokenData.value.description = extractNextDefaultName(
[...allTokens.value.map((el) => el?.description || '')], [...allTokens.value.map((el) => el?.description || '')],
@ -94,7 +96,7 @@ const updateAllTokens = (type: 'delete' | 'add', token: IApiTokenInfo) => {
setDefaultTokenName() setDefaultTokenName()
} }
const loadTokens = async (page = currentPage.value, limit = currentLimit.value) => { const loadTokens = async (page = currentPage.value, limit = currentLimit.value, hideShowNewToken = false) => {
currentPage.value = page currentPage.value = page
try { try {
const response: any = await api.orgTokens.list({ const response: any = await api.orgTokens.list({
@ -103,18 +105,30 @@ const loadTokens = async (page = currentPage.value, limit = currentLimit.value)
offset: searchText.value.length === 0 ? (page - 1) * limit : 0, offset: searchText.value.length === 0 ? (page - 1) * limit : 0,
}, },
} as RequestParams) } as RequestParams)
if (!response) return if (!response) {
isLoadingAllTokens.value = false
return
}
pagination.total = response.pageInfo.totalRows ?? 0 pagination.total = response.pageInfo.totalRows ?? 0
pagination.pageSize = 10 pagination.pageSize = 10
tokens.value = response.list as IApiTokenInfo[] tokens.value = response.list as IApiTokenInfo[]
if (hideShowNewToken) {
showNewTokenModal.value = false
selectedTokenData.value = {}
}
if (!allTokens.value.length) { if (!allTokens.value.length) {
await loadAllTokens(pagination.total) await loadAllTokens(pagination.total)
} }
} catch (e: any) { } catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e)) message.error(await extractSdkResponseErrorMsg(e))
} finally {
if (isLoadingAllTokens.value) {
isLoadingAllTokens.value = false
}
} }
} }
@ -159,11 +173,10 @@ const generateToken = async () => {
if (!isValidTokenName.value) return if (!isValidTokenName.value) return
try { try {
const token = await api.orgTokens.create(selectedTokenData.value) const token = await api.orgTokens.create(selectedTokenData.value)
showNewTokenModal.value = false
// Token generated successfully // Token generated successfully
// message.success(t('msg.success.tokenGenerated')) // message.success(t('msg.success.tokenGenerated'))
selectedTokenData.value = {} await loadTokens(currentPage.value, currentLimit.value, true)
await loadTokens()
updateAllTokens('add', token as IApiTokenInfo) updateAllTokens('add', token as IApiTokenInfo)
} catch (e: any) { } catch (e: any) {
@ -216,7 +229,7 @@ const handleCancel = () => {
<div class="max-w-202 mx-auto px-4 h-full" data-testid="nc-token-list"> <div class="max-w-202 mx-auto px-4 h-full" data-testid="nc-token-list">
<div class="py-2 flex gap-4 items-baseline justify-between"> <div class="py-2 flex gap-4 items-baseline justify-between">
<h6 class="text-2xl text-left font-bold" data-rec="true">{{ $t('title.apiTokens') }}</h6> <h6 class="text-2xl text-left font-bold" data-rec="true">{{ $t('title.apiTokens') }}</h6>
<NcTooltip :disabled="!(isEeUI && tokens.length)"> <NcTooltip v-if="tokens.length" :disabled="!(isEeUI && tokens.length)">
<template #title>{{ $t('labels.tokenLimit') }}</template> <template #title>{{ $t('labels.tokenLimit') }}</template>
<NcButton <NcButton
:disabled="showNewTokenModal || (isEeUI && tokens.length)" :disabled="showNewTokenModal || (isEeUI && tokens.length)"
@ -237,7 +250,7 @@ const handleCancel = () => {
</NcTooltip> </NcTooltip>
</div> </div>
<span data-rec="true">{{ $t('msg.apiTokenCreate') }}</span> <span data-rec="true">{{ $t('msg.apiTokenCreate') }}</span>
<div class="mt-5 h-[calc(100%-13rem)]"> <div v-if="!isLoadingAllTokens && (tokens.length || showNewTokenModal)" class="mt-5 h-[calc(100%-13rem)]">
<div class="h-full w-full !overflow-hidden rounded-md"> <div class="h-full w-full !overflow-hidden rounded-md">
<div class="flex w-full pl-5 bg-gray-50 border-1 rounded-t-md"> <div class="flex w-full pl-5 bg-gray-50 border-1 rounded-t-md">
<span class="py-3.5 text-gray-500 font-medium text-3.5 w-2/9" data-rec="true">{{ $t('title.tokenName') }}</span> <span class="py-3.5 text-gray-500 font-medium text-3.5 w-2/9" data-rec="true">{{ $t('title.tokenName') }}</span>
@ -268,6 +281,7 @@ const handleCancel = () => {
class="!rounded-lg !py-1" class="!rounded-lg !py-1"
placeholder="Token Name" placeholder="Token Name"
data-testid="nc-token-input" data-testid="nc-token-input"
:disabled="isLoading"
@press-enter="generateToken" @press-enter="generateToken"
/> />
<span v-if="!isValidTokenName" class="text-red-500 text-xs font-light mt-1.5 ml-1" data-rec="true" <span v-if="!isValidTokenName" class="text-red-500 text-xs font-light mt-1.5 ml-1" data-rec="true"
@ -278,13 +292,7 @@ const handleCancel = () => {
<NcButton v-if="!isLoading" type="secondary" size="small" @click="handleCancel"> <NcButton v-if="!isLoading" type="secondary" size="small" @click="handleCancel">
{{ $t('general.cancel') }} {{ $t('general.cancel') }}
</NcButton> </NcButton>
<NcButton <NcButton type="primary" size="sm" :loading="isLoading" data-testid="nc-token-save-btn" @click="generateToken">
type="primary"
size="sm"
:is-loading="isLoading"
data-testid="nc-token-save-btn"
@click="generateToken"
>
{{ $t('general.save') }} {{ $t('general.save') }}
</NcButton> </NcButton>
</div> </div>
@ -329,15 +337,15 @@ const handleCancel = () => {
@click="hideOrShowToken(el.token as string)" @click="hideOrShowToken(el.token as string)"
/> />
</NcTooltip> </NcTooltip>
<NcTooltip placement="top" class="h-4"> <NcTooltip placement="top">
<template #title>{{ $t('general.copy') }}</template> <template #title>{{ $t('general.copy') }}</template>
<component <component
:is="iconMap.copy" :is="iconMap.copy"
class="hover::cursor-pointer w-4 h-4 text-gray-600 mt-0.25" class="hover::cursor-pointer w-4 h-4 text-gray-600"
@click="copyToken(el.token)" @click="copyToken(el.token)"
/> />
</NcTooltip> </NcTooltip>
<NcTooltip placement="top" class="mb-0.5"> <NcTooltip placement="top">
<template #title>{{ $t('general.delete') }}</template> <template #title>{{ $t('general.delete') }}</template>
<component <component
:is="iconMap.delete" :is="iconMap.delete"
@ -351,6 +359,25 @@ const handleCancel = () => {
</div> </div>
</div> </div>
</div> </div>
<div
v-else-if="!isLoadingAllTokens && !tokens.length && !showNewTokenModal"
class="max-w-[40rem] border px-3 py-6 flex flex-col items-center justify-center gap-6 text-center"
>
<img src="~assets/img/placeholder/api-tokens.png" class="!w-[22rem] flex-none" />
<div class="text-2xl text-gray-800 font-bold">{{ $t('placeholder.noTokenCreated') }}</div>
<div class="text-sm text-gray-700">
{{ $t('placeholder.noTokenCreatedLabel') }}
</div>
<NcButton class="!rounded-lg !py-3 !h-10" data-testid="nc-token-create" type="primary" @click="showNewTokenModal = true">
<span class="hidden md:block" data-rec="true">
{{ $t('title.createNewToken') }}
</span>
<span class="flex items-center justify-center md:hidden" data-rec="true">
<component :is="iconMap.plus" />
</span>
</NcButton>
</div>
<div v-if="pagination.total > 10" class="flex items-center justify-center mt-5"> <div v-if="pagination.total > 10" class="flex items-center justify-center mt-5">
<a-pagination <a-pagination

20
packages/nc-gui/components/account/UserList.vue

@ -273,9 +273,9 @@ const openDeleteModal = (user: UserType) => {
class="w-4 h-4 text-primary" class="w-4 h-4 text-primary"
/> />
</div> </div>
<span class="text-gray-500 text-xs whitespace-normal" data-rec="true"> <div class="text-gray-500 text-xs whitespace-normal" data-rec="true">
{{ $t('msg.info.roles.orgCreator') }} {{ $t('msg.info.roles.orgCreator') }}
</span> </div>
</a-select-option> </a-select-option>
<a-select-option <a-select-option
@ -292,9 +292,9 @@ const openDeleteModal = (user: UserType) => {
class="w-4 h-4 text-primary" class="w-4 h-4 text-primary"
/> />
</div> </div>
<span class="text-gray-500 text-xs whitespace-normal" data-rec="true"> <div class="text-gray-500 text-xs whitespace-normal" data-rec="true">
{{ $t('msg.info.roles.orgViewer') }} {{ $t('msg.info.roles.orgViewer') }}
</span> </div>
</a-select-option> </a-select-option>
</NcSelect> </NcSelect>
<div v-else class="font-weight-bold" data-rec="true"> <div v-else class="font-weight-bold" data-rec="true">
@ -345,6 +345,18 @@ const openDeleteModal = (user: UserType) => {
</div> </div>
</span> </span>
</div> </div>
<div
v-if="sortedUsers.length === 1"
class="user pt-12 pb-4 px-2 flex flex-col items-center gap-6 text-center border-b-1 border-l-1 border-r-1"
>
<div class="text-2xl text-gray-800 font-bold">
{{ $t('placeholder.inviteYourTeam') }}
</div>
<div class="text-sm text-gray-700">
{{ $t('placeholder.inviteYourTeamLabel') }}
</div>
<img src="~assets/img/placeholder/invite-team.png" class="!w-[30rem] flex-none" />
</div>
</section> </section>
</div> </div>
<div v-if="pagination.total > 10" class="flex items-center justify-center mt-4"> <div v-if="pagination.total > 10" class="flex items-center justify-center mt-4">

6
packages/nc-gui/components/cell/Currency.vue

@ -84,15 +84,15 @@ const submitCurrency = () => {
} }
const onBlur = () => { const onBlur = () => {
// triggered by events like forcus-out / pressing enter // triggered by events like focus-out / pressing enter
// for non-firefox browsers only // for non-firefox browsers only
submitCurrency() submitCurrency()
} }
const onKeydownEnter = () => { const onKeydownEnter = () => {
// for firefox, onBlur is never executed // onBlur is never executed for firefox & safari
// we use keydown.enter to trigger submitCurrency // we use keydown.enter to trigger submitCurrency
if (/Firefox/.test(navigator.userAgent)) { if (/(Firefox|Safari)/.test(navigator.userAgent)) {
submitCurrency() submitCurrency()
} }
} }

8
packages/nc-gui/components/dashboard/Sidebar/UserInfo.vue

@ -134,7 +134,13 @@ onMounted(() => {
<span class="menu-btn"> {{ $t('labels.community.joinReddit') }} </span> <span class="menu-btn"> {{ $t('labels.community.joinReddit') }} </span>
</NcMenuItem> </NcMenuItem>
</a> </a>
<a v-e="['c:nocodb:twitter']" href="https://twitter.com/nocodb" target="_blank" class="!underline-transparent"> <a
v-e="['c:nocodb:twitter']"
href="https://twitter.com/nocodb"
target="_blank"
class="!underline-transparent"
rel="noopener noreferrer"
>
<NcMenuItem class="social-icon-wrapper group"> <NcMenuItem class="social-icon-wrapper group">
<GeneralIcon class="text-gray-500 group-hover:text-gray-800 my-0.5" icon="twitter" /> <GeneralIcon class="text-gray-500 group-hover:text-gray-800 my-0.5" icon="twitter" />
<span class="menu-btn"> {{ $t('labels.twitter') }} </span> <span class="menu-btn"> {{ $t('labels.twitter') }} </span>

2
packages/nc-gui/components/general/JoinCloud.vue

@ -28,7 +28,7 @@
</div> </div>
<div class="self-stretch text-gray-500 text-base leading-normal">/ month / workspace</div> <div class="self-stretch text-gray-500 text-base leading-normal">/ month / workspace</div>
<a href="https://app.nocodb.com/#/signin" target="_blank" class="!no-underline"> <a href="https://app.nocodb.com/#/signin" target="_blank" class="!no-underline" rel="noopener">
<NcButton class="text-gray-700 text-base font-semibold leading-tight py-4 w-full">Start for Free</NcButton> <NcButton class="text-gray-700 text-base font-semibold leading-tight py-4 w-full">Start for Free</NcButton>
</a> </a>
<div class="self-stretch text-center text-gray-500 text-xs font-medium leading-none mb-4"> <div class="self-stretch text-center text-gray-500 text-xs font-medium leading-none mb-4">

2
packages/nc-gui/components/general/ReleaseInfo.vue

@ -67,7 +67,7 @@ onMounted(async () => await fetchReleaseInfo())
<nuxt-link <nuxt-link
no-prefetch no-prefetch
no-rel rel="noopener"
class="!text-primary !no-underline" class="!text-primary !no-underline"
to="https://docs.nocodb.com/getting-started/upgrading" to="https://docs.nocodb.com/getting-started/upgrading"
target="_blank" target="_blank"

6
packages/nc-gui/components/general/SocialCard.vue

@ -33,7 +33,7 @@ function openKeyboardShortcutDialog() {
<nuxt-link <nuxt-link
v-e="['e:docs']" v-e="['e:docs']"
no-prefetch no-prefetch
no-rel rel="noopener"
class="text-primary !no-underline !text-current" class="text-primary !no-underline !text-current"
target="_blank" target="_blank"
to="https://docs.nocodb.com/" to="https://docs.nocodb.com/"
@ -49,7 +49,7 @@ function openKeyboardShortcutDialog() {
<nuxt-link <nuxt-link
v-e="['e:api-docs']" v-e="['e:api-docs']"
no-prefetch no-prefetch
no-rel rel="noopener"
class="text-primary !no-underline !text-current" class="text-primary !no-underline !text-current"
target="_blank" target="_blank"
to="https://apis.nocodb.com/" to="https://apis.nocodb.com/"
@ -152,7 +152,7 @@ function openKeyboardShortcutDialog() {
<nuxt-link <nuxt-link
v-e="['e:hiring']" v-e="['e:hiring']"
no-prefetch no-prefetch
no-rel rel="noopener"
class="!no-underline !text-current" class="!no-underline !text-current"
target="_blank" target="_blank"
to="http://careers.nocodb.com" to="http://careers.nocodb.com"

70
packages/nc-gui/components/project/AllTables.vue

@ -121,44 +121,56 @@ const onCreateBaseClick = () => {
</div> </div>
</component> </component>
</div> </div>
<div class="flex flex-row w-full text-gray-400 border-b-1 border-gray-50 py-3 px-2.5"> <template v-if="activeTables.length">
<div class="w-2/5">{{ $t('objects.table') }}</div> <div class="flex flex-row w-full text-gray-400 border-b-1 border-gray-50 py-3 px-2.5">
<div class="w-1/5">{{ $t('general.source') }}</div> <div class="w-2/5">{{ $t('objects.table') }}</div>
<div class="w-1/5">{{ $t('labels.createdOn') }}</div> <div class="w-1/5">{{ $t('general.source') }}</div>
</div> <div class="w-1/5">{{ $t('labels.createdOn') }}</div>
<div </div>
class="nc-base-view-all-table-list nc-scrollbar-md"
:style="{
height: 'calc(100vh - var(--topbar-height) - 18rem)',
}"
>
<div <div
v-for="table in [...activeTables].sort( class="nc-base-view-all-table-list nc-scrollbar-md"
:style="{
height: 'calc(100vh - var(--topbar-height) - 18rem)',
}"
>
<div
v-for="table in [...activeTables].sort(
(a, b) => a.source_id!.localeCompare(b.source_id!) * 20 (a, b) => a.source_id!.localeCompare(b.source_id!) * 20
)" )"
:key="table.id" :key="table.id"
class="py-4 flex flex-row w-full cursor-pointer hover:bg-gray-100 border-b-1 border-gray-100 px-2.25" class="py-4 flex flex-row w-full cursor-pointer hover:bg-gray-100 border-b-1 border-gray-100 px-2.25"
data-testid="proj-view-list__item" data-testid="proj-view-list__item"
@click="openTable(table)" @click="openTable(table)"
> >
<div class="flex flex-row w-2/5 items-center gap-x-2" data-testid="proj-view-list__item-title"> <div class="flex flex-row w-2/5 items-center gap-x-2" data-testid="proj-view-list__item-title">
<div class="min-w-5 flex items-center justify-center"> <div class="min-w-5 flex items-center justify-center">
<GeneralTableIcon :meta="table" class="text-gray-500" /> <GeneralTableIcon :meta="table" class="text-gray-500" />
</div>
{{ table?.title }}
</div> </div>
{{ table?.title }} <div class="w-1/5 text-gray-600" data-testid="proj-view-list__item-type">
</div> <div v-if="table.source_id === defaultBase?.id" class="ml-0.75">-</div>
<div class="w-1/5 text-gray-600" data-testid="proj-view-list__item-type"> <div v-else class="capitalize flex flex-row items-center gap-x-0.5">
<div v-if="table.source_id === defaultBase?.id" class="ml-0.75">-</div> <GeneralBaseLogo class="w-4 mr-1" />
<div v-else class="capitalize flex flex-row items-center gap-x-0.5"> {{ sources.get(table.source_id!)?.alias }}
<GeneralBaseLogo class="w-4 mr-1" /> </div>
{{ sources.get(table.source_id!)?.alias }} </div>
<div class="w-1/5 text-gray-400 ml-0.25" data-testid="proj-view-list__item-created-at">
{{ dayjs(table?.created_at).fromNow() }}
</div> </div>
</div> </div>
<div class="w-1/5 text-gray-400 ml-0.25" data-testid="proj-view-list__item-created-at"> </div>
{{ dayjs(table?.created_at).fromNow() }} </template>
<div v-else class="py-3 flex items-center gap-6 <lg:flex-col">
<img src="~assets/img/placeholder/table.png" class="!w-[23rem] flex-none" />
<div class="text-center lg:text-left">
<div class="text-2xl text-gray-800 font-bold">{{ $t('placeholder.createTable') }}</div>
<div class="text-sm text-gray-700 pt-6">
{{ $t('placeholder.createTableLabel') }}
</div> </div>
</div> </div>
</div> </div>
<ProjectImportModal v-if="defaultBase" v-model:visible="isImportModalOpen" :source="defaultBase" /> <ProjectImportModal v-if="defaultBase" v-model:visible="isImportModalOpen" :source="defaultBase" />
<LazyDashboardSettingsDataSourcesCreateBase v-model:open="isNewBaseModalOpen" /> <LazyDashboardSettingsDataSourcesCreateBase v-model:open="isNewBaseModalOpen" />
</div> </div>

2
packages/nc-gui/components/smartsheet/Cell.vue

@ -252,7 +252,7 @@ onUnmounted(() => {
<LazyCellJson v-else-if="isJSON(column)" v-model="vModel" /> <LazyCellJson v-else-if="isJSON(column)" v-model="vModel" />
<LazyCellText v-else v-model="vModel" /> <LazyCellText v-else v-model="vModel" />
<div <div
v-if="(isPublic && readOnly && !isForm) || (isSystemColumn(column) && !isAttachment(column) && !isTextArea(column))" v-if="((isPublic && readOnly && !isForm) || (isSystemColumn(column) && !isAttachment(column))) && !isTextArea(column)"
class="nc-locked-overlay" class="nc-locked-overlay"
/> />
</template> </template>

4
packages/nc-gui/components/smartsheet/Form.vue

@ -119,10 +119,6 @@ const { betaFeatureToggleState } = useBetaFeatureToggle()
const updateView = useDebounceFn( const updateView = useDebounceFn(
() => { () => {
if ((formViewData.value?.subheading?.length || 0) > 255) {
return message.error(t('msg.error.formDescriptionTooLong'))
}
updateFormView(formViewData.value) updateFormView(formViewData.value)
}, },
300, 300,

2
packages/nc-gui/components/smartsheet/details/Fields.vue

@ -1254,7 +1254,7 @@ watch(
@add="onFieldAdd" @add="onFieldAdd"
/> />
<div v-else class="w-[25rem] flex flex-col justify-center p-4 items-center"> <div v-else class="w-[25rem] flex flex-col justify-center p-4 items-center">
<img src="~assets/img/fieldPlaceholder.svg" class="!w-[18rem]" /> <img src="~assets/img/placeholder/multi-field-editor.png" class="!w-[18rem]" />
<div class="text-2xl text-gray-600 font-bold text-center pt-6">{{ $t('labels.multiField.selectField') }}</div> <div class="text-2xl text-gray-600 font-bold text-center pt-6">{{ $t('labels.multiField.selectField') }}</div>
<div class="text-center text-sm px-2 text-gray-500 pt-6"> <div class="text-center text-sm px-2 text-gray-500 pt-6">
{{ $t('labels.multiField.selectFieldLabel') }} {{ $t('labels.multiField.selectFieldLabel') }}

11
packages/nc-gui/components/smartsheet/details/Webhooks.vue

@ -197,13 +197,10 @@ watch(
</NcButton> </NcButton>
</div> </div>
<div v-if="!selectedHookId && !isDraftMode" class="flex flex-col h-full w-full items-center"> <div v-if="!selectedHookId && !isDraftMode" class="flex flex-col h-full w-full items-center">
<div v-if="hooks.length === 0" class="flex flex-col px-1.5 py-2.5 ml-1 h-full justify-center items-center gap-y-6"> <div v-if="hooks.length === 0" class="flex flex-col px-1.5 py-2.5 ml-1 h-full items-center gap-y-6 text-center">
<GeneralIcon icon="webhook" class="flex text-5xl h-10" style="-webkit-text-stroke: 0.5px" /> <img src="~assets/img/placeholder/webhooks.png" class="!w-[24rem] flex-none" />
<div class="flex text-gray-600 font-medium text-lg">{{ $t('msg.createWebhookMsg1') }}</div> <div class="text-gray-700 font-bold text-2xl">{{ $t('msg.createWebhookMsg1') }}</div>
<div class="flex flex-col items-center"> <div class="text-gray-700 max-w-[24rem]">{{ $t('msg.createWebhookMsg2') }}</div>
<div class="flex">{{ $t('msg.createWebhookMsg2') }}</div>
<div class="flex">{{ $t('msg.createWebhookMsg3') }}</div>
</div>
<NcButton v-e="['c:actions:webhook']" class="flex max-w-40" type="primary" @click="createWebhook()"> <NcButton v-e="['c:actions:webhook']" class="flex max-w-40" type="primary" @click="createWebhook()">
<div class="flex flex-row items-center justify-between w-full"> <div class="flex flex-row items-center justify-between w-full">
<span class="ml-1">{{ $t('activity.newWebhook') }}</span> <span class="ml-1">{{ $t('activity.newWebhook') }}</span>

12
packages/nc-gui/components/smartsheet/expanded-form/Comments.vue

@ -158,8 +158,8 @@ const onClickAudit = () => {
@click="tab = 'comments'" @click="tab = 'comments'"
> >
<div class="tab-title nc-tab"> <div class="tab-title nc-tab">
<MdiMessageOutline class="h-4 w-4" /> <MdiMessageOutline class="h-4 w-4 flex-none" />
Comments <span class="<lg:hidden">Comments</span>
</div> </div>
</div> </div>
<NcTooltip v-if="appInfo.ee" class="tab flex-1"> <NcTooltip v-if="appInfo.ee" class="tab flex-1">
@ -173,7 +173,7 @@ const onClickAudit = () => {
> >
<div class="tab-title nc-tab select-none"> <div class="tab-title nc-tab select-none">
<MdiFileDocumentOutline class="h-4 w-4" /> <MdiFileDocumentOutline class="h-4 w-4" />
Audits <span class="<lg:hidden">Audits</span>
</div> </div>
</div> </div>
</NcTooltip> </NcTooltip>
@ -188,7 +188,7 @@ const onClickAudit = () => {
> >
<div class="tab-title nc-tab"> <div class="tab-title nc-tab">
<MdiFileDocumentOutline class="h-4 w-4" /> <MdiFileDocumentOutline class="h-4 w-4" />
Audits <span class="<lg:hidden">Audits</span>
</div> </div>
</div> </div>
</div> </div>
@ -211,13 +211,13 @@ const onClickAudit = () => {
</div> </div>
<div v-else ref="commentsWrapperEl" class="flex flex-col h-full py-2 pl-2 pr-1 space-y-2 nc-scrollbar-md"> <div v-else ref="commentsWrapperEl" class="flex flex-col h-full py-2 pl-2 pr-1 space-y-2 nc-scrollbar-md">
<div v-for="log of comments" :key="log.id"> <div v-for="log of comments" :key="log.id">
<div class="bg-white rounded-xl group border-1 gap-2 border-gray-200"> <div class="bg-white rounded-xl group border-1 gap-2 border-gray-200 overflow-hidden">
<div class="flex flex-col p-4 gap-3"> <div class="flex flex-col p-4 gap-3">
<div class="flex justify-between"> <div class="flex justify-between">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<GeneralUserIcon size="base" :name="log.display_name" :email="log.user" /> <GeneralUserIcon size="base" :name="log.display_name" :email="log.user" />
<div class="flex flex-col"> <div class="flex flex-col <lg:max-w-22">
<NcTooltip class="truncate max-w-42" show-on-truncate-only> <NcTooltip class="truncate max-w-42" show-on-truncate-only>
<template #title> <template #title>
{{ log.display_name?.trim() || log.user || 'Shared source' }} {{ log.display_name?.trim() || log.user || 'Shared source' }}

38
packages/nc-gui/components/smartsheet/expanded-form/index.vue

@ -520,7 +520,7 @@ export default {
<div class="h-[85vh] xs:(max-h-full) max-h-215 flex flex-col p-6"> <div class="h-[85vh] xs:(max-h-full) max-h-215 flex flex-col p-6">
<div class="flex h-9.5 flex-shrink-0 w-full items-center nc-expanded-form-header relative mb-4 justify-between"> <div class="flex h-9.5 flex-shrink-0 w-full items-center nc-expanded-form-header relative mb-4 justify-between">
<template v-if="!isMobileMode"> <template v-if="!isMobileMode">
<div class="flex gap-3 w-100"> <div class="flex gap-3 w-100 <lg:max-w-64">
<div class="flex gap-2"> <div class="flex gap-2">
<NcButton <NcButton
v-if="props.showNextPrevIcons" v-if="props.showNextPrevIcons"
@ -560,7 +560,7 @@ export default {
<NcButton <NcButton
v-if="!isNew && rowId" v-if="!isNew && rowId"
type="secondary" type="secondary"
class="!xs:hidden text-gray-700" class="!<lg:hidden text-gray-700"
:disabled="isLoading" :disabled="isLoading"
@click="copyRecordUrl()" @click="copyRecordUrl()"
> >
@ -582,6 +582,18 @@ export default {
{{ $t('general.reload') }} {{ $t('general.reload') }}
</div> </div>
</NcMenuItem> </NcMenuItem>
<NcMenuItem
v-if="!isNew && rowId"
type="secondary"
class="!lg:hidden text-gray-700"
:disabled="isLoading"
@click="copyRecordUrl()"
>
<div v-e="['c:row-expand:copy-url']" data-testid="nc-expanded-form-copy-url" class="flex gap-2 items-center">
<component :is="iconMap.link" class="cursor-pointer" />
{{ $t('labels.copyRecordURL') }}
</div>
</NcMenuItem>
<NcMenuItem v-if="isUIAllowed('dataEdit')" class="text-gray-700" @click="!isNew ? onDuplicateRow() : () => {}"> <NcMenuItem v-if="isUIAllowed('dataEdit')" class="text-gray-700" @click="!isNew ? onDuplicateRow() : () => {}">
<div <div
v-e="['c:row-expand:duplicate']" v-e="['c:row-expand:duplicate']"
@ -664,13 +676,13 @@ export default {
v-for="(col, i) of fields" v-for="(col, i) of fields"
v-show="isFormula(col) || !isVirtualCol(col) || !isNew || isLinksOrLTAR(col)" v-show="isFormula(col) || !isVirtualCol(col) || !isNew || isLinksOrLTAR(col)"
:key="col.title" :key="col.title"
class="nc-expanded-form-row mt-2 py-2 xs:w-full" class="nc-expanded-form-row mt-2 py-2 <lg:w-full"
:class="`nc-expand-col-${col.title}`" :class="`nc-expand-col-${col.title}`"
:col-id="col.id" :col-id="col.id"
:data-testid="`nc-expand-col-${col.title}`" :data-testid="`nc-expand-col-${col.title}`"
> >
<div class="flex items-start flex-row sm:(gap-x-6) xs:(flex-col w-full) nc-expanded-cell min-h-10"> <div class="flex items-start flex-row sm:(gap-x-6) <lg:(flex-col w-full) nc-expanded-cell min-h-10">
<div class="w-48 xs:(w-full) mt-0.25 !h-[35px]"> <div class="w-48 <lg:(w-full) mt-0.25 !h-[35px]">
<LazySmartsheetHeaderVirtualCell <LazySmartsheetHeaderVirtualCell
v-if="isVirtualCol(col)" v-if="isVirtualCol(col)"
class="nc-expanded-cell-header h-full" class="nc-expanded-cell-header h-full"
@ -696,7 +708,7 @@ export default {
<SmartsheetDivDataCell <SmartsheetDivDataCell
v-if="col.title" v-if="col.title"
:ref="i ? null : (el: any) => (cellWrapperEl = el)" :ref="i ? null : (el: any) => (cellWrapperEl = el)"
class="bg-white w-80 xs:w-full px-1 sm:min-h-[35px] xs:min-h-13 flex items-center relative" class="bg-white w-80 <lg:w-full px-1 sm:min-h-[35px] xs:min-h-13 flex items-center relative"
:class="{ :class="{
'!bg-gray-50 !select-text nc-system-field': isReadOnlyVirtualCell(col), '!bg-gray-50 !select-text nc-system-field': isReadOnlyVirtualCell(col),
}" }"
@ -725,12 +737,12 @@ export default {
</template> </template>
</div> </div>
</div> </div>
<div v-if="hiddenFields.length > 0" class="flex w-full sm:px-12 xs:(px-1 mt-2) items-center py-3"> <div v-if="hiddenFields.length > 0" class="flex w-full lg:px-12 <lg:(px-1 mt-2) items-center py-3">
<div class="flex-grow h-px mr-1 bg-gray-100"></div> <div class="flex-grow h-px mr-1 bg-gray-100"></div>
<NcButton <NcButton
type="secondary" type="secondary"
:size="isMobileMode ? 'medium' : 'small'" :size="isMobileMode ? 'medium' : 'small'"
class="flex-shrink-1 !text-sm" class="flex-shrink !text-sm overflow-hidden"
@click="toggleHiddenFields" @click="toggleHiddenFields"
> >
{{ showHiddenFields ? `Hide ${hiddenFields.length} hidden` : `Show ${hiddenFields.length} hidden` }} {{ showHiddenFields ? `Hide ${hiddenFields.length} hidden` : `Show ${hiddenFields.length} hidden` }}
@ -744,12 +756,12 @@ export default {
v-for="(col, i) of hiddenFields" v-for="(col, i) of hiddenFields"
v-show="isFormula(col) || !isVirtualCol(col) || !isNew || isLinksOrLTAR(col)" v-show="isFormula(col) || !isVirtualCol(col) || !isNew || isLinksOrLTAR(col)"
:key="col.title" :key="col.title"
class="sm:(mt-2) py-2 xs:w-full" class="sm:(mt-2) py-2 <lg:w-full"
:class="`nc-expand-col-${col.title}`" :class="`nc-expand-col-${col.title}`"
:data-testid="`nc-expand-col-${col.title}`" :data-testid="`nc-expand-col-${col.title}`"
> >
<div class="sm:gap-x-6 flex sm:flex-row xs:(flex-col) items-start min-h-10"> <div class="sm:gap-x-6 flex sm:flex-row <lg:(flex-col w-full) items-start min-h-10">
<div class="sm:w-48 xs:w-full scale-110 !h-[35px]"> <div class="sm:w-48 <lg:w-full scale-110 !h-[35px]">
<LazySmartsheetHeaderVirtualCell v-if="isVirtualCol(col)" :column="col" class="nc-expanded-cell-header" /> <LazySmartsheetHeaderVirtualCell v-if="isVirtualCol(col)" :column="col" class="nc-expanded-cell-header" />
<LazySmartsheetHeaderCell v-else class="nc-expanded-cell-header" :column="col" /> <LazySmartsheetHeaderCell v-else class="nc-expanded-cell-header" :column="col" />
@ -771,7 +783,7 @@ export default {
<LazySmartsheetDivDataCell <LazySmartsheetDivDataCell
v-if="col.title" v-if="col.title"
:ref="i ? null : (el: any) => (cellWrapperEl = el)" :ref="i ? null : (el: any) => (cellWrapperEl = el)"
class="bg-white rounded-lg w-80 border-1 overflow-hidden border-gray-200 px-1 sm:min-h-[35px] xs:min-h-13 flex items-center relative" class="bg-white rounded-lg w-80 <lg:w-full border-1 overflow-hidden border-gray-200 px-1 sm:min-h-[35px] xs:min-h-13 flex items-center relative"
> >
<LazySmartsheetVirtualCell <LazySmartsheetVirtualCell
v-if="isVirtualCol(col)" v-if="isVirtualCol(col)"
@ -862,7 +874,7 @@ export default {
</div> </div>
<div <div
v-if="showRightSections" v-if="showRightSections"
class="nc-comments-drawer border-1 relative border-gray-200 w-1/3 max-w-125 bg-gray-50 rounded-xl min-w-0 overflow-hidden h-full xs:hidden" class="nc-comments-drawer border-1 relative border-gray-200 w-1/3 max-w-125 bg-gray-50 rounded-xl min-w-50 overflow-hidden h-full xs:hidden"
:class="{ active: commentsDrawer && isUIAllowed('commentList') }" :class="{ active: commentsDrawer && isUIAllowed('commentList') }"
> >
<SmartsheetExpandedFormComments :loading="isLoading" /> <SmartsheetExpandedFormComments :loading="isLoading" />

53
packages/nc-gui/components/smartsheet/grid/Table.vue

@ -558,7 +558,7 @@ const {
clearSelectedRangeOfCells, clearSelectedRangeOfCells,
makeEditable, makeEditable,
scrollToCell, scrollToCell,
(e: KeyboardEvent) => { async (e: KeyboardEvent) => {
// ignore navigating if picker(Date, Time, DateTime, Year) // ignore navigating if picker(Date, Time, DateTime, Year)
// or single/multi select options is open // or single/multi select options is open
const activePickerOrDropdownEl = document.querySelector( const activePickerOrDropdownEl = document.querySelector(
@ -601,6 +601,42 @@ const {
editEnabled.value = false editEnabled.value = false
return true return true
} }
} else if (e.key === 'Tab') {
if (e.shiftKey && activeCell.row === 0 && activeCell.col === 0 && !paginationDataRef.value?.isFirstPage) {
e.preventDefault()
await resetAndChangePage((paginationDataRef.value?.pageSize ?? 25) - 1, fields.value?.length - 1, -1)
return true
} else if (!e.shiftKey && activeCell.row === dataRef.value.length - 1 && activeCell.col === fields.value?.length - 1) {
e.preventDefault()
if (paginationDataRef.value?.isLastPage && isAddingEmptyRowAllowed.value) {
addEmptyRow()
await resetAndChangePage(dataRef.value.length - 1, 0)
return true
} else if (!paginationDataRef.value?.isLastPage) {
await resetAndChangePage(0, 0, 1)
return true
}
}
} else if (!cmdOrCtrl && !e.shiftKey && e.key === 'ArrowUp') {
if (activeCell.row === 0 && !paginationDataRef.value?.isFirstPage) {
e.preventDefault()
await resetAndChangePage((paginationDataRef.value?.pageSize ?? 25) - 1, activeCell.col!, -1)
return true
}
} else if (!cmdOrCtrl && !e.shiftKey && e.key === 'ArrowDown') {
if (activeCell.row === dataRef.value.length - 1) {
e.preventDefault()
if (paginationDataRef.value?.isLastPage && isAddingEmptyRowAllowed.value) {
addEmptyRow()
await resetAndChangePage(dataRef.value.length - 1, activeCell.col!)
return true
} else if (!paginationDataRef.value?.isLastPage) {
await resetAndChangePage(0, activeCell.col!, 1)
return true
}
}
} }
if (cmdOrCtrl) { if (cmdOrCtrl) {
@ -938,6 +974,21 @@ function scrollToCell(row?: number | null, col?: number | null) {
} }
} }
async function resetAndChangePage(row: number, col: number, pageChange?: number) {
clearSelectedRange()
if (pageChange !== undefined && paginationDataRef.value?.page) {
await changePage?.(paginationDataRef.value.page + pageChange)
await nextTick()
makeActive(row, col)
} else {
makeActive(row, col)
await nextTick()
}
scrollToCell?.()
}
const saveOrUpdateRecords = async (args: { metaValue?: TableType; viewMetaValue?: ViewType; data?: any } = {}) => { const saveOrUpdateRecords = async (args: { metaValue?: TableType; viewMetaValue?: ViewType; data?: any } = {}) => {
let index = -1 let index = -1
for (const currentRow of args.data || dataRef.value) { for (const currentRow of args.data || dataRef.value) {

12
packages/nc-gui/components/smartsheet/toolbar/RowHeight.vue

@ -1,4 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { OrgUserRoles, ProjectRoles, extractRolesObj } from 'nocodb-sdk'
import type { GridType } from 'nocodb-sdk' import type { GridType } from 'nocodb-sdk'
import { ActiveViewInj, IsLockedInj, iconMap, inject, ref, storeToRefs, useMenuCloseOnEsc, useUndoRedo } from '#imports' import { ActiveViewInj, IsLockedInj, iconMap, inject, ref, storeToRefs, useMenuCloseOnEsc, useUndoRedo } from '#imports'
@ -14,6 +15,8 @@ const { $api } = useNuxtApp()
const { addUndo, defineViewScope } = useUndoRedo() const { addUndo, defineViewScope } = useUndoRedo()
const { user } = useGlobal()
const open = ref(false) const open = ref(false)
const updateRowHeight = async (rh: number, undo = false) => { const updateRowHeight = async (rh: number, undo = false) => {
@ -35,7 +38,14 @@ const updateRowHeight = async (rh: number, undo = false) => {
} }
try { try {
if (!isPublic.value && !isSharedBase.value) { if (
!isPublic.value &&
!isSharedBase.value &&
!(
extractRolesObj(user.value?.roles ?? {})[ProjectRoles.VIEWER] ||
extractRolesObj(user.value?.roles ?? {})[OrgUserRoles.VIEWER]
)
) {
await $api.dbView.gridUpdate(view.value.id, { await $api.dbView.gridUpdate(view.value.id, {
row_height: rh, row_height: rh,
}) })

81
packages/nc-gui/components/virtual-cell/components/ListChildItems.vue

@ -1,17 +1,14 @@
<script lang="ts" setup> <script lang="ts" setup>
import { type ColumnType, isLinksOrLTAR, isSystemColumn } from 'nocodb-sdk' import { type ColumnType, isLinksOrLTAR, isSystemColumn } from 'nocodb-sdk'
import type { Row } from '#imports'
import InboxIcon from '~icons/nc-icons/inbox'
import { import {
ColumnInj, ColumnInj,
IsFormInj, IsFormInj,
IsPublicInj, IsPublicInj,
ReadonlyInj, ReadonlyInj,
type Row,
computed, computed,
inject, inject,
isPrimary, isPrimary,
onKeyStroke,
ref, ref,
useLTARStoreOrThrow, useLTARStoreOrThrow,
useSmartsheetRowStoreOrThrow, useSmartsheetRowStoreOrThrow,
@ -41,6 +38,8 @@ const injectedColumn = inject(ColumnInj, ref())
const readOnly = inject(ReadonlyInj, ref(false)) const readOnly = inject(ReadonlyInj, ref(false))
const filterQueryRef = ref<HTMLInputElement>()
const { isSharedBase } = storeToRefs(useBase()) const { isSharedBase } = storeToRefs(useBase())
const { const {
@ -92,8 +91,6 @@ const attachmentCol = computedInject(FieldsInj, (_fields) => {
return (relatedTableMeta.value.columns ?? []).filter((col) => isAttachment(col))[0] return (relatedTableMeta.value.columns ?? []).filter((col) => isAttachment(col))[0]
}) })
const isFocused = ref(false)
const fields = computedInject(FieldsInj, (_fields) => { const fields = computedInject(FieldsInj, (_fields) => {
return (relatedTableMeta.value.columns ?? []) return (relatedTableMeta.value.columns ?? [])
.filter((col) => !isSystemColumn(col) && !isPrimary(col) && !isLinksOrLTAR(col) && !isAttachment(col)) .filter((col) => !isSystemColumn(col) && !isPrimary(col) && !isLinksOrLTAR(col) && !isAttachment(col))
@ -129,10 +126,6 @@ watch(expandedFormDlg, () => {
} }
}) })
onKeyStroke('Escape', () => {
vModel.value = false
})
/* /*
to render same number of skeleton as the number of cards to render same number of skeleton as the number of cards
displayed displayed
@ -175,6 +168,40 @@ const linkOrUnLink = (rowRef: Record<string, string>, id: string) => {
linkRow(rowRef, parseInt(id)) linkRow(rowRef, parseInt(id))
} }
} }
watch([filterQueryRef, isDataExist], () => {
if (readOnly.value || isPublic.value ? isDataExist.value : true) {
filterQueryRef.value?.focus()
}
})
const linkedShortcuts = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
vModel.value = false
} else if (e.key === 'ArrowDown') {
e.preventDefault()
try {
e.target?.nextElementSibling?.focus()
} catch (e) {}
} else if (e.key === 'ArrowUp') {
e.preventDefault()
try {
e.target?.previousElementSibling?.focus()
} catch (e) {}
} else if (e.key !== 'Tab' && e.key !== 'Shift' && e.key !== 'Enter' && e.key !== ' ') {
try {
filterQueryRef.value?.focus()
} catch (e) {}
}
}
onMounted(() => {
window.addEventListener('keydown', linkedShortcuts)
})
onUnmounted(() => {
window.removeEventListener('keydown', linkedShortcuts)
})
</script> </script>
<template> <template>
@ -198,11 +225,8 @@ const linkOrUnLink = (rowRef: Record<string, string>, id: string) => {
:display-value="headerDisplayValue" :display-value="headerDisplayValue"
/> />
<div v-if="!isForm" class="flex mt-2 mb-2 items-center gap-2"> <div v-if="!isForm" class="flex mt-2 mb-2 items-center gap-2">
<div <div class="flex items-center border-1 p-1 rounded-md w-full border-gray-200 !focus-within:border-primary">
class="flex items-center border-1 p-1 rounded-md w-full border-gray-200" <MdiMagnify class="w-5 h-5 ml-2 text-gray-500" />
:class="{ '!border-primary': childrenListPagination.query.length !== 0 || isFocused }"
>
<MdiMagnify class="w-5 h-5 ml-2" />
<a-input <a-input
ref="filterQueryRef" ref="filterQueryRef"
v-model:value="childrenListPagination.query" v-model:value="childrenListPagination.query"
@ -210,9 +234,13 @@ const linkOrUnLink = (rowRef: Record<string, string>, id: string) => {
class="w-full !sm:rounded-md xs:min-h-8 !xs:rounded-xl" class="w-full !sm:rounded-md xs:min-h-8 !xs:rounded-xl"
size="small" size="small"
:bordered="false" :bordered="false"
@focus="isFocused = true" @keydown.capture.stop="
@blur="isFocused = false" (e) => {
@keydown.capture.stop if (e.key === 'Escape') {
filterQueryRef?.blur()
}
}
"
@change="childrenListPagination.page = 1" @change="childrenListPagination.page = 1"
> >
</a-input> </a-input>
@ -264,24 +292,27 @@ const linkOrUnLink = (rowRef: Record<string, string>, id: string) => {
:is-linked="childrenList?.list ? isChildrenListLinked[Number.parseInt(id)] : true" :is-linked="childrenList?.list ? isChildrenListLinked[Number.parseInt(id)] : true"
:is-loading="isChildrenListLoading[Number.parseInt(id)]" :is-loading="isChildrenListLoading[Number.parseInt(id)]"
@expand="onClick(refRow)" @expand="onClick(refRow)"
@keydown.space.prevent="linkOrUnLink(refRow, id)"
@keydown.enter.prevent="() => onClick(refRow, id)"
@click="linkOrUnLink(refRow, id)" @click="linkOrUnLink(refRow, id)"
/> />
</template> </template>
</div> </div>
</div> </div>
<div v-else class="pt-1 flex flex-col gap-3 my-auto items-center justify-center text-gray-500"> <div v-else class="pt-1 flex flex-col gap-4 my-auto items-center justify-center text-gray-500 text-center">
<InboxIcon class="w-16 h-16 mx-auto" /> <img src="~assets/img/placeholder/link-records.png" class="!w-[18.5rem] flex-none" />
<p> <div class="text-2xl text-gray-700 font-bold">{{ $t('msg.noLinkedRecords') }}</div>
{{ $t('msg.noRecordsAreLinkedFromTable') }} <div class="text-gray-700">
{{ relatedTableMeta?.title }} {{ $t('msg.clickLinkRecordsToAddLinkFromTable', { tableName: relatedTableMeta?.title }) }}
</p> </div>
<NcButton <NcButton
v-if="!readOnly && childrenListCount < 1" v-if="!readOnly && childrenListCount < 1"
v-e="['c:links:link']" v-e="['c:links:link']"
data-testid="nc-child-list-button-link-to" data-testid="nc-child-list-button-link-to"
@click="emit('attachRecord')" @click="emit('attachRecord')"
> >
<div class="flex items-center gap-1"><MdiPlus /> {{ $t('title.linkMoreRecords') }}</div> <div class="flex items-center gap-1"><MdiPlus /> {{ $t('title.linkRecords') }}</div>
</NcButton> </NcButton>
</div> </div>
</div> </div>

3
packages/nc-gui/components/virtual-cell/components/ListItem.vue

@ -89,7 +89,8 @@ const displayValue = computed(() => {
<template> <template>
<a-card <a-card
class="nc-list-item !border-1 group transition-all !rounded-xl relative !mb-2 !border-gray-200 hover:bg-gray-50" tabindex="0"
class="nc-list-item !outline-brand-500 !border-1 group transition-all !rounded-xl relative !mb-2 !border-gray-200 hover:bg-gray-50"
:class="{ :class="{
'!bg-white': isLoading, '!bg-white': isLoading,
'!border-1': isLinked && !isLoading, '!border-1': isLinked && !isLoading,

56
packages/nc-gui/components/virtual-cell/components/ListItems.vue

@ -1,6 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { RelationTypes, isLinksOrLTAR, isSystemColumn } from 'nocodb-sdk'
import type { ColumnType, LinkToAnotherRecordType } from 'nocodb-sdk' import type { ColumnType, LinkToAnotherRecordType } from 'nocodb-sdk'
import { RelationTypes, isLinksOrLTAR, isSystemColumn } from 'nocodb-sdk'
import InboxIcon from '~icons/nc-icons/inbox' import InboxIcon from '~icons/nc-icons/inbox'
import { import {
ColumnInj, ColumnInj,
@ -8,7 +8,6 @@ import {
SaveRowInj, SaveRowInj,
computed, computed,
inject, inject,
onKeyStroke,
ref, ref,
useLTARStoreOrThrow, useLTARStoreOrThrow,
useSmartsheetRowStoreOrThrow, useSmartsheetRowStoreOrThrow,
@ -27,7 +26,7 @@ const injectedColumn = inject(ColumnInj)
const { isSharedBase } = storeToRefs(useBase()) const { isSharedBase } = storeToRefs(useBase())
const filterQueryRef = ref() const filterQueryRef = ref<HTMLInputElement>()
const { t } = useI18n() const { t } = useI18n()
@ -64,8 +63,6 @@ const isForm = inject(IsFormInj, ref(false))
const saveRow = inject(SaveRowInj, () => {}) const saveRow = inject(SaveRowInj, () => {})
const isFocused = ref(false)
const linkRow = async (row: Record<string, any>, id: number) => { const linkRow = async (row: Record<string, any>, id: number) => {
if (isNew.value) { if (isNew.value) {
addLTARRef(row, injectedColumn?.value as ColumnType) addLTARRef(row, injectedColumn?.value as ColumnType)
@ -171,8 +168,8 @@ watch(expandedFormDlg, () => {
} }
}) })
onKeyStroke('Escape', () => { watch(filterQueryRef, () => {
vModel.value = false filterQueryRef.value?.focus()
}) })
const onClick = (refRow: any, id: string) => { const onClick = (refRow: any, id: string) => {
@ -219,6 +216,34 @@ const onCreatedRecord = (record: any) => {
message.success(msgVNode) message.success(msgVNode)
} }
const linkedShortcuts = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
vModel.value = false
} else if (e.key === 'ArrowDown') {
e.preventDefault()
try {
e.target?.nextElementSibling?.focus()
} catch (e) {}
} else if (e.key === 'ArrowUp') {
e.preventDefault()
try {
e.target?.previousElementSibling?.focus()
} catch (e) {}
} else if (e.key !== 'Tab' && e.key !== 'Shift' && e.key !== 'Enter' && e.key !== ' ') {
try {
filterQueryRef.value?.focus()
} catch (e) {}
}
}
onMounted(() => {
window.addEventListener('keydown', linkedShortcuts)
})
onUnmounted(() => {
window.removeEventListener('keydown', linkedShortcuts)
})
</script> </script>
<template> <template>
@ -240,10 +265,7 @@ const onCreatedRecord = (record: any) => {
:header="$t('activity.addNewLink')" :header="$t('activity.addNewLink')"
/> />
<div class="flex mt-2 mb-2 items-center gap-2"> <div class="flex mt-2 mb-2 items-center gap-2">
<div <div class="flex items-center border-1 p-1 rounded-md w-full border-gray-200 !focus-within:border-primary">
class="flex items-center border-1 p-1 rounded-md w-full border-gray-200"
:class="{ '!border-primary': childrenExcludedListPagination.query.length !== 0 || isFocused }"
>
<MdiMagnify class="w-5 h-5 ml-2 text-gray-500" /> <MdiMagnify class="w-5 h-5 ml-2 text-gray-500" />
<a-input <a-input
ref="filterQueryRef" ref="filterQueryRef"
@ -252,9 +274,13 @@ const onCreatedRecord = (record: any) => {
class="w-full !rounded-md nc-excluded-search xs:min-h-8" class="w-full !rounded-md nc-excluded-search xs:min-h-8"
size="small" size="small"
:bordered="false" :bordered="false"
@focus="isFocused = true" @keydown.capture.stop="
@blur="isFocused = false" (e) => {
@keydown.capture.stop if (e.key === 'Escape') {
filterQueryRef?.blur()
}
}
"
@change="childrenExcludedListPagination.page = 1" @change="childrenExcludedListPagination.page = 1"
> >
</a-input> </a-input>
@ -325,6 +351,8 @@ const onCreatedRecord = (record: any) => {
expandedFormDlg = true expandedFormDlg = true
} }
" "
@keydown.space.prevent="() => onClick(refRow, id)"
@keydown.enter.prevent="() => onClick(refRow, id)"
@click="() => onClick(refRow, id)" @click="() => onClick(refRow, id)"
/> />
</template> </template>

9
packages/nc-gui/components/workspace/CollaboratorsList.vue

@ -152,6 +152,15 @@ onMounted(async () => {
</NcDropdown> </NcDropdown>
</div> </div>
</div> </div>
<div v-if="sortedCollaborators.length === 1" class="pt-12 pb-4 px-2 flex flex-col items-center gap-6 text-center">
<div class="text-2xl text-gray-800 font-bold">
{{ $t('placeholder.inviteYourTeam') }}
</div>
<div class="text-sm text-gray-700">
{{ $t('placeholder.inviteYourTeamLabel') }}
</div>
<img src="~assets/img/placeholder/invite-team.png" class="!w-[30rem] flex-none" />
</div>
</div> </div>
</div> </div>
</div> </div>

18
packages/nc-gui/lang/ar.json

@ -332,6 +332,7 @@
"virtualRelation": "Virtual Relation", "virtualRelation": "Virtual Relation",
"linkMore": "Link More", "linkMore": "Link More",
"linkMoreRecords": "Link more records", "linkMoreRecords": "Link more records",
"linkRecords": "Link Records",
"downloadFile": "Download File", "downloadFile": "Download File",
"renameTable": "Rename Table", "renameTable": "Rename Table",
"renamingTable": "Renaming Table", "renamingTable": "Renaming Table",
@ -398,6 +399,7 @@
"findRowByScanningCode": "Find row by scanning a QR or Barcode", "findRowByScanningCode": "Find row by scanning a QR or Barcode",
"tokenManagement": "Token Management", "tokenManagement": "Token Management",
"addNewToken": "Add new token", "addNewToken": "Add new token",
"createNewToken": "Create new token",
"accountSettings": "Account Settings", "accountSettings": "Account Settings",
"resetPasswordMenu": "Reset Password", "resetPasswordMenu": "Reset Password",
"tokens": "Tokens", "tokens": "Tokens",
@ -665,7 +667,7 @@
"deletedField": "Deleted field", "deletedField": "Deleted field",
"incompleteConfiguration": "Incomplete configuration", "incompleteConfiguration": "Incomplete configuration",
"selectField": "Select a field", "selectField": "Select a field",
"selectFieldLabel": "Make changes to field properties by selecting a field from the list" "selectFieldLabel": "Begin by selecting a field to customise its properties and structure."
} }
}, },
"activity": { "activity": {
@ -959,7 +961,13 @@
"decimal7": "1.0000000", "decimal7": "1.0000000",
"decimal8": "1.00000000", "decimal8": "1.00000000",
"value": "Value", "value": "Value",
"key": "Key" "key": "Key",
"createTable": "Create your First Table!",
"createTableLabel": "Create your first table effortlessly, from scratch, or by importing/connecting to an external database.",
"noTokenCreated": "No API Tokens created",
"noTokenCreatedLabel": "Begin by creating API tokens to unlock advanced functionalities.",
"inviteYourTeam": "Invite your team",
"inviteYourTeamLabel": "Streamline collaboration and productivity with your team – start by inviting them to join your workspace."
}, },
"msg": { "msg": {
"clickToCopyFieldId": "Click to copy Field Id", "clickToCopyFieldId": "Click to copy Field Id",
@ -1044,8 +1052,9 @@
"tooltip_desc": "A single record from table ", "tooltip_desc": "A single record from table ",
"tooltip_desc2": " can be linked with a single record from table " "tooltip_desc2": " can be linked with a single record from table "
}, },
"noRecordsAreLinkedFromTable": "No records are linked from table", "clickLinkRecordsToAddLinkFromTable": "Click 'Link Records' to begin associating data with '{tableName}'.",
"noRecordsLinked": "No records linked", "noRecordsLinked": "No records linked",
"noLinkedRecords": "No linked records",
"recordsLinked": "records linked", "recordsLinked": "records linked",
"acceptOnlyValid": "Accepts only", "acceptOnlyValid": "Accepts only",
"apiTokenCreate": "Create personal API tokens to use in automation or external apps.", "apiTokenCreate": "Create personal API tokens to use in automation or external apps.",
@ -1053,8 +1062,7 @@
"selectFieldToGroup": "Select Field to Group", "selectFieldToGroup": "Select Field to Group",
"thereAreNoRecordsInTable": "There are no records in table", "thereAreNoRecordsInTable": "There are no records in table",
"createWebhookMsg1": "Get started with web-hooks!", "createWebhookMsg1": "Get started with web-hooks!",
"createWebhookMsg2": "Create web-hooks to power you automations,", "createWebhookMsg2": "Power your automations. Get notified as soon as there are changes in your data",
"createWebhookMsg3": "Get notified as soon as there are changes in your data",
"areYouSureUWantTo": "Are you sure you want to delete the following", "areYouSureUWantTo": "Are you sure you want to delete the following",
"areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following", "areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following",
"idColumnRequired": "ID field is required, you can rename this later if required.", "idColumnRequired": "ID field is required, you can rename this later if required.",

18
packages/nc-gui/lang/bn_IN.json

@ -332,6 +332,7 @@
"virtualRelation": "Virtual Relation", "virtualRelation": "Virtual Relation",
"linkMore": "Link More", "linkMore": "Link More",
"linkMoreRecords": "Link more records", "linkMoreRecords": "Link more records",
"linkRecords": "Link Records",
"downloadFile": "Download File", "downloadFile": "Download File",
"renameTable": "Rename Table", "renameTable": "Rename Table",
"renamingTable": "Renaming Table", "renamingTable": "Renaming Table",
@ -398,6 +399,7 @@
"findRowByScanningCode": "Find row by scanning a QR or Barcode", "findRowByScanningCode": "Find row by scanning a QR or Barcode",
"tokenManagement": "Token Management", "tokenManagement": "Token Management",
"addNewToken": "Add new token", "addNewToken": "Add new token",
"createNewToken": "Create new token",
"accountSettings": "Account Settings", "accountSettings": "Account Settings",
"resetPasswordMenu": "Reset Password", "resetPasswordMenu": "Reset Password",
"tokens": "Tokens", "tokens": "Tokens",
@ -665,7 +667,7 @@
"deletedField": "Deleted field", "deletedField": "Deleted field",
"incompleteConfiguration": "Incomplete configuration", "incompleteConfiguration": "Incomplete configuration",
"selectField": "Select a field", "selectField": "Select a field",
"selectFieldLabel": "Make changes to field properties by selecting a field from the list" "selectFieldLabel": "Begin by selecting a field to customise its properties and structure."
} }
}, },
"activity": { "activity": {
@ -959,7 +961,13 @@
"decimal7": "1.0000000", "decimal7": "1.0000000",
"decimal8": "1.00000000", "decimal8": "1.00000000",
"value": "Value", "value": "Value",
"key": "Key" "key": "Key",
"createTable": "Create your First Table!",
"createTableLabel": "Create your first table effortlessly, from scratch, or by importing/connecting to an external database.",
"noTokenCreated": "No API Tokens created",
"noTokenCreatedLabel": "Begin by creating API tokens to unlock advanced functionalities.",
"inviteYourTeam": "Invite your team",
"inviteYourTeamLabel": "Streamline collaboration and productivity with your team – start by inviting them to join your workspace."
}, },
"msg": { "msg": {
"clickToCopyFieldId": "Click to copy Field Id", "clickToCopyFieldId": "Click to copy Field Id",
@ -1044,8 +1052,9 @@
"tooltip_desc": "A single record from table ", "tooltip_desc": "A single record from table ",
"tooltip_desc2": " can be linked with a single record from table " "tooltip_desc2": " can be linked with a single record from table "
}, },
"noRecordsAreLinkedFromTable": "No records are linked from table", "clickLinkRecordsToAddLinkFromTable": "Click 'Link Records' to begin associating data with '{tableName}'.",
"noRecordsLinked": "No records linked", "noRecordsLinked": "No records linked",
"noLinkedRecords": "No linked records",
"recordsLinked": "records linked", "recordsLinked": "records linked",
"acceptOnlyValid": "Accepts only", "acceptOnlyValid": "Accepts only",
"apiTokenCreate": "Create personal API tokens to use in automation or external apps.", "apiTokenCreate": "Create personal API tokens to use in automation or external apps.",
@ -1053,8 +1062,7 @@
"selectFieldToGroup": "Select Field to Group", "selectFieldToGroup": "Select Field to Group",
"thereAreNoRecordsInTable": "There are no records in table", "thereAreNoRecordsInTable": "There are no records in table",
"createWebhookMsg1": "Get started with web-hooks!", "createWebhookMsg1": "Get started with web-hooks!",
"createWebhookMsg2": "Create web-hooks to power you automations,", "createWebhookMsg2": "Power your automations. Get notified as soon as there are changes in your data",
"createWebhookMsg3": "Get notified as soon as there are changes in your data",
"areYouSureUWantTo": "Are you sure you want to delete the following", "areYouSureUWantTo": "Are you sure you want to delete the following",
"areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following", "areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following",
"idColumnRequired": "ID field is required, you can rename this later if required.", "idColumnRequired": "ID field is required, you can rename this later if required.",

18
packages/nc-gui/lang/cs.json

@ -332,6 +332,7 @@
"virtualRelation": "Virtual Relation", "virtualRelation": "Virtual Relation",
"linkMore": "Link More", "linkMore": "Link More",
"linkMoreRecords": "Link more records", "linkMoreRecords": "Link more records",
"linkRecords": "Link Records",
"downloadFile": "Stáhnout soubor", "downloadFile": "Stáhnout soubor",
"renameTable": "Rename Table", "renameTable": "Rename Table",
"renamingTable": "Renaming Table", "renamingTable": "Renaming Table",
@ -398,6 +399,7 @@
"findRowByScanningCode": "Find row by scanning a QR or Barcode", "findRowByScanningCode": "Find row by scanning a QR or Barcode",
"tokenManagement": "Token Management", "tokenManagement": "Token Management",
"addNewToken": "Přidat nový token", "addNewToken": "Přidat nový token",
"createNewToken": "Create new token",
"accountSettings": "Nastavení účtu", "accountSettings": "Nastavení účtu",
"resetPasswordMenu": "Změnit heslo", "resetPasswordMenu": "Změnit heslo",
"tokens": "Tokens", "tokens": "Tokens",
@ -665,7 +667,7 @@
"deletedField": "Deleted field", "deletedField": "Deleted field",
"incompleteConfiguration": "Incomplete configuration", "incompleteConfiguration": "Incomplete configuration",
"selectField": "Select a field", "selectField": "Select a field",
"selectFieldLabel": "Make changes to field properties by selecting a field from the list" "selectFieldLabel": "Begin by selecting a field to customise its properties and structure."
} }
}, },
"activity": { "activity": {
@ -959,7 +961,13 @@
"decimal7": "1.0000000", "decimal7": "1.0000000",
"decimal8": "1.00000000", "decimal8": "1.00000000",
"value": "Value", "value": "Value",
"key": "Key" "key": "Key",
"createTable": "Create your First Table!",
"createTableLabel": "Create your first table effortlessly, from scratch, or by importing/connecting to an external database.",
"noTokenCreated": "No API Tokens created",
"noTokenCreatedLabel": "Begin by creating API tokens to unlock advanced functionalities.",
"inviteYourTeam": "Invite your team",
"inviteYourTeamLabel": "Streamline collaboration and productivity with your team – start by inviting them to join your workspace."
}, },
"msg": { "msg": {
"clickToCopyFieldId": "Click to copy Field Id", "clickToCopyFieldId": "Click to copy Field Id",
@ -1044,8 +1052,9 @@
"tooltip_desc": "A single record from table ", "tooltip_desc": "A single record from table ",
"tooltip_desc2": " can be linked with a single record from table " "tooltip_desc2": " can be linked with a single record from table "
}, },
"noRecordsAreLinkedFromTable": "No records are linked from table", "clickLinkRecordsToAddLinkFromTable": "Click 'Link Records' to begin associating data with '{tableName}'.",
"noRecordsLinked": "No records linked", "noRecordsLinked": "No records linked",
"noLinkedRecords": "No linked records",
"recordsLinked": "records linked", "recordsLinked": "records linked",
"acceptOnlyValid": "Accepts only", "acceptOnlyValid": "Accepts only",
"apiTokenCreate": "Vytvoření osobních API tokenů pro použití při automatizaci nebo v externích aplikacích.", "apiTokenCreate": "Vytvoření osobních API tokenů pro použití při automatizaci nebo v externích aplikacích.",
@ -1053,8 +1062,7 @@
"selectFieldToGroup": "Select Field to Group", "selectFieldToGroup": "Select Field to Group",
"thereAreNoRecordsInTable": "There are no records in table", "thereAreNoRecordsInTable": "There are no records in table",
"createWebhookMsg1": "Get started with web-hooks!", "createWebhookMsg1": "Get started with web-hooks!",
"createWebhookMsg2": "Create web-hooks to power you automations,", "createWebhookMsg2": "Power your automations. Get notified as soon as there are changes in your data",
"createWebhookMsg3": "Get notified as soon as there are changes in your data",
"areYouSureUWantTo": "Are you sure you want to delete the following", "areYouSureUWantTo": "Are you sure you want to delete the following",
"areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following", "areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following",
"idColumnRequired": "ID field is required, you can rename this later if required.", "idColumnRequired": "ID field is required, you can rename this later if required.",

18
packages/nc-gui/lang/da.json

@ -332,6 +332,7 @@
"virtualRelation": "Virtual Relation", "virtualRelation": "Virtual Relation",
"linkMore": "Link More", "linkMore": "Link More",
"linkMoreRecords": "Link more records", "linkMoreRecords": "Link more records",
"linkRecords": "Link Records",
"downloadFile": "Download File", "downloadFile": "Download File",
"renameTable": "Rename Table", "renameTable": "Rename Table",
"renamingTable": "Renaming Table", "renamingTable": "Renaming Table",
@ -398,6 +399,7 @@
"findRowByScanningCode": "Find row by scanning a QR or Barcode", "findRowByScanningCode": "Find row by scanning a QR or Barcode",
"tokenManagement": "Token Management", "tokenManagement": "Token Management",
"addNewToken": "Add new token", "addNewToken": "Add new token",
"createNewToken": "Create new token",
"accountSettings": "Account Settings", "accountSettings": "Account Settings",
"resetPasswordMenu": "Reset Password", "resetPasswordMenu": "Reset Password",
"tokens": "Tokens", "tokens": "Tokens",
@ -665,7 +667,7 @@
"deletedField": "Deleted field", "deletedField": "Deleted field",
"incompleteConfiguration": "Incomplete configuration", "incompleteConfiguration": "Incomplete configuration",
"selectField": "Select a field", "selectField": "Select a field",
"selectFieldLabel": "Make changes to field properties by selecting a field from the list" "selectFieldLabel": "Begin by selecting a field to customise its properties and structure."
} }
}, },
"activity": { "activity": {
@ -959,7 +961,13 @@
"decimal7": "1.0000000", "decimal7": "1.0000000",
"decimal8": "1.00000000", "decimal8": "1.00000000",
"value": "Value", "value": "Value",
"key": "Key" "key": "Key",
"createTable": "Create your First Table!",
"createTableLabel": "Create your first table effortlessly, from scratch, or by importing/connecting to an external database.",
"noTokenCreated": "No API Tokens created",
"noTokenCreatedLabel": "Begin by creating API tokens to unlock advanced functionalities.",
"inviteYourTeam": "Invite your team",
"inviteYourTeamLabel": "Streamline collaboration and productivity with your team – start by inviting them to join your workspace."
}, },
"msg": { "msg": {
"clickToCopyFieldId": "Click to copy Field Id", "clickToCopyFieldId": "Click to copy Field Id",
@ -1044,8 +1052,9 @@
"tooltip_desc": "A single record from table ", "tooltip_desc": "A single record from table ",
"tooltip_desc2": " can be linked with a single record from table " "tooltip_desc2": " can be linked with a single record from table "
}, },
"noRecordsAreLinkedFromTable": "No records are linked from table", "clickLinkRecordsToAddLinkFromTable": "Click 'Link Records' to begin associating data with '{tableName}'.",
"noRecordsLinked": "No records linked", "noRecordsLinked": "No records linked",
"noLinkedRecords": "No linked records",
"recordsLinked": "records linked", "recordsLinked": "records linked",
"acceptOnlyValid": "Accepts only", "acceptOnlyValid": "Accepts only",
"apiTokenCreate": "Create personal API tokens to use in automation or external apps.", "apiTokenCreate": "Create personal API tokens to use in automation or external apps.",
@ -1053,8 +1062,7 @@
"selectFieldToGroup": "Select Field to Group", "selectFieldToGroup": "Select Field to Group",
"thereAreNoRecordsInTable": "There are no records in table", "thereAreNoRecordsInTable": "There are no records in table",
"createWebhookMsg1": "Get started with web-hooks!", "createWebhookMsg1": "Get started with web-hooks!",
"createWebhookMsg2": "Create web-hooks to power you automations,", "createWebhookMsg2": "Power your automations. Get notified as soon as there are changes in your data",
"createWebhookMsg3": "Get notified as soon as there are changes in your data",
"areYouSureUWantTo": "Are you sure you want to delete the following", "areYouSureUWantTo": "Are you sure you want to delete the following",
"areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following", "areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following",
"idColumnRequired": "ID field is required, you can rename this later if required.", "idColumnRequired": "ID field is required, you can rename this later if required.",

18
packages/nc-gui/lang/de.json

@ -332,6 +332,7 @@
"virtualRelation": "Virtual Relation", "virtualRelation": "Virtual Relation",
"linkMore": "Link More", "linkMore": "Link More",
"linkMoreRecords": "Link more records", "linkMoreRecords": "Link more records",
"linkRecords": "Link Records",
"downloadFile": "Download File", "downloadFile": "Download File",
"renameTable": "Rename Table", "renameTable": "Rename Table",
"renamingTable": "Renaming Table", "renamingTable": "Renaming Table",
@ -398,6 +399,7 @@
"findRowByScanningCode": "Zeile durch Scannen eines QR-Codes oder Barcodes finden", "findRowByScanningCode": "Zeile durch Scannen eines QR-Codes oder Barcodes finden",
"tokenManagement": "Token Management", "tokenManagement": "Token Management",
"addNewToken": "Neuen Token hinzufügen", "addNewToken": "Neuen Token hinzufügen",
"createNewToken": "Create new token",
"accountSettings": "Kontoeinstellungen", "accountSettings": "Kontoeinstellungen",
"resetPasswordMenu": "Passwort zurücksetzen", "resetPasswordMenu": "Passwort zurücksetzen",
"tokens": "Tokens", "tokens": "Tokens",
@ -665,7 +667,7 @@
"deletedField": "Deleted field", "deletedField": "Deleted field",
"incompleteConfiguration": "Incomplete configuration", "incompleteConfiguration": "Incomplete configuration",
"selectField": "Select a field", "selectField": "Select a field",
"selectFieldLabel": "Make changes to field properties by selecting a field from the list" "selectFieldLabel": "Begin by selecting a field to customise its properties and structure."
} }
}, },
"activity": { "activity": {
@ -959,7 +961,13 @@
"decimal7": "1.0000000", "decimal7": "1.0000000",
"decimal8": "1.00000000", "decimal8": "1.00000000",
"value": "Wert", "value": "Wert",
"key": "Key" "key": "Key",
"createTable": "Create your First Table!",
"createTableLabel": "Create your first table effortlessly, from scratch, or by importing/connecting to an external database.",
"noTokenCreated": "No API Tokens created",
"noTokenCreatedLabel": "Begin by creating API tokens to unlock advanced functionalities.",
"inviteYourTeam": "Invite your team",
"inviteYourTeamLabel": "Streamline collaboration and productivity with your team – start by inviting them to join your workspace."
}, },
"msg": { "msg": {
"clickToCopyFieldId": "Click to copy Field Id", "clickToCopyFieldId": "Click to copy Field Id",
@ -1044,8 +1052,9 @@
"tooltip_desc": "A single record from table ", "tooltip_desc": "A single record from table ",
"tooltip_desc2": " can be linked with a single record from table " "tooltip_desc2": " can be linked with a single record from table "
}, },
"noRecordsAreLinkedFromTable": "No records are linked from table", "clickLinkRecordsToAddLinkFromTable": "Click 'Link Records' to begin associating data with '{tableName}'.",
"noRecordsLinked": "No records linked", "noRecordsLinked": "No records linked",
"noLinkedRecords": "No linked records",
"recordsLinked": "records linked", "recordsLinked": "records linked",
"acceptOnlyValid": "Accepts only", "acceptOnlyValid": "Accepts only",
"apiTokenCreate": "Personalisierte API Tokens zur Verwendung in Automatisierungen oder externen Apps erstellen.", "apiTokenCreate": "Personalisierte API Tokens zur Verwendung in Automatisierungen oder externen Apps erstellen.",
@ -1053,8 +1062,7 @@
"selectFieldToGroup": "Feld zum Gruppieren auswählen", "selectFieldToGroup": "Feld zum Gruppieren auswählen",
"thereAreNoRecordsInTable": "There are no records in table", "thereAreNoRecordsInTable": "There are no records in table",
"createWebhookMsg1": "Get started with web-hooks!", "createWebhookMsg1": "Get started with web-hooks!",
"createWebhookMsg2": "Create web-hooks to power you automations,", "createWebhookMsg2": "Power your automations. Get notified as soon as there are changes in your data",
"createWebhookMsg3": "Get notified as soon as there are changes in your data",
"areYouSureUWantTo": "Are you sure you want to delete the following", "areYouSureUWantTo": "Are you sure you want to delete the following",
"areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following", "areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following",
"idColumnRequired": "ID field is required, you can rename this later if required.", "idColumnRequired": "ID field is required, you can rename this later if required.",

18
packages/nc-gui/lang/en.json

@ -332,6 +332,7 @@
"virtualRelation": "Virtual Relation", "virtualRelation": "Virtual Relation",
"linkMore": "Link More", "linkMore": "Link More",
"linkMoreRecords": "Link more records", "linkMoreRecords": "Link more records",
"linkRecords": "Link Records",
"downloadFile": "Download File", "downloadFile": "Download File",
"renameTable": "Rename Table", "renameTable": "Rename Table",
"renamingTable": "Renaming Table", "renamingTable": "Renaming Table",
@ -398,6 +399,7 @@
"findRowByScanningCode": "Find record by scanning a QR or Barcode", "findRowByScanningCode": "Find record by scanning a QR or Barcode",
"tokenManagement": "Token Management", "tokenManagement": "Token Management",
"addNewToken": "Add new token", "addNewToken": "Add new token",
"createNewToken": "Create new token",
"accountSettings": "Account Settings", "accountSettings": "Account Settings",
"resetPasswordMenu": "Reset Password", "resetPasswordMenu": "Reset Password",
"tokens": "Tokens", "tokens": "Tokens",
@ -424,7 +426,7 @@
"audience-entityId": "Audience/ Entity ID", "audience-entityId": "Audience/ Entity ID",
"redirectUrl": "Redirect URL", "redirectUrl": "Redirect URL",
"oidc": "OpenID Connect (OIDC)", "oidc": "OpenID Connect (OIDC)",
"saml": "Security Assertion Markup Language (SAML)", "saml": "SAML",
"newProvider": "New Provider", "newProvider": "New Provider",
"generalSettings": "General Settings", "generalSettings": "General Settings",
"ssoSettings": "SSO Settings", "ssoSettings": "SSO Settings",
@ -959,7 +961,13 @@
"decimal7": "1.0000000", "decimal7": "1.0000000",
"decimal8": "1.00000000", "decimal8": "1.00000000",
"value": "Value", "value": "Value",
"key": "Key" "key": "Key",
"createTable": "Create your First Table!",
"createTableLabel": "From scratch or import or connect to external database",
"noTokenCreated": "No API Tokens created",
"noTokenCreatedLabel": "Looks like you haven’t generated any API tokens yet.",
"inviteYourTeam": "Invite your team",
"inviteYourTeamLabel": "Fast track your projects by collaborating on them with your team!"
}, },
"msg": { "msg": {
"clickToCopyFieldId": "Click to copy Field Id", "clickToCopyFieldId": "Click to copy Field Id",
@ -1044,8 +1052,9 @@
"tooltip_desc": "A single record from table ", "tooltip_desc": "A single record from table ",
"tooltip_desc2": " can be linked with a single record from table " "tooltip_desc2": " can be linked with a single record from table "
}, },
"noRecordsAreLinkedFromTable": "No records are linked from table", "clickLinkRecordsToAddLinkFromTable": "Click 'Link Records' to begin associating data with '{tableName}'.",
"noRecordsLinked": "No records linked", "noRecordsLinked": "No records linked",
"noLinkedRecords": "No linked records",
"recordsLinked": "records linked", "recordsLinked": "records linked",
"acceptOnlyValid": "Accepts only", "acceptOnlyValid": "Accepts only",
"apiTokenCreate": "Create personal API tokens to use in automation or external apps.", "apiTokenCreate": "Create personal API tokens to use in automation or external apps.",
@ -1053,8 +1062,7 @@
"selectFieldToGroup": "Select Field to Group", "selectFieldToGroup": "Select Field to Group",
"thereAreNoRecordsInTable": "There are no records in table", "thereAreNoRecordsInTable": "There are no records in table",
"createWebhookMsg1": "Get started with web-hooks!", "createWebhookMsg1": "Get started with web-hooks!",
"createWebhookMsg2": "Create web-hooks to power you automations,", "createWebhookMsg2": "Power you automations. Get notified as soon as there are changes in your data",
"createWebhookMsg3": "Get notified as soon as there are changes in your data",
"areYouSureUWantTo": "Are you sure you want to delete the following", "areYouSureUWantTo": "Are you sure you want to delete the following",
"areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following", "areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following",
"idColumnRequired": "ID field is required, you can rename this later if required.", "idColumnRequired": "ID field is required, you can rename this later if required.",

18
packages/nc-gui/lang/es.json

@ -332,6 +332,7 @@
"virtualRelation": "Relación virtual", "virtualRelation": "Relación virtual",
"linkMore": "Enlace más", "linkMore": "Enlace más",
"linkMoreRecords": "Vincular más registros", "linkMoreRecords": "Vincular más registros",
"linkRecords": "Link Records",
"downloadFile": "Descargar archivo", "downloadFile": "Descargar archivo",
"renameTable": "Renombrar tabla", "renameTable": "Renombrar tabla",
"renamingTable": "Renombrar tabla", "renamingTable": "Renombrar tabla",
@ -398,6 +399,7 @@
"findRowByScanningCode": "Find row by scanning a QR or Barcode", "findRowByScanningCode": "Find row by scanning a QR or Barcode",
"tokenManagement": "Gestión de Token", "tokenManagement": "Gestión de Token",
"addNewToken": "Añadir nuevo token", "addNewToken": "Añadir nuevo token",
"createNewToken": "Create new token",
"accountSettings": "Configuración de la cuenta", "accountSettings": "Configuración de la cuenta",
"resetPasswordMenu": "Restablecer contraseña", "resetPasswordMenu": "Restablecer contraseña",
"tokens": "Tokens", "tokens": "Tokens",
@ -665,7 +667,7 @@
"deletedField": "Campo eliminado", "deletedField": "Campo eliminado",
"incompleteConfiguration": "Configuración incompleta", "incompleteConfiguration": "Configuración incompleta",
"selectField": "Seleccione un campo", "selectField": "Seleccione un campo",
"selectFieldLabel": "Realice cambios en las propiedades de los campos seleccionando un campo de la lista" "selectFieldLabel": "Begin by selecting a field to customise its properties and structure."
} }
}, },
"activity": { "activity": {
@ -959,7 +961,13 @@
"decimal7": "1.0000000", "decimal7": "1.0000000",
"decimal8": "1.00000000", "decimal8": "1.00000000",
"value": "Value", "value": "Value",
"key": "Key" "key": "Key",
"createTable": "Create your First Table!",
"createTableLabel": "Create your first table effortlessly, from scratch, or by importing/connecting to an external database.",
"noTokenCreated": "No API Tokens created",
"noTokenCreatedLabel": "Begin by creating API tokens to unlock advanced functionalities.",
"inviteYourTeam": "Invite your team",
"inviteYourTeamLabel": "Streamline collaboration and productivity with your team – start by inviting them to join your workspace."
}, },
"msg": { "msg": {
"clickToCopyFieldId": "Click to copy Field Id", "clickToCopyFieldId": "Click to copy Field Id",
@ -1044,8 +1052,9 @@
"tooltip_desc": "Un único registro de la tabla ", "tooltip_desc": "Un único registro de la tabla ",
"tooltip_desc2": " puede vincularse con un único registro de la tabla " "tooltip_desc2": " puede vincularse con un único registro de la tabla "
}, },
"noRecordsAreLinkedFromTable": "No hay registros enlazados desde la tabla", "clickLinkRecordsToAddLinkFromTable": "Click 'Link Records' to begin associating data with '{tableName}'.",
"noRecordsLinked": "No hay registros vinculados", "noRecordsLinked": "No hay registros vinculados",
"noLinkedRecords": "No linked records",
"recordsLinked": "registros vinculados", "recordsLinked": "registros vinculados",
"acceptOnlyValid": "Sólo acepta", "acceptOnlyValid": "Sólo acepta",
"apiTokenCreate": "Cree tokens de API personales para utilizarlos en la automatización o en aplicaciones externas.", "apiTokenCreate": "Cree tokens de API personales para utilizarlos en la automatización o en aplicaciones externas.",
@ -1053,8 +1062,7 @@
"selectFieldToGroup": "Seleccione el campo al grupo", "selectFieldToGroup": "Seleccione el campo al grupo",
"thereAreNoRecordsInTable": "No hay registros en la tabla", "thereAreNoRecordsInTable": "No hay registros en la tabla",
"createWebhookMsg1": "¡Empieza con web-hooks!", "createWebhookMsg1": "¡Empieza con web-hooks!",
"createWebhookMsg2": "Crear web-hooks para potenciar tus automatizaciones,", "createWebhookMsg2": "Power your automations. Get notified as soon as there are changes in your data",
"createWebhookMsg3": "Reciba notificaciones en cuanto haya cambios en sus datos",
"areYouSureUWantTo": "¿Realmente quieres eliminar lo siguiente?", "areYouSureUWantTo": "¿Realmente quieres eliminar lo siguiente?",
"areYouSureUWantToDeleteLabel": "¿Estás seguro de que quieres eliminar la etiqueta {deleteLabel} siguiente?", "areYouSureUWantToDeleteLabel": "¿Estás seguro de que quieres eliminar la etiqueta {deleteLabel} siguiente?",
"idColumnRequired": "El campo ID es obligatorio, puede renombrarlo más tarde si es necesario.", "idColumnRequired": "El campo ID es obligatorio, puede renombrarlo más tarde si es necesario.",

18
packages/nc-gui/lang/eu.json

@ -332,6 +332,7 @@
"virtualRelation": "Virtual Relation", "virtualRelation": "Virtual Relation",
"linkMore": "Link More", "linkMore": "Link More",
"linkMoreRecords": "Link more records", "linkMoreRecords": "Link more records",
"linkRecords": "Link Records",
"downloadFile": "Download File", "downloadFile": "Download File",
"renameTable": "Rename Table", "renameTable": "Rename Table",
"renamingTable": "Renaming Table", "renamingTable": "Renaming Table",
@ -398,6 +399,7 @@
"findRowByScanningCode": "Find row by scanning a QR or Barcode", "findRowByScanningCode": "Find row by scanning a QR or Barcode",
"tokenManagement": "Token Management", "tokenManagement": "Token Management",
"addNewToken": "Add new token", "addNewToken": "Add new token",
"createNewToken": "Create new token",
"accountSettings": "Account Settings", "accountSettings": "Account Settings",
"resetPasswordMenu": "Reset Password", "resetPasswordMenu": "Reset Password",
"tokens": "Tokenak", "tokens": "Tokenak",
@ -665,7 +667,7 @@
"deletedField": "Deleted field", "deletedField": "Deleted field",
"incompleteConfiguration": "Incomplete configuration", "incompleteConfiguration": "Incomplete configuration",
"selectField": "Select a field", "selectField": "Select a field",
"selectFieldLabel": "Make changes to field properties by selecting a field from the list" "selectFieldLabel": "Begin by selecting a field to customise its properties and structure."
} }
}, },
"activity": { "activity": {
@ -959,7 +961,13 @@
"decimal7": "1.0000000", "decimal7": "1.0000000",
"decimal8": "1.00000000", "decimal8": "1.00000000",
"value": "Value", "value": "Value",
"key": "Key" "key": "Key",
"createTable": "Create your First Table!",
"createTableLabel": "Create your first table effortlessly, from scratch, or by importing/connecting to an external database.",
"noTokenCreated": "No API Tokens created",
"noTokenCreatedLabel": "Begin by creating API tokens to unlock advanced functionalities.",
"inviteYourTeam": "Invite your team",
"inviteYourTeamLabel": "Streamline collaboration and productivity with your team – start by inviting them to join your workspace."
}, },
"msg": { "msg": {
"clickToCopyFieldId": "Click to copy Field Id", "clickToCopyFieldId": "Click to copy Field Id",
@ -1044,8 +1052,9 @@
"tooltip_desc": "A single record from table ", "tooltip_desc": "A single record from table ",
"tooltip_desc2": " can be linked with a single record from table " "tooltip_desc2": " can be linked with a single record from table "
}, },
"noRecordsAreLinkedFromTable": "No records are linked from table", "clickLinkRecordsToAddLinkFromTable": "Click 'Link Records' to begin associating data with '{tableName}'.",
"noRecordsLinked": "No records linked", "noRecordsLinked": "No records linked",
"noLinkedRecords": "No linked records",
"recordsLinked": "records linked", "recordsLinked": "records linked",
"acceptOnlyValid": "Accepts only", "acceptOnlyValid": "Accepts only",
"apiTokenCreate": "Create personal API tokens to use in automation or external apps.", "apiTokenCreate": "Create personal API tokens to use in automation or external apps.",
@ -1053,8 +1062,7 @@
"selectFieldToGroup": "Select Field to Group", "selectFieldToGroup": "Select Field to Group",
"thereAreNoRecordsInTable": "There are no records in table", "thereAreNoRecordsInTable": "There are no records in table",
"createWebhookMsg1": "Get started with web-hooks!", "createWebhookMsg1": "Get started with web-hooks!",
"createWebhookMsg2": "Create web-hooks to power you automations,", "createWebhookMsg2": "Power your automations. Get notified as soon as there are changes in your data",
"createWebhookMsg3": "Get notified as soon as there are changes in your data",
"areYouSureUWantTo": "Are you sure you want to delete the following", "areYouSureUWantTo": "Are you sure you want to delete the following",
"areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following", "areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following",
"idColumnRequired": "ID field is required, you can rename this later if required.", "idColumnRequired": "ID field is required, you can rename this later if required.",

18
packages/nc-gui/lang/fa.json

@ -332,6 +332,7 @@
"virtualRelation": "Virtual Relation", "virtualRelation": "Virtual Relation",
"linkMore": "Link More", "linkMore": "Link More",
"linkMoreRecords": "Link more records", "linkMoreRecords": "Link more records",
"linkRecords": "Link Records",
"downloadFile": "Download File", "downloadFile": "Download File",
"renameTable": "Rename Table", "renameTable": "Rename Table",
"renamingTable": "Renaming Table", "renamingTable": "Renaming Table",
@ -398,6 +399,7 @@
"findRowByScanningCode": "پیدا کردن ردیف با اسکن کیو آر کد یا بارکد", "findRowByScanningCode": "پیدا کردن ردیف با اسکن کیو آر کد یا بارکد",
"tokenManagement": "Token Management", "tokenManagement": "Token Management",
"addNewToken": "Add new token", "addNewToken": "Add new token",
"createNewToken": "Create new token",
"accountSettings": "Account Settings", "accountSettings": "Account Settings",
"resetPasswordMenu": "Reset Password", "resetPasswordMenu": "Reset Password",
"tokens": "Tokens", "tokens": "Tokens",
@ -665,7 +667,7 @@
"deletedField": "Deleted field", "deletedField": "Deleted field",
"incompleteConfiguration": "Incomplete configuration", "incompleteConfiguration": "Incomplete configuration",
"selectField": "Select a field", "selectField": "Select a field",
"selectFieldLabel": "Make changes to field properties by selecting a field from the list" "selectFieldLabel": "Begin by selecting a field to customise its properties and structure."
} }
}, },
"activity": { "activity": {
@ -959,7 +961,13 @@
"decimal7": "1.0000000", "decimal7": "1.0000000",
"decimal8": "1.00000000", "decimal8": "1.00000000",
"value": "Value", "value": "Value",
"key": "Key" "key": "Key",
"createTable": "Create your First Table!",
"createTableLabel": "Create your first table effortlessly, from scratch, or by importing/connecting to an external database.",
"noTokenCreated": "No API Tokens created",
"noTokenCreatedLabel": "Begin by creating API tokens to unlock advanced functionalities.",
"inviteYourTeam": "Invite your team",
"inviteYourTeamLabel": "Streamline collaboration and productivity with your team – start by inviting them to join your workspace."
}, },
"msg": { "msg": {
"clickToCopyFieldId": "Click to copy Field Id", "clickToCopyFieldId": "Click to copy Field Id",
@ -1044,8 +1052,9 @@
"tooltip_desc": "A single record from table ", "tooltip_desc": "A single record from table ",
"tooltip_desc2": " can be linked with a single record from table " "tooltip_desc2": " can be linked with a single record from table "
}, },
"noRecordsAreLinkedFromTable": "No records are linked from table", "clickLinkRecordsToAddLinkFromTable": "Click 'Link Records' to begin associating data with '{tableName}'.",
"noRecordsLinked": "No records linked", "noRecordsLinked": "No records linked",
"noLinkedRecords": "No linked records",
"recordsLinked": "records linked", "recordsLinked": "records linked",
"acceptOnlyValid": "Accepts only", "acceptOnlyValid": "Accepts only",
"apiTokenCreate": "Create personal API tokens to use in automation or external apps.", "apiTokenCreate": "Create personal API tokens to use in automation or external apps.",
@ -1053,8 +1062,7 @@
"selectFieldToGroup": "Select Field to Group", "selectFieldToGroup": "Select Field to Group",
"thereAreNoRecordsInTable": "There are no records in table", "thereAreNoRecordsInTable": "There are no records in table",
"createWebhookMsg1": "Get started with web-hooks!", "createWebhookMsg1": "Get started with web-hooks!",
"createWebhookMsg2": "Create web-hooks to power you automations,", "createWebhookMsg2": "Power your automations. Get notified as soon as there are changes in your data",
"createWebhookMsg3": "Get notified as soon as there are changes in your data",
"areYouSureUWantTo": "Are you sure you want to delete the following", "areYouSureUWantTo": "Are you sure you want to delete the following",
"areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following", "areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following",
"idColumnRequired": "ID field is required, you can rename this later if required.", "idColumnRequired": "ID field is required, you can rename this later if required.",

18
packages/nc-gui/lang/fi.json

@ -332,6 +332,7 @@
"virtualRelation": "Virtual Relation", "virtualRelation": "Virtual Relation",
"linkMore": "Link More", "linkMore": "Link More",
"linkMoreRecords": "Link more records", "linkMoreRecords": "Link more records",
"linkRecords": "Link Records",
"downloadFile": "Download File", "downloadFile": "Download File",
"renameTable": "Rename Table", "renameTable": "Rename Table",
"renamingTable": "Renaming Table", "renamingTable": "Renaming Table",
@ -398,6 +399,7 @@
"findRowByScanningCode": "Find row by scanning a QR or Barcode", "findRowByScanningCode": "Find row by scanning a QR or Barcode",
"tokenManagement": "Token Management", "tokenManagement": "Token Management",
"addNewToken": "Add new token", "addNewToken": "Add new token",
"createNewToken": "Create new token",
"accountSettings": "Account Settings", "accountSettings": "Account Settings",
"resetPasswordMenu": "Reset Password", "resetPasswordMenu": "Reset Password",
"tokens": "Tokens", "tokens": "Tokens",
@ -665,7 +667,7 @@
"deletedField": "Deleted field", "deletedField": "Deleted field",
"incompleteConfiguration": "Incomplete configuration", "incompleteConfiguration": "Incomplete configuration",
"selectField": "Select a field", "selectField": "Select a field",
"selectFieldLabel": "Make changes to field properties by selecting a field from the list" "selectFieldLabel": "Begin by selecting a field to customise its properties and structure."
} }
}, },
"activity": { "activity": {
@ -959,7 +961,13 @@
"decimal7": "1.0000000", "decimal7": "1.0000000",
"decimal8": "1.00000000", "decimal8": "1.00000000",
"value": "Value", "value": "Value",
"key": "Key" "key": "Key",
"createTable": "Create your First Table!",
"createTableLabel": "Create your first table effortlessly, from scratch, or by importing/connecting to an external database.",
"noTokenCreated": "No API Tokens created",
"noTokenCreatedLabel": "Begin by creating API tokens to unlock advanced functionalities.",
"inviteYourTeam": "Invite your team",
"inviteYourTeamLabel": "Streamline collaboration and productivity with your team – start by inviting them to join your workspace."
}, },
"msg": { "msg": {
"clickToCopyFieldId": "Click to copy Field Id", "clickToCopyFieldId": "Click to copy Field Id",
@ -1044,8 +1052,9 @@
"tooltip_desc": "A single record from table ", "tooltip_desc": "A single record from table ",
"tooltip_desc2": " can be linked with a single record from table " "tooltip_desc2": " can be linked with a single record from table "
}, },
"noRecordsAreLinkedFromTable": "No records are linked from table", "clickLinkRecordsToAddLinkFromTable": "Click 'Link Records' to begin associating data with '{tableName}'.",
"noRecordsLinked": "No records linked", "noRecordsLinked": "No records linked",
"noLinkedRecords": "No linked records",
"recordsLinked": "records linked", "recordsLinked": "records linked",
"acceptOnlyValid": "Accepts only", "acceptOnlyValid": "Accepts only",
"apiTokenCreate": "Create personal API tokens to use in automation or external apps.", "apiTokenCreate": "Create personal API tokens to use in automation or external apps.",
@ -1053,8 +1062,7 @@
"selectFieldToGroup": "Select Field to Group", "selectFieldToGroup": "Select Field to Group",
"thereAreNoRecordsInTable": "There are no records in table", "thereAreNoRecordsInTable": "There are no records in table",
"createWebhookMsg1": "Get started with web-hooks!", "createWebhookMsg1": "Get started with web-hooks!",
"createWebhookMsg2": "Create web-hooks to power you automations,", "createWebhookMsg2": "Power your automations. Get notified as soon as there are changes in your data",
"createWebhookMsg3": "Get notified as soon as there are changes in your data",
"areYouSureUWantTo": "Are you sure you want to delete the following", "areYouSureUWantTo": "Are you sure you want to delete the following",
"areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following", "areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following",
"idColumnRequired": "ID field is required, you can rename this later if required.", "idColumnRequired": "ID field is required, you can rename this later if required.",

210
packages/nc-gui/lang/fr.json

@ -1,45 +1,45 @@
{ {
"dashboards": { "dashboards": {
"create_new_dashboard_project": "Create New Interface", "create_new_dashboard_project": "Créer une nouvelle interface",
"connect_data_sources": "Connecter des sources de données", "connect_data_sources": "Connecter des sources de données",
"alert": "Alert", "alert": "Alerte",
"alert-message": "No databases have been connected. Connect database bases to build interfaces. Skip this step and add databases from the base home page later.", "alert-message": "Aucune base de données n'a été connectée. Connectez les bases de base de données pour construire des interfaces. Ignorez cette étape et ajoutez des bases de données depuis la page d'accueil de base plus tard.",
"select_database_projects_that_you_want_to_link_to_this_dashboard_projects": "Select Database Bases that you want to link to this Interface.", "select_database_projects_that_you_want_to_link_to_this_dashboard_projects": "Sélectionnez les bases de données que vous souhaitez lier à cette interface.",
"create_interface": "Create interface", "create_interface": "Créer une interface",
"project_name": "Base Name", "project_name": "Nom de la base",
"connect": "Connect", "connect": "Connecter",
"buttonActionTypes": { "buttonActionTypes": {
"open_external_url": "Open external link", "open_external_url": "Ouvrir le lien externe",
"delete_record": "Delete record", "delete_record": "Supprimer la ligne",
"update_record": "Update record", "update_record": "Mettre à jour l'enregistrement",
"open_layout": "Open layout" "open_layout": "Ouvrir la mise en page"
}, },
"widgets": { "widgets": {
"static_text": "Texte", "static_text": "Texte",
"chart": "Graphique", "chart": "Graphique",
"table": "Tableau", "table": "Tableau",
"image": "Image", "image": "Image",
"map": "Map", "map": "Carte",
"button": "Bouton", "button": "Bouton",
"number": "Number", "number": "Nombre",
"bar_chart": "Diagramme à barres", "bar_chart": "Diagramme à barres",
"line_chart": "Graphique en courbe", "line_chart": "Graphique en courbe",
"area_chart": "Graphique en aires", "area_chart": "Graphique en aires",
"pie_chart": "Diagramme circulaire", "pie_chart": "Diagramme circulaire",
"donut_chart": "Donut Chart", "donut_chart": "Graphique en anneau",
"scatter_plot": "Nuage de points", "scatter_plot": "Nuage de points",
"bubble_chart": "Graphique à bulles", "bubble_chart": "Graphique à bulles",
"radar_chart": "Radar Chart", "radar_chart": "Carte radar",
"polar_area_chart": "Polar Area Chart", "polar_area_chart": "Carte des zones polaires",
"radial_bar_chart": "Radial Bar Chart", "radial_bar_chart": "Diagramme à barres radiales",
"heatmap_chart": "Heatmap Chart", "heatmap_chart": "Carte thermique",
"treemap_chart": "Treemap Chart", "treemap_chart": "Diagramme d'arborescence",
"box_plot_chart": "Box Plot Chart", "box_plot_chart": "Graphique Boîte à moustache",
"candlestick_chart": "Candlestick Chart" "candlestick_chart": "Graphique en chandeliers"
} }
}, },
"general": { "general": {
"quit": "Quit", "quit": "Quitter",
"home": "Accueil", "home": "Accueil",
"load": "Charger", "load": "Charger",
"open": "Ouvert", "open": "Ouvert",
@ -61,39 +61,39 @@
"changeIcon": "Changer l'icône", "changeIcon": "Changer l'icône",
"save": "Sauvegarder", "save": "Sauvegarder",
"available": "Disponible", "available": "Disponible",
"abort": "Abort", "abort": "Abandonner",
"saving": "Sauvegarde", "saving": "Sauvegarde",
"cancel": "Annuler", "cancel": "Annuler",
"null": "Null", "null": "Nul",
"escape": "Escape", "escape": "Échap",
"hex": "Hex", "hex": "Hexadécimal",
"clear": "Effacer", "clear": "Effacer",
"slack": "Slack", "slack": "Slack",
"comment": "Comment", "comment": "Commentaire",
"microsoftTeams": "Microsoft Teams", "microsoftTeams": "Microsoft Teams",
"discord": "Discord", "discord": "Discord",
"matterMost": "Mattermost", "matterMost": "Mattermost",
"twilio": "Twilio", "twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio", "whatsappTwilio": "WhatsApp Twilio",
"quote": "Quote", "quote": "Citation",
"submit": "Soumettre", "submit": "Soumettre",
"create": "Créer", "create": "Créer",
"createEntity": "Créer {entity}", "createEntity": "Créer {entity}",
"creating": "Création", "creating": "Création",
"creatingEntity": "Création de {entity}", "creatingEntity": "Création de {entity}",
"details": "Détails", "details": "Détails",
"skip": "Skip", "skip": "Ignorer",
"code": "Code", "code": "Code",
"duplicate": "Dupliquer", "duplicate": "Dupliquer",
"duplicating": "Duplicating", "duplicating": "Dupliquer",
"activate": "Activate", "activate": "Activer",
"action": "Action", "action": "Action",
"insert": "Insérer", "insert": "Insérer",
"delete": "Supprimer", "delete": "Supprimer",
"deleteEntity": "Delete {entity}", "deleteEntity": "Supprimer {entity}",
"bulkInsert": "Bulk Insert", "bulkInsert": "Insertion en lot",
"bulkDelete": "Bulk Delete", "bulkDelete": "Supprimer par lot",
"bulkUpdate": "Bulk Update", "bulkUpdate": "Mise à jour par lot",
"deleting": "Suppression", "deleting": "Suppression",
"update": "Mettre à jour", "update": "Mettre à jour",
"rename": "Renommer", "rename": "Renommer",
@ -101,10 +101,10 @@
"reset": "Réinitialiser", "reset": "Réinitialiser",
"install": "Installer", "install": "Installer",
"show": "Montrer", "show": "Montrer",
"access": "Access", "access": "Accès",
"visibility": "Visibilité", "visibility": "Visibilité",
"hide": "Cacher", "hide": "Cacher",
"deprecated": "Deprecated", "deprecated": "Déprécié",
"showAll": "Tout afficher", "showAll": "Tout afficher",
"hideAll": "Tout cacher", "hideAll": "Tout cacher",
"notFound": "Introuvable", "notFound": "Introuvable",
@ -136,14 +136,14 @@
"after": "Après", "after": "Après",
"before": "Avant", "before": "Avant",
"search": "Rechercher", "search": "Rechercher",
"searchIn": "Search In", "searchIn": "Chercher dans",
"notification": "Notification", "notification": "Notification",
"reference": "Référence", "reference": "Référence",
"function": "Fonction", "function": "Fonction",
"confirm": "Confirmer", "confirm": "Confirmer",
"generate": "Générer", "generate": "Générer",
"copy": "Copier", "copy": "Copier",
"are": "are", "are": "sont",
"misc": "Divers", "misc": "Divers",
"lock": "Verrouiller", "lock": "Verrouiller",
"unlock": "Déverrouiller", "unlock": "Déverrouiller",
@ -157,12 +157,12 @@
"groupingField": "Champ de regroupement", "groupingField": "Champ de regroupement",
"insertAfter": "Insérer après", "insertAfter": "Insérer après",
"insertBefore": "Insérer avant", "insertBefore": "Insérer avant",
"insertAbove": "Insert above", "insertAbove": "Insérer au-dessus",
"insertBelow": "Insert below", "insertBelow": "Insérer en-dessous",
"hideField": "Masquer le champ", "hideField": "Masquer le champ",
"sortAsc": "Trier par ordre croissant", "sortAsc": "Trier par ordre croissant",
"sortDesc": "Trier par ordre décroissant", "sortDesc": "Trier par ordre décroissant",
"move": "Move", "move": "Déplacer",
"geoDataField": "Champ de données géographiques (GeoData)", "geoDataField": "Champ de données géographiques (GeoData)",
"type": "Type", "type": "Type",
"name": "Nom", "name": "Nom",
@ -184,15 +184,15 @@
"countDistinct": "Compter Distinct", "countDistinct": "Compter Distinct",
"sumDistinct": "Somme Distinct", "sumDistinct": "Somme Distinct",
"avgDistinct": "Moyenne Distinct", "avgDistinct": "Moyenne Distinct",
"join": "Join", "join": "Rejoindre",
"options": "Options", "options": "Options",
"primaryValue": "Valeur primaire", "primaryValue": "Valeur primaire",
"useSurveyMode": "Utiliser le mode Sondage", "useSurveyMode": "Utiliser le mode Sondage",
"shift": "Maj", "shift": "Maj",
"enter": "Entrée", "enter": "Entrée",
"seconds": "Secondes", "seconds": "Secondes",
"paste": "Paste", "paste": "Coller",
"restore": "Restore" "restore": "Restaurer"
}, },
"objects": { "objects": {
"workspace": "Espace de travail", "workspace": "Espace de travail",
@ -233,8 +233,8 @@
"editor": "Éditeur", "editor": "Éditeur",
"commenter": "Commentateur", "commenter": "Commentateur",
"viewer": "Lecture seule", "viewer": "Lecture seule",
"noaccess": "No Access", "noaccess": "Accès interdit",
"superAdmin": "Super Admin", "superAdmin": "Super administrateur",
"orgLevelCreator": "Créateur au niveau de l'organisation", "orgLevelCreator": "Créateur au niveau de l'organisation",
"orgLevelViewer": "Visualiseur de niveau d'organisation" "orgLevelViewer": "Visualiseur de niveau d'organisation"
}, },
@ -332,6 +332,7 @@
"virtualRelation": "Virtual Relation", "virtualRelation": "Virtual Relation",
"linkMore": "Link More", "linkMore": "Link More",
"linkMoreRecords": "Link more records", "linkMoreRecords": "Link more records",
"linkRecords": "Link Records",
"downloadFile": "Download File", "downloadFile": "Download File",
"renameTable": "Renommer la table", "renameTable": "Renommer la table",
"renamingTable": "Renaming Table", "renamingTable": "Renaming Table",
@ -398,6 +399,7 @@
"findRowByScanningCode": "Find row by scanning a QR or Barcode", "findRowByScanningCode": "Find row by scanning a QR or Barcode",
"tokenManagement": "Gestion des jetons", "tokenManagement": "Gestion des jetons",
"addNewToken": "Ajouter un nouveau jeton", "addNewToken": "Ajouter un nouveau jeton",
"createNewToken": "Create new token",
"accountSettings": "Paramètres du compte", "accountSettings": "Paramètres du compte",
"resetPasswordMenu": "Réinitialiser le mot de passe", "resetPasswordMenu": "Réinitialiser le mot de passe",
"tokens": "Jetons", "tokens": "Jetons",
@ -422,15 +424,15 @@
"cancel": "Cancel", "cancel": "Cancel",
"metadataUrl": "Metadata URL", "metadataUrl": "Metadata URL",
"audience-entityId": "Audience/ Entity ID", "audience-entityId": "Audience/ Entity ID",
"redirectUrl": "Redirect URL", "redirectUrl": "URL de redirection",
"oidc": "OpenID Connect (OIDC)", "oidc": "OpenID Connect (OIDC)",
"saml": "Security Assertion Markup Language (SAML)", "saml": "Langage de balisage d'assertion de sécurité (SAML)",
"newProvider": "New Provider", "newProvider": "Nouveau fournisseur",
"generalSettings": "General Settings", "generalSettings": "Paramètres généraux",
"ssoSettings": "SSO Settings", "ssoSettings": "Paramètres SSO",
"heading1": "Heading 1", "heading1": "Titre 1",
"heading2": "Heading 2", "heading2": "Titre 2",
"heading3": "Heading 3", "heading3": "Titre 3",
"bold": "Gras", "bold": "Gras",
"italic": "Italique", "italic": "Italique",
"underline": "Souligner", "underline": "Souligner",
@ -443,66 +445,66 @@
"noToken": "Aucun jeton", "noToken": "Aucun jeton",
"tokenLimit": "Un seul jeton par utilisateur est autorisé", "tokenLimit": "Un seul jeton par utilisateur est autorisé",
"duplicateAttachment": "Un fichier avec le nom {filename} est déjà attaché", "duplicateAttachment": "Un fichier avec le nom {filename} est déjà attaché",
"viewIdColon": "VIEW ID: {viewId}", "viewIdColon": "ID DE VUE : {viewId}",
"toAddress": "To Address", "toAddress": "Vers l'adresse",
"subject": "Subject", "subject": "Objet",
"body": "Body", "body": "Corps",
"commaSeparatedMobileNumber": "Comma separated Mobile #", "commaSeparatedMobileNumber": "Numéro de portable séparé par des virgules",
"headerName": "Header Name", "headerName": "Nom de l'en-tête",
"icon": "Icon", "icon": "Icône",
"max": "Max", "max": "Maximum",
"enableRichText": "Enable Rich Text", "enableRichText": "Activer le texte enrichi",
"idColon": "Id:", "idColon": "Identifiant:",
"copiedRecordURL": "Copied Record URL", "copiedRecordURL": "URL de l'enregistrement copié",
"copyRecordURL": "Copy Record URL", "copyRecordURL": "Copier l'URL de l'enregistrement",
"duplicateRecord": "Duplicate record", "duplicateRecord": "Dupliquer la ligne",
"binaryEncodingFormat": "Binary encoding format", "binaryEncodingFormat": "Format d'encodage binaire",
"syntax": "Syntax", "syntax": "Syntaxe",
"examples": "Exemples", "examples": "Exemples",
"durationInfo": "A duration of time in minutes or seconds (e.g. 1:23).", "durationInfo": "Une durée en minutes ou en secondes (par exemple 1:23).",
"addHeader": "Add Header", "addHeader": "Ajouter un en-tête",
"enterDefaultUrlOptional": "Entrez l'URL par défaut (facultatif)", "enterDefaultUrlOptional": "Entrez l'URL par défaut (facultatif)",
"negative": "Negative", "negative": "Négatif",
"discard": "Abandonner", "discard": "Abandonner",
"default": "Default", "default": "Par défaut",
"defaultNumberPercent": "Default Number (%)", "defaultNumberPercent": "Nombre par défaut (%)",
"durationFormat": "Duration Format", "durationFormat": "Format de durée",
"dateFormat": "Format de date", "dateFormat": "Format de date",
"timeFormat": "Format d'heure", "timeFormat": "Format d'heure",
"singularLabel": "Libellé au singulier", "singularLabel": "Libellé au singulier",
"pluralLabel": "Libellé au pluriel", "pluralLabel": "Libellé au pluriel",
"optional": "(Facultatif)", "optional": "(Facultatif)",
"clickToMake": "Click to make", "clickToMake": "Cliquez pour faire",
"visibleForRole": "visible for role:", "visibleForRole": "visible pour le rôle :",
"inUI": "in UI Dashboard", "inUI": "accéder au tableau de bord",
"projectSettings": "Base Settings", "projectSettings": "Réglages de base",
"clickToHide": "Cliquez pour masquer", "clickToHide": "Cliquez pour masquer",
"clickToDownload": "Cliquez pour télécharger", "clickToDownload": "Cliquez pour télécharger",
"forRole": "for role", "forRole": "pour le rôle",
"clickToCopyViewID": "Click to copy View ID", "clickToCopyViewID": "Cliquer pour copier l'ID de la vue",
"viewMode": "Mode d'affichage", "viewMode": "Mode d'affichage",
"searchUsers": "Rechercher des utilisateurs", "searchUsers": "Rechercher des utilisateurs",
"superAdmin": "Super Admin", "superAdmin": "Super administrateur",
"allTables": "Toutes les tables", "allTables": "Toutes les tables",
"members": "Membres", "members": "Membres",
"dataSources": "Sources de données", "dataSources": "Sources de données",
"connectDataSource": "Connecter une source de données", "connectDataSource": "Connecter une source de données",
"searchProjects": "Search Bases", "searchProjects": "Base de recherche",
"createdBy": "Créé par", "createdBy": "Créé par",
"viewingAttachmentsOf": "Visualisation des pièces jointes de", "viewingAttachmentsOf": "Visualisation des pièces jointes de",
"readOnly": "Lecture seule", "readOnly": "Lecture seule",
"dropHere": "Déposer ici", "dropHere": "Déposer ici",
"createdOn": "Created On", "createdOn": "Créé le",
"notifyVia": "Notifier via", "notifyVia": "Notifier via",
"projName": "Nom du projet", "projName": "Nom du projet",
"profile": "Profil", "profile": "Profil",
"accountDetails": "Account Details", "accountDetails": "Détails du Compte",
"controlAppearance": "Control your Appearance.", "controlAppearance": "Contrôlez votre apparence.",
"accountEmailID": "Account Email ID", "accountEmailID": "E-mail du compte",
"backToWorkspace": "Retour à l'espace de travail", "backToWorkspace": "Retour à l'espace de travail",
"untitledToken": "Jeton sans titre", "untitledToken": "Jeton sans titre",
"tableName": "Nom du tableau", "tableName": "Nom du tableau",
"dashboardName": "Dashboard name", "dashboardName": "Nom du tableau de bord",
"createView": "Créer une vue", "createView": "Créer une vue",
"creatingView": "Création de la vue", "creatingView": "Création de la vue",
"duplicateView": "Dupliquer la vue", "duplicateView": "Dupliquer la vue",
@ -524,7 +526,7 @@
"databaseType": "Écrire dans la base de données", "databaseType": "Écrire dans la base de données",
"lengthValue": "Longueur / valeur", "lengthValue": "Longueur / valeur",
"dbType": "Type de base de données", "dbType": "Type de base de données",
"servername": "servername / hostAddr", "servername": "nom du serveur / adresse de l'hôte",
"sqliteFile": "Fichier SQLite", "sqliteFile": "Fichier SQLite",
"hostAddress": "Adresse de l'hôte", "hostAddress": "Adresse de l'hôte",
"port": "Numéro de port", "port": "Numéro de port",
@ -597,9 +599,9 @@
"childTable": "Table enfant", "childTable": "Table enfant",
"childColumn": "Colonne enfant", "childColumn": "Colonne enfant",
"childField": "Colonne enfant", "childField": "Colonne enfant",
"joinCloudForFree": "Join Cloud for Free", "joinCloudForFree": "Rejoignez gratuitement le Cloud",
"linkToAnotherRecord": "Lien vers un autre enregistrement", "linkToAnotherRecord": "Lien vers un autre enregistrement",
"links": "Links", "links": "Liens",
"onUpdate": "Mise à jour en cours", "onUpdate": "Mise à jour en cours",
"onDelete": "Suppression en cours", "onDelete": "Suppression en cours",
"account": "Compte", "account": "Compte",
@ -610,7 +612,7 @@
"requestDataSource": "Demandez une source de données dont vous avez besoin ?", "requestDataSource": "Demandez une source de données dont vous avez besoin ?",
"apiKey": "Clé d'API", "apiKey": "Clé d'API",
"personalAccessToken": "Jeton d'accès personnel", "personalAccessToken": "Jeton d'accès personnel",
"sharedBaseUrl": "Shared Base URL", "sharedBaseUrl": "URL de la base partagée",
"importData": "Importer des données", "importData": "Importer des données",
"importSecondaryViews": "Importer des vues secondaires", "importSecondaryViews": "Importer des vues secondaires",
"importRollupColumns": "Importer des colonnes de rollup", "importRollupColumns": "Importer des colonnes de rollup",
@ -643,9 +645,9 @@
"nextRow": "Rang suivant", "nextRow": "Rang suivant",
"prevRow": "Rang précédent", "prevRow": "Rang précédent",
"addRowGrid": "Ajouter manuellement des données dans la vue Grille", "addRowGrid": "Ajouter manuellement des données dans la vue Grille",
"addRowForm": "Enter record data through a form", "addRowForm": "Entrez les données de l'enregistrement via un formulaire",
"noAccess": "No access", "noAccess": "Accès interdit",
"restApis": "Rest APIs", "restApis": "API REST",
"apis": "APIs", "apis": "APIs",
"includeData": "Include Data", "includeData": "Include Data",
"includeView": "Include View", "includeView": "Include View",
@ -665,7 +667,7 @@
"deletedField": "Deleted field", "deletedField": "Deleted field",
"incompleteConfiguration": "Incomplete configuration", "incompleteConfiguration": "Incomplete configuration",
"selectField": "Select a field", "selectField": "Select a field",
"selectFieldLabel": "Make changes to field properties by selecting a field from the list" "selectFieldLabel": "Begin by selecting a field to customise its properties and structure."
} }
}, },
"activity": { "activity": {
@ -959,7 +961,13 @@
"decimal7": "1,0000000", "decimal7": "1,0000000",
"decimal8": "1,00000000", "decimal8": "1,00000000",
"value": "Valeur", "value": "Valeur",
"key": "Clé" "key": "Clé",
"createTable": "Create your First Table!",
"createTableLabel": "Create your first table effortlessly, from scratch, or by importing/connecting to an external database.",
"noTokenCreated": "No API Tokens created",
"noTokenCreatedLabel": "Begin by creating API tokens to unlock advanced functionalities.",
"inviteYourTeam": "Invite your team",
"inviteYourTeamLabel": "Streamline collaboration and productivity with your team – start by inviting them to join your workspace."
}, },
"msg": { "msg": {
"clickToCopyFieldId": "Cliquer pour copier l'ID du champ", "clickToCopyFieldId": "Cliquer pour copier l'ID du champ",
@ -1044,8 +1052,9 @@
"tooltip_desc": "A single record from table ", "tooltip_desc": "A single record from table ",
"tooltip_desc2": " can be linked with a single record from table " "tooltip_desc2": " can be linked with a single record from table "
}, },
"noRecordsAreLinkedFromTable": "No records are linked from table", "clickLinkRecordsToAddLinkFromTable": "Click 'Link Records' to begin associating data with '{tableName}'.",
"noRecordsLinked": "No records linked", "noRecordsLinked": "No records linked",
"noLinkedRecords": "No linked records",
"recordsLinked": "records linked", "recordsLinked": "records linked",
"acceptOnlyValid": "Accepts only", "acceptOnlyValid": "Accepts only",
"apiTokenCreate": "Create personal API tokens to use in automation or external apps.", "apiTokenCreate": "Create personal API tokens to use in automation or external apps.",
@ -1053,8 +1062,7 @@
"selectFieldToGroup": "Select Field to Group", "selectFieldToGroup": "Select Field to Group",
"thereAreNoRecordsInTable": "Il n'y a aucun enregistrement dans la table", "thereAreNoRecordsInTable": "Il n'y a aucun enregistrement dans la table",
"createWebhookMsg1": "Commencez à utiliser les web-hooks !", "createWebhookMsg1": "Commencez à utiliser les web-hooks !",
"createWebhookMsg2": "Créez des Web-hooks pour alimenter vos automatisations,", "createWebhookMsg2": "Power your automations. Get notified as soon as there are changes in your data",
"createWebhookMsg3": "Soyez informé dès que des modifications sont apportées à vos données",
"areYouSureUWantTo": "Êtes-vous sûr de vouloir supprimer les éléments suivants", "areYouSureUWantTo": "Êtes-vous sûr de vouloir supprimer les éléments suivants",
"areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following", "areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following",
"idColumnRequired": "ID field is required, you can rename this later if required.", "idColumnRequired": "ID field is required, you can rename this later if required.",

18
packages/nc-gui/lang/he.json

@ -332,6 +332,7 @@
"virtualRelation": "Virtual Relation", "virtualRelation": "Virtual Relation",
"linkMore": "Link More", "linkMore": "Link More",
"linkMoreRecords": "Link more records", "linkMoreRecords": "Link more records",
"linkRecords": "Link Records",
"downloadFile": "Download File", "downloadFile": "Download File",
"renameTable": "Rename Table", "renameTable": "Rename Table",
"renamingTable": "Renaming Table", "renamingTable": "Renaming Table",
@ -398,6 +399,7 @@
"findRowByScanningCode": "Find row by scanning a QR or Barcode", "findRowByScanningCode": "Find row by scanning a QR or Barcode",
"tokenManagement": "Token Management", "tokenManagement": "Token Management",
"addNewToken": "Add new token", "addNewToken": "Add new token",
"createNewToken": "Create new token",
"accountSettings": "Account Settings", "accountSettings": "Account Settings",
"resetPasswordMenu": "Reset Password", "resetPasswordMenu": "Reset Password",
"tokens": "Tokens", "tokens": "Tokens",
@ -665,7 +667,7 @@
"deletedField": "Deleted field", "deletedField": "Deleted field",
"incompleteConfiguration": "Incomplete configuration", "incompleteConfiguration": "Incomplete configuration",
"selectField": "Select a field", "selectField": "Select a field",
"selectFieldLabel": "Make changes to field properties by selecting a field from the list" "selectFieldLabel": "Begin by selecting a field to customise its properties and structure."
} }
}, },
"activity": { "activity": {
@ -959,7 +961,13 @@
"decimal7": "1.0000000", "decimal7": "1.0000000",
"decimal8": "1.00000000", "decimal8": "1.00000000",
"value": "Value", "value": "Value",
"key": "Key" "key": "Key",
"createTable": "Create your First Table!",
"createTableLabel": "Create your first table effortlessly, from scratch, or by importing/connecting to an external database.",
"noTokenCreated": "No API Tokens created",
"noTokenCreatedLabel": "Begin by creating API tokens to unlock advanced functionalities.",
"inviteYourTeam": "Invite your team",
"inviteYourTeamLabel": "Streamline collaboration and productivity with your team – start by inviting them to join your workspace."
}, },
"msg": { "msg": {
"clickToCopyFieldId": "Click to copy Field Id", "clickToCopyFieldId": "Click to copy Field Id",
@ -1044,8 +1052,9 @@
"tooltip_desc": "A single record from table ", "tooltip_desc": "A single record from table ",
"tooltip_desc2": " can be linked with a single record from table " "tooltip_desc2": " can be linked with a single record from table "
}, },
"noRecordsAreLinkedFromTable": "No records are linked from table", "clickLinkRecordsToAddLinkFromTable": "Click 'Link Records' to begin associating data with '{tableName}'.",
"noRecordsLinked": "No records linked", "noRecordsLinked": "No records linked",
"noLinkedRecords": "No linked records",
"recordsLinked": "records linked", "recordsLinked": "records linked",
"acceptOnlyValid": "Accepts only", "acceptOnlyValid": "Accepts only",
"apiTokenCreate": "Create personal API tokens to use in automation or external apps.", "apiTokenCreate": "Create personal API tokens to use in automation or external apps.",
@ -1053,8 +1062,7 @@
"selectFieldToGroup": "Select Field to Group", "selectFieldToGroup": "Select Field to Group",
"thereAreNoRecordsInTable": "There are no records in table", "thereAreNoRecordsInTable": "There are no records in table",
"createWebhookMsg1": "Get started with web-hooks!", "createWebhookMsg1": "Get started with web-hooks!",
"createWebhookMsg2": "Create web-hooks to power you automations,", "createWebhookMsg2": "Power your automations. Get notified as soon as there are changes in your data",
"createWebhookMsg3": "Get notified as soon as there are changes in your data",
"areYouSureUWantTo": "Are you sure you want to delete the following", "areYouSureUWantTo": "Are you sure you want to delete the following",
"areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following", "areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following",
"idColumnRequired": "ID field is required, you can rename this later if required.", "idColumnRequired": "ID field is required, you can rename this later if required.",

18
packages/nc-gui/lang/hi.json

@ -332,6 +332,7 @@
"virtualRelation": "Virtual Relation", "virtualRelation": "Virtual Relation",
"linkMore": "Link More", "linkMore": "Link More",
"linkMoreRecords": "Link more records", "linkMoreRecords": "Link more records",
"linkRecords": "Link Records",
"downloadFile": "Download File", "downloadFile": "Download File",
"renameTable": "Rename Table", "renameTable": "Rename Table",
"renamingTable": "Renaming Table", "renamingTable": "Renaming Table",
@ -398,6 +399,7 @@
"findRowByScanningCode": "Find row by scanning a QR or Barcode", "findRowByScanningCode": "Find row by scanning a QR or Barcode",
"tokenManagement": "Token Management", "tokenManagement": "Token Management",
"addNewToken": "Add new token", "addNewToken": "Add new token",
"createNewToken": "Create new token",
"accountSettings": "Account Settings", "accountSettings": "Account Settings",
"resetPasswordMenu": "Reset Password", "resetPasswordMenu": "Reset Password",
"tokens": "Tokens", "tokens": "Tokens",
@ -665,7 +667,7 @@
"deletedField": "Deleted field", "deletedField": "Deleted field",
"incompleteConfiguration": "Incomplete configuration", "incompleteConfiguration": "Incomplete configuration",
"selectField": "Select a field", "selectField": "Select a field",
"selectFieldLabel": "Make changes to field properties by selecting a field from the list" "selectFieldLabel": "Begin by selecting a field to customise its properties and structure."
} }
}, },
"activity": { "activity": {
@ -959,7 +961,13 @@
"decimal7": "1.0000000", "decimal7": "1.0000000",
"decimal8": "1.00000000", "decimal8": "1.00000000",
"value": "Value", "value": "Value",
"key": "Key" "key": "Key",
"createTable": "Create your First Table!",
"createTableLabel": "Create your first table effortlessly, from scratch, or by importing/connecting to an external database.",
"noTokenCreated": "No API Tokens created",
"noTokenCreatedLabel": "Begin by creating API tokens to unlock advanced functionalities.",
"inviteYourTeam": "Invite your team",
"inviteYourTeamLabel": "Streamline collaboration and productivity with your team – start by inviting them to join your workspace."
}, },
"msg": { "msg": {
"clickToCopyFieldId": "Click to copy Field Id", "clickToCopyFieldId": "Click to copy Field Id",
@ -1044,8 +1052,9 @@
"tooltip_desc": "A single record from table ", "tooltip_desc": "A single record from table ",
"tooltip_desc2": " can be linked with a single record from table " "tooltip_desc2": " can be linked with a single record from table "
}, },
"noRecordsAreLinkedFromTable": "No records are linked from table", "clickLinkRecordsToAddLinkFromTable": "Click 'Link Records' to begin associating data with '{tableName}'.",
"noRecordsLinked": "No records linked", "noRecordsLinked": "No records linked",
"noLinkedRecords": "No linked records",
"recordsLinked": "records linked", "recordsLinked": "records linked",
"acceptOnlyValid": "Accepts only", "acceptOnlyValid": "Accepts only",
"apiTokenCreate": "Create personal API tokens to use in automation or external apps.", "apiTokenCreate": "Create personal API tokens to use in automation or external apps.",
@ -1053,8 +1062,7 @@
"selectFieldToGroup": "Select Field to Group", "selectFieldToGroup": "Select Field to Group",
"thereAreNoRecordsInTable": "There are no records in table", "thereAreNoRecordsInTable": "There are no records in table",
"createWebhookMsg1": "Get started with web-hooks!", "createWebhookMsg1": "Get started with web-hooks!",
"createWebhookMsg2": "Create web-hooks to power you automations,", "createWebhookMsg2": "Power your automations. Get notified as soon as there are changes in your data",
"createWebhookMsg3": "Get notified as soon as there are changes in your data",
"areYouSureUWantTo": "Are you sure you want to delete the following", "areYouSureUWantTo": "Are you sure you want to delete the following",
"areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following", "areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following",
"idColumnRequired": "ID field is required, you can rename this later if required.", "idColumnRequired": "ID field is required, you can rename this later if required.",

18
packages/nc-gui/lang/hr.json

@ -332,6 +332,7 @@
"virtualRelation": "Virtual Relation", "virtualRelation": "Virtual Relation",
"linkMore": "Link More", "linkMore": "Link More",
"linkMoreRecords": "Link more records", "linkMoreRecords": "Link more records",
"linkRecords": "Link Records",
"downloadFile": "Download File", "downloadFile": "Download File",
"renameTable": "Rename Table", "renameTable": "Rename Table",
"renamingTable": "Renaming Table", "renamingTable": "Renaming Table",
@ -398,6 +399,7 @@
"findRowByScanningCode": "Find row by scanning a QR or Barcode", "findRowByScanningCode": "Find row by scanning a QR or Barcode",
"tokenManagement": "Token Management", "tokenManagement": "Token Management",
"addNewToken": "Add new token", "addNewToken": "Add new token",
"createNewToken": "Create new token",
"accountSettings": "Account Settings", "accountSettings": "Account Settings",
"resetPasswordMenu": "Reset Password", "resetPasswordMenu": "Reset Password",
"tokens": "Tokens", "tokens": "Tokens",
@ -665,7 +667,7 @@
"deletedField": "Deleted field", "deletedField": "Deleted field",
"incompleteConfiguration": "Incomplete configuration", "incompleteConfiguration": "Incomplete configuration",
"selectField": "Select a field", "selectField": "Select a field",
"selectFieldLabel": "Make changes to field properties by selecting a field from the list" "selectFieldLabel": "Begin by selecting a field to customise its properties and structure."
} }
}, },
"activity": { "activity": {
@ -959,7 +961,13 @@
"decimal7": "1.0000000", "decimal7": "1.0000000",
"decimal8": "1.00000000", "decimal8": "1.00000000",
"value": "Value", "value": "Value",
"key": "Key" "key": "Key",
"createTable": "Create your First Table!",
"createTableLabel": "Create your first table effortlessly, from scratch, or by importing/connecting to an external database.",
"noTokenCreated": "No API Tokens created",
"noTokenCreatedLabel": "Begin by creating API tokens to unlock advanced functionalities.",
"inviteYourTeam": "Invite your team",
"inviteYourTeamLabel": "Streamline collaboration and productivity with your team – start by inviting them to join your workspace."
}, },
"msg": { "msg": {
"clickToCopyFieldId": "Click to copy Field Id", "clickToCopyFieldId": "Click to copy Field Id",
@ -1044,8 +1052,9 @@
"tooltip_desc": "A single record from table ", "tooltip_desc": "A single record from table ",
"tooltip_desc2": " can be linked with a single record from table " "tooltip_desc2": " can be linked with a single record from table "
}, },
"noRecordsAreLinkedFromTable": "No records are linked from table", "clickLinkRecordsToAddLinkFromTable": "Click 'Link Records' to begin associating data with '{tableName}'.",
"noRecordsLinked": "No records linked", "noRecordsLinked": "No records linked",
"noLinkedRecords": "No linked records",
"recordsLinked": "records linked", "recordsLinked": "records linked",
"acceptOnlyValid": "Accepts only", "acceptOnlyValid": "Accepts only",
"apiTokenCreate": "Create personal API tokens to use in automation or external apps.", "apiTokenCreate": "Create personal API tokens to use in automation or external apps.",
@ -1053,8 +1062,7 @@
"selectFieldToGroup": "Select Field to Group", "selectFieldToGroup": "Select Field to Group",
"thereAreNoRecordsInTable": "There are no records in table", "thereAreNoRecordsInTable": "There are no records in table",
"createWebhookMsg1": "Get started with web-hooks!", "createWebhookMsg1": "Get started with web-hooks!",
"createWebhookMsg2": "Create web-hooks to power you automations,", "createWebhookMsg2": "Power your automations. Get notified as soon as there are changes in your data",
"createWebhookMsg3": "Get notified as soon as there are changes in your data",
"areYouSureUWantTo": "Are you sure you want to delete the following", "areYouSureUWantTo": "Are you sure you want to delete the following",
"areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following", "areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following",
"idColumnRequired": "ID field is required, you can rename this later if required.", "idColumnRequired": "ID field is required, you can rename this later if required.",

18
packages/nc-gui/lang/id.json

@ -332,6 +332,7 @@
"virtualRelation": "Virtual Relation", "virtualRelation": "Virtual Relation",
"linkMore": "Link More", "linkMore": "Link More",
"linkMoreRecords": "Link more records", "linkMoreRecords": "Link more records",
"linkRecords": "Link Records",
"downloadFile": "Download File", "downloadFile": "Download File",
"renameTable": "Rename Table", "renameTable": "Rename Table",
"renamingTable": "Renaming Table", "renamingTable": "Renaming Table",
@ -398,6 +399,7 @@
"findRowByScanningCode": "Find row by scanning a QR or Barcode", "findRowByScanningCode": "Find row by scanning a QR or Barcode",
"tokenManagement": "Token Management", "tokenManagement": "Token Management",
"addNewToken": "Add new token", "addNewToken": "Add new token",
"createNewToken": "Create new token",
"accountSettings": "Account Settings", "accountSettings": "Account Settings",
"resetPasswordMenu": "Reset Password", "resetPasswordMenu": "Reset Password",
"tokens": "Tokens", "tokens": "Tokens",
@ -665,7 +667,7 @@
"deletedField": "Deleted field", "deletedField": "Deleted field",
"incompleteConfiguration": "Incomplete configuration", "incompleteConfiguration": "Incomplete configuration",
"selectField": "Select a field", "selectField": "Select a field",
"selectFieldLabel": "Make changes to field properties by selecting a field from the list" "selectFieldLabel": "Begin by selecting a field to customise its properties and structure."
} }
}, },
"activity": { "activity": {
@ -959,7 +961,13 @@
"decimal7": "1.0000000", "decimal7": "1.0000000",
"decimal8": "1.00000000", "decimal8": "1.00000000",
"value": "Value", "value": "Value",
"key": "Key" "key": "Key",
"createTable": "Create your First Table!",
"createTableLabel": "Create your first table effortlessly, from scratch, or by importing/connecting to an external database.",
"noTokenCreated": "No API Tokens created",
"noTokenCreatedLabel": "Begin by creating API tokens to unlock advanced functionalities.",
"inviteYourTeam": "Invite your team",
"inviteYourTeamLabel": "Streamline collaboration and productivity with your team – start by inviting them to join your workspace."
}, },
"msg": { "msg": {
"clickToCopyFieldId": "Click to copy Field Id", "clickToCopyFieldId": "Click to copy Field Id",
@ -1044,8 +1052,9 @@
"tooltip_desc": "A single record from table ", "tooltip_desc": "A single record from table ",
"tooltip_desc2": " can be linked with a single record from table " "tooltip_desc2": " can be linked with a single record from table "
}, },
"noRecordsAreLinkedFromTable": "No records are linked from table", "clickLinkRecordsToAddLinkFromTable": "Click 'Link Records' to begin associating data with '{tableName}'.",
"noRecordsLinked": "No records linked", "noRecordsLinked": "No records linked",
"noLinkedRecords": "No linked records",
"recordsLinked": "records linked", "recordsLinked": "records linked",
"acceptOnlyValid": "Accepts only", "acceptOnlyValid": "Accepts only",
"apiTokenCreate": "Create personal API tokens to use in automation or external apps.", "apiTokenCreate": "Create personal API tokens to use in automation or external apps.",
@ -1053,8 +1062,7 @@
"selectFieldToGroup": "Select Field to Group", "selectFieldToGroup": "Select Field to Group",
"thereAreNoRecordsInTable": "There are no records in table", "thereAreNoRecordsInTable": "There are no records in table",
"createWebhookMsg1": "Get started with web-hooks!", "createWebhookMsg1": "Get started with web-hooks!",
"createWebhookMsg2": "Create web-hooks to power you automations,", "createWebhookMsg2": "Power your automations. Get notified as soon as there are changes in your data",
"createWebhookMsg3": "Get notified as soon as there are changes in your data",
"areYouSureUWantTo": "Are you sure you want to delete the following", "areYouSureUWantTo": "Are you sure you want to delete the following",
"areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following", "areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following",
"idColumnRequired": "ID field is required, you can rename this later if required.", "idColumnRequired": "ID field is required, you can rename this later if required.",

18
packages/nc-gui/lang/it.json

@ -332,6 +332,7 @@
"virtualRelation": "Relazione Virtuale", "virtualRelation": "Relazione Virtuale",
"linkMore": "Collega Altro", "linkMore": "Collega Altro",
"linkMoreRecords": "Collega più righe", "linkMoreRecords": "Collega più righe",
"linkRecords": "Link Records",
"downloadFile": "Scarica File", "downloadFile": "Scarica File",
"renameTable": "Rinomina Tabella", "renameTable": "Rinomina Tabella",
"renamingTable": "Rinominamento tabella", "renamingTable": "Rinominamento tabella",
@ -398,6 +399,7 @@
"findRowByScanningCode": "Find row by scanning a QR or Barcode", "findRowByScanningCode": "Find row by scanning a QR or Barcode",
"tokenManagement": "Gestione Token", "tokenManagement": "Gestione Token",
"addNewToken": "Aggiungi un nuovo token", "addNewToken": "Aggiungi un nuovo token",
"createNewToken": "Create new token",
"accountSettings": "Impostazioni account", "accountSettings": "Impostazioni account",
"resetPasswordMenu": "Reimposta Password", "resetPasswordMenu": "Reimposta Password",
"tokens": "Token", "tokens": "Token",
@ -665,7 +667,7 @@
"deletedField": "Cancella campo", "deletedField": "Cancella campo",
"incompleteConfiguration": "Configurazione incompleta", "incompleteConfiguration": "Configurazione incompleta",
"selectField": "Seleziona un campo", "selectField": "Seleziona un campo",
"selectFieldLabel": "Modifichi le proprietà del campo selezionando un campo dall'elenco" "selectFieldLabel": "Begin by selecting a field to customise its properties and structure."
} }
}, },
"activity": { "activity": {
@ -959,7 +961,13 @@
"decimal7": "1.0000000", "decimal7": "1.0000000",
"decimal8": "1.00000000", "decimal8": "1.00000000",
"value": "Valore", "value": "Valore",
"key": "Chiave" "key": "Chiave",
"createTable": "Create your First Table!",
"createTableLabel": "Create your first table effortlessly, from scratch, or by importing/connecting to an external database.",
"noTokenCreated": "No API Tokens created",
"noTokenCreatedLabel": "Begin by creating API tokens to unlock advanced functionalities.",
"inviteYourTeam": "Invite your team",
"inviteYourTeamLabel": "Streamline collaboration and productivity with your team – start by inviting them to join your workspace."
}, },
"msg": { "msg": {
"clickToCopyFieldId": "Fare clic per copiare l'Id del campo", "clickToCopyFieldId": "Fare clic per copiare l'Id del campo",
@ -1044,8 +1052,9 @@
"tooltip_desc": "Una singola riga dalla tabella ", "tooltip_desc": "Una singola riga dalla tabella ",
"tooltip_desc2": " può essere collegato con una riga della tabella " "tooltip_desc2": " può essere collegato con una riga della tabella "
}, },
"noRecordsAreLinkedFromTable": "Nessuna riga è collegata dalla tabella", "clickLinkRecordsToAddLinkFromTable": "Click 'Link Records' to begin associating data with '{tableName}'.",
"noRecordsLinked": "Nessun record collegato", "noRecordsLinked": "Nessun record collegato",
"noLinkedRecords": "No linked records",
"recordsLinked": "record collegati", "recordsLinked": "record collegati",
"acceptOnlyValid": "Accetta solo", "acceptOnlyValid": "Accetta solo",
"apiTokenCreate": "Creare token API personali da utilizzare nell'automazione o in applicazioni esterne.", "apiTokenCreate": "Creare token API personali da utilizzare nell'automazione o in applicazioni esterne.",
@ -1053,8 +1062,7 @@
"selectFieldToGroup": "Selezionare il campo da raggruppare", "selectFieldToGroup": "Selezionare il campo da raggruppare",
"thereAreNoRecordsInTable": "Non ci sono record nella tabella", "thereAreNoRecordsInTable": "Non ci sono record nella tabella",
"createWebhookMsg1": "Inizi con i web-hook!", "createWebhookMsg1": "Inizi con i web-hook!",
"createWebhookMsg2": "Crei dei web-hook per alimentare le sue automazioni,", "createWebhookMsg2": "Power your automations. Get notified as soon as there are changes in your data",
"createWebhookMsg3": "Ricevi una notifica non appena ci sono modifiche nei tuoi dati",
"areYouSureUWantTo": "Sei sicuro di voler eliminare quanto segue", "areYouSureUWantTo": "Sei sicuro di voler eliminare quanto segue",
"areYouSureUWantToDeleteLabel": "Sei sicuro di voler {deleteLabel} quanto segue", "areYouSureUWantToDeleteLabel": "Sei sicuro di voler {deleteLabel} quanto segue",
"idColumnRequired": "Il campo ID è obbligatorio, può rinominarlo in seguito, se necessario.", "idColumnRequired": "Il campo ID è obbligatorio, può rinominarlo in seguito, se necessario.",

18
packages/nc-gui/lang/ja.json

@ -332,6 +332,7 @@
"virtualRelation": "Virtual Relation", "virtualRelation": "Virtual Relation",
"linkMore": "Link More", "linkMore": "Link More",
"linkMoreRecords": "Link more records", "linkMoreRecords": "Link more records",
"linkRecords": "Link Records",
"downloadFile": "Download File", "downloadFile": "Download File",
"renameTable": "Rename Table", "renameTable": "Rename Table",
"renamingTable": "Renaming Table", "renamingTable": "Renaming Table",
@ -398,6 +399,7 @@
"findRowByScanningCode": "Find row by scanning a QR or Barcode", "findRowByScanningCode": "Find row by scanning a QR or Barcode",
"tokenManagement": "Token Management", "tokenManagement": "Token Management",
"addNewToken": "Add new token", "addNewToken": "Add new token",
"createNewToken": "Create new token",
"accountSettings": "Account Settings", "accountSettings": "Account Settings",
"resetPasswordMenu": "Reset Password", "resetPasswordMenu": "Reset Password",
"tokens": "Tokens", "tokens": "Tokens",
@ -665,7 +667,7 @@
"deletedField": "Deleted field", "deletedField": "Deleted field",
"incompleteConfiguration": "Incomplete configuration", "incompleteConfiguration": "Incomplete configuration",
"selectField": "Select a field", "selectField": "Select a field",
"selectFieldLabel": "Make changes to field properties by selecting a field from the list" "selectFieldLabel": "Begin by selecting a field to customise its properties and structure."
} }
}, },
"activity": { "activity": {
@ -959,7 +961,13 @@
"decimal7": "1.0000000", "decimal7": "1.0000000",
"decimal8": "1.00000000", "decimal8": "1.00000000",
"value": "Value", "value": "Value",
"key": "Key" "key": "Key",
"createTable": "Create your First Table!",
"createTableLabel": "Create your first table effortlessly, from scratch, or by importing/connecting to an external database.",
"noTokenCreated": "No API Tokens created",
"noTokenCreatedLabel": "Begin by creating API tokens to unlock advanced functionalities.",
"inviteYourTeam": "Invite your team",
"inviteYourTeamLabel": "Streamline collaboration and productivity with your team – start by inviting them to join your workspace."
}, },
"msg": { "msg": {
"clickToCopyFieldId": "Click to copy Field Id", "clickToCopyFieldId": "Click to copy Field Id",
@ -1044,8 +1052,9 @@
"tooltip_desc": "A single record from table ", "tooltip_desc": "A single record from table ",
"tooltip_desc2": " can be linked with a single record from table " "tooltip_desc2": " can be linked with a single record from table "
}, },
"noRecordsAreLinkedFromTable": "No records are linked from table", "clickLinkRecordsToAddLinkFromTable": "Click 'Link Records' to begin associating data with '{tableName}'.",
"noRecordsLinked": "No records linked", "noRecordsLinked": "No records linked",
"noLinkedRecords": "No linked records",
"recordsLinked": "records linked", "recordsLinked": "records linked",
"acceptOnlyValid": "Accepts only", "acceptOnlyValid": "Accepts only",
"apiTokenCreate": "Create personal API tokens to use in automation or external apps.", "apiTokenCreate": "Create personal API tokens to use in automation or external apps.",
@ -1053,8 +1062,7 @@
"selectFieldToGroup": "Select Field to Group", "selectFieldToGroup": "Select Field to Group",
"thereAreNoRecordsInTable": "There are no records in table", "thereAreNoRecordsInTable": "There are no records in table",
"createWebhookMsg1": "Get started with web-hooks!", "createWebhookMsg1": "Get started with web-hooks!",
"createWebhookMsg2": "Create web-hooks to power you automations,", "createWebhookMsg2": "Power your automations. Get notified as soon as there are changes in your data",
"createWebhookMsg3": "Get notified as soon as there are changes in your data",
"areYouSureUWantTo": "Are you sure you want to delete the following", "areYouSureUWantTo": "Are you sure you want to delete the following",
"areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following", "areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following",
"idColumnRequired": "ID field is required, you can rename this later if required.", "idColumnRequired": "ID field is required, you can rename this later if required.",

18
packages/nc-gui/lang/ko.json

@ -332,6 +332,7 @@
"virtualRelation": "가상 관계", "virtualRelation": "가상 관계",
"linkMore": "링크 더 보기", "linkMore": "링크 더 보기",
"linkMoreRecords": "더 많은 레코드 연결", "linkMoreRecords": "더 많은 레코드 연결",
"linkRecords": "Link Records",
"downloadFile": "파일 다운로드", "downloadFile": "파일 다운로드",
"renameTable": "테이블 이름 변경", "renameTable": "테이블 이름 변경",
"renamingTable": "테이블 이름 바꾸기", "renamingTable": "테이블 이름 바꾸기",
@ -398,6 +399,7 @@
"findRowByScanningCode": "QR 또는 바코드를 스캔하여 행 찾기", "findRowByScanningCode": "QR 또는 바코드를 스캔하여 행 찾기",
"tokenManagement": "토큰 관리", "tokenManagement": "토큰 관리",
"addNewToken": "새 토큰 추가", "addNewToken": "새 토큰 추가",
"createNewToken": "Create new token",
"accountSettings": "계정 설정", "accountSettings": "계정 설정",
"resetPasswordMenu": "비밀번호 재설정", "resetPasswordMenu": "비밀번호 재설정",
"tokens": "토큰", "tokens": "토큰",
@ -665,7 +667,7 @@
"deletedField": "Deleted field", "deletedField": "Deleted field",
"incompleteConfiguration": "Incomplete configuration", "incompleteConfiguration": "Incomplete configuration",
"selectField": "Select a field", "selectField": "Select a field",
"selectFieldLabel": "Make changes to field properties by selecting a field from the list" "selectFieldLabel": "Begin by selecting a field to customise its properties and structure."
} }
}, },
"activity": { "activity": {
@ -959,7 +961,13 @@
"decimal7": "1.0000000", "decimal7": "1.0000000",
"decimal8": "1.00000000", "decimal8": "1.00000000",
"value": "값", "value": "값",
"key": "키" "key": "키",
"createTable": "Create your First Table!",
"createTableLabel": "Create your first table effortlessly, from scratch, or by importing/connecting to an external database.",
"noTokenCreated": "No API Tokens created",
"noTokenCreatedLabel": "Begin by creating API tokens to unlock advanced functionalities.",
"inviteYourTeam": "Invite your team",
"inviteYourTeamLabel": "Streamline collaboration and productivity with your team – start by inviting them to join your workspace."
}, },
"msg": { "msg": {
"clickToCopyFieldId": "클릭하여 필드 ID 복사", "clickToCopyFieldId": "클릭하여 필드 ID 복사",
@ -1044,8 +1052,9 @@
"tooltip_desc": "테이블의 단일 레코드 ", "tooltip_desc": "테이블의 단일 레코드 ",
"tooltip_desc2": " 테이블의 단일 레코드와 연결될 수 있습니다." "tooltip_desc2": " 테이블의 단일 레코드와 연결될 수 있습니다."
}, },
"noRecordsAreLinkedFromTable": "테이블에 연결된 레코드가 없습니다.", "clickLinkRecordsToAddLinkFromTable": "Click 'Link Records' to begin associating data with '{tableName}'.",
"noRecordsLinked": "레코드가 연결되지 않았습니다.", "noRecordsLinked": "레코드가 연결되지 않았습니다.",
"noLinkedRecords": "No linked records",
"recordsLinked": "레코드가 연결되었습니다.", "recordsLinked": "레코드가 연결되었습니다.",
"acceptOnlyValid": "유효한 값만 허용", "acceptOnlyValid": "유효한 값만 허용",
"apiTokenCreate": "새 API 토큰 생성", "apiTokenCreate": "새 API 토큰 생성",
@ -1053,8 +1062,7 @@
"selectFieldToGroup": "Select Field to Group", "selectFieldToGroup": "Select Field to Group",
"thereAreNoRecordsInTable": "테이블에 레코드가 없습니다.", "thereAreNoRecordsInTable": "테이블에 레코드가 없습니다.",
"createWebhookMsg1": "웹훅을 생성하여 자동화를 구동하고,", "createWebhookMsg1": "웹훅을 생성하여 자동화를 구동하고,",
"createWebhookMsg2": "자동화를 지원하는 웹 후크를 만들고,", "createWebhookMsg2": "Power your automations. Get notified as soon as there are changes in your data",
"createWebhookMsg3": "데이터에 변경 사항이 있는 경우 즉시 알림 받기",
"areYouSureUWantTo": "삭제하시겠습니까?", "areYouSureUWantTo": "삭제하시겠습니까?",
"areYouSureUWantToDeleteLabel": "{deleteLabel}을 삭제하시겠습니까?", "areYouSureUWantToDeleteLabel": "{deleteLabel}을 삭제하시겠습니까?",
"idColumnRequired": "ID 필드가 필요합니다. 필요한 경우 나중에 이름을 변경할 수 있습니다.", "idColumnRequired": "ID 필드가 필요합니다. 필요한 경우 나중에 이름을 변경할 수 있습니다.",

18
packages/nc-gui/lang/lv.json

@ -332,6 +332,7 @@
"virtualRelation": "Virtual Relation", "virtualRelation": "Virtual Relation",
"linkMore": "Link More", "linkMore": "Link More",
"linkMoreRecords": "Link more records", "linkMoreRecords": "Link more records",
"linkRecords": "Link Records",
"downloadFile": "Download File", "downloadFile": "Download File",
"renameTable": "Rename Table", "renameTable": "Rename Table",
"renamingTable": "Renaming Table", "renamingTable": "Renaming Table",
@ -398,6 +399,7 @@
"findRowByScanningCode": "Find row by scanning a QR or Barcode", "findRowByScanningCode": "Find row by scanning a QR or Barcode",
"tokenManagement": "Token Management", "tokenManagement": "Token Management",
"addNewToken": "Add new token", "addNewToken": "Add new token",
"createNewToken": "Create new token",
"accountSettings": "Account Settings", "accountSettings": "Account Settings",
"resetPasswordMenu": "Reset Password", "resetPasswordMenu": "Reset Password",
"tokens": "Tokens", "tokens": "Tokens",
@ -665,7 +667,7 @@
"deletedField": "Deleted field", "deletedField": "Deleted field",
"incompleteConfiguration": "Incomplete configuration", "incompleteConfiguration": "Incomplete configuration",
"selectField": "Select a field", "selectField": "Select a field",
"selectFieldLabel": "Make changes to field properties by selecting a field from the list" "selectFieldLabel": "Begin by selecting a field to customise its properties and structure."
} }
}, },
"activity": { "activity": {
@ -959,7 +961,13 @@
"decimal7": "1.0000000", "decimal7": "1.0000000",
"decimal8": "1.00000000", "decimal8": "1.00000000",
"value": "Value", "value": "Value",
"key": "Key" "key": "Key",
"createTable": "Create your First Table!",
"createTableLabel": "Create your first table effortlessly, from scratch, or by importing/connecting to an external database.",
"noTokenCreated": "No API Tokens created",
"noTokenCreatedLabel": "Begin by creating API tokens to unlock advanced functionalities.",
"inviteYourTeam": "Invite your team",
"inviteYourTeamLabel": "Streamline collaboration and productivity with your team – start by inviting them to join your workspace."
}, },
"msg": { "msg": {
"clickToCopyFieldId": "Click to copy Field Id", "clickToCopyFieldId": "Click to copy Field Id",
@ -1044,8 +1052,9 @@
"tooltip_desc": "A single record from table ", "tooltip_desc": "A single record from table ",
"tooltip_desc2": " can be linked with a single record from table " "tooltip_desc2": " can be linked with a single record from table "
}, },
"noRecordsAreLinkedFromTable": "No records are linked from table", "clickLinkRecordsToAddLinkFromTable": "Click 'Link Records' to begin associating data with '{tableName}'.",
"noRecordsLinked": "No records linked", "noRecordsLinked": "No records linked",
"noLinkedRecords": "No linked records",
"recordsLinked": "records linked", "recordsLinked": "records linked",
"acceptOnlyValid": "Accepts only", "acceptOnlyValid": "Accepts only",
"apiTokenCreate": "Create personal API tokens to use in automation or external apps.", "apiTokenCreate": "Create personal API tokens to use in automation or external apps.",
@ -1053,8 +1062,7 @@
"selectFieldToGroup": "Select Field to Group", "selectFieldToGroup": "Select Field to Group",
"thereAreNoRecordsInTable": "There are no records in table", "thereAreNoRecordsInTable": "There are no records in table",
"createWebhookMsg1": "Get started with web-hooks!", "createWebhookMsg1": "Get started with web-hooks!",
"createWebhookMsg2": "Create web-hooks to power you automations,", "createWebhookMsg2": "Power your automations. Get notified as soon as there are changes in your data",
"createWebhookMsg3": "Get notified as soon as there are changes in your data",
"areYouSureUWantTo": "Are you sure you want to delete the following", "areYouSureUWantTo": "Are you sure you want to delete the following",
"areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following", "areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following",
"idColumnRequired": "ID field is required, you can rename this later if required.", "idColumnRequired": "ID field is required, you can rename this later if required.",

18
packages/nc-gui/lang/nl.json

@ -332,6 +332,7 @@
"virtualRelation": "Virtual Relation", "virtualRelation": "Virtual Relation",
"linkMore": "Link More", "linkMore": "Link More",
"linkMoreRecords": "Link more records", "linkMoreRecords": "Link more records",
"linkRecords": "Link Records",
"downloadFile": "Download File", "downloadFile": "Download File",
"renameTable": "Rename Table", "renameTable": "Rename Table",
"renamingTable": "Renaming Table", "renamingTable": "Renaming Table",
@ -398,6 +399,7 @@
"findRowByScanningCode": "Find row by scanning a QR or Barcode", "findRowByScanningCode": "Find row by scanning a QR or Barcode",
"tokenManagement": "Token Management", "tokenManagement": "Token Management",
"addNewToken": "Add new token", "addNewToken": "Add new token",
"createNewToken": "Create new token",
"accountSettings": "Account Settings", "accountSettings": "Account Settings",
"resetPasswordMenu": "Reset Password", "resetPasswordMenu": "Reset Password",
"tokens": "Tokens", "tokens": "Tokens",
@ -665,7 +667,7 @@
"deletedField": "Deleted field", "deletedField": "Deleted field",
"incompleteConfiguration": "Incomplete configuration", "incompleteConfiguration": "Incomplete configuration",
"selectField": "Select a field", "selectField": "Select a field",
"selectFieldLabel": "Make changes to field properties by selecting a field from the list" "selectFieldLabel": "Begin by selecting a field to customise its properties and structure."
} }
}, },
"activity": { "activity": {
@ -959,7 +961,13 @@
"decimal7": "1.0000000", "decimal7": "1.0000000",
"decimal8": "1.00000000", "decimal8": "1.00000000",
"value": "Value", "value": "Value",
"key": "Key" "key": "Key",
"createTable": "Create your First Table!",
"createTableLabel": "Create your first table effortlessly, from scratch, or by importing/connecting to an external database.",
"noTokenCreated": "No API Tokens created",
"noTokenCreatedLabel": "Begin by creating API tokens to unlock advanced functionalities.",
"inviteYourTeam": "Invite your team",
"inviteYourTeamLabel": "Streamline collaboration and productivity with your team – start by inviting them to join your workspace."
}, },
"msg": { "msg": {
"clickToCopyFieldId": "Click to copy Field Id", "clickToCopyFieldId": "Click to copy Field Id",
@ -1044,8 +1052,9 @@
"tooltip_desc": "A single record from table ", "tooltip_desc": "A single record from table ",
"tooltip_desc2": " can be linked with a single record from table " "tooltip_desc2": " can be linked with a single record from table "
}, },
"noRecordsAreLinkedFromTable": "No records are linked from table", "clickLinkRecordsToAddLinkFromTable": "Click 'Link Records' to begin associating data with '{tableName}'.",
"noRecordsLinked": "No records linked", "noRecordsLinked": "No records linked",
"noLinkedRecords": "No linked records",
"recordsLinked": "records linked", "recordsLinked": "records linked",
"acceptOnlyValid": "Accepts only", "acceptOnlyValid": "Accepts only",
"apiTokenCreate": "Create personal API tokens to use in automation or external apps.", "apiTokenCreate": "Create personal API tokens to use in automation or external apps.",
@ -1053,8 +1062,7 @@
"selectFieldToGroup": "Select Field to Group", "selectFieldToGroup": "Select Field to Group",
"thereAreNoRecordsInTable": "There are no records in table", "thereAreNoRecordsInTable": "There are no records in table",
"createWebhookMsg1": "Get started with web-hooks!", "createWebhookMsg1": "Get started with web-hooks!",
"createWebhookMsg2": "Create web-hooks to power you automations,", "createWebhookMsg2": "Power your automations. Get notified as soon as there are changes in your data",
"createWebhookMsg3": "Get notified as soon as there are changes in your data",
"areYouSureUWantTo": "Are you sure you want to delete the following", "areYouSureUWantTo": "Are you sure you want to delete the following",
"areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following", "areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following",
"idColumnRequired": "ID field is required, you can rename this later if required.", "idColumnRequired": "ID field is required, you can rename this later if required.",

18
packages/nc-gui/lang/no.json

@ -332,6 +332,7 @@
"virtualRelation": "Virtual Relation", "virtualRelation": "Virtual Relation",
"linkMore": "Link More", "linkMore": "Link More",
"linkMoreRecords": "Link more records", "linkMoreRecords": "Link more records",
"linkRecords": "Link Records",
"downloadFile": "Download File", "downloadFile": "Download File",
"renameTable": "Rename Table", "renameTable": "Rename Table",
"renamingTable": "Renaming Table", "renamingTable": "Renaming Table",
@ -398,6 +399,7 @@
"findRowByScanningCode": "Find row by scanning a QR or Barcode", "findRowByScanningCode": "Find row by scanning a QR or Barcode",
"tokenManagement": "Token Management", "tokenManagement": "Token Management",
"addNewToken": "Add new token", "addNewToken": "Add new token",
"createNewToken": "Create new token",
"accountSettings": "Account Settings", "accountSettings": "Account Settings",
"resetPasswordMenu": "Reset Password", "resetPasswordMenu": "Reset Password",
"tokens": "Tokens", "tokens": "Tokens",
@ -665,7 +667,7 @@
"deletedField": "Deleted field", "deletedField": "Deleted field",
"incompleteConfiguration": "Incomplete configuration", "incompleteConfiguration": "Incomplete configuration",
"selectField": "Select a field", "selectField": "Select a field",
"selectFieldLabel": "Make changes to field properties by selecting a field from the list" "selectFieldLabel": "Begin by selecting a field to customise its properties and structure."
} }
}, },
"activity": { "activity": {
@ -959,7 +961,13 @@
"decimal7": "1.0000000", "decimal7": "1.0000000",
"decimal8": "1.00000000", "decimal8": "1.00000000",
"value": "Value", "value": "Value",
"key": "Key" "key": "Key",
"createTable": "Create your First Table!",
"createTableLabel": "Create your first table effortlessly, from scratch, or by importing/connecting to an external database.",
"noTokenCreated": "No API Tokens created",
"noTokenCreatedLabel": "Begin by creating API tokens to unlock advanced functionalities.",
"inviteYourTeam": "Invite your team",
"inviteYourTeamLabel": "Streamline collaboration and productivity with your team – start by inviting them to join your workspace."
}, },
"msg": { "msg": {
"clickToCopyFieldId": "Click to copy Field Id", "clickToCopyFieldId": "Click to copy Field Id",
@ -1044,8 +1052,9 @@
"tooltip_desc": "A single record from table ", "tooltip_desc": "A single record from table ",
"tooltip_desc2": " can be linked with a single record from table " "tooltip_desc2": " can be linked with a single record from table "
}, },
"noRecordsAreLinkedFromTable": "No records are linked from table", "clickLinkRecordsToAddLinkFromTable": "Click 'Link Records' to begin associating data with '{tableName}'.",
"noRecordsLinked": "No records linked", "noRecordsLinked": "No records linked",
"noLinkedRecords": "No linked records",
"recordsLinked": "records linked", "recordsLinked": "records linked",
"acceptOnlyValid": "Accepts only", "acceptOnlyValid": "Accepts only",
"apiTokenCreate": "Create personal API tokens to use in automation or external apps.", "apiTokenCreate": "Create personal API tokens to use in automation or external apps.",
@ -1053,8 +1062,7 @@
"selectFieldToGroup": "Select Field to Group", "selectFieldToGroup": "Select Field to Group",
"thereAreNoRecordsInTable": "There are no records in table", "thereAreNoRecordsInTable": "There are no records in table",
"createWebhookMsg1": "Get started with web-hooks!", "createWebhookMsg1": "Get started with web-hooks!",
"createWebhookMsg2": "Create web-hooks to power you automations,", "createWebhookMsg2": "Power your automations. Get notified as soon as there are changes in your data",
"createWebhookMsg3": "Get notified as soon as there are changes in your data",
"areYouSureUWantTo": "Are you sure you want to delete the following", "areYouSureUWantTo": "Are you sure you want to delete the following",
"areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following", "areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following",
"idColumnRequired": "ID field is required, you can rename this later if required.", "idColumnRequired": "ID field is required, you can rename this later if required.",

18
packages/nc-gui/lang/pl.json

@ -332,6 +332,7 @@
"virtualRelation": "Relacja wirtualna", "virtualRelation": "Relacja wirtualna",
"linkMore": "Połącz więcej", "linkMore": "Połącz więcej",
"linkMoreRecords": "Połącz więcej rekordów", "linkMoreRecords": "Połącz więcej rekordów",
"linkRecords": "Link Records",
"downloadFile": "Pobierz plik", "downloadFile": "Pobierz plik",
"renameTable": "Zmień nazwę tabeli", "renameTable": "Zmień nazwę tabeli",
"renamingTable": "Zmiana nazwy tabeli", "renamingTable": "Zmiana nazwy tabeli",
@ -398,6 +399,7 @@
"findRowByScanningCode": "Znajdź wiersz poprzez skanowanie kodu", "findRowByScanningCode": "Znajdź wiersz poprzez skanowanie kodu",
"tokenManagement": "Zarządzanie tokenami", "tokenManagement": "Zarządzanie tokenami",
"addNewToken": "Dodaj nowy token", "addNewToken": "Dodaj nowy token",
"createNewToken": "Create new token",
"accountSettings": "Ustawienia konta", "accountSettings": "Ustawienia konta",
"resetPasswordMenu": "Menu resetowania hasła", "resetPasswordMenu": "Menu resetowania hasła",
"tokens": "Tokeny", "tokens": "Tokeny",
@ -665,7 +667,7 @@
"deletedField": "Usunięte pole", "deletedField": "Usunięte pole",
"incompleteConfiguration": "Niekompletna konfiguracja", "incompleteConfiguration": "Niekompletna konfiguracja",
"selectField": "Wybierz pole", "selectField": "Wybierz pole",
"selectFieldLabel": "Dokonaj zmian we właściwościach pola wybierając pole z listy" "selectFieldLabel": "Begin by selecting a field to customise its properties and structure."
} }
}, },
"activity": { "activity": {
@ -959,7 +961,13 @@
"decimal7": "1.0000000", "decimal7": "1.0000000",
"decimal8": "1.00000000", "decimal8": "1.00000000",
"value": "Wartość", "value": "Wartość",
"key": "Klucz" "key": "Klucz",
"createTable": "Create your First Table!",
"createTableLabel": "Create your first table effortlessly, from scratch, or by importing/connecting to an external database.",
"noTokenCreated": "No API Tokens created",
"noTokenCreatedLabel": "Begin by creating API tokens to unlock advanced functionalities.",
"inviteYourTeam": "Invite your team",
"inviteYourTeamLabel": "Streamline collaboration and productivity with your team – start by inviting them to join your workspace."
}, },
"msg": { "msg": {
"clickToCopyFieldId": "Kliknij, aby skopiować Identyfikator Pola", "clickToCopyFieldId": "Kliknij, aby skopiować Identyfikator Pola",
@ -1044,8 +1052,9 @@
"tooltip_desc": "Pojedynczy rekord z tabeli ", "tooltip_desc": "Pojedynczy rekord z tabeli ",
"tooltip_desc2": " może być powiązany z pojedynczym rekordem z tabeli " "tooltip_desc2": " może być powiązany z pojedynczym rekordem z tabeli "
}, },
"noRecordsAreLinkedFromTable": "Żadne rekordy nie są powiązane z tabeli", "clickLinkRecordsToAddLinkFromTable": "Click 'Link Records' to begin associating data with '{tableName}'.",
"noRecordsLinked": "Żadne rekordy nie są powiązane", "noRecordsLinked": "Żadne rekordy nie są powiązane",
"noLinkedRecords": "No linked records",
"recordsLinked": "rekordy powiązane", "recordsLinked": "rekordy powiązane",
"acceptOnlyValid": "Akceptuje tylko", "acceptOnlyValid": "Akceptuje tylko",
"apiTokenCreate": "Utwórz osobiste tokeny API do użytku w automatyzacji lub zewnętrznych aplikacjach.", "apiTokenCreate": "Utwórz osobiste tokeny API do użytku w automatyzacji lub zewnętrznych aplikacjach.",
@ -1053,8 +1062,7 @@
"selectFieldToGroup": "Wybierz pole do grupowania", "selectFieldToGroup": "Wybierz pole do grupowania",
"thereAreNoRecordsInTable": "Nie ma rekordów w tabeli", "thereAreNoRecordsInTable": "Nie ma rekordów w tabeli",
"createWebhookMsg1": "Rozpocznij pracę z webhookami!", "createWebhookMsg1": "Rozpocznij pracę z webhookami!",
"createWebhookMsg2": "Utwórz webhoooki do zasilania twoich automatyzacji,", "createWebhookMsg2": "Power your automations. Get notified as soon as there are changes in your data",
"createWebhookMsg3": "Bądź informowany jak tylko pojawią się zmiany w twoich danych",
"areYouSureUWantTo": "Czy na pewno chcesz usunąć następujące", "areYouSureUWantTo": "Czy na pewno chcesz usunąć następujące",
"areYouSureUWantToDeleteLabel": "Czy na pewno chcesz {deleteLabel} następujące", "areYouSureUWantToDeleteLabel": "Czy na pewno chcesz {deleteLabel} następujące",
"idColumnRequired": "Pole ID jest wymagane, możesz to później zmienić, jeśli będzie to konieczne.", "idColumnRequired": "Pole ID jest wymagane, możesz to później zmienić, jeśli będzie to konieczne.",

18
packages/nc-gui/lang/pt.json

@ -332,6 +332,7 @@
"virtualRelation": "Virtual Relation", "virtualRelation": "Virtual Relation",
"linkMore": "Link More", "linkMore": "Link More",
"linkMoreRecords": "Link more records", "linkMoreRecords": "Link more records",
"linkRecords": "Link Records",
"downloadFile": "Download File", "downloadFile": "Download File",
"renameTable": "Rename Table", "renameTable": "Rename Table",
"renamingTable": "Renaming Table", "renamingTable": "Renaming Table",
@ -398,6 +399,7 @@
"findRowByScanningCode": "Find row by scanning a QR or Barcode", "findRowByScanningCode": "Find row by scanning a QR or Barcode",
"tokenManagement": "Token Management", "tokenManagement": "Token Management",
"addNewToken": "Add new token", "addNewToken": "Add new token",
"createNewToken": "Create new token",
"accountSettings": "Account Settings", "accountSettings": "Account Settings",
"resetPasswordMenu": "Reset Password", "resetPasswordMenu": "Reset Password",
"tokens": "Tokens", "tokens": "Tokens",
@ -665,7 +667,7 @@
"deletedField": "Deleted field", "deletedField": "Deleted field",
"incompleteConfiguration": "Incomplete configuration", "incompleteConfiguration": "Incomplete configuration",
"selectField": "Select a field", "selectField": "Select a field",
"selectFieldLabel": "Make changes to field properties by selecting a field from the list" "selectFieldLabel": "Begin by selecting a field to customise its properties and structure."
} }
}, },
"activity": { "activity": {
@ -959,7 +961,13 @@
"decimal7": "1.0000000", "decimal7": "1.0000000",
"decimal8": "1.00000000", "decimal8": "1.00000000",
"value": "Value", "value": "Value",
"key": "Key" "key": "Key",
"createTable": "Create your First Table!",
"createTableLabel": "Create your first table effortlessly, from scratch, or by importing/connecting to an external database.",
"noTokenCreated": "No API Tokens created",
"noTokenCreatedLabel": "Begin by creating API tokens to unlock advanced functionalities.",
"inviteYourTeam": "Invite your team",
"inviteYourTeamLabel": "Streamline collaboration and productivity with your team – start by inviting them to join your workspace."
}, },
"msg": { "msg": {
"clickToCopyFieldId": "Click to copy Field Id", "clickToCopyFieldId": "Click to copy Field Id",
@ -1044,8 +1052,9 @@
"tooltip_desc": "A single record from table ", "tooltip_desc": "A single record from table ",
"tooltip_desc2": " can be linked with a single record from table " "tooltip_desc2": " can be linked with a single record from table "
}, },
"noRecordsAreLinkedFromTable": "No records are linked from table", "clickLinkRecordsToAddLinkFromTable": "Click 'Link Records' to begin associating data with '{tableName}'.",
"noRecordsLinked": "No records linked", "noRecordsLinked": "No records linked",
"noLinkedRecords": "No linked records",
"recordsLinked": "records linked", "recordsLinked": "records linked",
"acceptOnlyValid": "Accepts only", "acceptOnlyValid": "Accepts only",
"apiTokenCreate": "Create personal API tokens to use in automation or external apps.", "apiTokenCreate": "Create personal API tokens to use in automation or external apps.",
@ -1053,8 +1062,7 @@
"selectFieldToGroup": "Select Field to Group", "selectFieldToGroup": "Select Field to Group",
"thereAreNoRecordsInTable": "There are no records in table", "thereAreNoRecordsInTable": "There are no records in table",
"createWebhookMsg1": "Get started with web-hooks!", "createWebhookMsg1": "Get started with web-hooks!",
"createWebhookMsg2": "Create web-hooks to power you automations,", "createWebhookMsg2": "Power your automations. Get notified as soon as there are changes in your data",
"createWebhookMsg3": "Get notified as soon as there are changes in your data",
"areYouSureUWantTo": "Are you sure you want to delete the following", "areYouSureUWantTo": "Are you sure you want to delete the following",
"areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following", "areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following",
"idColumnRequired": "ID field is required, you can rename this later if required.", "idColumnRequired": "ID field is required, you can rename this later if required.",

20
packages/nc-gui/lang/pt_BR.json

@ -69,7 +69,7 @@
"hex": "Hex", "hex": "Hex",
"clear": "Clear", "clear": "Clear",
"slack": "Slack", "slack": "Slack",
"comment": "Comment", "comment": "Comentário",
"microsoftTeams": "Microsoft Teams", "microsoftTeams": "Microsoft Teams",
"discord": "Discord", "discord": "Discord",
"matterMost": "Mattermost", "matterMost": "Mattermost",
@ -332,6 +332,7 @@
"virtualRelation": "Virtual Relation", "virtualRelation": "Virtual Relation",
"linkMore": "Link More", "linkMore": "Link More",
"linkMoreRecords": "Link more records", "linkMoreRecords": "Link more records",
"linkRecords": "Link Records",
"downloadFile": "Download File", "downloadFile": "Download File",
"renameTable": "Rename Table", "renameTable": "Rename Table",
"renamingTable": "Renaming Table", "renamingTable": "Renaming Table",
@ -398,6 +399,7 @@
"findRowByScanningCode": "Find row by scanning a QR or Barcode", "findRowByScanningCode": "Find row by scanning a QR or Barcode",
"tokenManagement": "Token Management", "tokenManagement": "Token Management",
"addNewToken": "Add new token", "addNewToken": "Add new token",
"createNewToken": "Create new token",
"accountSettings": "Account Settings", "accountSettings": "Account Settings",
"resetPasswordMenu": "Reset Password", "resetPasswordMenu": "Reset Password",
"tokens": "Tokens", "tokens": "Tokens",
@ -665,7 +667,7 @@
"deletedField": "Deleted field", "deletedField": "Deleted field",
"incompleteConfiguration": "Incomplete configuration", "incompleteConfiguration": "Incomplete configuration",
"selectField": "Select a field", "selectField": "Select a field",
"selectFieldLabel": "Make changes to field properties by selecting a field from the list" "selectFieldLabel": "Begin by selecting a field to customise its properties and structure."
} }
}, },
"activity": { "activity": {
@ -959,7 +961,13 @@
"decimal7": "1.0000000", "decimal7": "1.0000000",
"decimal8": "1.00000000", "decimal8": "1.00000000",
"value": "Value", "value": "Value",
"key": "Key" "key": "Key",
"createTable": "Create your First Table!",
"createTableLabel": "Create your first table effortlessly, from scratch, or by importing/connecting to an external database.",
"noTokenCreated": "No API Tokens created",
"noTokenCreatedLabel": "Begin by creating API tokens to unlock advanced functionalities.",
"inviteYourTeam": "Invite your team",
"inviteYourTeamLabel": "Streamline collaboration and productivity with your team – start by inviting them to join your workspace."
}, },
"msg": { "msg": {
"clickToCopyFieldId": "Click to copy Field Id", "clickToCopyFieldId": "Click to copy Field Id",
@ -1044,8 +1052,9 @@
"tooltip_desc": "A single record from table ", "tooltip_desc": "A single record from table ",
"tooltip_desc2": " can be linked with a single record from table " "tooltip_desc2": " can be linked with a single record from table "
}, },
"noRecordsAreLinkedFromTable": "No records are linked from table", "clickLinkRecordsToAddLinkFromTable": "Click 'Link Records' to begin associating data with '{tableName}'.",
"noRecordsLinked": "No records linked", "noRecordsLinked": "No records linked",
"noLinkedRecords": "No linked records",
"recordsLinked": "records linked", "recordsLinked": "records linked",
"acceptOnlyValid": "Accepts only", "acceptOnlyValid": "Accepts only",
"apiTokenCreate": "Create personal API tokens to use in automation or external apps.", "apiTokenCreate": "Create personal API tokens to use in automation or external apps.",
@ -1053,8 +1062,7 @@
"selectFieldToGroup": "Select Field to Group", "selectFieldToGroup": "Select Field to Group",
"thereAreNoRecordsInTable": "There are no records in table", "thereAreNoRecordsInTable": "There are no records in table",
"createWebhookMsg1": "Get started with web-hooks!", "createWebhookMsg1": "Get started with web-hooks!",
"createWebhookMsg2": "Create web-hooks to power you automations,", "createWebhookMsg2": "Power your automations. Get notified as soon as there are changes in your data",
"createWebhookMsg3": "Get notified as soon as there are changes in your data",
"areYouSureUWantTo": "Are you sure you want to delete the following", "areYouSureUWantTo": "Are you sure you want to delete the following",
"areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following", "areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following",
"idColumnRequired": "ID field is required, you can rename this later if required.", "idColumnRequired": "ID field is required, you can rename this later if required.",

38
packages/nc-gui/lang/ru.json

@ -332,6 +332,7 @@
"virtualRelation": "Виртуальные отношения", "virtualRelation": "Виртуальные отношения",
"linkMore": "Ссылка Подробнее", "linkMore": "Ссылка Подробнее",
"linkMoreRecords": "Связать больше записей", "linkMoreRecords": "Связать больше записей",
"linkRecords": "Link Records",
"downloadFile": "Скачать файл", "downloadFile": "Скачать файл",
"renameTable": "Переименовать таблицу", "renameTable": "Переименовать таблицу",
"renamingTable": "Переименование таблицы", "renamingTable": "Переименование таблицы",
@ -398,6 +399,7 @@
"findRowByScanningCode": "Найти строку путем сканирования QR или штрих-кода", "findRowByScanningCode": "Найти строку путем сканирования QR или штрих-кода",
"tokenManagement": "Управление токенами", "tokenManagement": "Управление токенами",
"addNewToken": "Добавить новый токен", "addNewToken": "Добавить новый токен",
"createNewToken": "Create new token",
"accountSettings": "Настройки аккаунта", "accountSettings": "Настройки аккаунта",
"resetPasswordMenu": "Сбросить пароль", "resetPasswordMenu": "Сбросить пароль",
"tokens": "Токены", "tokens": "Токены",
@ -659,13 +661,13 @@
"newFormLoaded": "Новая форма будет загружена после", "newFormLoaded": "Новая форма будет загружена после",
"webhook": "Вебхук", "webhook": "Вебхук",
"multiField": { "multiField": {
"newField": "New field", "newField": "Новое поле",
"saveChanges": "Save changes", "saveChanges": "Сохранить изменения",
"updatedField": "Updated field", "updatedField": "Обновить поле",
"deletedField": "Deleted field", "deletedField": "Удалить поле",
"incompleteConfiguration": "Incomplete configuration", "incompleteConfiguration": "Incomplete configuration",
"selectField": "Select a field", "selectField": "Выбрать поле",
"selectFieldLabel": "Make changes to field properties by selecting a field from the list" "selectFieldLabel": "Begin by selecting a field to customise its properties and structure."
} }
}, },
"activity": { "activity": {
@ -917,10 +919,10 @@
"clientCA": "Выберите файл CA" "clientCA": "Выберите файл CA"
}, },
"placeholder": { "placeholder": {
"selectSlackChannels": "Select Slack channels", "selectSlackChannels": "Выберите каналы Slack",
"selectTeamsChannels": "Select Microsoft Teams channels", "selectTeamsChannels": "Выберите каналы Microsoft Teams",
"selectDiscordChannels": "Select Discord channels", "selectDiscordChannels": "Выберите каналы Discord",
"selectMattermostChannels": "Select Mattermost channels", "selectMattermostChannels": "Выберите каналы Mattermost",
"webhookTitle": "Webhook Title", "webhookTitle": "Webhook Title",
"barcodeColumn": "Select a field for the Barcode value", "barcodeColumn": "Select a field for the Barcode value",
"notFoundContent": "No valid field Type can be found.", "notFoundContent": "No valid field Type can be found.",
@ -938,7 +940,7 @@
"confirm": "Подтвердите новый пароль" "confirm": "Подтвердите новый пароль"
}, },
"selectAColumnForTheQRCodeValue": "Select a field for the QR code value", "selectAColumnForTheQRCodeValue": "Select a field for the QR code value",
"allowNegativeNumbers": "Allow negative numbers", "allowNegativeNumbers": "Разрешить отрицательные числа",
"searchProjectTree": "Искать таблицы", "searchProjectTree": "Искать таблицы",
"searchFields": "Поиск полей", "searchFields": "Поиск полей",
"searchColumn": "Поиск {поиск} столбец", "searchColumn": "Поиск {поиск} столбец",
@ -959,7 +961,13 @@
"decimal7": "1.0000000", "decimal7": "1.0000000",
"decimal8": "1.00000000", "decimal8": "1.00000000",
"value": "Значение", "value": "Значение",
"key": "Ключ" "key": "Ключ",
"createTable": "Create your First Table!",
"createTableLabel": "Create your first table effortlessly, from scratch, or by importing/connecting to an external database.",
"noTokenCreated": "No API Tokens created",
"noTokenCreatedLabel": "Begin by creating API tokens to unlock advanced functionalities.",
"inviteYourTeam": "Invite your team",
"inviteYourTeamLabel": "Streamline collaboration and productivity with your team – start by inviting them to join your workspace."
}, },
"msg": { "msg": {
"clickToCopyFieldId": "Click to copy Field Id", "clickToCopyFieldId": "Click to copy Field Id",
@ -1044,8 +1052,9 @@
"tooltip_desc": "A single record from table ", "tooltip_desc": "A single record from table ",
"tooltip_desc2": " can be linked with a single record from table " "tooltip_desc2": " can be linked with a single record from table "
}, },
"noRecordsAreLinkedFromTable": "No records are linked from table", "clickLinkRecordsToAddLinkFromTable": "Click 'Link Records' to begin associating data with '{tableName}'.",
"noRecordsLinked": "No records linked", "noRecordsLinked": "No records linked",
"noLinkedRecords": "No linked records",
"recordsLinked": "records linked", "recordsLinked": "records linked",
"acceptOnlyValid": "Accepts only", "acceptOnlyValid": "Accepts only",
"apiTokenCreate": "Create personal API tokens to use in automation or external apps.", "apiTokenCreate": "Create personal API tokens to use in automation or external apps.",
@ -1053,8 +1062,7 @@
"selectFieldToGroup": "Select Field to Group", "selectFieldToGroup": "Select Field to Group",
"thereAreNoRecordsInTable": "There are no records in table", "thereAreNoRecordsInTable": "There are no records in table",
"createWebhookMsg1": "Get started with web-hooks!", "createWebhookMsg1": "Get started with web-hooks!",
"createWebhookMsg2": "Create web-hooks to power you automations,", "createWebhookMsg2": "Power your automations. Get notified as soon as there are changes in your data",
"createWebhookMsg3": "Get notified as soon as there are changes in your data",
"areYouSureUWantTo": "Are you sure you want to delete the following", "areYouSureUWantTo": "Are you sure you want to delete the following",
"areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following", "areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following",
"idColumnRequired": "ID field is required, you can rename this later if required.", "idColumnRequired": "ID field is required, you can rename this later if required.",

18
packages/nc-gui/lang/sk.json

@ -332,6 +332,7 @@
"virtualRelation": "Virtual Relation", "virtualRelation": "Virtual Relation",
"linkMore": "Link More", "linkMore": "Link More",
"linkMoreRecords": "Link more records", "linkMoreRecords": "Link more records",
"linkRecords": "Link Records",
"downloadFile": "Download File", "downloadFile": "Download File",
"renameTable": "Rename Table", "renameTable": "Rename Table",
"renamingTable": "Renaming Table", "renamingTable": "Renaming Table",
@ -398,6 +399,7 @@
"findRowByScanningCode": "Find row by scanning a QR or Barcode", "findRowByScanningCode": "Find row by scanning a QR or Barcode",
"tokenManagement": "Token Management", "tokenManagement": "Token Management",
"addNewToken": "Add new token", "addNewToken": "Add new token",
"createNewToken": "Create new token",
"accountSettings": "Account Settings", "accountSettings": "Account Settings",
"resetPasswordMenu": "Reset Password", "resetPasswordMenu": "Reset Password",
"tokens": "Tokens", "tokens": "Tokens",
@ -665,7 +667,7 @@
"deletedField": "Deleted field", "deletedField": "Deleted field",
"incompleteConfiguration": "Incomplete configuration", "incompleteConfiguration": "Incomplete configuration",
"selectField": "Select a field", "selectField": "Select a field",
"selectFieldLabel": "Make changes to field properties by selecting a field from the list" "selectFieldLabel": "Begin by selecting a field to customise its properties and structure."
} }
}, },
"activity": { "activity": {
@ -959,7 +961,13 @@
"decimal7": "1.0000000", "decimal7": "1.0000000",
"decimal8": "1.00000000", "decimal8": "1.00000000",
"value": "Value", "value": "Value",
"key": "Key" "key": "Key",
"createTable": "Create your First Table!",
"createTableLabel": "Create your first table effortlessly, from scratch, or by importing/connecting to an external database.",
"noTokenCreated": "No API Tokens created",
"noTokenCreatedLabel": "Begin by creating API tokens to unlock advanced functionalities.",
"inviteYourTeam": "Invite your team",
"inviteYourTeamLabel": "Streamline collaboration and productivity with your team – start by inviting them to join your workspace."
}, },
"msg": { "msg": {
"clickToCopyFieldId": "Click to copy Field Id", "clickToCopyFieldId": "Click to copy Field Id",
@ -1044,8 +1052,9 @@
"tooltip_desc": "A single record from table ", "tooltip_desc": "A single record from table ",
"tooltip_desc2": " can be linked with a single record from table " "tooltip_desc2": " can be linked with a single record from table "
}, },
"noRecordsAreLinkedFromTable": "No records are linked from table", "clickLinkRecordsToAddLinkFromTable": "Click 'Link Records' to begin associating data with '{tableName}'.",
"noRecordsLinked": "No records linked", "noRecordsLinked": "No records linked",
"noLinkedRecords": "No linked records",
"recordsLinked": "records linked", "recordsLinked": "records linked",
"acceptOnlyValid": "Accepts only", "acceptOnlyValid": "Accepts only",
"apiTokenCreate": "Create personal API tokens to use in automation or external apps.", "apiTokenCreate": "Create personal API tokens to use in automation or external apps.",
@ -1053,8 +1062,7 @@
"selectFieldToGroup": "Select Field to Group", "selectFieldToGroup": "Select Field to Group",
"thereAreNoRecordsInTable": "There are no records in table", "thereAreNoRecordsInTable": "There are no records in table",
"createWebhookMsg1": "Get started with web-hooks!", "createWebhookMsg1": "Get started with web-hooks!",
"createWebhookMsg2": "Create web-hooks to power you automations,", "createWebhookMsg2": "Power your automations. Get notified as soon as there are changes in your data",
"createWebhookMsg3": "Get notified as soon as there are changes in your data",
"areYouSureUWantTo": "Are you sure you want to delete the following", "areYouSureUWantTo": "Are you sure you want to delete the following",
"areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following", "areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following",
"idColumnRequired": "ID field is required, you can rename this later if required.", "idColumnRequired": "ID field is required, you can rename this later if required.",

18
packages/nc-gui/lang/sl.json

@ -332,6 +332,7 @@
"virtualRelation": "Virtual Relation", "virtualRelation": "Virtual Relation",
"linkMore": "Link More", "linkMore": "Link More",
"linkMoreRecords": "Link more records", "linkMoreRecords": "Link more records",
"linkRecords": "Link Records",
"downloadFile": "Download File", "downloadFile": "Download File",
"renameTable": "Rename Table", "renameTable": "Rename Table",
"renamingTable": "Renaming Table", "renamingTable": "Renaming Table",
@ -398,6 +399,7 @@
"findRowByScanningCode": "Find row by scanning a QR or Barcode", "findRowByScanningCode": "Find row by scanning a QR or Barcode",
"tokenManagement": "Token Management", "tokenManagement": "Token Management",
"addNewToken": "Add new token", "addNewToken": "Add new token",
"createNewToken": "Create new token",
"accountSettings": "Account Settings", "accountSettings": "Account Settings",
"resetPasswordMenu": "Reset Password", "resetPasswordMenu": "Reset Password",
"tokens": "Tokens", "tokens": "Tokens",
@ -665,7 +667,7 @@
"deletedField": "Deleted field", "deletedField": "Deleted field",
"incompleteConfiguration": "Incomplete configuration", "incompleteConfiguration": "Incomplete configuration",
"selectField": "Select a field", "selectField": "Select a field",
"selectFieldLabel": "Make changes to field properties by selecting a field from the list" "selectFieldLabel": "Begin by selecting a field to customise its properties and structure."
} }
}, },
"activity": { "activity": {
@ -959,7 +961,13 @@
"decimal7": "1.0000000", "decimal7": "1.0000000",
"decimal8": "1.00000000", "decimal8": "1.00000000",
"value": "Value", "value": "Value",
"key": "Key" "key": "Key",
"createTable": "Create your First Table!",
"createTableLabel": "Create your first table effortlessly, from scratch, or by importing/connecting to an external database.",
"noTokenCreated": "No API Tokens created",
"noTokenCreatedLabel": "Begin by creating API tokens to unlock advanced functionalities.",
"inviteYourTeam": "Invite your team",
"inviteYourTeamLabel": "Streamline collaboration and productivity with your team – start by inviting them to join your workspace."
}, },
"msg": { "msg": {
"clickToCopyFieldId": "Click to copy Field Id", "clickToCopyFieldId": "Click to copy Field Id",
@ -1044,8 +1052,9 @@
"tooltip_desc": "A single record from table ", "tooltip_desc": "A single record from table ",
"tooltip_desc2": " can be linked with a single record from table " "tooltip_desc2": " can be linked with a single record from table "
}, },
"noRecordsAreLinkedFromTable": "No records are linked from table", "clickLinkRecordsToAddLinkFromTable": "Click 'Link Records' to begin associating data with '{tableName}'.",
"noRecordsLinked": "No records linked", "noRecordsLinked": "No records linked",
"noLinkedRecords": "No linked records",
"recordsLinked": "records linked", "recordsLinked": "records linked",
"acceptOnlyValid": "Accepts only", "acceptOnlyValid": "Accepts only",
"apiTokenCreate": "Create personal API tokens to use in automation or external apps.", "apiTokenCreate": "Create personal API tokens to use in automation or external apps.",
@ -1053,8 +1062,7 @@
"selectFieldToGroup": "Select Field to Group", "selectFieldToGroup": "Select Field to Group",
"thereAreNoRecordsInTable": "There are no records in table", "thereAreNoRecordsInTable": "There are no records in table",
"createWebhookMsg1": "Get started with web-hooks!", "createWebhookMsg1": "Get started with web-hooks!",
"createWebhookMsg2": "Create web-hooks to power you automations,", "createWebhookMsg2": "Power your automations. Get notified as soon as there are changes in your data",
"createWebhookMsg3": "Get notified as soon as there are changes in your data",
"areYouSureUWantTo": "Are you sure you want to delete the following", "areYouSureUWantTo": "Are you sure you want to delete the following",
"areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following", "areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following",
"idColumnRequired": "ID field is required, you can rename this later if required.", "idColumnRequired": "ID field is required, you can rename this later if required.",

62
packages/nc-gui/lang/sv.json

@ -52,13 +52,13 @@
"or": "Eller", "or": "Eller",
"add": "Lägg till", "add": "Lägg till",
"edit": "Redigera", "edit": "Redigera",
"link": "Link", "link": "Länk",
"links": "Links", "links": "Länkar",
"remove": "Avlägsna", "remove": "Avlägsna",
"import": "Import", "import": "Import",
"logout": "Log Out", "logout": "Logga ut",
"empty": "Empty", "empty": "Empty",
"changeIcon": "Change Icon", "changeIcon": "Ändra ikon",
"save": "Spara", "save": "Spara",
"available": "Available", "available": "Available",
"abort": "Abort", "abort": "Abort",
@ -67,7 +67,7 @@
"null": "Null", "null": "Null",
"escape": "Escape", "escape": "Escape",
"hex": "Hex", "hex": "Hex",
"clear": "Clear", "clear": "Rensa",
"slack": "Slack", "slack": "Slack",
"comment": "Comment", "comment": "Comment",
"microsoftTeams": "Microsoft Teams", "microsoftTeams": "Microsoft Teams",
@ -83,7 +83,7 @@
"creatingEntity": "Creating {entity}", "creatingEntity": "Creating {entity}",
"details": "Details", "details": "Details",
"skip": "Skip", "skip": "Skip",
"code": "Code", "code": "Kod",
"duplicate": "Dubbla", "duplicate": "Dubbla",
"duplicating": "Duplicating", "duplicating": "Duplicating",
"activate": "Activate", "activate": "Activate",
@ -165,7 +165,7 @@
"move": "Move", "move": "Move",
"geoDataField": "GeoData Field", "geoDataField": "GeoData Field",
"type": "Type", "type": "Type",
"name": "Name", "name": "Namn",
"changes": "Changes", "changes": "Changes",
"new": "New", "new": "New",
"old": "Old", "old": "Old",
@ -301,7 +301,7 @@
"isNotNull": "är inte noll" "isNotNull": "är inte noll"
}, },
"title": { "title": {
"sso": "Authentication (SSO)", "sso": "Autentisering (SSO)",
"docs": "Docs", "docs": "Docs",
"forum": "Forum", "forum": "Forum",
"parameter": "Parameter", "parameter": "Parameter",
@ -316,8 +316,8 @@
"inDesktop": "in Desktop", "inDesktop": "in Desktop",
"rowData": "Record data", "rowData": "Record data",
"creator": "Creator", "creator": "Creator",
"qrCode": "QR Code", "qrCode": "QR-kod",
"termsOfService": "Terms of Service", "termsOfService": "Användarvillkor",
"updateSelectedRows": "Update Selected Records", "updateSelectedRows": "Update Selected Records",
"noFiltersAdded": "No filters added", "noFiltersAdded": "No filters added",
"editCards": "Edit Cards", "editCards": "Edit Cards",
@ -332,6 +332,7 @@
"virtualRelation": "Virtual Relation", "virtualRelation": "Virtual Relation",
"linkMore": "Link More", "linkMore": "Link More",
"linkMoreRecords": "Link more records", "linkMoreRecords": "Link more records",
"linkRecords": "Link Records",
"downloadFile": "Download File", "downloadFile": "Download File",
"renameTable": "Rename Table", "renameTable": "Rename Table",
"renamingTable": "Renaming Table", "renamingTable": "Renaming Table",
@ -381,7 +382,7 @@
"generateToken": "Generera en token", "generateToken": "Generera en token",
"APIsAndSupport": "API:er och stöd", "APIsAndSupport": "API:er och stöd",
"helpCenter": "Hjälpcenter", "helpCenter": "Hjälpcenter",
"noLabels": "No Labels", "noLabels": "Inga etiketter",
"swaggerDocumentation": "Dokumentation om Swagger", "swaggerDocumentation": "Dokumentation om Swagger",
"quickImportFrom": "Snabb import från", "quickImportFrom": "Snabb import från",
"quickImport": "Snabb import", "quickImport": "Snabb import",
@ -398,12 +399,13 @@
"findRowByScanningCode": "Find row by scanning a QR or Barcode", "findRowByScanningCode": "Find row by scanning a QR or Barcode",
"tokenManagement": "Token Management", "tokenManagement": "Token Management",
"addNewToken": "Add new token", "addNewToken": "Add new token",
"accountSettings": "Account Settings", "createNewToken": "Create new token",
"resetPasswordMenu": "Reset Password", "accountSettings": "Kontoinställningar",
"resetPasswordMenu": "Återställ lösenord",
"tokens": "Tokens", "tokens": "Tokens",
"userManagement": "User Management", "userManagement": "User Management",
"accountManagement": "Account management", "accountManagement": "Account management",
"licence": "Licence", "licence": "Licens",
"allowAllMimeTypes": "Allow All Mime Types", "allowAllMimeTypes": "Allow All Mime Types",
"defaultView": "Default View", "defaultView": "Default View",
"relations": "Relations", "relations": "Relations",
@ -418,8 +420,8 @@
} }
}, },
"labels": { "labels": {
"save": "Save", "save": "Spara",
"cancel": "Cancel", "cancel": "Avbryt",
"metadataUrl": "Metadata URL", "metadataUrl": "Metadata URL",
"audience-entityId": "Audience/ Entity ID", "audience-entityId": "Audience/ Entity ID",
"redirectUrl": "Redirect URL", "redirectUrl": "Redirect URL",
@ -445,11 +447,11 @@
"duplicateAttachment": "File with name {filename} already attached", "duplicateAttachment": "File with name {filename} already attached",
"viewIdColon": "VIEW ID: {viewId}", "viewIdColon": "VIEW ID: {viewId}",
"toAddress": "To Address", "toAddress": "To Address",
"subject": "Subject", "subject": "Ämne",
"body": "Body", "body": "Body",
"commaSeparatedMobileNumber": "Comma separated Mobile #", "commaSeparatedMobileNumber": "Comma separated Mobile #",
"headerName": "Header Name", "headerName": "Header Name",
"icon": "Icon", "icon": "Ikon",
"max": "Max", "max": "Max",
"enableRichText": "Enable Rich Text", "enableRichText": "Enable Rich Text",
"idColon": "Id:", "idColon": "Id:",
@ -495,7 +497,7 @@
"createdOn": "Created On", "createdOn": "Created On",
"notifyVia": "Meddela via", "notifyVia": "Meddela via",
"projName": "Projektnamn", "projName": "Projektnamn",
"profile": "Profile", "profile": "Profil",
"accountDetails": "Account Details", "accountDetails": "Account Details",
"controlAppearance": "Control your Appearance.", "controlAppearance": "Control your Appearance.",
"accountEmailID": "Account Email ID", "accountEmailID": "Account Email ID",
@ -660,12 +662,12 @@
"webhook": "Webhook", "webhook": "Webhook",
"multiField": { "multiField": {
"newField": "New field", "newField": "New field",
"saveChanges": "Save changes", "saveChanges": "Spara ändringar",
"updatedField": "Updated field", "updatedField": "Updated field",
"deletedField": "Deleted field", "deletedField": "Deleted field",
"incompleteConfiguration": "Incomplete configuration", "incompleteConfiguration": "Incomplete configuration",
"selectField": "Select a field", "selectField": "Select a field",
"selectFieldLabel": "Make changes to field properties by selecting a field from the list" "selectFieldLabel": "Begin by selecting a field to customise its properties and structure."
} }
}, },
"activity": { "activity": {
@ -959,11 +961,17 @@
"decimal7": "1.0000000", "decimal7": "1.0000000",
"decimal8": "1.00000000", "decimal8": "1.00000000",
"value": "Value", "value": "Value",
"key": "Key" "key": "Key",
"createTable": "Create your First Table!",
"createTableLabel": "Create your first table effortlessly, from scratch, or by importing/connecting to an external database.",
"noTokenCreated": "No API Tokens created",
"noTokenCreatedLabel": "Begin by creating API tokens to unlock advanced functionalities.",
"inviteYourTeam": "Invite your team",
"inviteYourTeamLabel": "Streamline collaboration and productivity with your team – start by inviting them to join your workspace."
}, },
"msg": { "msg": {
"clickToCopyFieldId": "Click to copy Field Id", "clickToCopyFieldId": "Click to copy Field Id",
"enterPassword": "Enter password", "enterPassword": "Ange lösenord",
"bySigningUp": "By signing up, you agree to the", "bySigningUp": "By signing up, you agree to the",
"subscribeToOurWeeklyNewsletter": "Subscribe to our weekly newsletter", "subscribeToOurWeeklyNewsletter": "Subscribe to our weekly newsletter",
"verifyingPassword": "Verifying Password", "verifyingPassword": "Verifying Password",
@ -1044,8 +1052,9 @@
"tooltip_desc": "A single record from table ", "tooltip_desc": "A single record from table ",
"tooltip_desc2": " can be linked with a single record from table " "tooltip_desc2": " can be linked with a single record from table "
}, },
"noRecordsAreLinkedFromTable": "No records are linked from table", "clickLinkRecordsToAddLinkFromTable": "Click 'Link Records' to begin associating data with '{tableName}'.",
"noRecordsLinked": "No records linked", "noRecordsLinked": "No records linked",
"noLinkedRecords": "No linked records",
"recordsLinked": "records linked", "recordsLinked": "records linked",
"acceptOnlyValid": "Accepts only", "acceptOnlyValid": "Accepts only",
"apiTokenCreate": "Create personal API tokens to use in automation or external apps.", "apiTokenCreate": "Create personal API tokens to use in automation or external apps.",
@ -1053,8 +1062,7 @@
"selectFieldToGroup": "Select Field to Group", "selectFieldToGroup": "Select Field to Group",
"thereAreNoRecordsInTable": "There are no records in table", "thereAreNoRecordsInTable": "There are no records in table",
"createWebhookMsg1": "Get started with web-hooks!", "createWebhookMsg1": "Get started with web-hooks!",
"createWebhookMsg2": "Create web-hooks to power you automations,", "createWebhookMsg2": "Power your automations. Get notified as soon as there are changes in your data",
"createWebhookMsg3": "Get notified as soon as there are changes in your data",
"areYouSureUWantTo": "Are you sure you want to delete the following", "areYouSureUWantTo": "Are you sure you want to delete the following",
"areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following", "areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following",
"idColumnRequired": "ID field is required, you can rename this later if required.", "idColumnRequired": "ID field is required, you can rename this later if required.",
@ -1244,7 +1252,7 @@
"makeLineBreak": "to make a line break", "makeLineBreak": "to make a line break",
"goToPrevious": "Go to previous", "goToPrevious": "Go to previous",
"goToNext": "Go to next", "goToNext": "Go to next",
"thankYou": "Thank you!", "thankYou": "Tack!",
"submittedFormData": "You have successfully submitted the form data.", "submittedFormData": "You have successfully submitted the form data.",
"editingSystemKeyNotSupported": "Editing system key not supported", "editingSystemKeyNotSupported": "Editing system key not supported",
"notAvailableAtTheMoment": "Not available at the moment" "notAvailableAtTheMoment": "Not available at the moment"

18
packages/nc-gui/lang/th.json

@ -332,6 +332,7 @@
"virtualRelation": "Virtual Relation", "virtualRelation": "Virtual Relation",
"linkMore": "Link More", "linkMore": "Link More",
"linkMoreRecords": "Link more records", "linkMoreRecords": "Link more records",
"linkRecords": "Link Records",
"downloadFile": "Download File", "downloadFile": "Download File",
"renameTable": "Rename Table", "renameTable": "Rename Table",
"renamingTable": "Renaming Table", "renamingTable": "Renaming Table",
@ -398,6 +399,7 @@
"findRowByScanningCode": "หาแถวโดยสแกน QR โคด", "findRowByScanningCode": "หาแถวโดยสแกน QR โคด",
"tokenManagement": "Token Management", "tokenManagement": "Token Management",
"addNewToken": "Add new token", "addNewToken": "Add new token",
"createNewToken": "Create new token",
"accountSettings": "Account Settings", "accountSettings": "Account Settings",
"resetPasswordMenu": "Reset Password", "resetPasswordMenu": "Reset Password",
"tokens": "Tokens", "tokens": "Tokens",
@ -665,7 +667,7 @@
"deletedField": "Deleted field", "deletedField": "Deleted field",
"incompleteConfiguration": "Incomplete configuration", "incompleteConfiguration": "Incomplete configuration",
"selectField": "Select a field", "selectField": "Select a field",
"selectFieldLabel": "Make changes to field properties by selecting a field from the list" "selectFieldLabel": "Begin by selecting a field to customise its properties and structure."
} }
}, },
"activity": { "activity": {
@ -959,7 +961,13 @@
"decimal7": "1.0000000", "decimal7": "1.0000000",
"decimal8": "1.00000000", "decimal8": "1.00000000",
"value": "Value", "value": "Value",
"key": "Key" "key": "Key",
"createTable": "Create your First Table!",
"createTableLabel": "Create your first table effortlessly, from scratch, or by importing/connecting to an external database.",
"noTokenCreated": "No API Tokens created",
"noTokenCreatedLabel": "Begin by creating API tokens to unlock advanced functionalities.",
"inviteYourTeam": "Invite your team",
"inviteYourTeamLabel": "Streamline collaboration and productivity with your team – start by inviting them to join your workspace."
}, },
"msg": { "msg": {
"clickToCopyFieldId": "Click to copy Field Id", "clickToCopyFieldId": "Click to copy Field Id",
@ -1044,8 +1052,9 @@
"tooltip_desc": "A single record from table ", "tooltip_desc": "A single record from table ",
"tooltip_desc2": " can be linked with a single record from table " "tooltip_desc2": " can be linked with a single record from table "
}, },
"noRecordsAreLinkedFromTable": "No records are linked from table", "clickLinkRecordsToAddLinkFromTable": "Click 'Link Records' to begin associating data with '{tableName}'.",
"noRecordsLinked": "No records linked", "noRecordsLinked": "No records linked",
"noLinkedRecords": "No linked records",
"recordsLinked": "records linked", "recordsLinked": "records linked",
"acceptOnlyValid": "Accepts only", "acceptOnlyValid": "Accepts only",
"apiTokenCreate": "Create personal API tokens to use in automation or external apps.", "apiTokenCreate": "Create personal API tokens to use in automation or external apps.",
@ -1053,8 +1062,7 @@
"selectFieldToGroup": "Select Field to Group", "selectFieldToGroup": "Select Field to Group",
"thereAreNoRecordsInTable": "There are no records in table", "thereAreNoRecordsInTable": "There are no records in table",
"createWebhookMsg1": "Get started with web-hooks!", "createWebhookMsg1": "Get started with web-hooks!",
"createWebhookMsg2": "Create web-hooks to power you automations,", "createWebhookMsg2": "Power your automations. Get notified as soon as there are changes in your data",
"createWebhookMsg3": "Get notified as soon as there are changes in your data",
"areYouSureUWantTo": "Are you sure you want to delete the following", "areYouSureUWantTo": "Are you sure you want to delete the following",
"areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following", "areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following",
"idColumnRequired": "ID field is required, you can rename this later if required.", "idColumnRequired": "ID field is required, you can rename this later if required.",

18
packages/nc-gui/lang/tr.json

@ -332,6 +332,7 @@
"virtualRelation": "Virtual Relation", "virtualRelation": "Virtual Relation",
"linkMore": "Link More", "linkMore": "Link More",
"linkMoreRecords": "Link more records", "linkMoreRecords": "Link more records",
"linkRecords": "Link Records",
"downloadFile": "Download File", "downloadFile": "Download File",
"renameTable": "Rename Table", "renameTable": "Rename Table",
"renamingTable": "Renaming Table", "renamingTable": "Renaming Table",
@ -398,6 +399,7 @@
"findRowByScanningCode": "QR veya Barkod okutarak satır bulun", "findRowByScanningCode": "QR veya Barkod okutarak satır bulun",
"tokenManagement": "Token Management", "tokenManagement": "Token Management",
"addNewToken": "Add new token", "addNewToken": "Add new token",
"createNewToken": "Create new token",
"accountSettings": "Account Settings", "accountSettings": "Account Settings",
"resetPasswordMenu": "Reset Password", "resetPasswordMenu": "Reset Password",
"tokens": "Tokens", "tokens": "Tokens",
@ -665,7 +667,7 @@
"deletedField": "Deleted field", "deletedField": "Deleted field",
"incompleteConfiguration": "Incomplete configuration", "incompleteConfiguration": "Incomplete configuration",
"selectField": "Select a field", "selectField": "Select a field",
"selectFieldLabel": "Make changes to field properties by selecting a field from the list" "selectFieldLabel": "Begin by selecting a field to customise its properties and structure."
} }
}, },
"activity": { "activity": {
@ -959,7 +961,13 @@
"decimal7": "1.0000000", "decimal7": "1.0000000",
"decimal8": "1.00000000", "decimal8": "1.00000000",
"value": "Value", "value": "Value",
"key": "Key" "key": "Key",
"createTable": "Create your First Table!",
"createTableLabel": "Create your first table effortlessly, from scratch, or by importing/connecting to an external database.",
"noTokenCreated": "No API Tokens created",
"noTokenCreatedLabel": "Begin by creating API tokens to unlock advanced functionalities.",
"inviteYourTeam": "Invite your team",
"inviteYourTeamLabel": "Streamline collaboration and productivity with your team – start by inviting them to join your workspace."
}, },
"msg": { "msg": {
"clickToCopyFieldId": "Click to copy Field Id", "clickToCopyFieldId": "Click to copy Field Id",
@ -1044,8 +1052,9 @@
"tooltip_desc": "A single record from table ", "tooltip_desc": "A single record from table ",
"tooltip_desc2": " can be linked with a single record from table " "tooltip_desc2": " can be linked with a single record from table "
}, },
"noRecordsAreLinkedFromTable": "No records are linked from table", "clickLinkRecordsToAddLinkFromTable": "Click 'Link Records' to begin associating data with '{tableName}'.",
"noRecordsLinked": "No records linked", "noRecordsLinked": "No records linked",
"noLinkedRecords": "No linked records",
"recordsLinked": "records linked", "recordsLinked": "records linked",
"acceptOnlyValid": "Accepts only", "acceptOnlyValid": "Accepts only",
"apiTokenCreate": "Create personal API tokens to use in automation or external apps.", "apiTokenCreate": "Create personal API tokens to use in automation or external apps.",
@ -1053,8 +1062,7 @@
"selectFieldToGroup": "Select Field to Group", "selectFieldToGroup": "Select Field to Group",
"thereAreNoRecordsInTable": "There are no records in table", "thereAreNoRecordsInTable": "There are no records in table",
"createWebhookMsg1": "Get started with web-hooks!", "createWebhookMsg1": "Get started with web-hooks!",
"createWebhookMsg2": "Create web-hooks to power you automations,", "createWebhookMsg2": "Power your automations. Get notified as soon as there are changes in your data",
"createWebhookMsg3": "Get notified as soon as there are changes in your data",
"areYouSureUWantTo": "Are you sure you want to delete the following", "areYouSureUWantTo": "Are you sure you want to delete the following",
"areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following", "areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following",
"idColumnRequired": "ID field is required, you can rename this later if required.", "idColumnRequired": "ID field is required, you can rename this later if required.",

18
packages/nc-gui/lang/uk.json

@ -332,6 +332,7 @@
"virtualRelation": "Virtual Relation", "virtualRelation": "Virtual Relation",
"linkMore": "Link More", "linkMore": "Link More",
"linkMoreRecords": "Link more records", "linkMoreRecords": "Link more records",
"linkRecords": "Link Records",
"downloadFile": "Download File", "downloadFile": "Download File",
"renameTable": "Rename Table", "renameTable": "Rename Table",
"renamingTable": "Renaming Table", "renamingTable": "Renaming Table",
@ -398,6 +399,7 @@
"findRowByScanningCode": "Знайти рядок, відсканувавши QR-код або штрих-код", "findRowByScanningCode": "Знайти рядок, відсканувавши QR-код або штрих-код",
"tokenManagement": "Token Management", "tokenManagement": "Token Management",
"addNewToken": "Add new token", "addNewToken": "Add new token",
"createNewToken": "Create new token",
"accountSettings": "Account Settings", "accountSettings": "Account Settings",
"resetPasswordMenu": "Reset Password", "resetPasswordMenu": "Reset Password",
"tokens": "Tokens", "tokens": "Tokens",
@ -665,7 +667,7 @@
"deletedField": "Deleted field", "deletedField": "Deleted field",
"incompleteConfiguration": "Incomplete configuration", "incompleteConfiguration": "Incomplete configuration",
"selectField": "Select a field", "selectField": "Select a field",
"selectFieldLabel": "Make changes to field properties by selecting a field from the list" "selectFieldLabel": "Begin by selecting a field to customise its properties and structure."
} }
}, },
"activity": { "activity": {
@ -959,7 +961,13 @@
"decimal7": "1.0000000", "decimal7": "1.0000000",
"decimal8": "1.00000000", "decimal8": "1.00000000",
"value": "Value", "value": "Value",
"key": "Key" "key": "Key",
"createTable": "Create your First Table!",
"createTableLabel": "Create your first table effortlessly, from scratch, or by importing/connecting to an external database.",
"noTokenCreated": "No API Tokens created",
"noTokenCreatedLabel": "Begin by creating API tokens to unlock advanced functionalities.",
"inviteYourTeam": "Invite your team",
"inviteYourTeamLabel": "Streamline collaboration and productivity with your team – start by inviting them to join your workspace."
}, },
"msg": { "msg": {
"clickToCopyFieldId": "Click to copy Field Id", "clickToCopyFieldId": "Click to copy Field Id",
@ -1044,8 +1052,9 @@
"tooltip_desc": "A single record from table ", "tooltip_desc": "A single record from table ",
"tooltip_desc2": " can be linked with a single record from table " "tooltip_desc2": " can be linked with a single record from table "
}, },
"noRecordsAreLinkedFromTable": "No records are linked from table", "clickLinkRecordsToAddLinkFromTable": "Click 'Link Records' to begin associating data with '{tableName}'.",
"noRecordsLinked": "No records linked", "noRecordsLinked": "No records linked",
"noLinkedRecords": "No linked records",
"recordsLinked": "records linked", "recordsLinked": "records linked",
"acceptOnlyValid": "Accepts only", "acceptOnlyValid": "Accepts only",
"apiTokenCreate": "Create personal API tokens to use in automation or external apps.", "apiTokenCreate": "Create personal API tokens to use in automation or external apps.",
@ -1053,8 +1062,7 @@
"selectFieldToGroup": "Select Field to Group", "selectFieldToGroup": "Select Field to Group",
"thereAreNoRecordsInTable": "There are no records in table", "thereAreNoRecordsInTable": "There are no records in table",
"createWebhookMsg1": "Get started with web-hooks!", "createWebhookMsg1": "Get started with web-hooks!",
"createWebhookMsg2": "Create web-hooks to power you automations,", "createWebhookMsg2": "Power your automations. Get notified as soon as there are changes in your data",
"createWebhookMsg3": "Get notified as soon as there are changes in your data",
"areYouSureUWantTo": "Are you sure you want to delete the following", "areYouSureUWantTo": "Are you sure you want to delete the following",
"areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following", "areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following",
"idColumnRequired": "ID field is required, you can rename this later if required.", "idColumnRequired": "ID field is required, you can rename this later if required.",

18
packages/nc-gui/lang/vi.json

@ -332,6 +332,7 @@
"virtualRelation": "Virtual Relation", "virtualRelation": "Virtual Relation",
"linkMore": "Link More", "linkMore": "Link More",
"linkMoreRecords": "Link more records", "linkMoreRecords": "Link more records",
"linkRecords": "Link Records",
"downloadFile": "Download File", "downloadFile": "Download File",
"renameTable": "Rename Table", "renameTable": "Rename Table",
"renamingTable": "Renaming Table", "renamingTable": "Renaming Table",
@ -398,6 +399,7 @@
"findRowByScanningCode": "Find row by scanning a QR or Barcode", "findRowByScanningCode": "Find row by scanning a QR or Barcode",
"tokenManagement": "Token Management", "tokenManagement": "Token Management",
"addNewToken": "Thêm mới token", "addNewToken": "Thêm mới token",
"createNewToken": "Create new token",
"accountSettings": "Account Settings", "accountSettings": "Account Settings",
"resetPasswordMenu": "Reset Password", "resetPasswordMenu": "Reset Password",
"tokens": "Tokens", "tokens": "Tokens",
@ -665,7 +667,7 @@
"deletedField": "Deleted field", "deletedField": "Deleted field",
"incompleteConfiguration": "Incomplete configuration", "incompleteConfiguration": "Incomplete configuration",
"selectField": "Select a field", "selectField": "Select a field",
"selectFieldLabel": "Make changes to field properties by selecting a field from the list" "selectFieldLabel": "Begin by selecting a field to customise its properties and structure."
} }
}, },
"activity": { "activity": {
@ -959,7 +961,13 @@
"decimal7": "1.0000000", "decimal7": "1.0000000",
"decimal8": "1.00000000", "decimal8": "1.00000000",
"value": "Value", "value": "Value",
"key": "Key" "key": "Key",
"createTable": "Create your First Table!",
"createTableLabel": "Create your first table effortlessly, from scratch, or by importing/connecting to an external database.",
"noTokenCreated": "No API Tokens created",
"noTokenCreatedLabel": "Begin by creating API tokens to unlock advanced functionalities.",
"inviteYourTeam": "Invite your team",
"inviteYourTeamLabel": "Streamline collaboration and productivity with your team – start by inviting them to join your workspace."
}, },
"msg": { "msg": {
"clickToCopyFieldId": "Click to copy Field Id", "clickToCopyFieldId": "Click to copy Field Id",
@ -1044,8 +1052,9 @@
"tooltip_desc": "A single record from table ", "tooltip_desc": "A single record from table ",
"tooltip_desc2": " can be linked with a single record from table " "tooltip_desc2": " can be linked with a single record from table "
}, },
"noRecordsAreLinkedFromTable": "No records are linked from table", "clickLinkRecordsToAddLinkFromTable": "Click 'Link Records' to begin associating data with '{tableName}'.",
"noRecordsLinked": "No records linked", "noRecordsLinked": "No records linked",
"noLinkedRecords": "No linked records",
"recordsLinked": "records linked", "recordsLinked": "records linked",
"acceptOnlyValid": "Accepts only", "acceptOnlyValid": "Accepts only",
"apiTokenCreate": "Create personal API tokens to use in automation or external apps.", "apiTokenCreate": "Create personal API tokens to use in automation or external apps.",
@ -1053,8 +1062,7 @@
"selectFieldToGroup": "Select Field to Group", "selectFieldToGroup": "Select Field to Group",
"thereAreNoRecordsInTable": "There are no records in table", "thereAreNoRecordsInTable": "There are no records in table",
"createWebhookMsg1": "Get started with web-hooks!", "createWebhookMsg1": "Get started with web-hooks!",
"createWebhookMsg2": "Create web-hooks to power you automations,", "createWebhookMsg2": "Power your automations. Get notified as soon as there are changes in your data",
"createWebhookMsg3": "Get notified as soon as there are changes in your data",
"areYouSureUWantTo": "Are you sure you want to delete the following", "areYouSureUWantTo": "Are you sure you want to delete the following",
"areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following", "areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following",
"idColumnRequired": "ID field is required, you can rename this later if required.", "idColumnRequired": "ID field is required, you can rename this later if required.",

18
packages/nc-gui/lang/zh-Hans.json

@ -332,6 +332,7 @@
"virtualRelation": "虚拟关系", "virtualRelation": "虚拟关系",
"linkMore": "链接更多", "linkMore": "链接更多",
"linkMoreRecords": "链接更多记录", "linkMoreRecords": "链接更多记录",
"linkRecords": "Link Records",
"downloadFile": "下载文件", "downloadFile": "下载文件",
"renameTable": "重命名表格", "renameTable": "重命名表格",
"renamingTable": "重新命名表格", "renamingTable": "重新命名表格",
@ -398,6 +399,7 @@
"findRowByScanningCode": "通过扫描二维码或条码查找行", "findRowByScanningCode": "通过扫描二维码或条码查找行",
"tokenManagement": "Token 管理", "tokenManagement": "Token 管理",
"addNewToken": "添加新令牌(Token)", "addNewToken": "添加新令牌(Token)",
"createNewToken": "Create new token",
"accountSettings": "账户设置", "accountSettings": "账户设置",
"resetPasswordMenu": "密码重置", "resetPasswordMenu": "密码重置",
"tokens": "令牌", "tokens": "令牌",
@ -665,7 +667,7 @@
"deletedField": "已删除的字段", "deletedField": "已删除的字段",
"incompleteConfiguration": "配置不完整", "incompleteConfiguration": "配置不完整",
"selectField": "请设置需要查询的字段", "selectField": "请设置需要查询的字段",
"selectFieldLabel": "从列表中选择一个字段来更改字段属性" "selectFieldLabel": "Begin by selecting a field to customise its properties and structure."
} }
}, },
"activity": { "activity": {
@ -959,7 +961,13 @@
"decimal7": "1.0000000", "decimal7": "1.0000000",
"decimal8": "1.00000000", "decimal8": "1.00000000",
"value": "值", "value": "值",
"key": "键" "key": "键",
"createTable": "Create your First Table!",
"createTableLabel": "Create your first table effortlessly, from scratch, or by importing/connecting to an external database.",
"noTokenCreated": "No API Tokens created",
"noTokenCreatedLabel": "Begin by creating API tokens to unlock advanced functionalities.",
"inviteYourTeam": "Invite your team",
"inviteYourTeamLabel": "Streamline collaboration and productivity with your team – start by inviting them to join your workspace."
}, },
"msg": { "msg": {
"clickToCopyFieldId": "点击复制字段ID", "clickToCopyFieldId": "点击复制字段ID",
@ -1044,8 +1052,9 @@
"tooltip_desc": "表中的一条记录 ", "tooltip_desc": "表中的一条记录 ",
"tooltip_desc2": " 可以从表中链接到单条记录 " "tooltip_desc2": " 可以从表中链接到单条记录 "
}, },
"noRecordsAreLinkedFromTable": "表中没有链接记录", "clickLinkRecordsToAddLinkFromTable": "Click 'Link Records' to begin associating data with '{tableName}'.",
"noRecordsLinked": "没有链接记录", "noRecordsLinked": "没有链接记录",
"noLinkedRecords": "No linked records",
"recordsLinked": "链接的记录", "recordsLinked": "链接的记录",
"acceptOnlyValid": "仅接受", "acceptOnlyValid": "仅接受",
"apiTokenCreate": "创建个人 API tokens,以便在自动化或外部应用程序中使用。", "apiTokenCreate": "创建个人 API tokens,以便在自动化或外部应用程序中使用。",
@ -1053,8 +1062,7 @@
"selectFieldToGroup": "选择要分组的字段", "selectFieldToGroup": "选择要分组的字段",
"thereAreNoRecordsInTable": "表中没有记录", "thereAreNoRecordsInTable": "表中没有记录",
"createWebhookMsg1": "开始使用Web Hooks!", "createWebhookMsg1": "开始使用Web Hooks!",
"createWebhookMsg2": "创建Web Hooks来实现自动化", "createWebhookMsg2": "Power your automations. Get notified as soon as there are changes in your data",
"createWebhookMsg3": "一旦您的数据发生变化,您将立即收到通知",
"areYouSureUWantTo": "您确定要删除以下内容", "areYouSureUWantTo": "您确定要删除以下内容",
"areYouSureUWantToDeleteLabel": "您确定要 {deleteLabel} 以下内容吗?", "areYouSureUWantToDeleteLabel": "您确定要 {deleteLabel} 以下内容吗?",
"idColumnRequired": "ID 字段是必填字段,如果需要,您可以稍后重新命名。", "idColumnRequired": "ID 字段是必填字段,如果需要,您可以稍后重新命名。",

18
packages/nc-gui/lang/zh-Hant.json

@ -332,6 +332,7 @@
"virtualRelation": "Virtual Relation", "virtualRelation": "Virtual Relation",
"linkMore": "Link More", "linkMore": "Link More",
"linkMoreRecords": "Link more records", "linkMoreRecords": "Link more records",
"linkRecords": "Link Records",
"downloadFile": "Download File", "downloadFile": "Download File",
"renameTable": "Rename Table", "renameTable": "Rename Table",
"renamingTable": "Renaming Table", "renamingTable": "Renaming Table",
@ -398,6 +399,7 @@
"findRowByScanningCode": "Find row by scanning a QR or Barcode", "findRowByScanningCode": "Find row by scanning a QR or Barcode",
"tokenManagement": "Token Management", "tokenManagement": "Token Management",
"addNewToken": "Add new token", "addNewToken": "Add new token",
"createNewToken": "Create new token",
"accountSettings": "Account Settings", "accountSettings": "Account Settings",
"resetPasswordMenu": "Reset Password", "resetPasswordMenu": "Reset Password",
"tokens": "Tokens", "tokens": "Tokens",
@ -665,7 +667,7 @@
"deletedField": "Deleted field", "deletedField": "Deleted field",
"incompleteConfiguration": "Incomplete configuration", "incompleteConfiguration": "Incomplete configuration",
"selectField": "Select a field", "selectField": "Select a field",
"selectFieldLabel": "Make changes to field properties by selecting a field from the list" "selectFieldLabel": "Begin by selecting a field to customise its properties and structure."
} }
}, },
"activity": { "activity": {
@ -959,7 +961,13 @@
"decimal7": "1.0000000", "decimal7": "1.0000000",
"decimal8": "1.00000000", "decimal8": "1.00000000",
"value": "Value", "value": "Value",
"key": "Key" "key": "Key",
"createTable": "Create your First Table!",
"createTableLabel": "Create your first table effortlessly, from scratch, or by importing/connecting to an external database.",
"noTokenCreated": "No API Tokens created",
"noTokenCreatedLabel": "Begin by creating API tokens to unlock advanced functionalities.",
"inviteYourTeam": "Invite your team",
"inviteYourTeamLabel": "Streamline collaboration and productivity with your team – start by inviting them to join your workspace."
}, },
"msg": { "msg": {
"clickToCopyFieldId": "Click to copy Field Id", "clickToCopyFieldId": "Click to copy Field Id",
@ -1044,8 +1052,9 @@
"tooltip_desc": "A single record from table ", "tooltip_desc": "A single record from table ",
"tooltip_desc2": " can be linked with a single record from table " "tooltip_desc2": " can be linked with a single record from table "
}, },
"noRecordsAreLinkedFromTable": "No records are linked from table", "clickLinkRecordsToAddLinkFromTable": "Click 'Link Records' to begin associating data with '{tableName}'.",
"noRecordsLinked": "No records linked", "noRecordsLinked": "No records linked",
"noLinkedRecords": "No linked records",
"recordsLinked": "records linked", "recordsLinked": "records linked",
"acceptOnlyValid": "Accepts only", "acceptOnlyValid": "Accepts only",
"apiTokenCreate": "Create personal API tokens to use in automation or external apps.", "apiTokenCreate": "Create personal API tokens to use in automation or external apps.",
@ -1053,8 +1062,7 @@
"selectFieldToGroup": "Select Field to Group", "selectFieldToGroup": "Select Field to Group",
"thereAreNoRecordsInTable": "There are no records in table", "thereAreNoRecordsInTable": "There are no records in table",
"createWebhookMsg1": "Get started with web-hooks!", "createWebhookMsg1": "Get started with web-hooks!",
"createWebhookMsg2": "Create web-hooks to power you automations,", "createWebhookMsg2": "Power your automations. Get notified as soon as there are changes in your data",
"createWebhookMsg3": "Get notified as soon as there are changes in your data",
"areYouSureUWantTo": "Are you sure you want to delete the following", "areYouSureUWantTo": "Are you sure you want to delete the following",
"areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following", "areYouSureUWantToDeleteLabel": "Are you sure you want to {deleteLabel} the following",
"idColumnRequired": "ID field is required, you can rename this later if required.", "idColumnRequired": "ID field is required, you can rename this later if required.",

6
packages/nc-gui/package.json

@ -106,7 +106,7 @@
"@esbuild-plugins/node-modules-polyfill": "^0.2.2", "@esbuild-plugins/node-modules-polyfill": "^0.2.2",
"@iconify-json/ant-design": "^1.1.15", "@iconify-json/ant-design": "^1.1.15",
"@iconify-json/bi": "^1.1.23", "@iconify-json/bi": "^1.1.23",
"@iconify-json/carbon": "^1.1.29", "@iconify-json/carbon": "^1.1.30",
"@iconify-json/cil": "^1.1.8", "@iconify-json/cil": "^1.1.8",
"@iconify-json/clarity": "^1.1.12", "@iconify-json/clarity": "^1.1.12",
"@iconify-json/eva": "^1.1.10", "@iconify-json/eva": "^1.1.10",
@ -114,13 +114,13 @@
"@iconify-json/ion": "^1.1.15", "@iconify-json/ion": "^1.1.15",
"@iconify-json/la": "^1.1.8", "@iconify-json/la": "^1.1.8",
"@iconify-json/logos": "^1.1.42", "@iconify-json/logos": "^1.1.42",
"@iconify-json/lucide": "^1.1.163", "@iconify-json/lucide": "^1.1.165",
"@iconify-json/material-symbols": "^1.1.72", "@iconify-json/material-symbols": "^1.1.72",
"@iconify-json/mdi": "^1.1.64", "@iconify-json/mdi": "^1.1.64",
"@iconify-json/mi": "^1.1.8", "@iconify-json/mi": "^1.1.8",
"@iconify-json/ph": "^1.1.11", "@iconify-json/ph": "^1.1.11",
"@iconify-json/ri": "^1.1.19", "@iconify-json/ri": "^1.1.19",
"@iconify-json/simple-icons": "^1.1.90", "@iconify-json/simple-icons": "^1.1.91",
"@iconify-json/system-uicons": "^1.1.12", "@iconify-json/system-uicons": "^1.1.12",
"@iconify-json/tabler": "^1.1.105", "@iconify-json/tabler": "^1.1.105",
"@iconify-json/vscode-icons": "^1.1.33", "@iconify-json/vscode-icons": "^1.1.33",

8
packages/noco-docs/package-lock.json generated

@ -14,7 +14,7 @@
"@docusaurus/plugin-ideal-image": "3.0.1", "@docusaurus/plugin-ideal-image": "3.0.1",
"@docusaurus/plugin-sitemap": "3.0.1", "@docusaurus/plugin-sitemap": "3.0.1",
"@docusaurus/preset-classic": "3.0.1", "@docusaurus/preset-classic": "3.0.1",
"@mdx-js/react": "^3.0.0", "@mdx-js/react": "^3.0.1",
"clsx": "^1.2.1", "clsx": "^1.2.1",
"docusaurus-plugin-sass": "^0.2.5", "docusaurus-plugin-sass": "^0.2.5",
"docusaurus-theme-search-typesense": "^0.14.1", "docusaurus-theme-search-typesense": "^0.14.1",
@ -3031,9 +3031,9 @@
} }
}, },
"node_modules/@mdx-js/react": { "node_modules/@mdx-js/react": {
"version": "3.0.0", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.0.0.tgz", "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.0.1.tgz",
"integrity": "sha512-nDctevR9KyYFyV+m+/+S4cpzCWHqj+iHDHq3QrsWezcC+B17uZdIWgCguESUkwFhM3n/56KxWVE3V6EokrmONQ==", "integrity": "sha512-9ZrPIU4MGf6et1m1ov3zKf+q9+deetI51zprKB1D/z3NOb+rUxxtEl3mCjW5wTGh6VhRdwPueh1oRzi6ezkA8A==",
"dependencies": { "dependencies": {
"@types/mdx": "^2.0.0" "@types/mdx": "^2.0.0"
}, },

2
packages/noco-docs/package.json

@ -33,7 +33,7 @@
"@docusaurus/plugin-ideal-image": "3.0.1", "@docusaurus/plugin-ideal-image": "3.0.1",
"@docusaurus/plugin-sitemap": "3.0.1", "@docusaurus/plugin-sitemap": "3.0.1",
"@docusaurus/preset-classic": "3.0.1", "@docusaurus/preset-classic": "3.0.1",
"@mdx-js/react": "^3.0.0", "@mdx-js/react": "^3.0.1",
"clsx": "^1.2.1", "clsx": "^1.2.1",
"docusaurus-plugin-sass": "^0.2.5", "docusaurus-plugin-sass": "^0.2.5",
"docusaurus-theme-search-typesense": "^0.14.1", "docusaurus-theme-search-typesense": "^0.14.1",

626
packages/nocodb/src/cache/CacheMgr.ts vendored

@ -1,36 +1,620 @@
import debug from 'debug';
import { Logger } from '@nestjs/common';
import type IORedis from 'ioredis';
import { CacheDelDirection, CacheGetType } from '~/utils/globals';
const log = debug('nc:cache');
const logger = new Logger('CacheMgr');
/*
- keys are stored as following:
- simple key: nc:<orgs>:<scope>:<model_id_1>
- value: { value: { ... }, parentKeys: [ "nc:<orgs>:<scope>:<model_id_1>:list" ], timestamp: 1234567890 }
- stored as stringified JSON
- list key: nc:<orgs>:<scope>:<model_id_1>:list
- stored as SET
- get returns `value` only
- getRaw returns the whole cache object with metadata
*/
const NC_REDIS_TTL = +process.env.NC_REDIS_TTL || 60 * 60 * 24 * 3; // 3 days
const NC_REDIS_GRACE_TTL = +process.env.NC_REDIS_GRACE_TTL || 60 * 60 * 24 * 1; // 1 day
export default abstract class CacheMgr { export default abstract class CacheMgr {
public abstract get(key: string, type: string): Promise<any>; client: IORedis;
public abstract set(key: string, value: any): Promise<any>; prefix: string;
public abstract setExpiring( context: string;
// avoid circular structure to JSON
getCircularReplacer = () => {
const seen = new WeakSet();
return (_, value) => {
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) {
return;
}
seen.add(value);
}
return value;
};
};
// @ts-ignore
async del(key: string[] | string): Promise<any> {
log(`${this.context}::del: deleting key ${key}`);
if (Array.isArray(key)) {
if (key.length) {
return this.client.del(key);
}
} else if (key) {
return this.client.del(key);
}
}
// @ts-ignore
private async getRaw(
key: string,
type?: string,
skipTTL = false,
): Promise<any> {
log(`${this.context}::getRaw: getting key ${key} with type ${type}`);
if (type === CacheGetType.TYPE_ARRAY) {
return this.client.smembers(key);
} else {
const res = await this.client.get(key);
if (res) {
try {
const o = JSON.parse(res);
if (typeof o === 'object') {
if (
o &&
Object.keys(o).length === 0 &&
Object.getPrototypeOf(o) === Object.prototype
) {
log(`${this.context}::get: object is empty!`);
}
if (!skipTTL && o.timestamp) {
const diff = Date.now() - o.timestamp;
if (diff > NC_REDIS_GRACE_TTL * 1000) {
await this.refreshTTL(key);
}
}
return Promise.resolve(o);
}
} catch (e) {
logger.error(`Bad value stored for key ${key} : ${res}`);
return Promise.resolve(res);
}
}
return Promise.resolve(res);
}
}
// @ts-ignore
async get(key: string, type: string): Promise<any> {
return this.getRaw(key, type).then((res) => {
if (res && res.value) {
return res.value;
}
return res;
});
}
// @ts-ignore
async set(
key: string,
value: any,
options: {
// when we prepare beforehand, we don't need to prepare again
skipPrepare?: boolean;
// timestamp for the value, if not provided, it will be set to current time
timestamp?: number;
} = {
skipPrepare: false,
},
): Promise<any> {
const { skipPrepare, timestamp } = options;
if (typeof value !== 'undefined' && value) {
log(`${this.context}::set: setting key ${key} with value ${value}`);
// if provided value is an array store it as a set
if (Array.isArray(value) && value.length) {
return new Promise((resolve) => {
this.client
.pipeline()
.sadd(key, value)
// - 60 seconds to avoid expiring list before any of its children
.expire(key, NC_REDIS_TTL - 60)
.exec((err) => {
if (err) {
logger.error(
`${this.context}::set: error setting key ${key} with value ${value}`,
);
}
resolve(true);
});
});
}
if (!skipPrepare) {
// try to get old key value
const keyValue = await this.getRaw(key);
// prepare new key value
value = this.prepareValue({
value,
parentKeys: this.getParents(keyValue),
timestamp,
});
}
return this.client
.set(
key,
JSON.stringify(value, this.getCircularReplacer()),
'EX',
NC_REDIS_TTL,
)
.then(async () => {
await this.refreshTTL(key, timestamp);
return true;
});
} else {
log(`${this.context}::set: value is empty for ${key}. Skipping ...`);
return Promise.resolve(true);
}
}
// @ts-ignore
async setExpiring(
key: string, key: string,
value: any, value: any,
seconds: number, seconds: number,
): Promise<any>; options: {
public abstract incrby(key: string, value: number): Promise<any>; // when we prepare beforehand, we don't need to prepare again
public abstract del(key: string[] | string): Promise<any>; skipPrepare?: boolean;
public abstract getList( // timestamp for the value, if not provided, it will be set to current time
timestamp?: number;
} = {
skipPrepare: false,
},
): Promise<any> {
const { skipPrepare, timestamp } = options;
if (typeof value !== 'undefined' && value) {
log(
`${this.context}::setExpiring: setting key ${key} with value ${value}`,
);
if (Array.isArray(value) && value.length) {
return new Promise((resolve) => {
this.client
.pipeline()
.sadd(key, value)
.expire(key, seconds)
.exec((err) => {
if (err) {
logger.error(
`${this.context}::set: error setting key ${key} with value ${value}`,
);
}
resolve(true);
});
});
}
if (!skipPrepare) {
// try to get old key value
const keyValue = await this.getRaw(key);
// prepare new key value
value = this.prepareValue({
value,
parentKeys: this.getParents(keyValue),
timestamp,
});
}
return this.client.set(
key,
JSON.stringify(value, this.getCircularReplacer()),
'EX',
seconds,
);
} else {
log(`${this.context}::set: value is empty for ${key}. Skipping ...`);
return Promise.resolve(true);
}
}
// @ts-ignore
async incrby(key: string, value = 1): Promise<any> {
return this.client.incrby(key, value);
}
async getList(
scope: string, scope: string,
list: string[], subKeys: string[],
): Promise<{ ): Promise<{
list: any[]; list: any[];
isNoneList: boolean; isNoneList: boolean;
}>; }> {
public abstract setList( // remove null from arrays
subKeys = subKeys.filter((k) => k);
// e.g. key = nc:<orgs>:<scope>:<project_id_1>:<source_id_1>:list
const key =
subKeys.length === 0
? `${this.prefix}:${scope}:list`
: `${this.prefix}:${scope}:${subKeys.join(':')}:list`;
// e.g. arr = ["nc:<orgs>:<scope>:<model_id_1>", "nc:<orgs>:<scope>:<model_id_2>"]
const arr = (await this.get(key, CacheGetType.TYPE_ARRAY)) || [];
log(`${this.context}::getList: getting list with key ${key}`);
const isNoneList = arr.length && arr.includes('NONE');
if (isNoneList || !arr.length) {
return Promise.resolve({
list: [],
isNoneList,
});
}
log(`${this.context}::getList: getting list with keys ${arr}`);
const values = await this.client.mget(arr);
if (values.some((v) => v === null)) {
// FALLBACK: a key is missing from list, this should never happen
logger.error(`${this.context}::getList: missing value for ${key}`);
const allParents = [];
// get all parents from children
values.forEach((v) => {
if (v) {
try {
const o = JSON.parse(v);
if (typeof o === 'object') {
allParents.push(...this.getParents(o));
}
} catch (e) {
logger.error(
`${this.context}::getList: Bad value stored for key ${arr[0]} : ${v}`,
);
}
}
});
// remove duplicates
const uniqueParents = [...new Set(allParents)];
// delete all parents and children
await Promise.all(
uniqueParents.map(async (p) => {
await this.deepDel(p, CacheDelDirection.PARENT_TO_CHILD);
}),
);
return Promise.resolve({
list: [],
isNoneList,
});
}
if (values.length) {
try {
const o = JSON.parse(values[0]);
if (typeof o === 'object') {
const diff = Date.now() - o.timestamp;
if (diff > NC_REDIS_GRACE_TTL * 1000) {
await this.refreshTTL(key);
}
}
} catch (e) {
logger.error(
`${this.context}::getList: Bad value stored for key ${arr[0]} : ${values[0]}`,
);
}
}
return {
list: values.map((res) => {
try {
const o = JSON.parse(res);
if (typeof o === 'object') {
return o.value;
}
} catch (e) {
return res;
}
return res;
}),
isNoneList,
};
}
async setList(
scope: string, scope: string,
subListKeys: string[], subListKeys: string[],
list: any[], list: any[],
props?: string[], props: string[] = [],
): Promise<boolean>; ): Promise<boolean> {
public abstract deepDel( // remove null from arrays
scope: string, subListKeys = subListKeys.filter((k) => k);
key: string, // construct key for List
direction: string, // e.g. nc:<orgs>:<scope>:<project_id_1>:<source_id_1>:list
): Promise<boolean>; const listKey =
public abstract appendToList( subListKeys.length === 0
? `${this.prefix}:${scope}:list`
: `${this.prefix}:${scope}:${subListKeys.join(':')}:list`;
if (!list.length) {
// Set NONE here so that it won't hit the DB on each page load
return this.set(listKey, ['NONE']);
}
// timestamp for list
const timestamp = Date.now();
// remove existing list
await this.deepDel(listKey, CacheDelDirection.PARENT_TO_CHILD);
const listOfGetKeys = [];
for (const o of list) {
// construct key for Get
let getKey = `${this.prefix}:${scope}:${o.id}`;
if (props.length) {
const propValues = props.map((p) => o[p]);
// e.g. nc:<orgs>:<scope>:<prop_value_1>:<prop_value_2>
getKey = `${this.prefix}:${scope}:${propValues.join(':')}`;
}
log(`${this.context}::setList: get key ${getKey}`);
// get key
let rawValue = await this.getRaw(getKey, CacheGetType.TYPE_OBJECT);
if (rawValue) {
log(`${this.context}::setList: preparing key ${getKey}`);
// prepare key
rawValue = this.prepareValue({
value: o,
parentKeys: this.getParents(rawValue),
newKey: listKey,
timestamp,
});
} else {
rawValue = this.prepareValue({
value: o,
parentKeys: [listKey],
timestamp,
});
}
// set key
log(`${this.context}::setList: setting key ${getKey}`);
await this.set(getKey, rawValue, {
skipPrepare: true,
timestamp,
});
// push key to list
listOfGetKeys.push(getKey);
}
// set list
log(`${this.context}::setList: setting list with key ${listKey}`);
return this.set(listKey, listOfGetKeys);
}
async deepDel(key: string, direction: string): Promise<boolean> {
log(`${this.context}::deepDel: choose direction ${direction}`);
if (direction === CacheDelDirection.CHILD_TO_PARENT) {
const childKey = await this.getRaw(key, CacheGetType.TYPE_OBJECT);
// given a child key, delete all keys in corresponding parent lists
const scopeList = this.getParents(childKey);
for (const listKey of scopeList) {
// get target list
let list = (await this.get(listKey, CacheGetType.TYPE_ARRAY)) || [];
if (!list.length) {
continue;
}
// remove target Key
list = list.filter((k) => k !== key);
// delete list
log(`${this.context}::deepDel: remove listKey ${listKey}`);
await this.del(listKey);
if (list.length) {
// set target list
log(`${this.context}::deepDel: set key ${listKey}`);
await this.set(listKey, list);
}
}
log(`${this.context}::deepDel: remove key ${key}`);
return await this.del(key);
} else if (direction === CacheDelDirection.PARENT_TO_CHILD) {
key = /:list$/.test(key) ? key : `${key}:list`;
// given a list key, delete all the children
const listOfChildren = await this.get(key, CacheGetType.TYPE_ARRAY);
// delete each child key
await this.del(listOfChildren);
// delete list key
return await this.del(key);
} else {
log(`Invalid deepDel direction found : ${direction}`);
return Promise.resolve(false);
}
}
async appendToList(
scope: string, scope: string,
subListKeys: string[], subListKeys: string[],
key: string, key: string,
): Promise<boolean>; ): Promise<boolean> {
public abstract destroy(): Promise<boolean>; // remove null from arrays
public abstract export(): Promise<any>; subListKeys = subListKeys.filter((k) => k);
// e.g. key = nc:<orgs>:<scope>:<project_id_1>:<source_id_1>:list
const listKey =
subListKeys.length === 0
? `${this.prefix}:${scope}:list`
: `${this.prefix}:${scope}:${subListKeys.join(':')}:list`;
log(`${this.context}::appendToList: append key ${key} to ${listKey}`);
let list = await this.get(listKey, CacheGetType.TYPE_ARRAY);
if (!list || !list.length) {
return false;
}
if (list.includes('NONE')) {
list = [];
await this.del(listKey);
}
log(`${this.context}::appendToList: get key ${key}`);
// get Get Key
const rawValue = await this.getRaw(key, CacheGetType.TYPE_OBJECT);
log(`${this.context}::appendToList: preparing key ${key}`);
if (!rawValue) {
// FALLBACK: this is to get rid of all keys that would be effected by this (should never happen)
logger.error(`${this.context}::appendToList: value is empty for ${key}`);
const allParents = [];
// get all children
const listValues = await this.getList(scope, subListKeys);
// get all parents from children
listValues.list.forEach((v) => {
allParents.push(...this.getParents(v));
});
// remove duplicates
const uniqueParents = [...new Set(allParents)];
// delete all parents and children
await Promise.all(
uniqueParents.map(async (p) => {
await this.deepDel(p, CacheDelDirection.PARENT_TO_CHILD);
}),
);
return false;
}
// prepare Get Key
const preparedValue = this.prepareValue({
value: rawValue.value ?? rawValue,
parentKeys: this.getParents(rawValue),
newKey: listKey,
});
// set Get Key
log(`${this.context}::appendToList: setting key ${key}`);
await this.set(key, preparedValue, {
skipPrepare: true,
});
list.push(key);
return this.set(listKey, list).then(async (res) => {
await this.refreshTTL(listKey);
return res;
});
}
// wrap value with metadata
prepareValue(args: {
value: any;
parentKeys: string[];
newKey?: string;
timestamp?: number;
}) {
const { value, parentKeys, newKey, timestamp } = args;
if (newKey && !parentKeys.includes(newKey)) {
parentKeys.push(newKey);
}
const cacheObj = {
value,
parentKeys,
timestamp: timestamp || Date.now(),
};
return cacheObj;
}
getParents(rawValue) {
if (rawValue && rawValue.parentKeys) {
return rawValue.parentKeys;
} else if (!rawValue) {
return [];
} else {
logger.error(
`${this.context}::getParents: parentKeys not found ${JSON.stringify(
rawValue,
)}`,
);
return [];
}
}
async refreshTTL(key: string, timestamp?: number): Promise<void> {
log(`${this.context}::refreshTTL: refreshing TTL for ${key}`);
const isParent = /:list$/.test(key);
timestamp = timestamp || Date.now();
if (isParent) {
const list =
(await this.getRaw(key, CacheGetType.TYPE_ARRAY, true)) || [];
if (list && list.length) {
const listValues = await this.client.mget(list);
const pipeline = this.client.pipeline();
for (const [i, v] of listValues.entries()) {
const key = list[i];
if (v) {
try {
const o = JSON.parse(v);
if (typeof o === 'object') {
if (o.timestamp !== timestamp) {
o.timestamp = timestamp;
pipeline.set(
key,
JSON.stringify(o, this.getCircularReplacer()),
'EX',
NC_REDIS_TTL,
);
}
}
} catch (e) {
logger.error(
`${this.context}::refreshTTL: Bad value stored for key ${key} : ${v}`,
);
}
}
}
pipeline.expire(key, NC_REDIS_TTL - 60);
await pipeline.exec();
}
} else {
const rawValue = await this.getRaw(key, null, true);
if (rawValue) {
if (rawValue.parentKeys && rawValue.parentKeys.length) {
for (const parent of rawValue.parentKeys) {
await this.refreshTTL(parent, timestamp);
}
} else {
if (rawValue.timestamp !== timestamp) {
rawValue.timestamp = timestamp;
await this.client.set(
key,
JSON.stringify(rawValue, this.getCircularReplacer()),
'EX',
NC_REDIS_TTL,
);
}
}
}
}
}
async destroy(): Promise<boolean> {
log('${this.context}::destroy: destroy redis');
return this.client.flushdb().then((r) => r === 'OK');
}
async export(): Promise<any> {
log('${this.context}::export: export data');
const data = await this.client.keys('*');
const res = {};
return await Promise.all(
data.map(async (k) => {
res[k] = await this.get(
k,
k.slice(-4) === 'list'
? CacheGetType.TYPE_ARRAY
: CacheGetType.TYPE_OBJECT,
);
}),
).then(() => {
return res;
});
}
} }

3
packages/nocodb/src/cache/NocoCache.ts vendored

@ -85,12 +85,11 @@ export default class NocoCache {
} }
public static async deepDel( public static async deepDel(
scope: string,
key: string, key: string,
direction: string, direction: string,
): Promise<boolean> { ): Promise<boolean> {
if (this.cacheDisabled) return Promise.resolve(true); if (this.cacheDisabled) return Promise.resolve(true);
return this.client.deepDel(scope, `${this.prefix}:${key}`, direction); return this.client.deepDel(`${this.prefix}:${key}`, direction);
} }
public static async appendToList( public static async appendToList(

414
packages/nocodb/src/cache/RedisCacheMgr.ts vendored

@ -1,18 +1,10 @@
import debug from 'debug'; import debug from 'debug';
import Redis from 'ioredis'; import Redis from 'ioredis';
import CacheMgr from './CacheMgr'; import CacheMgr from './CacheMgr';
import {
CacheDelDirection,
CacheGetType,
CacheListProp,
} from '~/utils/globals';
const log = debug('nc:cache'); const _log = debug('nc:cache');
export default class RedisCacheMgr extends CacheMgr { export default class RedisCacheMgr extends CacheMgr {
client: Redis;
prefix: string;
constructor(config: any) { constructor(config: any) {
super(); super();
this.client = new Redis(config); this.client = new Redis(config);
@ -20,7 +12,7 @@ export default class RedisCacheMgr extends CacheMgr {
// avoid flushing db in worker container // avoid flushing db in worker container
if ( if (
process.env.NC_WORKER_CONTAINER !== 'true' && process.env.NC_WORKER_CONTAINER !== 'true' &&
process.env.NC_CLOUD !== 'true' (process.env.NC_FLUSH_CACHE === 'true' || process.env.NC_CLOUD !== 'true')
) { ) {
// flush the existing db with selected key (Default: 0) // flush the existing db with selected key (Default: 0)
this.client.flushdb(); this.client.flushdb();
@ -29,406 +21,6 @@ export default class RedisCacheMgr extends CacheMgr {
// TODO(cache): fetch orgs once it's implemented // TODO(cache): fetch orgs once it's implemented
const orgs = 'noco'; const orgs = 'noco';
this.prefix = `nc:${orgs}`; this.prefix = `nc:${orgs}`;
} this.context = 'RedisCacheMgr';
// avoid circular structure to JSON
getCircularReplacer = () => {
const seen = new WeakSet();
return (_, value) => {
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) {
return;
}
seen.add(value);
}
return value;
};
};
// @ts-ignore
async del(key: string[] | string): Promise<any> {
log(`RedisCacheMgr::del: deleting key ${key}`);
if (Array.isArray(key)) {
if (key.length) {
return this.client.del(key);
}
} else if (key) {
return this.client.del(key);
}
}
// @ts-ignore
async get(key: string, type: string): Promise<any> {
log(`RedisCacheMgr::get: getting key ${key} with type ${type}`);
if (type === CacheGetType.TYPE_ARRAY) {
return this.client.smembers(key);
} else if (type === CacheGetType.TYPE_OBJECT) {
const res = await this.client.get(key);
try {
const o = JSON.parse(res);
if (typeof o === 'object') {
if (
o &&
Object.keys(o).length === 0 &&
Object.getPrototypeOf(o) === Object.prototype
) {
log(`RedisCacheMgr::get: object is empty!`);
}
return Promise.resolve(o);
}
} catch (e) {
return Promise.resolve(res);
}
return Promise.resolve(res);
} else if (type === CacheGetType.TYPE_STRING) {
return await this.client.get(key);
}
log(`Invalid CacheGetType: ${type}`);
return Promise.resolve(false);
}
// @ts-ignore
async set(key: string, value: any): Promise<any> {
if (typeof value !== 'undefined' && value) {
log(`RedisCacheMgr::set: setting key ${key} with value ${value}`);
if (typeof value === 'object') {
if (Array.isArray(value) && value.length) {
return this.client.sadd(key, value);
}
const keyValue = await this.get(key, CacheGetType.TYPE_OBJECT);
if (keyValue) {
value = await this.prepareValue(value, this.getParents(keyValue));
}
return this.client.set(
key,
JSON.stringify(value, this.getCircularReplacer()),
);
}
return this.client.set(key, value);
} else {
log(`RedisCacheMgr::set: value is empty for ${key}. Skipping ...`);
return Promise.resolve(true);
}
}
// @ts-ignore
async setExpiring(key: string, value: any, seconds: number): Promise<any> {
if (typeof value !== 'undefined' && value) {
log(
`RedisCacheMgr::setExpiring: setting key ${key} with value ${value} for ${seconds} seconds`,
);
if (typeof value === 'object') {
if (Array.isArray(value) && value.length) {
return this.client.sadd(key, value);
}
return this.client.set(
key,
JSON.stringify(value, this.getCircularReplacer()),
'EX',
seconds,
);
}
return this.client.set(key, value, 'EX', seconds);
} else {
log(`RedisCacheMgr::set: value is empty for ${key}. Skipping ...`);
return Promise.resolve(true);
}
}
// @ts-ignore
async incrby(key: string, value = 1): Promise<any> {
return this.client.incrby(key, value);
}
async getList(
scope: string,
subKeys: string[],
): Promise<{
list: any[];
isNoneList: boolean;
}> {
// remove null from arrays
subKeys = subKeys.filter((k) => k);
// e.g. key = nc:<orgs>:<scope>:<project_id_1>:<source_id_1>:list
const key =
subKeys.length === 0
? `${this.prefix}:${scope}:list`
: `${this.prefix}:${scope}:${subKeys.join(':')}:list`;
// e.g. arr = ["nc:<orgs>:<scope>:<model_id_1>", "nc:<orgs>:<scope>:<model_id_2>"]
const arr = (await this.get(key, CacheGetType.TYPE_ARRAY)) || [];
log(`RedisCacheMgr::getList: getting list with key ${key}`);
const isNoneList = arr.length && arr.includes('NONE');
if (isNoneList || !arr.length) {
return Promise.resolve({
list: [],
isNoneList,
});
}
log(`RedisCacheMgr::getList: getting list with keys ${arr}`);
const values = await this.client.mget(arr);
if (values.some((v) => v === null)) {
// FALLBACK: a key is missing from list, this should never happen
console.error(`RedisCacheMgr::getList: missing value for ${key}`);
const allParents = [];
// get all parents from children
values.forEach((v) => {
allParents.push(...this.getParents(v));
});
// remove duplicates
const uniqueParents = [...new Set(allParents)];
// delete all parents and children
await Promise.all(
uniqueParents.map(async (p) => {
await this.deepDel(scope, p, CacheDelDirection.PARENT_TO_CHILD);
}),
);
return Promise.resolve({
list: [],
isNoneList,
});
}
return {
list: values.map((res) => {
try {
const o = JSON.parse(res);
if (typeof o === 'object') {
return o;
}
} catch (e) {
return res;
}
return res;
}),
isNoneList,
};
}
async setList(
scope: string,
subListKeys: string[],
list: any[],
props: string[] = [],
): Promise<boolean> {
// remove null from arrays
subListKeys = subListKeys.filter((k) => k);
// construct key for List
// e.g. nc:<orgs>:<scope>:<project_id_1>:<source_id_1>:list
const listKey =
subListKeys.length === 0
? `${this.prefix}:${scope}:list`
: `${this.prefix}:${scope}:${subListKeys.join(':')}:list`;
if (!list.length) {
// Set NONE here so that it won't hit the DB on each page load
return this.set(listKey, ['NONE']);
}
// fetch existing list
const listOfGetKeys =
(await this.get(listKey, CacheGetType.TYPE_ARRAY)) || [];
for (const o of list) {
// construct key for Get
let getKey = `${this.prefix}:${scope}:${o.id}`;
if (props.length) {
const propValues = props.map((p) => o[p]);
// e.g. nc:<orgs>:<scope>:<prop_value_1>:<prop_value_2>
getKey = `${this.prefix}:${scope}:${propValues.join(':')}`;
}
log(`RedisCacheMgr::setList: get key ${getKey}`);
// get Get Key
let value = await this.get(getKey, CacheGetType.TYPE_OBJECT);
if (value) {
log(`RedisCacheMgr::setList: preparing key ${getKey}`);
// prepare Get Key
value = await this.prepareValue(o, this.getParents(value), listKey);
} else {
value = await this.prepareValue(o, [], listKey);
}
// set Get Key
log(`RedisCacheMgr::setList: setting key ${getKey}`);
await this.set(getKey, JSON.stringify(value, this.getCircularReplacer()));
// push Get Key to List
listOfGetKeys.push(getKey);
}
// set List Key
log(`RedisCacheMgr::setList: setting list with key ${listKey}`);
return this.set(listKey, listOfGetKeys);
}
async deepDel(
scope: string,
key: string,
direction: string,
): Promise<boolean> {
log(`RedisCacheMgr::deepDel: choose direction ${direction}`);
if (direction === CacheDelDirection.CHILD_TO_PARENT) {
const childKey = await this.get(key, CacheGetType.TYPE_OBJECT);
// given a child key, delete all keys in corresponding parent lists
const scopeList = this.getParents(childKey);
for (const listKey of scopeList) {
// get target list
let list = (await this.get(listKey, CacheGetType.TYPE_ARRAY)) || [];
if (!list.length) {
continue;
}
// remove target Key
list = list.filter((k) => k !== key);
// delete list
log(`RedisCacheMgr::deepDel: remove listKey ${listKey}`);
await this.del(listKey);
if (list.length) {
// set target list
log(`RedisCacheMgr::deepDel: set key ${listKey}`);
await this.set(listKey, list);
}
}
log(`RedisCacheMgr::deepDel: remove key ${key}`);
return await this.del(key);
} else if (direction === CacheDelDirection.PARENT_TO_CHILD) {
key = /:list$/.test(key) ? key : `${key}:list`;
// given a list key, delete all the children
const listOfChildren = await this.get(key, CacheGetType.TYPE_ARRAY);
// delete each child key
await this.del(listOfChildren);
// delete list key
return await this.del(key);
} else {
log(`Invalid deepDel direction found : ${direction}`);
return Promise.resolve(false);
}
}
async appendToList(
scope: string,
subListKeys: string[],
key: string,
): Promise<boolean> {
// remove null from arrays
subListKeys = subListKeys.filter((k) => k);
// e.g. key = nc:<orgs>:<scope>:<project_id_1>:<source_id_1>:list
const listKey =
subListKeys.length === 0
? `${this.prefix}:${scope}:list`
: `${this.prefix}:${scope}:${subListKeys.join(':')}:list`;
log(`RedisCacheMgr::appendToList: append key ${key} to ${listKey}`);
let list = await this.get(listKey, CacheGetType.TYPE_ARRAY);
if (!list || !list.length) {
return false;
}
if (list.includes('NONE')) {
list = [];
await this.del(listKey);
}
log(`RedisCacheMgr::appendToList: get key ${key}`);
// get Get Key
const value = await this.get(key, CacheGetType.TYPE_OBJECT);
log(`RedisCacheMgr::appendToList: preparing key ${key}`);
if (!value) {
// FALLBACK: this is to get rid of all keys that would be effected by this (should never happen)
console.error(`RedisCacheMgr::appendToList: value is empty for ${key}`);
const allParents = [];
// get all children
const listValues = await this.getList(scope, subListKeys);
// get all parents from children
listValues.list.forEach((v) => {
allParents.push(...this.getParents(v));
});
// remove duplicates
const uniqueParents = [...new Set(allParents)];
// delete all parents and children
await Promise.all(
uniqueParents.map(async (p) => {
await this.deepDel(scope, p, CacheDelDirection.PARENT_TO_CHILD);
}),
);
return false;
}
// prepare Get Key
const preparedValue = await this.prepareValue(
value,
this.getParents(value),
listKey,
);
// set Get Key
log(`RedisCacheMgr::appendToList: setting key ${key}`);
await this.set(
key,
JSON.stringify(preparedValue, this.getCircularReplacer()),
);
list.push(key);
return this.set(listKey, list);
}
prepareValue(value, listKeys = [], newParent?) {
if (newParent) {
listKeys.push(newParent);
}
if (value && typeof value === 'object') {
value[CacheListProp] = listKeys;
} else if (value && typeof value === 'string') {
const keyHelper = value.split(CacheListProp);
if (listKeys.length) {
value = `${keyHelper[0]}${CacheListProp}${listKeys.join(',')}`;
}
} else if (value) {
console.error(
`RedisCacheMgr::prepareListKey: keyValue is not object or string`,
value,
);
throw new Error(
`RedisCacheMgr::prepareListKey: keyValue is not object or string`,
);
}
return value;
}
getParents(value) {
if (value && typeof value === 'object') {
if (CacheListProp in value) {
const listsForKey = value[CacheListProp];
if (listsForKey && listsForKey.length) {
return listsForKey;
}
}
} else if (value && typeof value === 'string') {
if (value.includes(CacheListProp)) {
const keyHelper = value.split(CacheListProp);
const listsForKey = keyHelper[1].split(',');
if (listsForKey.length) {
return listsForKey;
}
}
}
return [];
}
async destroy(): Promise<boolean> {
log('RedisCacheMgr::destroy: destroy redis');
return this.client.flushdb().then((r) => r === 'OK');
}
async export(): Promise<any> {
log('RedisCacheMgr::export: export data');
const data = await this.client.keys('*');
const res = {};
return await Promise.all(
data.map(async (k) => {
res[k] = await this.get(
k,
k.slice(-4) === 'list'
? CacheGetType.TYPE_ARRAY
: CacheGetType.TYPE_OBJECT,
);
}),
).then(() => {
return res;
});
} }
} }

420
packages/nocodb/src/cache/RedisMockCacheMgr.ts vendored

@ -1,18 +1,10 @@
import debug from 'debug'; import debug from 'debug';
import Redis from 'ioredis-mock'; import Redis from 'ioredis-mock';
import CacheMgr from './CacheMgr'; import CacheMgr from './CacheMgr';
import type IORedis from 'ioredis';
import {
CacheDelDirection,
CacheGetType,
CacheListProp,
} from '~/utils/globals';
const log = debug('nc:cache');
export default class RedisMockCacheMgr extends CacheMgr { const _log = debug('nc:cache');
client: IORedis;
prefix: string;
export default class RedisMockCacheMgr extends CacheMgr {
constructor() { constructor() {
super(); super();
this.client = new Redis(); this.client = new Redis();
@ -22,412 +14,6 @@ export default class RedisMockCacheMgr extends CacheMgr {
// TODO(cache): fetch orgs once it's implemented // TODO(cache): fetch orgs once it's implemented
const orgs = 'noco'; const orgs = 'noco';
this.prefix = `nc:${orgs}`; this.prefix = `nc:${orgs}`;
} this.context = 'RedisMockCacheMgr';
// avoid circular structure to JSON
getCircularReplacer = () => {
const seen = new WeakSet();
return (_, value) => {
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) {
return;
}
seen.add(value);
}
return value;
};
};
// @ts-ignore
async del(key: string[] | string): Promise<any> {
log(`RedisMockCacheMgr::del: deleting key ${key}`);
if (Array.isArray(key)) {
if (key.length) {
return this.client.del(key);
}
} else if (key) {
return this.client.del(key);
}
}
// @ts-ignore
async get(key: string, type: string): Promise<any> {
log(`RedisMockCacheMgr::get: getting key ${key} with type ${type}`);
if (type === CacheGetType.TYPE_ARRAY) {
return this.client.smembers(key);
} else if (type === CacheGetType.TYPE_OBJECT) {
const res = await this.client.get(key);
try {
const o = JSON.parse(res);
if (typeof o === 'object') {
if (
o &&
Object.keys(o).length === 0 &&
Object.getPrototypeOf(o) === Object.prototype
) {
log(`RedisMockCacheMgr::get: object is empty!`);
}
return Promise.resolve(o);
}
} catch (e) {
return Promise.resolve(res);
}
return Promise.resolve(res);
} else if (type === CacheGetType.TYPE_STRING) {
return await this.client.get(key);
}
log(`Invalid CacheGetType: ${type}`);
return Promise.resolve(false);
}
// @ts-ignore
async set(key: string, value: any): Promise<any> {
if (typeof value !== 'undefined' && value) {
log(`RedisMockCacheMgr::set: setting key ${key} with value ${value}`);
if (typeof value === 'object') {
if (Array.isArray(value) && value.length) {
return this.client.sadd(key, value);
}
const keyValue = await this.get(key, CacheGetType.TYPE_OBJECT);
if (keyValue) {
value = await this.prepareValue(value, this.getParents(keyValue));
}
return this.client.set(
key,
JSON.stringify(value, this.getCircularReplacer()),
);
}
return this.client.set(key, value);
} else {
log(`RedisMockCacheMgr::set: value is empty for ${key}. Skipping ...`);
return Promise.resolve(true);
}
}
// @ts-ignore
async setExpiring(key: string, value: any, seconds: number): Promise<any> {
if (typeof value !== 'undefined' && value) {
log(
`RedisMockCacheMgr::setExpiring: setting key ${key} with value ${value} for ${seconds} seconds`,
);
// TODO: better way to handle expiration in mock redis
setTimeout(() => {
this.del(key);
}, seconds * 1000);
if (typeof value === 'object') {
if (Array.isArray(value) && value.length) {
return this.client.sadd(key, value);
}
return this.client.set(
key,
JSON.stringify(value, this.getCircularReplacer()),
);
}
return this.client.set(key, value);
} else {
log(`RedisMockCacheMgr::set: value is empty for ${key}. Skipping ...`);
return Promise.resolve(true);
}
}
// @ts-ignore
async incrby(key: string, value = 1): Promise<any> {
return this.client.incrby(key, value);
}
async getList(
scope: string,
subKeys: string[],
): Promise<{
list: any[];
isNoneList: boolean;
}> {
// remove null from arrays
subKeys = subKeys.filter((k) => k);
// e.g. key = nc:<orgs>:<scope>:<project_id_1>:<source_id_1>:list
const key =
subKeys.length === 0
? `${this.prefix}:${scope}:list`
: `${this.prefix}:${scope}:${subKeys.join(':')}:list`;
// e.g. arr = ["nc:<orgs>:<scope>:<model_id_1>", "nc:<orgs>:<scope>:<model_id_2>"]
const arr = (await this.get(key, CacheGetType.TYPE_ARRAY)) || [];
log(`RedisMockCacheMgr::getList: getting list with key ${key}`);
const isNoneList = arr.length && arr[0] === 'NONE';
if (isNoneList || !arr.length) {
return Promise.resolve({
list: [],
isNoneList,
});
}
log(`RedisMockCacheMgr::getList: getting list with keys ${arr}`);
const values = await this.client.mget(arr);
if (values.some((v) => v === null)) {
// FALLBACK: a key is missing from list, this should never happen
console.error(`RedisMockCacheMgr::getList: missing value for ${key}`);
const allParents = [];
// get all parents from children
values.forEach((v) => {
allParents.push(...this.getParents(v));
});
// remove duplicates
const uniqueParents = [...new Set(allParents)];
// delete all parents and children
await Promise.all(
uniqueParents.map(async (p) => {
await this.deepDel(scope, p, CacheDelDirection.PARENT_TO_CHILD);
}),
);
return Promise.resolve({
list: [],
isNoneList,
});
}
return {
list: values.map((res) => {
try {
const o = JSON.parse(res);
if (typeof o === 'object') {
return o;
}
} catch (e) {
return res;
}
return res;
}),
isNoneList,
};
}
async setList(
scope: string,
subListKeys: string[],
list: any[],
props: string[] = [],
): Promise<boolean> {
// remove null from arrays
subListKeys = subListKeys.filter((k) => k);
// construct key for List
// e.g. nc:<orgs>:<scope>:<project_id_1>:<source_id_1>:list
const listKey =
subListKeys.length === 0
? `${this.prefix}:${scope}:list`
: `${this.prefix}:${scope}:${subListKeys.join(':')}:list`;
if (!list.length) {
// Set NONE here so that it won't hit the DB on each page load
return this.set(listKey, ['NONE']);
}
// fetch existing list
const listOfGetKeys =
(await this.get(listKey, CacheGetType.TYPE_ARRAY)) || [];
for (const o of list) {
// construct key for Get
let getKey = `${this.prefix}:${scope}:${o.id}`;
if (props.length) {
const propValues = props.map((p) => o[p]);
// e.g. nc:<orgs>:<scope>:<prop_value_1>:<prop_value_2>
getKey = `${this.prefix}:${scope}:${propValues.join(':')}`;
}
log(`RedisMockCacheMgr::setList: get key ${getKey}`);
// get Get Key
let value = await this.get(getKey, CacheGetType.TYPE_OBJECT);
if (value) {
log(`RedisMockCacheMgr::setList: preparing key ${getKey}`);
// prepare Get Key
value = await this.prepareValue(o, this.getParents(value), listKey);
} else {
value = await this.prepareValue(o, [], listKey);
}
// set Get Key
log(`RedisMockCacheMgr::setList: setting key ${getKey}`);
await this.set(getKey, JSON.stringify(value, this.getCircularReplacer()));
// push Get Key to List
listOfGetKeys.push(getKey);
}
// set List Key
log(`RedisMockCacheMgr::setList: setting list with key ${listKey}`);
return this.set(listKey, listOfGetKeys);
}
async deepDel(
scope: string,
key: string,
direction: string,
): Promise<boolean> {
log(`RedisMockCacheMgr::deepDel: choose direction ${direction}`);
if (direction === CacheDelDirection.CHILD_TO_PARENT) {
const childKey = await this.get(key, CacheGetType.TYPE_OBJECT);
// given a child key, delete all keys in corresponding parent lists
const scopeList = this.getParents(childKey);
for (const listKey of scopeList) {
// get target list
let list = (await this.get(listKey, CacheGetType.TYPE_ARRAY)) || [];
if (!list.length) {
continue;
}
// remove target Key
list = list.filter((k) => k !== key);
// delete list
log(`RedisMockCacheMgr::deepDel: remove listKey ${listKey}`);
await this.del(listKey);
if (list.length) {
// set target list
log(`RedisMockCacheMgr::deepDel: set key ${listKey}`);
await this.set(listKey, list);
}
}
log(`RedisMockCacheMgr::deepDel: remove key ${key}`);
return await this.del(key);
} else if (direction === CacheDelDirection.PARENT_TO_CHILD) {
key = /:list$/.test(key) ? key : `${key}:list`;
// given a list key, delete all the children
const listOfChildren = await this.get(key, CacheGetType.TYPE_ARRAY);
// delete each child key
await this.del(listOfChildren);
// delete list key
return await this.del(key);
} else {
log(`Invalid deepDel direction found : ${direction}`);
return Promise.resolve(false);
}
}
async appendToList(
scope: string,
subListKeys: string[],
key: string,
): Promise<boolean> {
// remove null from arrays
subListKeys = subListKeys.filter((k) => k);
// e.g. key = nc:<orgs>:<scope>:<project_id_1>:<source_id_1>:list
const listKey =
subListKeys.length === 0
? `${this.prefix}:${scope}:list`
: `${this.prefix}:${scope}:${subListKeys.join(':')}:list`;
log(`RedisMockCacheMgr::appendToList: append key ${key} to ${listKey}`);
let list = await this.get(listKey, CacheGetType.TYPE_ARRAY);
if (!list || !list.length) {
return false;
}
if (list[0] === 'NONE') {
list = [];
await this.del(listKey);
}
log(`RedisMockCacheMgr::appendToList: get key ${key}`);
// get Get Key
const value = await this.get(key, CacheGetType.TYPE_OBJECT);
log(`RedisMockCacheMgr::appendToList: preparing key ${key}`);
if (!value) {
// FALLBACK: this is to get rid of all keys that would be effected by this (should never happen)
console.error(
`RedisMockCacheMgr::appendToList: value is empty for ${key}`,
);
const allParents = [];
// get all children
const listValues = await this.getList(scope, subListKeys);
// get all parents from children
listValues.list.forEach((v) => {
allParents.push(...this.getParents(v));
});
// remove duplicates
const uniqueParents = [...new Set(allParents)];
// delete all parents and children
await Promise.all(
uniqueParents.map(async (p) => {
await this.deepDel(scope, p, CacheDelDirection.PARENT_TO_CHILD);
}),
);
return false;
}
// prepare Get Key
const preparedValue = await this.prepareValue(
value,
this.getParents(value),
listKey,
);
// set Get Key
log(`RedisMockCacheMgr::appendToList: setting key ${key}`);
await this.set(
key,
JSON.stringify(preparedValue, this.getCircularReplacer()),
);
list.push(key);
return this.set(listKey, list);
}
prepareValue(value, listKeys = [], newParent?) {
if (newParent) {
listKeys.push(newParent);
}
if (value && typeof value === 'object') {
value[CacheListProp] = listKeys;
} else if (value && typeof value === 'string') {
const keyHelper = value.split(CacheListProp);
if (listKeys.length) {
value = `${keyHelper[0]}${CacheListProp}${listKeys.join(',')}`;
}
} else if (value) {
console.error(
`RedisMockCacheMgr::prepareListKey: keyValue is not object or string`,
value,
);
throw new Error(
`RedisMockCacheMgr::prepareListKey: keyValue is not object or string`,
);
}
return value;
}
getParents(value) {
if (value && typeof value === 'object') {
if (CacheListProp in value) {
const listsForKey = value[CacheListProp];
if (listsForKey && listsForKey.length) {
return listsForKey;
}
}
} else if (value && typeof value === 'string') {
if (value.includes(CacheListProp)) {
const keyHelper = value.split(CacheListProp);
const listsForKey = keyHelper[1].split(',');
if (listsForKey.length) {
return listsForKey;
}
}
}
return [];
}
async destroy(): Promise<boolean> {
log('RedisMockCacheMgr::destroy: destroy redis');
return this.client.flushdb().then((r) => r === 'OK');
}
async export(): Promise<any> {
log('RedisMockCacheMgr::export: export data');
const data = await this.client.keys('*');
const res = {};
return await Promise.all(
data.map(async (k) => {
res[k] = await this.get(
k,
k.slice(-4) === 'list'
? CacheGetType.TYPE_ARRAY
: CacheGetType.TYPE_OBJECT,
);
}),
).then(() => {
return res;
});
} }
} }

31
packages/nocodb/src/db/BaseModelSqlv2.ts

@ -419,8 +419,11 @@ class BaseModelSqlv2 {
// if autogenerated string sort by created_at column if present // if autogenerated string sort by created_at column if present
if (this.model.primaryKey && this.model.primaryKey.ai) { if (this.model.primaryKey && this.model.primaryKey.ai) {
qb.orderBy(this.model.primaryKey.column_name); qb.orderBy(this.model.primaryKey.column_name);
} else if (this.model.columns.find((c) => c.column_name === 'created_at')) { } else {
qb.orderBy('created_at'); const createdCol = this.model.columns.find(
(c) => c.uidt === UITypes.CreatedTime && c.system,
);
if (createdCol) qb.orderBy(createdCol.column_name);
} }
if (!ignorePagination) applyPaginate(qb, rest); if (!ignorePagination) applyPaginate(qb, rest);
@ -5149,7 +5152,7 @@ class BaseModelSqlv2 {
// validate Ids // validate Ids
{ {
const childRowsQb = this.dbDriver(parentTn) const childRowsQb = this.dbDriver(parentTn)
.select(parentColumn.column_name) .select(`${parentTable.table_name}.${parentColumn.column_name}`)
.select(`${vTable.table_name}.${vChildCol.column_name}`) .select(`${vTable.table_name}.${vChildCol.column_name}`)
.leftJoin(vTn, (qb) => { .leftJoin(vTn, (qb) => {
qb.on( qb.on(
@ -5162,7 +5165,6 @@ class BaseModelSqlv2 {
]), ]),
); );
}); });
if (parentTable.primaryKeys.length > 1) { if (parentTable.primaryKeys.length > 1) {
childRowsQb.where((qb) => { childRowsQb.where((qb) => {
for (const childId of childIds) { for (const childId of childIds) {
@ -5171,7 +5173,7 @@ class BaseModelSqlv2 {
}); });
} else { } else {
childRowsQb.whereIn( childRowsQb.whereIn(
parentTable.primaryKey.column_name, `${parentTable.table_name}.${parentTable.primaryKey.column_name}`,
typeof childIds[0] === 'object' typeof childIds[0] === 'object'
? childIds.map( ? childIds.map(
(c) => (c) =>
@ -5183,7 +5185,9 @@ class BaseModelSqlv2 {
} }
if (parentTable.primaryKey.column_name !== parentColumn.column_name) if (parentTable.primaryKey.column_name !== parentColumn.column_name)
childRowsQb.select(parentTable.primaryKey.column_name); childRowsQb.select(
`${parentTable.table_name}.${parentTable.primaryKey.column_name}`,
);
const childRows = await this.execAndParse(childRowsQb, null, { const childRows = await this.execAndParse(childRowsQb, null, {
raw: true, raw: true,
@ -5441,7 +5445,7 @@ class BaseModelSqlv2 {
}); });
} else if (typeof childIds[0] === 'object') { } else if (typeof childIds[0] === 'object') {
childRowsQb.whereIn( childRowsQb.whereIn(
parentTable.primaryKey.column_name, `${parentTable.table_name}.${parentTable.primaryKey.column_name}`,
childIds.map( childIds.map(
(c) => (c) =>
c[parentTable.primaryKey.title] || c[parentTable.primaryKey.title] ||
@ -5449,11 +5453,16 @@ class BaseModelSqlv2 {
), ),
); );
} else { } else {
childRowsQb.whereIn(parentTable.primaryKey.column_name, childIds); childRowsQb.whereIn(
`${parentTable.table_name}.${parentTable.primaryKey.column_name}`,
childIds,
);
} }
if (parentTable.primaryKey.column_name !== parentColumn.column_name) if (parentTable.primaryKey.column_name !== parentColumn.column_name)
childRowsQb.select(parentTable.primaryKey.column_name); childRowsQb.select(
`${parentTable.table_name}.${parentTable.primaryKey.column_name}`,
);
const childRows = await this.execAndParse(childRowsQb, null, { const childRows = await this.execAndParse(childRowsQb, null, {
raw: true, raw: true,
@ -5492,7 +5501,7 @@ class BaseModelSqlv2 {
.delete(); .delete();
delQb.whereIn( delQb.whereIn(
vParentCol.column_name, `${vTable.table_name}.${vParentCol.column_name}`,
typeof childIds[0] === 'object' typeof childIds[0] === 'object'
? childIds.map( ? childIds.map(
(c) => (c) =>
@ -5774,7 +5783,7 @@ class BaseModelSqlv2 {
const qb = knex(this.getTnPath(model.table_name)).update(updateObject); const qb = knex(this.getTnPath(model.table_name)).update(updateObject);
for (const rowId of Array.isArray(rowIds) ? rowIds : [rowIds]) { for (const rowId of Array.isArray(rowIds) ? rowIds : [rowIds]) {
qb.orWhere(await this._wherePk(rowId)); qb.orWhere(_wherePk(model.primaryKeys, rowId));
} }
await this.execAndParse(qb, null, { raw: true }); await this.execAndParse(qb, null, { raw: true });

6
packages/nocodb/src/db/conditionV2.ts

@ -667,7 +667,11 @@ const parseConditionV2 = async (
} }
} }
if (isNumericCol(column.uidt) && typeof genVal === 'string') { if (
isNumericCol(column.uidt) &&
typeof genVal === 'string' &&
!isNaN(+genVal)
) {
// convert to number // convert to number
genVal = +genVal; genVal = +genVal;
} }

2
packages/nocodb/src/db/sortV2.ts

@ -69,7 +69,7 @@ export default async function sortV2(
model, model,
column, column,
{}, {},
alias alias,
) )
).builder; ).builder;
qb.orderBy(builder, sort.direction || 'asc', nulls); qb.orderBy(builder, sort.direction || 'asc', nulls);

8
packages/nocodb/src/meta/migrations/XcMigrationSourcev2.ts

@ -25,6 +25,8 @@ import * as nc_035_add_username_to_users from '~/meta/migrations/v2/nc_035_add_u
import * as nc_036_base_deleted from '~/meta/migrations/v2/nc_036_base_deleted'; import * as nc_036_base_deleted from '~/meta/migrations/v2/nc_036_base_deleted';
import * as nc_037_rename_project_and_base from '~/meta/migrations/v2/nc_037_rename_project_and_base'; import * as nc_037_rename_project_and_base from '~/meta/migrations/v2/nc_037_rename_project_and_base';
import * as nc_038_formula_parsed_tree_column from '~/meta/migrations/v2/nc_038_formula_parsed_tree_column'; import * as nc_038_formula_parsed_tree_column from '~/meta/migrations/v2/nc_038_formula_parsed_tree_column';
import * as nc_039_sqlite_alter_column_types from '~/meta/migrations/v2/nc_039_sqlite_alter_column_types';
import * as nc_040_form_view_alter_column_types from '~/meta/migrations/v2/nc_040_form_view_alter_column_types';
// Create a custom migration source class // Create a custom migration source class
export default class XcMigrationSourcev2 { export default class XcMigrationSourcev2 {
@ -61,6 +63,8 @@ export default class XcMigrationSourcev2 {
'nc_036_base_deleted', 'nc_036_base_deleted',
'nc_037_rename_project_and_base', 'nc_037_rename_project_and_base',
'nc_038_formula_parsed_tree_column', 'nc_038_formula_parsed_tree_column',
'nc_039_sqlite_alter_column_types',
'nc_040_form_view_alter_column_types',
]); ]);
} }
@ -124,6 +128,10 @@ export default class XcMigrationSourcev2 {
return nc_037_rename_project_and_base; return nc_037_rename_project_and_base;
case 'nc_038_formula_parsed_tree_column': case 'nc_038_formula_parsed_tree_column':
return nc_038_formula_parsed_tree_column; return nc_038_formula_parsed_tree_column;
case 'nc_039_sqlite_alter_column_types':
return nc_039_sqlite_alter_column_types;
case 'nc_040_form_view_alter_column_types':
return nc_040_form_view_alter_column_types;
} }
} }
} }

88
packages/nocodb/src/meta/migrations/v2/nc_039_sqlite_alter_column_types.ts

@ -0,0 +1,88 @@
import type { Knex } from 'knex';
import { MetaTable } from '~/utils/globals';
const up = async (knex: Knex) => {
if (knex.client.config.client === 'sqlite3') {
//nc_012_alter_colum_data_types.ts
await knex.schema.alterTable(MetaTable.COLUMNS, (table) => {
table.text('cdf').alter();
});
await knex.schema.alterTable(MetaTable.COLUMNS, (table) => {
table.text('dtxp').alter();
});
await knex.schema.alterTable(MetaTable.COLUMNS, (table) => {
table.text('cc').alter();
});
await knex.schema.alterTable(MetaTable.COLUMNS, (table) => {
table.text('ct').alter();
});
//nc_014_alter_colum_data_types.ts
await knex.schema.alterTable(MetaTable.FORM_VIEW, (table) => {
table.text('success_msg').alter();
});
await knex.schema.alterTable(MetaTable.FORM_VIEW, (table) => {
table.text('redirect_url').alter();
});
await knex.schema.alterTable(MetaTable.FORM_VIEW, (table) => {
table.text('banner_image_url').alter();
});
await knex.schema.alterTable(MetaTable.FORM_VIEW, (table) => {
table.text('logo_url').alter();
});
await knex.schema.alterTable(MetaTable.FORM_VIEW_COLUMNS, (table) => {
table.text('description').alter();
});
//nc_016_alter_hooklog_payload_types.ts
await knex.schema.alterTable(MetaTable.HOOK_LOGS, (table) => {
table.text('payload').alter();
});
//nc_029_webhook.ts
await knex.schema.alterTable(MetaTable.HOOK_LOGS, (table) => {
table.text('response').alter();
});
}
};
const down = async (knex) => {
if (knex.client.config.client === 'sqlite3') {
//nc_012_alter_colum_data_types.ts
await knex.schema.alterTable(MetaTable.COLUMNS, (table) => {
table.string('cdf').alter();
});
await knex.schema.alterTable(MetaTable.COLUMNS, (table) => {
table.string('dtxp').alter();
});
await knex.schema.alterTable(MetaTable.COLUMNS, (table) => {
table.string('cc').alter();
});
await knex.schema.alterTable(MetaTable.COLUMNS, (table) => {
table.string('ct').alter();
});
//nc_014_alter_colum_data_types.ts
await knex.schema.alterTable(MetaTable.FORM_VIEW, (table) => {
table.string('success_msg').alter();
});
await knex.schema.alterTable(MetaTable.FORM_VIEW, (table) => {
table.string('redirect_url').alter();
});
await knex.schema.alterTable(MetaTable.FORM_VIEW, (table) => {
table.string('banner_image_url').alter();
});
await knex.schema.alterTable(MetaTable.FORM_VIEW, (table) => {
table.string('logo_url').alter();
});
await knex.schema.alterTable(MetaTable.FORM_VIEW_COLUMNS, (table) => {
table.string('description').alter();
});
//nc_016_alter_hooklog_payload_types.ts
await knex.schema.alterTable(MetaTable.HOOK_LOGS, (table) => {
table.boolean('payload').alter();
});
//nc_029_webhook.ts
await knex.schema.alterTable(MetaTable.HOOK_LOGS, (table) => {
table.boolean('response').alter();
});
}
};
export { up, down };

28
packages/nocodb/src/meta/migrations/v2/nc_040_form_view_alter_column_types.ts

@ -0,0 +1,28 @@
import type { Knex } from 'knex';
import { MetaTable } from '~/utils/globals';
const up = async (knex: Knex) => {
await knex.schema.alterTable(MetaTable.FORM_VIEW, (table) => {
table.text('subheading').alter();
});
await knex.schema.alterTable(MetaTable.FORM_VIEW_COLUMNS, (table) => {
table.text('label').alter();
});
await knex.schema.alterTable(MetaTable.FORM_VIEW_COLUMNS, (table) => {
table.text('help').alter();
});
};
const down = async (knex: Knex) => {
await knex.schema.alterTable(MetaTable.FORM_VIEW, (table) => {
table.string('subheading').alter();
});
await knex.schema.alterTable(MetaTable.FORM_VIEW_COLUMNS, (table) => {
table.string('label').alter();
});
await knex.schema.alterTable(MetaTable.FORM_VIEW_COLUMNS, (table) => {
table.string('help').alter();
});
};
export { up, down };

1
packages/nocodb/src/models/ApiToken.ts

@ -56,7 +56,6 @@ export default class ApiToken implements ApiTokenType {
static async delete(token, ncMeta = Noco.ncMeta) { static async delete(token, ncMeta = Noco.ncMeta) {
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.API_TOKEN,
`${CacheScope.API_TOKEN}:${token}`, `${CacheScope.API_TOKEN}:${token}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );

2
packages/nocodb/src/models/Base.ts

@ -240,7 +240,6 @@ export default class Base implements BaseType {
// remove item in cache list // remove item in cache list
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.PROJECT,
`${CacheScope.PROJECT}:${baseId}`, `${CacheScope.PROJECT}:${baseId}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );
@ -353,7 +352,6 @@ export default class Base implements BaseType {
} }
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.PROJECT,
`${CacheScope.PROJECT}:${baseId}`, `${CacheScope.PROJECT}:${baseId}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );

14
packages/nocodb/src/models/Column.ts

@ -481,7 +481,6 @@ export default class Column<T = any> implements ColumnType {
public static async clearList({ fk_model_id }) { public static async clearList({ fk_model_id }) {
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.COLUMN,
`${CacheScope.COLUMN}:${fk_model_id}:list`, `${CacheScope.COLUMN}:${fk_model_id}:list`,
CacheDelDirection.PARENT_TO_CHILD, CacheDelDirection.PARENT_TO_CHILD,
); );
@ -822,7 +821,6 @@ export default class Column<T = any> implements ColumnType {
fk_column_id: col.id, fk_column_id: col.id,
}); });
await NocoCache.deepDel( await NocoCache.deepDel(
cacheScopeName,
`${cacheScopeName}:${col.id}`, `${cacheScopeName}:${col.id}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );
@ -838,7 +836,6 @@ export default class Column<T = any> implements ColumnType {
); );
if (gridViewColumnId) { if (gridViewColumnId) {
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.GRID_VIEW_COLUMN,
`${CacheScope.GRID_VIEW_COLUMN}:${gridViewColumnId}`, `${CacheScope.GRID_VIEW_COLUMN}:${gridViewColumnId}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );
@ -854,7 +851,6 @@ export default class Column<T = any> implements ColumnType {
); );
if (formViewColumnId) { if (formViewColumnId) {
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.FORM_VIEW_COLUMN,
`${CacheScope.FORM_VIEW_COLUMN}:${formViewColumnId}`, `${CacheScope.FORM_VIEW_COLUMN}:${formViewColumnId}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );
@ -870,7 +866,6 @@ export default class Column<T = any> implements ColumnType {
); );
if (kanbanViewColumnId) { if (kanbanViewColumnId) {
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.KANBAN_VIEW_COLUMN,
`${CacheScope.KANBAN_VIEW_COLUMN}:${kanbanViewColumnId}`, `${CacheScope.KANBAN_VIEW_COLUMN}:${kanbanViewColumnId}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );
@ -886,7 +881,6 @@ export default class Column<T = any> implements ColumnType {
); );
if (galleryViewColumnId) { if (galleryViewColumnId) {
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.GALLERY_VIEW_COLUMN,
`${CacheScope.GALLERY_VIEW_COLUMN}:${galleryViewColumnId}`, `${CacheScope.GALLERY_VIEW_COLUMN}:${galleryViewColumnId}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );
@ -917,7 +911,6 @@ export default class Column<T = any> implements ColumnType {
// Columns // Columns
await ncMeta.metaDelete(null, null, MetaTable.COLUMNS, col.id); await ncMeta.metaDelete(null, null, MetaTable.COLUMNS, col.id);
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.COLUMN,
`${CacheScope.COLUMN}:${col.id}`, `${CacheScope.COLUMN}:${col.id}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );
@ -944,7 +937,6 @@ export default class Column<T = any> implements ColumnType {
fk_column_id: colId, fk_column_id: colId,
}); });
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.COL_LOOKUP,
`${CacheScope.COL_LOOKUP}:${colId}`, `${CacheScope.COL_LOOKUP}:${colId}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );
@ -955,7 +947,6 @@ export default class Column<T = any> implements ColumnType {
fk_column_id: colId, fk_column_id: colId,
}); });
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.COL_ROLLUP,
`${CacheScope.COL_ROLLUP}:${colId}`, `${CacheScope.COL_ROLLUP}:${colId}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );
@ -967,7 +958,6 @@ export default class Column<T = any> implements ColumnType {
fk_column_id: colId, fk_column_id: colId,
}); });
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.COL_RELATION,
`${CacheScope.COL_RELATION}:${colId}`, `${CacheScope.COL_RELATION}:${colId}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );
@ -979,7 +969,6 @@ export default class Column<T = any> implements ColumnType {
}); });
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.COL_FORMULA,
`${CacheScope.COL_FORMULA}:${colId}`, `${CacheScope.COL_FORMULA}:${colId}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );
@ -991,7 +980,6 @@ export default class Column<T = any> implements ColumnType {
}); });
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.COL_QRCODE,
`${CacheScope.COL_QRCODE}:${colId}`, `${CacheScope.COL_QRCODE}:${colId}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );
@ -1004,7 +992,6 @@ export default class Column<T = any> implements ColumnType {
}); });
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.COL_BARCODE,
`${CacheScope.COL_BARCODE}:${colId}`, `${CacheScope.COL_BARCODE}:${colId}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );
@ -1018,7 +1005,6 @@ export default class Column<T = any> implements ColumnType {
}); });
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.COL_SELECT_OPTION,
`${CacheScope.COL_SELECT_OPTION}:${colId}:list`, `${CacheScope.COL_SELECT_OPTION}:${colId}:list`,
CacheDelDirection.PARENT_TO_CHILD, CacheDelDirection.PARENT_TO_CHILD,
); );

3
packages/nocodb/src/models/Filter.ts

@ -276,7 +276,6 @@ export default class Filter implements FilterType {
await deleteRecursively(f); await deleteRecursively(f);
await ncMeta.metaDelete(null, null, MetaTable.FILTER_EXP, filter.id); await ncMeta.metaDelete(null, null, MetaTable.FILTER_EXP, filter.id);
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.FILTER_EXP,
`${CacheScope.FILTER_EXP}:${filter.id}`, `${CacheScope.FILTER_EXP}:${filter.id}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );
@ -435,7 +434,6 @@ export default class Filter implements FilterType {
if (filter.id) { if (filter.id) {
await ncMeta.metaDelete(null, null, MetaTable.FILTER_EXP, filter.id); await ncMeta.metaDelete(null, null, MetaTable.FILTER_EXP, filter.id);
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.FILTER_EXP,
`${CacheScope.FILTER_EXP}:${filter.id}`, `${CacheScope.FILTER_EXP}:${filter.id}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );
@ -459,7 +457,6 @@ export default class Filter implements FilterType {
if (filter.id) { if (filter.id) {
await ncMeta.metaDelete(null, null, MetaTable.FILTER_EXP, filter.id); await ncMeta.metaDelete(null, null, MetaTable.FILTER_EXP, filter.id);
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.FILTER_EXP,
`${CacheScope.FILTER_EXP}:${filter.id}`, `${CacheScope.FILTER_EXP}:${filter.id}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );

2
packages/nocodb/src/models/Hook.ts

@ -238,7 +238,6 @@ export default class Hook implements HookType {
); );
for (const filter of filterList) { for (const filter of filterList) {
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.FILTER_EXP,
`${CacheScope.FILTER_EXP}:${filter.id}`, `${CacheScope.FILTER_EXP}:${filter.id}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );
@ -246,7 +245,6 @@ export default class Hook implements HookType {
} }
// Delete Hook // Delete Hook
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.HOOK,
`${CacheScope.HOOK}:${hookId}`, `${CacheScope.HOOK}:${hookId}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );

2
packages/nocodb/src/models/HookFilter.ts

@ -174,7 +174,6 @@ export default class Filter {
await deleteRecursively(f); await deleteRecursively(f);
await ncMeta.metaDelete(null, null, MetaTable.FILTER_EXP, filter.id); await ncMeta.metaDelete(null, null, MetaTable.FILTER_EXP, filter.id);
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.FILTER_EXP,
`${CacheScope.FILTER_EXP}:${filter.id}`, `${CacheScope.FILTER_EXP}:${filter.id}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );
@ -307,7 +306,6 @@ export default class Filter {
// if (filter.id) { // if (filter.id) {
// await ncMeta.metaDelete(null, null, MetaTable.FILTER_EXP, filter.id); // await ncMeta.metaDelete(null, null, MetaTable.FILTER_EXP, filter.id);
// await NocoCache.deepDel( // await NocoCache.deepDel(
// CacheScope.FILTER_EXP,
// `${CacheScope.FILTER_EXP}:${filter.id}`, // `${CacheScope.FILTER_EXP}:${filter.id}`,
// CacheDelDirection.CHILD_TO_PARENT // CacheDelDirection.CHILD_TO_PARENT
// ); // );

4
packages/nocodb/src/models/Model.ts

@ -463,7 +463,6 @@ export default class Model implements TableType {
fk_column_id: col.id, fk_column_id: col.id,
}); });
await NocoCache.deepDel( await NocoCache.deepDel(
cacheScopeName,
`${cacheScopeName}:${col.id}`, `${cacheScopeName}:${col.id}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );
@ -484,7 +483,6 @@ export default class Model implements TableType {
for (const col of leftOverColumns) { for (const col of leftOverColumns) {
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.COL_RELATION,
`${CacheScope.COL_RELATION}:${col.fk_column_id}`, `${CacheScope.COL_RELATION}:${col.fk_column_id}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );
@ -496,7 +494,6 @@ export default class Model implements TableType {
} }
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.COLUMN,
`${CacheScope.COLUMN}:${this.id}`, `${CacheScope.COLUMN}:${this.id}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );
@ -505,7 +502,6 @@ export default class Model implements TableType {
}); });
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.MODEL,
`${CacheScope.MODEL}:${this.id}`, `${CacheScope.MODEL}:${this.id}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );

1
packages/nocodb/src/models/ModelRoleVisibility.ts

@ -124,7 +124,6 @@ export default class ModelRoleVisibility implements ModelRoleVisibilityType {
}, },
); );
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.MODEL_ROLE_VISIBILITY,
`${CacheScope.MODEL_ROLE_VISIBILITY}:${fk_view_id}:${role}`, `${CacheScope.MODEL_ROLE_VISIBILITY}:${fk_view_id}:${role}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );

2
packages/nocodb/src/models/Sort.ts

@ -27,7 +27,6 @@ export default class Sort {
public static async deleteAll(viewId: string, ncMeta = Noco.ncMeta) { public static async deleteAll(viewId: string, ncMeta = Noco.ncMeta) {
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.SORT,
`${CacheScope.SORT}:${viewId}`, `${CacheScope.SORT}:${viewId}`,
CacheDelDirection.PARENT_TO_CHILD, CacheDelDirection.PARENT_TO_CHILD,
); );
@ -187,7 +186,6 @@ export default class Sort {
await ncMeta.metaDelete(null, null, MetaTable.SORT, sortId); await ncMeta.metaDelete(null, null, MetaTable.SORT, sortId);
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.SORT,
`${CacheScope.SORT}:${sortId}`, `${CacheScope.SORT}:${sortId}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );

3
packages/nocodb/src/models/Source.ts

@ -414,7 +414,6 @@ export default class Source implements SourceType {
fk_column_id: relCol.col.id, fk_column_id: relCol.col.id,
}); });
await NocoCache.deepDel( await NocoCache.deepDel(
relCol.cacheScopeName,
`${relCol.cacheScopeName}:${relCol.col.id}`, `${relCol.cacheScopeName}:${relCol.col.id}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );
@ -434,7 +433,6 @@ export default class Source implements SourceType {
const res = await ncMeta.metaDelete(null, null, MetaTable.BASES, this.id); const res = await ncMeta.metaDelete(null, null, MetaTable.BASES, this.id);
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.BASE,
`${CacheScope.BASE}:${this.id}`, `${CacheScope.BASE}:${this.id}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );
@ -460,7 +458,6 @@ export default class Source implements SourceType {
); );
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.BASE,
`${CacheScope.BASE}:${this.id}`, `${CacheScope.BASE}:${this.id}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );

2
packages/nocodb/src/models/User.ts

@ -75,7 +75,6 @@ export default class User implements UserType {
const bases = await Base.list({}, ncMeta); const bases = await Base.list({}, ncMeta);
for (const base of bases) { for (const base of bases) {
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.BASE_USER,
`${CacheScope.BASE_USER}:${base.id}:list`, `${CacheScope.BASE_USER}:${base.id}:list`,
CacheDelDirection.PARENT_TO_CHILD, CacheDelDirection.PARENT_TO_CHILD,
); );
@ -299,7 +298,6 @@ export default class User implements UserType {
for (const base of bases) { for (const base of bases) {
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.BASE_USER,
`${CacheScope.BASE_USER}:${base.id}:list`, `${CacheScope.BASE_USER}:${base.id}:list`,
CacheDelDirection.PARENT_TO_CHILD, CacheDelDirection.PARENT_TO_CHILD,
); );

3
packages/nocodb/src/models/View.ts

@ -1139,17 +1139,14 @@ export default class View implements ViewType {
}); });
await ncMeta.metaDelete(null, null, MetaTable.VIEWS, viewId); await ncMeta.metaDelete(null, null, MetaTable.VIEWS, viewId);
await NocoCache.deepDel( await NocoCache.deepDel(
tableScope,
`${tableScope}:${viewId}`, `${tableScope}:${viewId}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );
await NocoCache.deepDel( await NocoCache.deepDel(
columnTableScope,
`${columnTableScope}:${viewId}`, `${columnTableScope}:${viewId}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );
await NocoCache.deepDel( await NocoCache.deepDel(
CacheScope.VIEW,
`${CacheScope.VIEW}:${viewId}`, `${CacheScope.VIEW}:${viewId}`,
CacheDelDirection.CHILD_TO_PARENT, CacheDelDirection.CHILD_TO_PARENT,
); );

8
packages/nocodb/src/modules/jobs/jobs/at-import/at-import.processor.ts

@ -2077,12 +2077,12 @@ export class AtImportProcessor {
filter.operator = 'isNoneOf'; filter.operator = 'isNoneOf';
} }
for (let j = 0; j < filter.value.length; j++) {
filter.value[j] = await sMap.getNcNameFromAtId(filter.value[j]);
}
// if array, break it down to multiple filters // if array, break it down to multiple filters
if (Array.isArray(filter.value)) { if (Array.isArray(filter.value)) {
for (let j = 0; j < filter.value.length; j++) {
filter.value[j] = await sMap.getNcNameFromAtId(filter.value[j]);
}
const fx = { const fx = {
fk_column_id: columnId, fk_column_id: columnId,
logical_op: f.conjunction, logical_op: f.conjunction,

5
packages/nocodb/src/modules/jobs/jobs/at-import/helpers/readAndProcessData.ts

@ -414,8 +414,9 @@ export async function importLTARData({
[assocMeta.refCol.title]: id, [assocMeta.refCol.title]: id,
}); });
// links can be [] & hence assocTableDta[assocMeta.modelMeta.id] can be [].
if ( if (
assocTableData[assocMeta.modelMeta.id].length >= assocTableData[assocMeta.modelMeta.id]?.length >=
BULK_LINK_BATCH_COUNT BULK_LINK_BATCH_COUNT
) { ) {
let insertArray = assocTableData[ let insertArray = assocTableData[
@ -469,7 +470,7 @@ export async function importLTARData({
for (const assocMeta of assocTableMetas) { for (const assocMeta of assocTableMetas) {
// insert remaining data // insert remaining data
if (assocTableData[assocMeta.modelMeta.id].length >= 0) { if (assocTableData[assocMeta.modelMeta.id]?.length >= 0) {
logBasic( logBasic(
`:: Importing '${table.title}' LTAR data :: ${importedCount} - ${ `:: Importing '${table.title}' LTAR data :: ${importedCount} - ${
importedCount + assocTableData[assocMeta.modelMeta.id].length importedCount + assocTableData[assocMeta.modelMeta.id].length

59
packages/nocodb/src/schema/swagger-v2.json

@ -12947,7 +12947,7 @@
} }
}, },
"banner_image_url": { "banner_image_url": {
"$ref": "#/components/schemas/StringOrNull", "$ref": "#/components/schemas/TextOrNull",
"description": "Banner Image URL. Not in use currently." "description": "Banner Image URL. Not in use currently."
}, },
"columns": { "columns": {
@ -12990,7 +12990,7 @@
"example": "collaborative" "example": "collaborative"
}, },
"logo_url": { "logo_url": {
"$ref": "#/components/schemas/StringOrNull", "$ref": "#/components/schemas/TextOrNull",
"description": "Logo URL. Not in use currently." "description": "Logo URL. Not in use currently."
}, },
"meta": { "meta": {
@ -13002,7 +13002,7 @@
"description": "The numbers of seconds to redirect after form submission" "description": "The numbers of seconds to redirect after form submission"
}, },
"redirect_url": { "redirect_url": {
"$ref": "#/components/schemas/StringOrNull", "$ref": "#/components/schemas/TextOrNull",
"description": "URL to redirect after submission" "description": "URL to redirect after submission"
}, },
"show_blank_form": { "show_blank_form": {
@ -13010,7 +13010,7 @@
"description": "Show `Blank Form` after 5 seconds" "description": "Show `Blank Form` after 5 seconds"
}, },
"subheading": { "subheading": {
"type": "string", "$ref": "#/components/schemas/TextOrNull",
"description": "The subheading of the form", "description": "The subheading of the form",
"example": "My Form Subheading" "example": "My Form Subheading"
}, },
@ -13019,7 +13019,7 @@
"description": "Show `Submit Another Form` button" "description": "Show `Submit Another Form` button"
}, },
"success_msg": { "success_msg": {
"$ref": "#/components/schemas/StringOrNull", "$ref": "#/components/schemas/TextOrNull",
"description": "Custom message after the form is successfully submitted" "description": "Custom message after the form is successfully submitted"
}, },
"title": { "title": {
@ -13053,7 +13053,7 @@
"type": "object", "type": "object",
"properties": { "properties": {
"banner_image_url": { "banner_image_url": {
"$ref": "#/components/schemas/StringOrNull", "$ref": "#/components/schemas/TextOrNull",
"description": "Banner Image URL. Not in use currently." "description": "Banner Image URL. Not in use currently."
}, },
"email": { "email": {
@ -13067,7 +13067,7 @@
"type": "string" "type": "string"
}, },
"logo_url": { "logo_url": {
"$ref": "#/components/schemas/StringOrNull", "$ref": "#/components/schemas/TextOrNull",
"description": "Logo URL. Not in use currently." "description": "Logo URL. Not in use currently."
}, },
"meta": { "meta": {
@ -13079,7 +13079,7 @@
"description": "The numbers of seconds to redirect after form submission" "description": "The numbers of seconds to redirect after form submission"
}, },
"redirect_url": { "redirect_url": {
"$ref": "#/components/schemas/StringOrNull", "$ref": "#/components/schemas/TextOrNull",
"description": "URL to redirect after submission" "description": "URL to redirect after submission"
}, },
"show_blank_form": { "show_blank_form": {
@ -13087,15 +13087,16 @@
"description": "Show `Blank Form` after 5 seconds" "description": "Show `Blank Form` after 5 seconds"
}, },
"subheading": { "subheading": {
"$ref": "#/components/schemas/StringOrNull", "$ref": "#/components/schemas/TextOrNull",
"description": "The subheading of the form" "description": "The subheading of the form",
"example": "My Form Subheading"
}, },
"submit_another_form": { "submit_another_form": {
"$ref": "#/components/schemas/Bool", "$ref": "#/components/schemas/Bool",
"description": "Show `Submit Another Form` button" "description": "Show `Submit Another Form` button"
}, },
"success_msg": { "success_msg": {
"$ref": "#/components/schemas/StringOrNull", "$ref": "#/components/schemas/TextOrNull",
"description": "Custom message after the form is successfully submitted" "description": "Custom message after the form is successfully submitted"
} }
}, },
@ -13147,8 +13148,8 @@
"description": "Unique ID" "description": "Unique ID"
}, },
"description": { "description": {
"$ref": "#/components/schemas/StringOrNull", "$ref": "#/components/schemas/TextOrNull",
"description": "Form Column Description (Not in use)" "description": "Form Column Description"
}, },
"fk_column_id": { "fk_column_id": {
"$ref": "#/components/schemas/Id", "$ref": "#/components/schemas/Id",
@ -13159,11 +13160,11 @@
"description": "Foreign Key to View" "description": "Foreign Key to View"
}, },
"help": { "help": {
"$ref": "#/components/schemas/StringOrNull", "$ref": "#/components/schemas/TextOrNull",
"description": "Form Column Help Text" "description": "Form Column Help Text (Not in use)"
}, },
"label": { "label": {
"$ref": "#/components/schemas/StringOrNull", "$ref": "#/components/schemas/TextOrNull",
"description": "Form Column Label" "description": "Form Column Label"
}, },
"meta": { "meta": {
@ -13232,15 +13233,15 @@
}, },
"properties": { "properties": {
"description": { "description": {
"$ref": "#/components/schemas/StringOrNull", "$ref": "#/components/schemas/TextOrNull",
"description": "Form Column Description (Not in use)" "description": "Form Column Description"
}, },
"help": { "help": {
"$ref": "#/components/schemas/StringOrNull", "$ref": "#/components/schemas/TextOrNull",
"description": "Form Column Help Text" "description": "Form Column Help Text (Not in use)"
}, },
"label": { "label": {
"$ref": "#/components/schemas/StringOrNull", "$ref": "#/components/schemas/TextOrNull",
"description": "Form Column Label" "description": "Form Column Label"
}, },
"meta": { "meta": {
@ -16751,6 +16752,22 @@
"id": "8v8qzwm3w4v11" "id": "8v8qzwm3w4v11"
} }
}, },
"TextOrNull": {
"description": "Model for TextOrNull",
"examples": [
"string"
],
"oneOf": [
{
"maxLength": 8192,
"type": "string"
},
{
"type": "null"
}
],
"title": "TextOrNull Model"
},
"StringOrNull": { "StringOrNull": {
"description": "Model for StringOrNull", "description": "Model for StringOrNull",
"examples": [ "examples": [

59
packages/nocodb/src/schema/swagger.json

@ -18193,7 +18193,7 @@
} }
}, },
"banner_image_url": { "banner_image_url": {
"$ref": "#/components/schemas/StringOrNull", "$ref": "#/components/schemas/TextOrNull",
"description": "Banner Image URL. Not in use currently." "description": "Banner Image URL. Not in use currently."
}, },
"columns": { "columns": {
@ -18236,7 +18236,7 @@
"example": "collaborative" "example": "collaborative"
}, },
"logo_url": { "logo_url": {
"$ref": "#/components/schemas/StringOrNull", "$ref": "#/components/schemas/TextOrNull",
"description": "Logo URL. Not in use currently." "description": "Logo URL. Not in use currently."
}, },
"meta": { "meta": {
@ -18248,7 +18248,7 @@
"description": "The numbers of seconds to redirect after form submission" "description": "The numbers of seconds to redirect after form submission"
}, },
"redirect_url": { "redirect_url": {
"$ref": "#/components/schemas/StringOrNull", "$ref": "#/components/schemas/TextOrNull",
"description": "URL to redirect after submission" "description": "URL to redirect after submission"
}, },
"show_blank_form": { "show_blank_form": {
@ -18256,7 +18256,7 @@
"description": "Show `Blank Form` after 5 seconds" "description": "Show `Blank Form` after 5 seconds"
}, },
"subheading": { "subheading": {
"type": "string", "$ref": "#/components/schemas/TextOrNull",
"description": "The subheading of the form", "description": "The subheading of the form",
"example": "My Form Subheading" "example": "My Form Subheading"
}, },
@ -18265,7 +18265,7 @@
"description": "Show `Submit Another Form` button" "description": "Show `Submit Another Form` button"
}, },
"success_msg": { "success_msg": {
"$ref": "#/components/schemas/StringOrNull", "$ref": "#/components/schemas/TextOrNull",
"description": "Custom message after the form is successfully submitted" "description": "Custom message after the form is successfully submitted"
}, },
"title": { "title": {
@ -18299,7 +18299,7 @@
"type": "object", "type": "object",
"properties": { "properties": {
"banner_image_url": { "banner_image_url": {
"$ref": "#/components/schemas/StringOrNull", "$ref": "#/components/schemas/TextOrNull",
"description": "Banner Image URL. Not in use currently." "description": "Banner Image URL. Not in use currently."
}, },
"email": { "email": {
@ -18313,7 +18313,7 @@
"type": "string" "type": "string"
}, },
"logo_url": { "logo_url": {
"$ref": "#/components/schemas/StringOrNull", "$ref": "#/components/schemas/TextOrNull",
"description": "Logo URL. Not in use currently." "description": "Logo URL. Not in use currently."
}, },
"meta": { "meta": {
@ -18325,7 +18325,7 @@
"description": "The numbers of seconds to redirect after form submission" "description": "The numbers of seconds to redirect after form submission"
}, },
"redirect_url": { "redirect_url": {
"$ref": "#/components/schemas/StringOrNull", "$ref": "#/components/schemas/TextOrNull",
"description": "URL to redirect after submission" "description": "URL to redirect after submission"
}, },
"show_blank_form": { "show_blank_form": {
@ -18333,15 +18333,16 @@
"description": "Show `Blank Form` after 5 seconds" "description": "Show `Blank Form` after 5 seconds"
}, },
"subheading": { "subheading": {
"$ref": "#/components/schemas/StringOrNull", "$ref": "#/components/schemas/TextOrNull",
"description": "The subheading of the form" "description": "The subheading of the form",
"example": "My Form Subheading"
}, },
"submit_another_form": { "submit_another_form": {
"$ref": "#/components/schemas/Bool", "$ref": "#/components/schemas/Bool",
"description": "Show `Submit Another Form` button" "description": "Show `Submit Another Form` button"
}, },
"success_msg": { "success_msg": {
"$ref": "#/components/schemas/StringOrNull", "$ref": "#/components/schemas/TextOrNull",
"description": "Custom message after the form is successfully submitted" "description": "Custom message after the form is successfully submitted"
} }
}, },
@ -18393,8 +18394,8 @@
"description": "Unique ID" "description": "Unique ID"
}, },
"description": { "description": {
"$ref": "#/components/schemas/StringOrNull", "$ref": "#/components/schemas/TextOrNull",
"description": "Form Column Description (Not in use)" "description": "Form Column Description"
}, },
"fk_column_id": { "fk_column_id": {
"$ref": "#/components/schemas/Id", "$ref": "#/components/schemas/Id",
@ -18405,11 +18406,11 @@
"description": "Foreign Key to View" "description": "Foreign Key to View"
}, },
"help": { "help": {
"$ref": "#/components/schemas/StringOrNull", "$ref": "#/components/schemas/TextOrNull",
"description": "Form Column Help Text" "description": "Form Column Help Text (Not in use)"
}, },
"label": { "label": {
"$ref": "#/components/schemas/StringOrNull", "$ref": "#/components/schemas/TextOrNull",
"description": "Form Column Label" "description": "Form Column Label"
}, },
"meta": { "meta": {
@ -18478,15 +18479,15 @@
}, },
"properties": { "properties": {
"description": { "description": {
"$ref": "#/components/schemas/StringOrNull", "$ref": "#/components/schemas/TextOrNull",
"description": "Form Column Description (Not in use)" "description": "Form Column Description"
}, },
"help": { "help": {
"$ref": "#/components/schemas/StringOrNull", "$ref": "#/components/schemas/TextOrNull",
"description": "Form Column Help Text" "description": "Form Column Help Text (Not in use)"
}, },
"label": { "label": {
"$ref": "#/components/schemas/StringOrNull", "$ref": "#/components/schemas/TextOrNull",
"description": "Form Column Label" "description": "Form Column Label"
}, },
"meta": { "meta": {
@ -22007,6 +22008,22 @@
"id": "8v8qzwm3w4v11" "id": "8v8qzwm3w4v11"
} }
}, },
"TextOrNull": {
"description": "Model for TextOrNull",
"examples": [
"string"
],
"oneOf": [
{
"maxLength": 8192,
"type": "string"
},
{
"type": "null"
}
],
"title": "TextOrNull Model"
},
"StringOrNull": { "StringOrNull": {
"description": "Model for StringOrNull", "description": "Model for StringOrNull",
"examples": [ "examples": [

2
packages/nocodb/src/utils/globals.ts

@ -176,8 +176,6 @@ export enum CacheDelDirection {
CHILD_TO_PARENT = 'CHILD_TO_PARENT', CHILD_TO_PARENT = 'CHILD_TO_PARENT',
} }
export const CacheListProp = '__nc_list__';
export const GROUPBY_COMPARISON_OPS = <const>[ export const GROUPBY_COMPARISON_OPS = <const>[
// these are used for groupby // these are used for groupby
'gb_eq', 'gb_eq',

30
pnpm-lock.yaml

@ -233,8 +233,8 @@ importers:
specifier: ^1.1.23 specifier: ^1.1.23
version: 1.1.23 version: 1.1.23
'@iconify-json/carbon': '@iconify-json/carbon':
specifier: ^1.1.29 specifier: ^1.1.30
version: 1.1.29 version: 1.1.30
'@iconify-json/cil': '@iconify-json/cil':
specifier: ^1.1.8 specifier: ^1.1.8
version: 1.1.8 version: 1.1.8
@ -257,8 +257,8 @@ importers:
specifier: ^1.1.42 specifier: ^1.1.42
version: 1.1.42 version: 1.1.42
'@iconify-json/lucide': '@iconify-json/lucide':
specifier: ^1.1.163 specifier: ^1.1.165
version: 1.1.163 version: 1.1.165
'@iconify-json/material-symbols': '@iconify-json/material-symbols':
specifier: ^1.1.72 specifier: ^1.1.72
version: 1.1.72 version: 1.1.72
@ -275,8 +275,8 @@ importers:
specifier: ^1.1.19 specifier: ^1.1.19
version: 1.1.19 version: 1.1.19
'@iconify-json/simple-icons': '@iconify-json/simple-icons':
specifier: ^1.1.90 specifier: ^1.1.91
version: 1.1.90 version: 1.1.91
'@iconify-json/system-uicons': '@iconify-json/system-uicons':
specifier: ^1.1.12 specifier: ^1.1.12
version: 1.1.12 version: 1.1.12
@ -4563,8 +4563,8 @@ packages:
'@iconify/types': 2.0.0 '@iconify/types': 2.0.0
dev: true dev: true
/@iconify-json/carbon@1.1.29: /@iconify-json/carbon@1.1.30:
resolution: {integrity: sha512-zfyvX/kPItpBEU0fV0FhMW8Ln8PJX6is/L/GX7z9OOoVWEz1k8IlbK3KoBBH6ODZ8HHKG7HQ9FZ4nNl6RDe0Lw==} resolution: {integrity: sha512-tEvEmxCO0J0t0p2NT2IvJ+iiSNqqnabygSo/S8wkeh2Guhc4tQOgl9dxvDMpy6RqwtKRTKsaROPtnRfu/bl+Tg==}
dependencies: dependencies:
'@iconify/types': 2.0.0 '@iconify/types': 2.0.0
dev: true dev: true
@ -4611,8 +4611,8 @@ packages:
'@iconify/types': 2.0.0 '@iconify/types': 2.0.0
dev: true dev: true
/@iconify-json/lucide@1.1.163: /@iconify-json/lucide@1.1.165:
resolution: {integrity: sha512-zZWM3FJfsUk6RQOrh3rd9DEek4QLCndiFGHyC6sRTMZQgCQ3muI3q20OQJ5QlvX2Md87OKcxPLgFiY2+Z/wA0w==} resolution: {integrity: sha512-JkfIpIxKKpvJ2Xx7mGpUkYSpxVy3WrJTp3F/dhmX3ShxE47e90YMFfl8TyANNLSVgxSC/LMnY4I4Xh+7h34h9w==}
dependencies: dependencies:
'@iconify/types': 2.0.0 '@iconify/types': 2.0.0
dev: true dev: true
@ -4647,8 +4647,8 @@ packages:
'@iconify/types': 2.0.0 '@iconify/types': 2.0.0
dev: true dev: true
/@iconify-json/simple-icons@1.1.90: /@iconify-json/simple-icons@1.1.91:
resolution: {integrity: sha512-IYDCQD3VJ5verAwWwMgnbJ6SvPzEqH0SG6JsI2ySuI64d7qVbcJMFycc0kGPfSsi2yAPYHk+tD6Iln17y1MuNA==} resolution: {integrity: sha512-hFWxeQWjCh26nObKnEm+AMB5W+bh4pXtmT3PnecS7rP2Crh0AHi5QBHPtH+6L8R6xZtBk5I2TLoA0TRzCgrF8A==}
dependencies: dependencies:
'@iconify/types': 2.0.0 '@iconify/types': 2.0.0
dev: true dev: true
@ -23474,9 +23474,6 @@ packages:
/sqlite3@5.1.6: /sqlite3@5.1.6:
resolution: {integrity: sha512-olYkWoKFVNSSSQNvxVUfjiVbz3YtBwTJj+mfV5zpHmqW3sELx2Cf4QCdirMelhM5Zh+KDVaKgQHqCxrqiWHybw==} resolution: {integrity: sha512-olYkWoKFVNSSSQNvxVUfjiVbz3YtBwTJj+mfV5zpHmqW3sELx2Cf4QCdirMelhM5Zh+KDVaKgQHqCxrqiWHybw==}
requiresBuild: true requiresBuild: true
peerDependenciesMeta:
node-gyp:
optional: true
dependencies: dependencies:
'@mapbox/node-pre-gyp': 1.0.11 '@mapbox/node-pre-gyp': 1.0.11
node-addon-api: 4.3.0 node-addon-api: 4.3.0
@ -23491,9 +23488,6 @@ packages:
/sqlite3@5.1.7: /sqlite3@5.1.7:
resolution: {integrity: sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog==} resolution: {integrity: sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog==}
requiresBuild: true requiresBuild: true
peerDependenciesMeta:
node-gyp:
optional: true
dependencies: dependencies:
bindings: 1.5.0 bindings: 1.5.0
node-addon-api: 7.0.0 node-addon-api: 7.0.0

2
tests/playwright/constants/index.ts

@ -1,4 +1,4 @@
const airtableApiKey = 'keyn1MR87qgyUsYg4'; const airtableApiKey = 'patnDizoItL6GsweQ.88f74da91af272b42326802c212e382d39dd38be9b81f4ad25beaae7de793535';
const airtableApiBase = 'https://airtable.com/shr4z0qmh6dg5s3eB'; const airtableApiBase = 'https://airtable.com/shr4z0qmh6dg5s3eB';
const defaultBaseName = 'Base'; const defaultBaseName = 'Base';

55
tests/playwright/tests/db/features/keyboardShortcuts.spec.ts

@ -137,6 +137,61 @@ test.describe('Verify shortcuts', () => {
await page.keyboard.press((await grid.isMacOs()) ? 'Meta+Enter' : 'Control+Enter'); await page.keyboard.press((await grid.isMacOs()) ? 'Meta+Enter' : 'Control+Enter');
await page.reload(); await page.reload();
await grid.cell.verify({ index: 1, columnHeader: 'Country', value: 'NewAlgeria' }); await grid.cell.verify({ index: 1, columnHeader: 'Country', value: 'NewAlgeria' });
// Tab:
// If current page is not last page and and current cell is last column of last row and user press `Tab` then current page will be incremented by 1
await grid.cell.click({ index: 24, columnHeader: 'Cities' });
await page.keyboard.press('Tab');
await grid.verifyActivePage({ pageNumber: '2' });
await grid.cell.verifyCellActiveSelected({ index: 0, columnHeader: 'Country' });
// If current page is last page and and current cell is last column of last row and user press `Tab` then new empty row will be added
await grid.clickPagination({ type: 'last-page' });
await grid.cell.click({ index: 8, columnHeader: 'Cities' });
await page.keyboard.press('Tab');
await grid.verifyRowCount({ count: 10 });
await grid.cell.verifyCellActiveSelected({ index: 9, columnHeader: 'Country' });
// If current page is not first page and and current cell is first column of first row and user press `Shift+Tab` then current page will be decremented by 1
await grid.cell.click({ index: 0, columnHeader: 'Country' });
await page.keyboard.press('Shift+Tab');
await grid.verifyActivePage({ pageNumber: '4' });
await grid.cell.verifyCellActiveSelected({ index: 24, columnHeader: 'Cities' });
// If current page is first page and and current cell is first column of first row and user press `Shift+Tab` then current page will not change
await grid.clickPagination({ type: 'first-page' });
await grid.cell.click({ index: 0, columnHeader: 'Country' });
await page.keyboard.press('Shift+Tab');
await grid.verifyActivePage({ pageNumber: '1' });
await grid.cell.verifyCellActiveSelected({ index: 0, columnHeader: 'Country' });
// ArrowDown:
// If current page is not last page and and current cell is in last row and user press `ArrowDown` then current page will be incremented by 1
await grid.cell.click({ index: 24, columnHeader: 'Cities' });
await page.keyboard.press('ArrowDown');
await grid.verifyActivePage({ pageNumber: '2' });
await grid.cell.verifyCellActiveSelected({ index: 0, columnHeader: 'Cities' });
// If current page is last page and and current cell is in last row and user press `ArrowDown` then new empty row will be added
await grid.clickPagination({ type: 'last-page' });
await grid.cell.click({ index: 8, columnHeader: 'Cities' });
await page.keyboard.press('ArrowDown');
await grid.verifyRowCount({ count: 10 });
await grid.cell.verifyCellActiveSelected({ index: 9, columnHeader: 'Country' });
// ArrowUp:
// If current page is not first page and and current cell is in first row and user press `ArrwoUp` then current page will be decremented by 1
await grid.cell.click({ index: 0, columnHeader: 'Cities' });
await page.keyboard.press('ArrowUp');
await grid.verifyActivePage({ pageNumber: '4' });
await grid.cell.verifyCellActiveSelected({ index: 24, columnHeader: 'Cities' });
// If current page is first page and and current cell is in first row and user press `ArrwoUp` then current page will not change
await grid.clickPagination({ type: 'first-page' });
await grid.cell.click({ index: 0, columnHeader: 'Cities' });
await page.keyboard.press('ArrowUp');
await grid.verifyActivePage({ pageNumber: '1' });
await grid.cell.verifyCellActiveSelected({ index: 0, columnHeader: 'Cities' });
}); });
}); });

Loading…
Cancel
Save