mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
(core) updates from grist-core
This commit is contained in:
commit
c27f832851
2
.github/workflows/docker.yml
vendored
2
.github/workflows/docker.yml
vendored
@ -19,7 +19,7 @@ env:
|
||||
jobs:
|
||||
push_to_registry:
|
||||
name: Push Docker images to Docker Hub
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
matrix:
|
||||
image:
|
||||
|
6
.github/workflows/docker_latest.yml
vendored
6
.github/workflows/docker_latest.yml
vendored
@ -45,12 +45,12 @@ env:
|
||||
jobs:
|
||||
push_to_registry:
|
||||
name: Push latest Docker image to Docker Hub
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
if: ${{ vars.RUN_DAILY_BUILD }}
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [3.11]
|
||||
node-version: [18.x]
|
||||
node-version: [22.x]
|
||||
image:
|
||||
# We build two images, `grist-oss` and `grist`.
|
||||
# See https://github.com/gristlabs/grist-core?tab=readme-ov-file#available-docker-images
|
||||
@ -169,7 +169,7 @@ jobs:
|
||||
|
||||
update_latest_branch:
|
||||
name: Update latest branch
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
needs: push_to_registry
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
|
2
.github/workflows/fly-build.yml
vendored
2
.github/workflows/fly-build.yml
vendored
@ -12,7 +12,7 @@ on:
|
||||
jobs:
|
||||
build:
|
||||
name: Build Docker image
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
# Build when the 'preview' label is added, or when PR is updated with this label present.
|
||||
if: >
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
|
2
.github/workflows/fly-cleanup.yml
vendored
2
.github/workflows/fly-cleanup.yml
vendored
@ -13,7 +13,7 @@ env:
|
||||
jobs:
|
||||
clean:
|
||||
name: Clean stale deployed apps
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
if: github.repository_owner == 'gristlabs'
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
2
.github/workflows/fly-deploy.yml
vendored
2
.github/workflows/fly-deploy.yml
vendored
@ -12,7 +12,7 @@ on:
|
||||
jobs:
|
||||
deploy:
|
||||
name: Deploy app to fly.io
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
if: |
|
||||
github.event.workflow_run.event == 'pull_request' &&
|
||||
github.event.workflow_run.conclusion == 'success'
|
||||
|
2
.github/workflows/fly-destroy.yml
vendored
2
.github/workflows/fly-destroy.yml
vendored
@ -14,7 +14,7 @@ on:
|
||||
jobs:
|
||||
destroy:
|
||||
name: Remove app from fly.io
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
# Remove the deployment when 'preview' label is removed, or the PR is closed.
|
||||
if: |
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
|
19
.github/workflows/main.yml
vendored
19
.github/workflows/main.yml
vendored
@ -11,26 +11,26 @@ on:
|
||||
|
||||
jobs:
|
||||
build_and_test:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
# it is helpful to know which sets of tests would have succeeded,
|
||||
# even when there is a failure.
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: [3.11]
|
||||
node-version: [18.x]
|
||||
node-version: [22.x]
|
||||
tests:
|
||||
- ':lint:python:client:common:smoke:stubs:'
|
||||
- ':server-1-of-2:'
|
||||
- ':server-2-of-2:'
|
||||
- ':nbrowser-^[A-G]:'
|
||||
- ':nbrowser-^[H-L]:'
|
||||
- ':nbrowser-^[M-O]:'
|
||||
- ':nbrowser-^[P-S]:'
|
||||
- ':nbrowser-^[^A-S]:'
|
||||
- ':nbrowser-^[A-D]:'
|
||||
- ':nbrowser-^[E-L]:'
|
||||
- ':nbrowser-^[M-N]:'
|
||||
- ':nbrowser-^[O-R]:'
|
||||
- ':nbrowser-^[^A-R]:'
|
||||
include:
|
||||
- tests: ':lint:python:client:common:smoke:'
|
||||
node-version: 18.x
|
||||
node-version: 22.x
|
||||
python-version: '3.10'
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@ -125,6 +125,7 @@ jobs:
|
||||
ARTIFACT_NAME=logs-$(echo $TESTS | sed 's/[^-a-zA-Z0-9]/_/g')
|
||||
echo "Artifact name is '$ARTIFACT_NAME'"
|
||||
echo "ARTIFACT_NAME=$ARTIFACT_NAME" >> $GITHUB_ENV
|
||||
mkdir -p $TESTDIR
|
||||
find $TESTDIR -iname "*.socket" -exec rm {} \;
|
||||
env:
|
||||
TESTS: ${{ matrix.tests }}
|
||||
@ -166,7 +167,7 @@ jobs:
|
||||
candidate:
|
||||
needs: build_and_test
|
||||
if: ${{ success() && github.event_name == 'push' }}
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Fetch new candidate branch
|
||||
uses: actions/checkout@v3
|
||||
|
2
.github/workflows/self-hosted.yml
vendored
2
.github/workflows/self-hosted.yml
vendored
@ -9,7 +9,7 @@ on:
|
||||
jobs:
|
||||
add-to-project:
|
||||
name: Add issue to project
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/add-to-project@v1.0.1
|
||||
with:
|
||||
|
2
.github/workflows/translation_keys.yml
vendored
2
.github/workflows/translation_keys.yml
vendored
@ -12,7 +12,7 @@ permissions:
|
||||
jobs:
|
||||
build:
|
||||
if: github.repository_owner == 'gristlabs'
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
|
49
Dockerfile
49
Dockerfile
@ -45,19 +45,30 @@ RUN \
|
||||
## Python collection stage
|
||||
################################################################################
|
||||
|
||||
# Fetch python3.11 and python2.7
|
||||
FROM python:3.11-slim-buster AS collector
|
||||
|
||||
# Install all python dependencies.
|
||||
ADD sandbox/requirements.txt requirements.txt
|
||||
# Fetch python3.11
|
||||
FROM python:3.11-slim-buster AS collector-py3
|
||||
ADD sandbox/requirements3.txt requirements3.txt
|
||||
RUN \
|
||||
pip3 install -r requirements3.txt
|
||||
|
||||
# Fetch <shame>python2.7</shame>
|
||||
# This is to support users with old documents.
|
||||
# If you have documents with python2.7 formulas, try switching
|
||||
# to python3 in the document settings. It'll probably work fine!
|
||||
# And we'll be forced to turn off python2 support eventually,
|
||||
# the workarounds needed to keep it are getting silly.
|
||||
# It doesn't exist in recent Debian, so we need to reach back
|
||||
# to buster.
|
||||
FROM python:2.7-slim-buster AS collector-py2
|
||||
ADD sandbox/requirements.txt requirements.txt
|
||||
RUN \
|
||||
apt update && \
|
||||
apt install -y --no-install-recommends python2 python-pip python-setuptools \
|
||||
build-essential libxml2-dev libxslt-dev python-dev zlib1g-dev && \
|
||||
pip2 install wheel && \
|
||||
pip2 install -r requirements.txt && \
|
||||
pip3 install -r requirements3.txt
|
||||
pip2 install six && \
|
||||
find /usr/lib -iname "libffi.so.6*" -exec cp {} /usr/local/lib \;
|
||||
|
||||
################################################################################
|
||||
## Sandbox collection stage
|
||||
@ -66,6 +77,8 @@ RUN \
|
||||
# Fetch gvisor-based sandbox. Note, to enable it to run within default
|
||||
# unprivileged docker, layers of protection that require privilege have
|
||||
# been stripped away, see https://github.com/google/gvisor/issues/4371
|
||||
# The sandbox binary is built on buster, but remains compatible with recent
|
||||
# Debian.
|
||||
FROM docker.io/gristlabs/gvisor-unprivileged:buster AS sandbox
|
||||
|
||||
################################################################################
|
||||
@ -91,13 +104,23 @@ COPY --from=builder /grist/node_modules /grist/node_modules
|
||||
COPY --from=builder /grist/_build /grist/_build
|
||||
COPY --from=builder /grist/static /grist/static-built
|
||||
|
||||
# Copy python files.
|
||||
COPY --from=collector /usr/bin/python2.7 /usr/bin/python2.7
|
||||
COPY --from=collector /usr/lib/python2.7 /usr/lib/python2.7
|
||||
COPY --from=collector /usr/local/lib/python2.7 /usr/local/lib/python2.7
|
||||
COPY --from=collector /usr/local/bin/python3.11 /usr/bin/python3.11
|
||||
COPY --from=collector /usr/local/lib/python3.11 /usr/local/lib/python3.11
|
||||
COPY --from=collector /usr/local/lib/libpython3.11.* /usr/local/lib/
|
||||
# Copy python2 files.
|
||||
COPY --from=collector-py2 /usr/bin/python2.7 /usr/bin/python2.7
|
||||
COPY --from=collector-py2 /usr/lib/python2.7 /usr/lib/python2.7
|
||||
COPY --from=collector-py2 /usr/local/lib/python2.7 /usr/local/lib/python2.7
|
||||
# Make a small python2 tweak so that material in /usr/local/lib is found.
|
||||
RUN \
|
||||
mkdir /etc/python2.7 && \
|
||||
echo "import sys\nsys.path.append('/usr/local/lib/python2.7/site-packages')" > /etc/python2.7/sitecustomize.py
|
||||
# Copy across an older libffi library binary needed by python2.
|
||||
# We moved it a bit sleazily to a predictable location to avoid awkward
|
||||
# architecture-dependent logic.
|
||||
COPY --from=collector-py2 /usr/local/lib/libffi.so.6* /usr/local/lib
|
||||
|
||||
# Copy python3 files.
|
||||
COPY --from=collector-py3 /usr/local/bin/python3.11 /usr/bin/python3.11
|
||||
COPY --from=collector-py3 /usr/local/lib/python3.11 /usr/local/lib/python3.11
|
||||
COPY --from=collector-py3 /usr/local/lib/libpython3.11.* /usr/local/lib/
|
||||
# Set default to python3
|
||||
RUN \
|
||||
ln -s /usr/bin/python3.11 /usr/bin/python && \
|
||||
|
@ -262,6 +262,8 @@ Grist can be configured in many ways. Here are the main environment variables it
|
||||
| APP_STATIC_URL | url prefix for static resources |
|
||||
| APP_STATIC_INCLUDE_CUSTOM_CSS | set to "true" to include custom.css (from APP_STATIC_URL) in static pages |
|
||||
| APP_UNTRUSTED_URL | URL at which to serve/expect plugin content. |
|
||||
| GRIST_ACTION_HISTORY_MAX_ROWS | Maximum number of rows allowed in ActionHistory before pruning (up to a 1.25 grace factor). Defaults to 1000. ⚠️ A too low value may make the "[Work on a copy](https://support.getgrist.com/newsletters/2021-06/#work-on-a-copy)" feature [malfunction](https://github.com/gristlabs/grist-core/issues/1121#issuecomment-2248112023) |
|
||||
| GRIST_ACTION_HISTORY_MAX_BYTES | Maximum number of rows allowed in ActionHistory before pruning (up to a 1.25 grace factor). Defaults to 1Gb. ⚠️ A too low value may make the "[Work on a copy](https://support.getgrist.com/newsletters/2021-06/#work-on-a-copy)" feature [malfunction](https://github.com/gristlabs/grist-core/issues/1121#issuecomment-2248112023) |
|
||||
| GRIST_ADAPT_DOMAIN | set to "true" to support multiple base domains (careful, host header should be trustworthy) |
|
||||
| GRIST_APP_ROOT | directory containing Grist sandbox and assets (specifically the sandbox and static subdirectories). |
|
||||
| GRIST_BACKUP_DELAY_SECS | wait this long after a doc change before making a backup |
|
||||
|
@ -1084,6 +1084,16 @@ abstract class ObsRuleSet extends Disposable {
|
||||
public getCustomRules(): ObsRulePart[] {
|
||||
return this._body.get().filter(rule => !rule.isBuiltInOrEmpty());
|
||||
}
|
||||
|
||||
/**
|
||||
* If the set applies to a special column, return its name.
|
||||
*/
|
||||
public getSpecialColumn(): string|undefined {
|
||||
if (this._ruleSet?.tableId === SPECIAL_RULES_TABLE_ID &&
|
||||
this._ruleSet.colIds.length === 1) {
|
||||
return this._ruleSet.colIds[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ColumnObsRuleSet extends ObsRuleSet {
|
||||
@ -1635,6 +1645,14 @@ class ObsRulePart extends Disposable {
|
||||
!isEqual(use(this._permissions), this._rulePart?.permissions ?? emptyPerms)
|
||||
);
|
||||
});
|
||||
// The formula may be invalid from the beginning. Make sure we show errors in this
|
||||
// case.
|
||||
const text = this._aclFormula.get();
|
||||
if (text) {
|
||||
this._setAclFormula(text, true).catch(e => {
|
||||
console.error(e);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public getRulePart(): RuleRec {
|
||||
@ -1790,8 +1808,8 @@ class ObsRulePart extends Disposable {
|
||||
return this.isBuiltIn() && this._ruleSet.getFirstBuiltIn() !== this;
|
||||
}
|
||||
|
||||
private async _setAclFormula(text: string) {
|
||||
if (text === this._aclFormula.get()) { return; }
|
||||
private async _setAclFormula(text: string, initial: boolean = false) {
|
||||
if (text === this._aclFormula.get() && !initial) { return; }
|
||||
this._aclFormula.set(text);
|
||||
this._checkPending.set(true);
|
||||
this._formulaProperties.set({});
|
||||
@ -1809,6 +1827,12 @@ class ObsRulePart extends Disposable {
|
||||
private _warnInvalidColIds(colIds?: string[]) {
|
||||
if (!colIds || !colIds.length) { return false; }
|
||||
const allValid = new Set(this._ruleSet.getValidColIds());
|
||||
const specialColumn = this._ruleSet.getSpecialColumn();
|
||||
if (specialColumn === 'SeedRule') {
|
||||
// We allow seed rules to refer to columns without checking
|
||||
// them (until the seed rules are used).
|
||||
return false;
|
||||
}
|
||||
const invalid = colIds.filter(c => !allValid.has(c));
|
||||
if (invalid.length > 0) {
|
||||
return `Invalid columns: ${invalid.join(', ')}`;
|
||||
|
@ -71,7 +71,13 @@ export class GristClientSocket {
|
||||
}
|
||||
|
||||
private _createWSSocket() {
|
||||
if (typeof WebSocket !== 'undefined') {
|
||||
// We used to check if WebSocket was defined here, and use it
|
||||
// if so, secure in the fact that we were in the browser and
|
||||
// the browser would pass along cookie information. But recent
|
||||
// node defines WebSocket, so we narrow down this path to when
|
||||
// a global document is defined (window doesn't work because
|
||||
// some tests mock it).
|
||||
if (typeof document !== 'undefined') {
|
||||
this._wsSocket = new WebSocket(this._url);
|
||||
} else {
|
||||
this._wsSocket = new WS(this._url, undefined, this._options);
|
||||
|
@ -12,14 +12,28 @@ import mapValues = require('lodash/mapValues');
|
||||
import {ActionGroupOptions, ActionHistory, ActionHistoryUndoInfo, asActionGroup,
|
||||
asMinimalActionGroup} from './ActionHistory';
|
||||
import {ISQLiteDB, ResultRow} from './SQLiteDB';
|
||||
import { appSettings } from './AppSettings';
|
||||
|
||||
const section = appSettings.section('history').section('action');
|
||||
|
||||
// History will from time to time be pruned back to within these limits
|
||||
// on rows and the maximum total number of bytes in the "body" column.
|
||||
// Pruning is done when the history has grown above these limits, to
|
||||
// the specified factor.
|
||||
const ACTION_HISTORY_MAX_ROWS = 1000;
|
||||
const ACTION_HISTORY_MAX_BYTES = 1000 * 1000 * 1000; // 1 GB.
|
||||
const ACTION_HISTORY_GRACE_FACTOR = 1.25; // allow growth to 1250 rows / 1.25 GB.
|
||||
const ACTION_HISTORY_MAX_ROWS = section.flag('maxRows').requireInt({
|
||||
envVar: 'GRIST_ACTION_HISTORY_MAX_ROWS',
|
||||
defaultValue: 1000,
|
||||
|
||||
minValue: 1,
|
||||
});
|
||||
|
||||
const ACTION_HISTORY_MAX_BYTES = section.flag('maxBytes').requireInt({
|
||||
envVar: 'GRIST_ACTION_HISTORY_MAX_BYTES',
|
||||
defaultValue: 1e9, // 1 GB.
|
||||
minValue: 1, // 1 B.
|
||||
});
|
||||
|
||||
const ACTION_HISTORY_GRACE_FACTOR = 1.25; // allow growth to 1.25 times the above limits.
|
||||
const ACTION_HISTORY_CHECK_PERIOD = 10; // number of actions between size checks.
|
||||
|
||||
/**
|
||||
|
@ -99,7 +99,7 @@ export class AppSettings {
|
||||
/**
|
||||
* As for readInt() but fail if nothing was found.
|
||||
*/
|
||||
public requireInt(query: AppSettingQuery): number {
|
||||
public requireInt(query: AppSettingQueryInt): number {
|
||||
const result = this.readInt(query);
|
||||
if (result === undefined) {
|
||||
throw new Error(`missing environment variable: ${query.envVar}`);
|
||||
@ -122,9 +122,19 @@ export class AppSettings {
|
||||
* As for read() but type (and store, and report) the result as
|
||||
* an integer (well, a number).
|
||||
*/
|
||||
public readInt(query: AppSettingQuery): number|undefined {
|
||||
public readInt(query: AppSettingQueryInt): number|undefined {
|
||||
this.readString(query);
|
||||
const result = this.getAsInt();
|
||||
|
||||
if (result !== undefined) {
|
||||
if (query.minValue !== undefined && result < query.minValue) {
|
||||
throw new Error(`value ${result} is less than minimum ${query.minValue}`);
|
||||
}
|
||||
if (query.maxValue !== undefined && result > query.maxValue) {
|
||||
throw new Error(`value ${result} is greater than maximum ${query.maxValue}`);
|
||||
}
|
||||
}
|
||||
|
||||
this._value = result;
|
||||
return result;
|
||||
}
|
||||
@ -213,11 +223,39 @@ export const appSettings = new AppSettings('grist');
|
||||
* environment variables and default values.
|
||||
*/
|
||||
export interface AppSettingQuery {
|
||||
envVar: string|string[]; // environment variable(s) to check.
|
||||
preferredEnvVar?: string; // "Canonical" environment variable to suggest.
|
||||
// Should be in envVar (though this is not checked).
|
||||
defaultValue?: JSONValue; // value to use if variable(s) unavailable.
|
||||
censor?: boolean; // should the value of the setting be obscured when printed.
|
||||
/**
|
||||
* Environment variable(s) to check.
|
||||
*/
|
||||
envVar: string|string[];
|
||||
/**
|
||||
* "Canonical" environment variable to suggest. Should be in envVar (though this is not checked).
|
||||
*/
|
||||
preferredEnvVar?: string;
|
||||
/**
|
||||
* Value to use if the variable(s) is/are unavailable.
|
||||
*/
|
||||
defaultValue?: JSONValue;
|
||||
/**
|
||||
* When set to true, the value is obscured when printed.
|
||||
*/
|
||||
censor?: boolean;
|
||||
}
|
||||
|
||||
export interface AppSettingQueryInt extends AppSettingQuery {
|
||||
/**
|
||||
* Value to use if variable(s) unavailable.
|
||||
*/
|
||||
defaultValue?: number;
|
||||
/**
|
||||
* Minimum value allowed. Raises an error if the value is lower than this.
|
||||
* If the value is undefined, the setting is not checked.
|
||||
*/
|
||||
minValue?: number;
|
||||
/**
|
||||
* Maximum value allowed. Raises an error if the value is greater than this.
|
||||
* If the value is undefined, the setting is not checked.
|
||||
*/
|
||||
maxValue?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -141,6 +141,7 @@ export class OIDCConfig {
|
||||
});
|
||||
const httpTimeout = section.flag('httpTimeout').readInt({
|
||||
envVar: 'GRIST_OIDC_SP_HTTP_TIMEOUT',
|
||||
minValue: 0, // 0 means no timeout
|
||||
});
|
||||
this._namePropertyKey = section.flag('namePropertyKey').readString({
|
||||
envVar: 'GRIST_OIDC_SP_PROFILE_NAME_ATTR',
|
||||
|
1
static/locales/ar.server.json
Normal file
1
static/locales/ar.server.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
1
static/locales/bg.server.json
Normal file
1
static/locales/bg.server.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
1
static/locales/ca.server.json
Normal file
1
static/locales/ca.server.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
1
static/locales/cs.server.json
Normal file
1
static/locales/cs.server.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
@ -154,7 +154,8 @@
|
||||
"Comment": "Kommentar",
|
||||
"Copy": "Kopieren",
|
||||
"Cut": "Schneiden",
|
||||
"Paste": "Einfügen"
|
||||
"Paste": "Einfügen",
|
||||
"Copy with headers": "Kopieren mit Kopfzeilen"
|
||||
},
|
||||
"ChartView": {
|
||||
"Create separate series for each value of the selected column.": "Erstellen Sie separate Datenreihen für jeden Wert der ausgewählten Spalte.",
|
||||
@ -212,7 +213,15 @@
|
||||
"{{wrongTypeCount}} non-{{columnType}} columns are not shown_one": "Spalte {{wrongTypeCount}} Nicht-{{columnType}} wird nicht angezeigt",
|
||||
"{{wrongTypeCount}} non-{{columnType}} columns are not shown_other": "Spalten {{wrongTypeCount}} Nicht-{{columnType}} werden nicht angezeigt",
|
||||
"No {{columnType}} columns in table.": "Keine {{columnType}} Spalten in der Tabelle.",
|
||||
"Clear selection": "Auswahl löschen"
|
||||
"Clear selection": "Auswahl löschen",
|
||||
"ACCESS LEVEL": "ZUGRIFFSEBENE",
|
||||
"Custom URL": "Benutzerdefinierte URL",
|
||||
"Last updated:": "Letzte Aktualisierung:",
|
||||
"Missing description and author information.": "Fehlende Beschreibung und Autorenangaben.",
|
||||
"Reject": "Ablehnen",
|
||||
"Widget": "Widget",
|
||||
"Developer:": "Entwickler:",
|
||||
"Accept": "Akzeptieren"
|
||||
},
|
||||
"DataTables": {
|
||||
"Click to copy": "Zum Kopieren anklicken",
|
||||
@ -277,7 +286,11 @@
|
||||
"Workspace not found": "Arbeitsbereich nicht gefunden",
|
||||
"You are on the {{siteName}} site. You also have access to the following sites:": "Sie sind auf der {{siteName}} Seite. Sie haben auch Zugriff auf die folgenden Seiten:",
|
||||
"You are on your personal site. You also have access to the following sites:": "Sie sind auf Ihrer persönlichen Seite. Sie haben auch Zugriff auf die folgenden Seiten:",
|
||||
"You may delete a workspace forever once it has no documents in it.": "Sie können einen Arbeitsbereich für immer löschen, sobald er keine Dokumente enthält."
|
||||
"You may delete a workspace forever once it has no documents in it.": "Sie können einen Arbeitsbereich für immer löschen, sobald er keine Dokumente enthält.",
|
||||
"Any documents created in this site will appear here.": "Alle Dokumente, die auf dieser Seite erstellt wurden, werden hier angezeigt.",
|
||||
"Create my first document": "Mein erstes Dokument erstellen",
|
||||
"You have read-only access to this site. Currently there are no documents.": "Sie haben nur Lesezugriff auf diese Seite. Derzeit sind keine Dokumente vorhanden.",
|
||||
"personal site": "persönliche Seite"
|
||||
},
|
||||
"DocPageModel": {
|
||||
"Add Empty Table": "Leere Tabelle hinzufügen",
|
||||
@ -555,7 +568,8 @@
|
||||
"Visit our {{link}} to learn more about Grist.": "Besuchen Sie unsere {{link}}, um mehr über Grist zu erfahren.",
|
||||
"Sign in": "Anmelden",
|
||||
"To use Grist, please either sign up or sign in.": "Um Grist zu nutzen, melden Sie sich bitte an oder registrieren Sie sich.",
|
||||
"Learn more in our {{helpCenterLink}}.": "Erfahren Sie mehr in unserem {{helpCenterLink}}."
|
||||
"Learn more in our {{helpCenterLink}}.": "Erfahren Sie mehr in unserem {{helpCenterLink}}.",
|
||||
"Only show documents": "Nur Dokumente anzeigen"
|
||||
},
|
||||
"HomeLeftPane": {
|
||||
"Access Details": "Zugangsdetails",
|
||||
@ -838,7 +852,8 @@
|
||||
"API Console": "API-Konsole"
|
||||
},
|
||||
"TopBar": {
|
||||
"Manage Team": "Team verwalten"
|
||||
"Manage Team": "Team verwalten",
|
||||
"Manage team": "Team verwalten"
|
||||
},
|
||||
"TriggerFormulas": {
|
||||
"(data cleaning)": "(Datenreinigung)",
|
||||
@ -991,7 +1006,9 @@
|
||||
"An unknown error occurred.": "Ein unbekannter Fehler ist aufgetreten.",
|
||||
"Powered by": "Angetrieben durch",
|
||||
"Build your own form": "Erstellen Sie Ihr eigenes Formular",
|
||||
"Form not found": "Formular nicht gefunden"
|
||||
"Form not found": "Formular nicht gefunden",
|
||||
"Failed to log in.{{separator}}Please try again or contact support.": "Die Anmeldung ist fehlgeschlagen.{{separator}}Bitte versuchen Sie es erneut oder kontaktieren Sie den Support.",
|
||||
"Sign-in failed{{suffix}}": "Anmelden fehlgeschlagen{{suffix}}"
|
||||
},
|
||||
"menus": {
|
||||
"* Workspaces are available on team plans. ": "* Arbeitsbereiche sind in Teamplänen verfügbar. ",
|
||||
@ -1257,7 +1274,14 @@
|
||||
"Forms are here!": "Die Formulare sind da!",
|
||||
"These rules are applied after all column rules have been processed, if applicable.": "Diese Regeln werden angewendet, nachdem alle Spaltenregeln abgearbeitet wurden, falls zutreffend.",
|
||||
"Example: {{example}}": "Beispiel: {{example}}",
|
||||
"Filter displayed dropdown values with a condition.": "Filtern angezeigte Dropdown-Werte mit einer Bedingung."
|
||||
"Filter displayed dropdown values with a condition.": "Filtern angezeigte Dropdown-Werte mit einer Bedingung.",
|
||||
"Community widgets are created and maintained by Grist community members.": "Community Widgets werden von Mitgliedern der Grist Community erstellt und gepflegt.",
|
||||
"Creates a reverse column in target table that can be edited from either end.": "Erzeugt eine umgekehrte Spalte in der Zieltabelle, die von beiden Seiten bearbeitet werden kann.",
|
||||
"To allow multiple assignments, change the type of the Reference column to Reference List.": "Um mehrere Zuordnungen zu ermöglichen, ändern Sie den Typ der Referenzspalte auf Referenzliste.",
|
||||
"To allow multiple assignments, change the referenced column's type to Reference List.": "Um mehrere Zuordnungen zu ermöglichen, ändern Sie den Typ der referenzierten Spalte auf Referenzliste.",
|
||||
"This limitation occurs when one end of a two-way reference is configured as a single Reference.": "Diese Einschränkung tritt auf, wenn ein Ende einer Zwei-Wege-Referenz als Einzelreferenz konfiguriert ist.",
|
||||
"This limitation occurs when one column in a two-way reference has the Reference type.": "Diese Einschränkung tritt auf, wenn eine Spalte in einer zweiseitigen Referenz den Typ Referenz hat.",
|
||||
"Two-way references are not currently supported for Formula or Trigger Formula columns": "Zwei-Wege-Referenzen werden derzeit nicht für Formel- oder Trigger-Formelspalten unterstützt"
|
||||
},
|
||||
"DescriptionConfig": {
|
||||
"DESCRIPTION": "BESCHREIBUNG"
|
||||
@ -1620,7 +1644,8 @@
|
||||
"Key to sign sessions with": "Schlüssel zum Anmelden von Sitzungen mit",
|
||||
"Session Secret": "Sitzungsgeheimnis",
|
||||
"Enterprise": "Unternehmen",
|
||||
"Enable Grist Enterprise": "Aktivieren Sie Grist Enterprise"
|
||||
"Enable Grist Enterprise": "Aktivieren Sie Grist Enterprise",
|
||||
"checking": "Überprüfung"
|
||||
},
|
||||
"Section": {
|
||||
"Insert section above": "Abschnitt oben einfügen",
|
||||
@ -1730,7 +1755,8 @@
|
||||
"Complete the tutorial": "Fertigen Sie das Tutorial",
|
||||
"3 minute video tour": "3 Minuten Video-Tour",
|
||||
"Learn the basic of reference columns, linked widgets, column types, & cards.": "Lernen Sie die Grundlagen von Referenzspalten, verknüpften Widgets, Spaltentypen und Karten kennen.",
|
||||
"Complete our basics tutorial": "Vervollständigen Sie unser Grundlagen-Tutorial"
|
||||
"Complete our basics tutorial": "Vervollständigen Sie unser Grundlagen-Tutorial",
|
||||
"Learn the basics of reference columns, linked widgets, column types, & cards.": "Lernen Sie die Grundlagen der Referenzspalten, verknüpfte Widgets, Spaltentypen, & Karten."
|
||||
},
|
||||
"ToggleEnterpriseWidget": {
|
||||
"Disable Grist Enterprise": "Grist Enterprise deaktivieren",
|
||||
@ -1744,5 +1770,87 @@
|
||||
"raw data page": "Rohdaten-Seite",
|
||||
"Delete data and this widget.": "Daten und dieses Widget löschen.",
|
||||
"Keep data and delete widget. Table will remain available in {{rawDataLink}}": "Daten behalten und Widget löschen. Die Tabelle bleibt verfügbar in {{rawDataLink}}"
|
||||
},
|
||||
"AdminPanelName": {
|
||||
"Admin Panel": "Verwaltungsbereich"
|
||||
},
|
||||
"CustomWidgetGallery": {
|
||||
"(Missing info)": "(Fehlende Informationen)",
|
||||
"Add Widget": "Widget hinzufügen",
|
||||
"Add Your Own Widget": "Eigenes Widget hinzufügen",
|
||||
"Cancel": "Abbrechen",
|
||||
"Change Widget": "Widget ändern",
|
||||
"Choose Custom Widget": "Benutzerdefiniertes Widget auswählen",
|
||||
"Developer:": "Entwickler:",
|
||||
"Grist Widget": "Grist Widget",
|
||||
"Last updated:": "Letzte Aktualisierung:",
|
||||
"No matching widgets": "Keine passenden Widgets",
|
||||
"Search": "Suchen",
|
||||
"Widget URL": "Widget-URL",
|
||||
"Learn more about Custom Widgets": "Erfahren Sie mehr über benutzerdefinierte Widgets",
|
||||
"Add a widget from outside this gallery.": "Fügen Sie ein Widget von außerhalb dieser Galerie hinzu.",
|
||||
"Community Widget": "Community Widget",
|
||||
"Custom URL": "Benutzerdefinierte URL"
|
||||
},
|
||||
"markdown": {
|
||||
"The toggle is **off**": "Der Kippschalter ist **aus**",
|
||||
"The toggle is **on**": "Der Kippschalter ist **an**",
|
||||
"# New Markdown Function\n *\n * We can _write_ [the usual Markdown](https:": {
|
||||
"": {
|
||||
"markdownguide.org) *inside*\n * a Grainjs element.": "# Neue Markdown-Funktion\n *\n * Wir können [das übliche Markdown](https://markdownguide.org) _innerhalb*\n * eines Grainjs-Elements schreiben."
|
||||
}
|
||||
}
|
||||
},
|
||||
"markdown.d": {
|
||||
"# New Markdown Function\n *\n * We can _write_ [the usual Markdown](https:": {
|
||||
"": {
|
||||
"markdownguide.org) *inside*\n * a Grainjs element.": "# Neue Markdown-Funktion\n *\n * Wir können [das übliche Markdown](https://markdownguide.org) _innerhalb*\n * eines Grainjs-Elements schreiben."
|
||||
}
|
||||
},
|
||||
"The toggle is **off**": "Der Kippschalter ist **aus**",
|
||||
"The toggle is **on**": "Der Kippschalter ist **an**"
|
||||
},
|
||||
"HomeIntroCards": {
|
||||
"3 minute video tour": "3 Minuten Video-Tour",
|
||||
"Blank document": "Leeres Dokument",
|
||||
"Find solutions and explore more resources {{helpCenterLink}}": "Lösungen finden und weitere Ressourcen erkunden {{helpCenterLink}}",
|
||||
"Templates": "Vorlagen",
|
||||
"Tutorial": "Tutorial",
|
||||
"Import file": "Datei importieren",
|
||||
"Finish our basics tutorial": "Beenden Sie unser Grundlagen-Tutorial",
|
||||
"Start a new document": "Ein neues Dokument beginnen",
|
||||
"Help center": "Hilfezentrum",
|
||||
"Learn more {{webinarsLinks}}": "Mehr erfahren {{webinarsLinks}}",
|
||||
"Webinars": "Webinare"
|
||||
},
|
||||
"ReverseReferenceConfig": {
|
||||
"Delete column {{column}} in table {{table}}?": "Spalte {{column}} in der Tabelle {{table}} löschen?",
|
||||
"It is the reverse of the reference column {{column}} in table {{table}}.": "Es ist die Umkehrung der Referenzspalte {{column}} in Tabelle {{table}}.",
|
||||
"Table": "Tabelle",
|
||||
"Two-way Reference": "Zwei-Wege-Referenz",
|
||||
"Target table": "Zieltabelle",
|
||||
"Add two-way reference": "Zwei-Wege-Referenz hinzufügen",
|
||||
"Column": "Spalte",
|
||||
"Delete": "Löschen",
|
||||
"Delete two-way reference?": "Zwei-Wege-Referenz löschen?"
|
||||
},
|
||||
"SupportGristButton": {
|
||||
"Admin Panel": "Verwaltungsbereich",
|
||||
"Close": "Schließen",
|
||||
"Opted In": "Angemeldet",
|
||||
"Support Grist": "Unterstützen Sie Grist",
|
||||
"Opt in to Telemetry": "Melden Sie sich für Telemetrie an",
|
||||
"Help Center": "Hilfezentrum",
|
||||
"Thank you! Your trust and support is greatly appreciated. Opt out any time from the {{link}} in the user menu.": "Danke! Ihr Vertrauen und Ihre Unterstützung ist sehr geschätzt. Sie können sich jederzeit unter {{link}} im Benutzermenü abmelden."
|
||||
},
|
||||
"buildReassignModal": {
|
||||
"Cancel": "Abbrechen",
|
||||
"Each {{targetTable}} record may only be assigned to a single {{sourceTable}} record.": "Jeder {{targetTable}} Datensatz kann nur einem einzigen {{sourceTable}} Datensatz zugeordnet werden.",
|
||||
"Reassign": "Neu zuordnen",
|
||||
"Reassign to {{sourceTable}} record {{sourceName}}.": "Zuweisen auf {{sourceTable}}-Datensatz {{sourceName}}.",
|
||||
"Record already assigned_one": "Datensatz bereits zugeordnet",
|
||||
"Record already assigned_other": "Datensatz bereits zugeordnet",
|
||||
"Reassign to new {{sourceTable}} records.": "Neuzuordnung zu neuen {{sourceTable}} Datensätzen.",
|
||||
"{{targetTable}} record {{targetName}} is already assigned to {{sourceTable}} record {{oldSourceName}}.": "{{targetTable}} Der Datensatz {{targetName}} ist bereits dem Datensatz {{sourceTable}} {{oldSourceName}} zugeordnet."
|
||||
}
|
||||
}
|
||||
|
@ -1,2 +1,8 @@
|
||||
{
|
||||
"sendAppPage": {
|
||||
"Loading": "Laden"
|
||||
},
|
||||
"oidc": {
|
||||
"emailNotVerifiedError": "Bitte überprüfen Sie Ihre E-Mail mit dem Identitätsanbieter und melden Sie sich erneut an."
|
||||
}
|
||||
}
|
||||
|
1
static/locales/en_GB.server.json
Normal file
1
static/locales/en_GB.server.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
@ -1,2 +1,8 @@
|
||||
{
|
||||
"sendAppPage": {
|
||||
"Loading": "Cargando"
|
||||
},
|
||||
"oidc": {
|
||||
"emailNotVerifiedError": "Por favor, compruebe su correo electrónico con el proveedor de identidad y vuelva a iniciar sesión."
|
||||
}
|
||||
}
|
||||
|
1
static/locales/eu.server.json
Normal file
1
static/locales/eu.server.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
1
static/locales/fa.server.json
Normal file
1
static/locales/fa.server.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
1
static/locales/fi.server.json
Normal file
1
static/locales/fi.server.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
1
static/locales/ig.client.json
Normal file
1
static/locales/ig.client.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
1
static/locales/ig.server.json
Normal file
1
static/locales/ig.server.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
1
static/locales/it.server.json
Normal file
1
static/locales/it.server.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
1
static/locales/ja.server.json
Normal file
1
static/locales/ja.server.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
1
static/locales/nl.server.json
Normal file
1
static/locales/nl.server.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
1
static/locales/pl.server.json
Normal file
1
static/locales/pl.server.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
1
static/locales/pt.server.json
Normal file
1
static/locales/pt.server.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
@ -154,7 +154,8 @@
|
||||
"Comment": "Comentário",
|
||||
"Copy": "Copiar",
|
||||
"Cut": "Cortar",
|
||||
"Paste": "Colar"
|
||||
"Paste": "Colar",
|
||||
"Copy with headers": "Copiar com cabeçalhos"
|
||||
},
|
||||
"ChartView": {
|
||||
"Create separate series for each value of the selected column.": "Crie séries separadas para cada valor da coluna selecionada.",
|
||||
@ -212,7 +213,15 @@
|
||||
"{{wrongTypeCount}} non-{{columnType}} columns are not shown_one": "{{wrongTypeCount}} a não-{{columnType}} coluna não é mostrada",
|
||||
"{{wrongTypeCount}} non-{{columnType}} columns are not shown_other": "{{wrongTypeCount}} as não-{{columnType}} colunas não são mostradas",
|
||||
"No {{columnType}} columns in table.": "Não há colunas {{columnType}} na tabela.",
|
||||
"Clear selection": "Limpar seleção"
|
||||
"Clear selection": "Limpar seleção",
|
||||
"Developer:": "Desenvolvedor:",
|
||||
"Last updated:": "Última atualização:",
|
||||
"Missing description and author information.": "Faltam a descrição e as informações do autor.",
|
||||
"ACCESS LEVEL": "NÍVEL DE ACESSO",
|
||||
"Custom URL": "URL personalizado",
|
||||
"Accept": "Aceitar",
|
||||
"Reject": "Rejeitar",
|
||||
"Widget": "Widget"
|
||||
},
|
||||
"DataTables": {
|
||||
"Click to copy": "Clique para copiar",
|
||||
@ -277,7 +286,11 @@
|
||||
"Workspace not found": "Área de trabalho não encontrada",
|
||||
"You are on the {{siteName}} site. You also have access to the following sites:": "Você está no site {{siteName}}. Você também tem acesso aos seguintes sites:",
|
||||
"You are on your personal site. You also have access to the following sites:": "Você está na sua página pessoal. Você também tem acesso às seguintes páginas:",
|
||||
"You may delete a workspace forever once it has no documents in it.": "Você pode excluir uma área de trabalho para sempre uma vez que ela não contenha documentos."
|
||||
"You may delete a workspace forever once it has no documents in it.": "Você pode excluir uma área de trabalho para sempre uma vez que ela não contenha documentos.",
|
||||
"Any documents created in this site will appear here.": "Todos os documentos criados neste site aparecerão aqui.",
|
||||
"Create my first document": "Criar meu primeiro documento",
|
||||
"You have read-only access to this site. Currently there are no documents.": "Você tem acesso somente de leitura a este site. No momento, não há documentos.",
|
||||
"personal site": "site pessoal"
|
||||
},
|
||||
"DocPageModel": {
|
||||
"Add Empty Table": "Adicionar Tabela Vazia",
|
||||
@ -555,7 +568,8 @@
|
||||
"Visit our {{link}} to learn more about Grist.": "Visite nosso site {{link}} para saber mais sobre o Grist.",
|
||||
"Sign in": "Entrar",
|
||||
"To use Grist, please either sign up or sign in.": "Para usar o Grist, inscreva-se ou faça login.",
|
||||
"Learn more in our {{helpCenterLink}}.": "Saiba mais em nosso {{helpCenterLink}}."
|
||||
"Learn more in our {{helpCenterLink}}.": "Saiba mais em nosso {{helpCenterLink}}.",
|
||||
"Only show documents": "Mostrar apenas documentos"
|
||||
},
|
||||
"HomeLeftPane": {
|
||||
"Access Details": "Detalhes de Acesso",
|
||||
@ -838,7 +852,8 @@
|
||||
"API Console": "Consola API"
|
||||
},
|
||||
"TopBar": {
|
||||
"Manage Team": "Gerenciar Equipe"
|
||||
"Manage Team": "Gerenciar Equipe",
|
||||
"Manage team": "Gerenciar a equipe"
|
||||
},
|
||||
"TriggerFormulas": {
|
||||
"(data cleaning)": "(limpeza de dados)",
|
||||
@ -991,7 +1006,9 @@
|
||||
"An unknown error occurred.": "Ocorreu um erro desconhecido.",
|
||||
"Form not found": "Formulário não encontrado",
|
||||
"Powered by": "Desenvolvido por",
|
||||
"Build your own form": "Construa seu próprio formulário"
|
||||
"Build your own form": "Construa seu próprio formulário",
|
||||
"Failed to log in.{{separator}}Please try again or contact support.": "Falha ao fazer login.{{separator}}Tente novamente ou entre em contato com o suporte.",
|
||||
"Sign-in failed{{suffix}}": "Falha no login{{suffix}}"
|
||||
},
|
||||
"menus": {
|
||||
"* Workspaces are available on team plans. ": "* As áreas de trabalho estão disponíveis nos planos de equipe. ",
|
||||
@ -1257,7 +1274,14 @@
|
||||
"Learn more": "Saiba mais",
|
||||
"These rules are applied after all column rules have been processed, if applicable.": "Estas regras são aplicadas após todas as regras da coluna terem sido processadas, se aplicável.",
|
||||
"Example: {{example}}": "Exemplo: {{example}}",
|
||||
"Filter displayed dropdown values with a condition.": "Filtre os valores exibidos no menu suspenso com uma condição."
|
||||
"Filter displayed dropdown values with a condition.": "Filtre os valores exibidos no menu suspenso com uma condição.",
|
||||
"Creates a reverse column in target table that can be edited from either end.": "Cria uma coluna reversa na tabela de destino que pode ser editada de qualquer lado.",
|
||||
"This limitation occurs when one column in a two-way reference has the Reference type.": "Esta limitação ocorre quando uma coluna em uma referência bidirecional tem o tipo Referência.",
|
||||
"To allow multiple assignments, change the type of the Reference column to Reference List.": "Para permitir várias atribuições, altere o tipo da coluna Referência para Lista de Referência.",
|
||||
"To allow multiple assignments, change the referenced column's type to Reference List.": "Para permitir várias atribuições, altere o tipo da coluna referenciada para Lista de referência.",
|
||||
"Two-way references are not currently supported for Formula or Trigger Formula columns": "Atualmente, não há suporte para referências bidirecionais em Colunas de Fórmula ou Fórmula de disparo",
|
||||
"Community widgets are created and maintained by Grist community members.": "Os widgets comunitários são criados e mantidos pelos membros da comunidade Grist.",
|
||||
"This limitation occurs when one end of a two-way reference is configured as a single Reference.": "Essa limitação ocorre quando uma extremidade de uma referência bidirecional é configurada como uma única referência."
|
||||
},
|
||||
"DescriptionConfig": {
|
||||
"DESCRIPTION": "DESCRIÇÃO"
|
||||
@ -1624,7 +1648,8 @@
|
||||
"Grist signs user session cookies with a secret key. Please set this key via the environment variable GRIST_SESSION_SECRET. Grist falls back to a hard-coded default when it is not set. We may remove this notice in the future since session IDs have been updated to be inherently cryptographically secure.": "O Grist assina os cookies de sessão do usuário com uma chave secreta. Defina essa chave por meio da variável de ambiente GRIST_SESSION_SECRET. O Grist retorna a um padrão codificado quando ele não está definido. Poderemos remover esse aviso no futuro, pois os IDs de sessão gerados desde a versão 1.1.16 são inerentemente seguros em termos de criptografia.",
|
||||
"Grist signs user session cookies with a secret key. Please set this key via the environment variable GRIST_SESSION_SECRET. Grist falls back to a hard-coded default when it is not set. We may remove this notice in the future as session IDs generated since v1.1.16 are inherently cryptographically secure.": "O Grist assina os cookies de sessão do usuário com uma chave secreta. Defina essa chave por meio da variável de ambiente GRIST_SESSION_SECRET. O Grist retorna a um padrão codificado quando ele não está definido. Poderemos remover esse aviso no futuro, pois os IDs de sessão gerados desde a versão 1.1.16 são inerentemente seguros em termos de criptografia.",
|
||||
"Enterprise": "Empresarial",
|
||||
"Enable Grist Enterprise": "Habilitar a Grist Empresarial"
|
||||
"Enable Grist Enterprise": "Habilitar a Grist Empresarial",
|
||||
"checking": "verificando"
|
||||
},
|
||||
"Field": {
|
||||
"No choices configured": "Nenhuma opção configurada",
|
||||
@ -1713,7 +1738,8 @@
|
||||
"3 minute video tour": "Vídeo tour de 3 minutos",
|
||||
"Complete our basics tutorial": "Conclua nosso tutorial básico",
|
||||
"Complete the tutorial": "Concluir o tutorial",
|
||||
"Learn the basic of reference columns, linked widgets, column types, & cards.": "Aprenda o básico sobre Colunas de referência, widgets vinculados, tipos de colunas e cartões."
|
||||
"Learn the basic of reference columns, linked widgets, column types, & cards.": "Aprenda o básico sobre Colunas de referência, widgets vinculados, tipos de colunas e cartões.",
|
||||
"Learn the basics of reference columns, linked widgets, column types, & cards.": "Aprenda os conceitos básicos de Colunas de referência, widgets vinculados, tipos de colunas e cartões."
|
||||
},
|
||||
"OnboardingPage": {
|
||||
"Discover Grist in 3 minutes": "Descubra Grist em 3 minutos",
|
||||
@ -1744,5 +1770,87 @@
|
||||
"Keep data and delete widget. Table will remain available in {{rawDataLink}}": "Mantenha os dados e exclua o widget. A tabela permanecerá disponível em {{rawDataLink}}",
|
||||
"Table {{tableName}} will no longer be visible": "A tabela {{tableName}} não estará mais visível",
|
||||
"raw data page": "página de dados brutos"
|
||||
},
|
||||
"AdminPanelName": {
|
||||
"Admin Panel": "Painel de administração"
|
||||
},
|
||||
"CustomWidgetGallery": {
|
||||
"(Missing info)": "(Informação ausente)",
|
||||
"Add Widget": "Adicionar Widget",
|
||||
"Community Widget": "Widget da comunidade",
|
||||
"Custom URL": "URL personalizado",
|
||||
"Learn more about Custom Widgets": "Saiba mais sobre Widgets personalizados",
|
||||
"Widget URL": "URL do widget",
|
||||
"Add Your Own Widget": "Adicione seu próprio widget",
|
||||
"Cancel": "Cancelar",
|
||||
"Change Widget": "Alterar widget",
|
||||
"Choose Custom Widget": "Escolha o widget personalizado",
|
||||
"Add a widget from outside this gallery.": "Adicione um widget de fora dessa galeria.",
|
||||
"Developer:": "Desenvolvedor:",
|
||||
"Grist Widget": "Widget Grist",
|
||||
"Last updated:": "Última atualização:",
|
||||
"No matching widgets": "Nenhum widget correspondente",
|
||||
"Search": "Pesquisar"
|
||||
},
|
||||
"markdown": {
|
||||
"The toggle is **off**": "O interruptor está **desligado**",
|
||||
"The toggle is **on**": "O interruptor está **ligado**",
|
||||
"# New Markdown Function\n *\n * We can _write_ [the usual Markdown](https:": {
|
||||
"": {
|
||||
"markdownguide.org) *inside*\n * a Grainjs element.": "# Nova função Markdown\n *\n * Podemos _escrever_ [o Markdown usual] (https://markdownguide.org) *dentro*\n * um elemento Grainjs."
|
||||
}
|
||||
}
|
||||
},
|
||||
"markdown.d": {
|
||||
"The toggle is **off**": "O interruptor está **desligado**",
|
||||
"The toggle is **on**": "O interruptor está **ligado**",
|
||||
"# New Markdown Function\n *\n * We can _write_ [the usual Markdown](https:": {
|
||||
"": {
|
||||
"markdownguide.org) *inside*\n * a Grainjs element.": "# Nova função Markdown\n *\n * Podemos _escrever_ [o Markdown usual] (https://markdownguide.org) *dentro*\n * um elemento Grainjs."
|
||||
}
|
||||
}
|
||||
},
|
||||
"HomeIntroCards": {
|
||||
"3 minute video tour": "Vídeo tour de 3 minutos",
|
||||
"Help center": "Centro de Ajuda",
|
||||
"Import file": "Importar arquivo",
|
||||
"Learn more {{webinarsLinks}}": "Saiba mais {{webinarsLinks}}",
|
||||
"Tutorial": "Tutorial",
|
||||
"Webinars": "Webinars",
|
||||
"Blank document": "Documento em branco",
|
||||
"Find solutions and explore more resources {{helpCenterLink}}": "Encontre soluções e explore mais recursos {{helpCenterLink}}",
|
||||
"Finish our basics tutorial": "Termine nosso tutorial básico",
|
||||
"Start a new document": "Inicie um novo documento",
|
||||
"Templates": "Modelos"
|
||||
},
|
||||
"ReverseReferenceConfig": {
|
||||
"Add two-way reference": "Adicionar referência bidirecional",
|
||||
"Column": "Coluna",
|
||||
"Delete": "Excluir",
|
||||
"Delete column {{column}} in table {{table}}?": "Excluir a Coluna {{column}} na tabela {{table}}?",
|
||||
"It is the reverse of the reference column {{column}} in table {{table}}.": "É o inverso da coluna de referência {{column}} na tabela {{table}}.",
|
||||
"Table": "Tabela",
|
||||
"Two-way Reference": "Referência bidirecional",
|
||||
"Delete two-way reference?": "Excluir referência bidirecional?",
|
||||
"Target table": "Tabela de destino"
|
||||
},
|
||||
"SupportGristButton": {
|
||||
"Admin Panel": "Painel de administração",
|
||||
"Close": "Fechar",
|
||||
"Help Center": "Centro de Ajuda",
|
||||
"Opt in to Telemetry": "Aceitar a Telemetria",
|
||||
"Opted In": "Optou por participar",
|
||||
"Thank you! Your trust and support is greatly appreciated. Opt out any time from the {{link}} in the user menu.": "Obrigado! Sua confiança e seu apoio são muito apreciados. Cancele a qualquer momento no {{link}} no menu do usuário.",
|
||||
"Support Grist": "Apoiar o Grist"
|
||||
},
|
||||
"buildReassignModal": {
|
||||
"Cancel": "Cancelar",
|
||||
"Each {{targetTable}} record may only be assigned to a single {{sourceTable}} record.": "Cada registro {{targetTable}} só pode ser atribuído a um único registro {{sourceTable}}.",
|
||||
"Reassign": "Reatribuir",
|
||||
"Record already assigned_one": "Registro já atribuído",
|
||||
"Reassign to new {{sourceTable}} records.": "Reatribuir a novos registros {{sourceTable}}.",
|
||||
"Reassign to {{sourceTable}} record {{sourceName}}.": "Reatribuir para o registro {{sourceTable}} {{sourceName}} .",
|
||||
"Record already assigned_other": "Registro já atribuído",
|
||||
"{{targetTable}} record {{targetName}} is already assigned to {{sourceTable}} record {{oldSourceName}}.": "{{targetTable}} O registro {{targetName}} já está atribuído ao registro {{sourceTable}} {{oldSourceName}} ."
|
||||
}
|
||||
}
|
||||
|
@ -1,2 +1,8 @@
|
||||
{
|
||||
"sendAppPage": {
|
||||
"Loading": "Carregando"
|
||||
},
|
||||
"oidc": {
|
||||
"emailNotVerifiedError": "Verifique seu e-mail com o provedor de identidade e faça login novamente."
|
||||
}
|
||||
}
|
||||
|
1
static/locales/ro.server.json
Normal file
1
static/locales/ro.server.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
@ -411,7 +411,11 @@
|
||||
"You are on your personal site. You also have access to the following sites:": "Вы находитесь на своем личном сайте. У вас также есть доступ к следующим сайтам:",
|
||||
"You may delete a workspace forever once it has no documents in it.": "Вы можете навсегда удалить рабочую область, если в ней нет документов.",
|
||||
"Trash": "Корзина",
|
||||
"Document will be moved to Trash.": "Документ будет перемещен в Корзину."
|
||||
"Document will be moved to Trash.": "Документ будет перемещен в Корзину.",
|
||||
"Any documents created in this site will appear here.": "Все документы, созданные на этом сайте, будут отображаться здесь.",
|
||||
"Create my first document": "Создаю свой первый документ",
|
||||
"You have read-only access to this site. Currently there are no documents.": "У вас есть доступ к этому сайту только для чтения. На данный момент документов нет.",
|
||||
"personal site": "персональный сайт"
|
||||
},
|
||||
"DocTour": {
|
||||
"Cannot construct a document tour from the data in this document. Ensure there is a table named GristDocTour with columns Title, Body, Placement, and Location.": "Не удается создать тур по документу на основе данных в этом документе. Убедитесь, что существует таблица с именем GristDocTour со столбцами Title, Body, Placement и Location.",
|
||||
@ -559,7 +563,8 @@
|
||||
"Sign in": "Вход",
|
||||
"To use Grist, please either sign up or sign in.": "Для использования Grist, зарегистрируйтесь или войдите в систему.",
|
||||
"Learn more in our {{helpCenterLink}}, or find an expert via our {{sproutsProgram}}.": "Узнайте больше в {{helpCenterLink}}, или найдите специалиста с {{sproutsProgram}}.",
|
||||
"Learn more in our {{helpCenterLink}}.": "Узнайте больше в нашем {{helpCenterLink}}."
|
||||
"Learn more in our {{helpCenterLink}}.": "Узнайте больше в нашем {{helpCenterLink}}.",
|
||||
"Only show documents": "Показывать только документы"
|
||||
},
|
||||
"HomeLeftPane": {
|
||||
"Import Document": "Импорт документа",
|
||||
@ -814,7 +819,8 @@
|
||||
"Switch Sites": "Переключить сайты"
|
||||
},
|
||||
"TopBar": {
|
||||
"Manage Team": "Управление командой"
|
||||
"Manage Team": "Управление командой",
|
||||
"Manage team": "Управлять командой"
|
||||
},
|
||||
"TriggerFormulas": {
|
||||
"Any field": "Любое поле",
|
||||
@ -913,7 +919,9 @@
|
||||
"An unknown error occurred.": "Произошла неизвестная ошибка.",
|
||||
"Build your own form": "Создайте свою собственную форму",
|
||||
"Form not found": "Форма не найдена",
|
||||
"Powered by": "Разработано"
|
||||
"Powered by": "Разработано",
|
||||
"Failed to log in.{{separator}}Please try again or contact support.": "Не удалось войти в систему.{{separator}}Пожалуйста, повторите попытку или обратитесь в службу поддержки.",
|
||||
"Sign-in failed{{suffix}}": "Не удалось выполнить вход в систему{{suffix}}"
|
||||
},
|
||||
"CellStyle": {
|
||||
"CELL STYLE": "СТИЛЬ ЯЧЕЙКИ",
|
||||
@ -1203,7 +1211,12 @@
|
||||
"Learn more": "Узнать больше",
|
||||
"These rules are applied after all column rules have been processed, if applicable.": "Эти правила применяются после обработки всех правил столбцов, если это применимо.",
|
||||
"Example: {{example}}": "Пример: {{example}}",
|
||||
"Filter displayed dropdown values with a condition.": "Отфильтровать выпадающие значения по условиям."
|
||||
"Filter displayed dropdown values with a condition.": "Отфильтровать выпадающие значения по условиям.",
|
||||
"Two-way references are not currently supported for Formula or Trigger Formula columns": "Двусторонние ссылки в настоящее время не поддерживаются для столбцов формулы или триггерных формул",
|
||||
"Community widgets are created and maintained by Grist community members.": "Виджеты сообщества создаются и поддерживаются участниками сообщества Grist.",
|
||||
"Creates a reverse column in target table that can be edited from either end.": "Создает обратный столбец в целевой таблице, который можно редактировать с любого конца.",
|
||||
"This limitation occurs when one end of a two-way reference is configured as a single Reference.": "Это ограничение возникает, когда один конец двусторонней ссылки сконфигурирован как одиночная ссылка.",
|
||||
"This limitation occurs when one column in a two-way reference has the Reference type.": "Это ограничение возникает, когда один столбец в двусторонней ссылке имеет ссылочный тип."
|
||||
},
|
||||
"DescriptionConfig": {
|
||||
"DESCRIPTION": "ОПИСАНИЕ"
|
||||
@ -1688,5 +1701,61 @@
|
||||
"Keep data and delete widget. Table will remain available in {{rawDataLink}}": "Сохранить данные и удалить виджет. Таблица останется доступной в {{rawDataLink}}",
|
||||
"Table {{tableName}} will no longer be visible": "Таблица {{tableName}} больше не будет видима",
|
||||
"raw data page": "Страница исходных данных"
|
||||
},
|
||||
"CustomWidgetGallery": {
|
||||
"Cancel": "Отменить",
|
||||
"Change Widget": "Изменить виджет",
|
||||
"Choose Custom Widget": "Выберите Пользовательский виджет",
|
||||
"Search": "Поиск",
|
||||
"(Missing info)": "(Недостающая информация)",
|
||||
"Add Widget": "Добавить виджет",
|
||||
"Add Your Own Widget": "Добавьте Свой собственный виджет",
|
||||
"Add a widget from outside this gallery.": "Добавьте виджет из-за пределов этой галереи.",
|
||||
"Community Widget": "Виджет от сообщества",
|
||||
"Custom URL": "Пользовательский URL-адрес",
|
||||
"Developer:": "Разработчик:",
|
||||
"Grist Widget": "Grist Виджет",
|
||||
"Last updated:": "Последнее обновление:",
|
||||
"Learn more about Custom Widgets": "Learn more about Custom Widgets",
|
||||
"No matching widgets": "Нет подходящих виджетов",
|
||||
"Widget URL": "URL виджета"
|
||||
},
|
||||
"HomeIntroCards": {
|
||||
"Blank document": "Пустой документ",
|
||||
"Find solutions and explore more resources {{helpCenterLink}}": "Находите решения и изучайте больше ресурсов {{helpCenterLink}}",
|
||||
"Tutorial": "Учебное пособие",
|
||||
"3 minute video tour": "3-минутный видео-тур",
|
||||
"Finish our basics tutorial": "Завершите наше учебное пособие по основам",
|
||||
"Help center": "Справочный центр",
|
||||
"Import file": "Импорт файла",
|
||||
"Learn more {{webinarsLinks}}": "Изучить больше {{webinarsLinks}}",
|
||||
"Start a new document": "Начать новый документ",
|
||||
"Templates": "Шаблоны",
|
||||
"Webinars": "Вебинары"
|
||||
},
|
||||
"ReverseReferenceConfig": {
|
||||
"Delete two-way reference?": "Удалить двустороннюю ссылку?",
|
||||
"Target table": "Целевая таблица",
|
||||
"Column": "Столбец",
|
||||
"Delete": "Удалить",
|
||||
"Delete column {{column}} in table {{table}}?": "Удалить столбец {{column}} в таблице {{table}}?",
|
||||
"It is the reverse of the reference column {{column}} in table {{table}}.": "Это обратная сторона ссылочного столбца. {{column}} в таблице {{table}}.",
|
||||
"Table": "Таблица",
|
||||
"Two-way Reference": "Двусторонняя Ссылка"
|
||||
},
|
||||
"SupportGristButton": {
|
||||
"Help Center": "Справочный центр",
|
||||
"Thank you! Your trust and support is greatly appreciated. Opt out any time from the {{link}} in the user menu.": "Спасибо! Ваше доверие и поддержка очень ценны. Отказаться в любое время от {{link}} в меню пользователя.",
|
||||
"Admin Panel": "Панель администратора",
|
||||
"Close": "Закрыть",
|
||||
"Opt in to Telemetry": "Включите телеметрию",
|
||||
"Opted In": "Включено",
|
||||
"Support Grist": "Поддержать Grist"
|
||||
},
|
||||
"AdminPanelName": {
|
||||
"Admin Panel": "Панель администратора"
|
||||
},
|
||||
"buildReassignModal": {
|
||||
"Cancel": "Отмена"
|
||||
}
|
||||
}
|
||||
|
5
static/locales/ru.server.json
Normal file
5
static/locales/ru.server.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"sendAppPage": {
|
||||
"Loading": "Загрузка"
|
||||
}
|
||||
}
|
@ -145,7 +145,8 @@
|
||||
"Clear cell": "Vymazať bunku",
|
||||
"Clear values": "Vyčistiť hodnoty",
|
||||
"Copy anchor link": "Kopírovať odkaz na kotvu",
|
||||
"Delete {{count}} rows_one": "Odstrániť riadok"
|
||||
"Delete {{count}} rows_one": "Odstrániť riadok",
|
||||
"Copy with headers": "Kopírovať s hlavičkami"
|
||||
},
|
||||
"ChartView": {
|
||||
"Create separate series for each value of the selected column.": "Vytvoriť samostatné série pre každú hodnotu vybratého stĺpca.",
|
||||
@ -179,20 +180,28 @@
|
||||
"Add": "Pridať",
|
||||
"Enter Custom URL": "Zadať vlastnú adresu URL",
|
||||
"Full document access": "Úplný prístup k dokumentu",
|
||||
"Learn more about custom widgets": "Prečítať si viac o vlastných widgetoch",
|
||||
"Learn more about custom widgets": "Prečítať si viac o vlastných miniaplikáciách",
|
||||
"Open configuration": "Otvoriť konfiguráciu",
|
||||
"Pick a {{columnType}} column": "Vybrať stĺpec {{columnType}}",
|
||||
"Select Custom Widget": "Vybrať Vlastný Widget",
|
||||
"Select Custom Widget": "Vybrať Vlastnú Miniaplikáciu",
|
||||
"Pick a column": "Vybrať stĺpec",
|
||||
"Read selected table": "Prečítať vybranú tabuľku",
|
||||
"Widget does not require any permissions.": "Widget nevyžaduje žiadne povolenia.",
|
||||
"Widget needs to {{read}} the current table.": "Widget vyžaduje {{read}} aktuálnu tabuľku.",
|
||||
"Widget needs {{fullAccess}} to this document.": "Widget vyžaduje {{fullAccess}} k tomuto dokumentu.",
|
||||
"Widget does not require any permissions.": "Miniaplikácia nevyžaduje žiadne povolenia.",
|
||||
"Widget needs to {{read}} the current table.": "Miniaplikácia vyžaduje {{read}} aktuálnu tabuľku.",
|
||||
"Widget needs {{fullAccess}} to this document.": "Miniaplikácia vyžaduje {{fullAccess}} k tomuto dokumentu.",
|
||||
"No document access": "Bez prístupu k dokumentu",
|
||||
"Clear selection": "Vyčistiť výber",
|
||||
"No {{columnType}} columns in table.": "V tabuľke nie sú žiadne stĺpce {{columnType}}.",
|
||||
"{{wrongTypeCount}} non-{{columnType}} columns are not shown_other": "stĺpce {{wrongTypeCount}} iné ako {{columnType}} sa nezobrazujú",
|
||||
"{{wrongTypeCount}} non-{{columnType}} columns are not shown_one": "stĺpec {{wrongTypeCount}} iný ako {{columnType}} sa nezobrazuje"
|
||||
"{{wrongTypeCount}} non-{{columnType}} columns are not shown_one": "stĺpec {{wrongTypeCount}} iný ako {{columnType}} sa nezobrazuje",
|
||||
"Custom URL": "Vlastná URL",
|
||||
"Developer:": "Vývojár:",
|
||||
"Last updated:": "Posledná aktualizácia:",
|
||||
"Missing description and author information.": "Chýba popis a informácie o autorovi.",
|
||||
"ACCESS LEVEL": "ÚROVEŇ PRÍSTUPU",
|
||||
"Accept": "Prijať",
|
||||
"Reject": "Odmietnuť",
|
||||
"Widget": "Miniaplikácia"
|
||||
},
|
||||
"AppModel": {
|
||||
"This team site is suspended. Documents can be read, but not modified.": "Táto tímová stránka je pozastavená. Dokumenty je možné čítať, ale nie upravovať."
|
||||
@ -269,7 +278,11 @@
|
||||
"Restore": "Obnoviť",
|
||||
"You are on the {{siteName}} site. You also have access to the following sites:": "Nachádzate sa na stránke {{siteName}}. Máte tiež prístup k nasledujúcim stránkam:",
|
||||
"You may delete a workspace forever once it has no documents in it.": "Keď pracovný priestor neobsahuje žiadne dokumenty, môžete ho natrvalo odstrániť.",
|
||||
"Delete {{name}}": "Odstrániť {{name}}"
|
||||
"Delete {{name}}": "Odstrániť {{name}}",
|
||||
"Create my first document": "Vytvoriť môj prvý dokument",
|
||||
"personal site": "osobná stránka",
|
||||
"Any documents created in this site will appear here.": "Všetky dokumenty vytvorené na tejto stránke sa zobrazia tu.",
|
||||
"You have read-only access to this site. Currently there are no documents.": "Máte prístup len na čítanie tejto stránky. V súčasnosti neexistujú žiadne dokumenty."
|
||||
},
|
||||
"DocPageModel": {
|
||||
"Enter recovery mode": "Spustiť režim obnovenia",
|
||||
@ -518,7 +531,8 @@
|
||||
"Learn more in our {{helpCenterLink}}, or find an expert via our {{sproutsProgram}}.": "Viac informácií nájdete v našom {{helpCenterLink}} alebo nájdite odborníka prostredníctvom nášho {{sproutsProgram}}.",
|
||||
"Interested in using Grist outside of your team? Visit your free ": "Máte záujem používať Grist mimo váš tím? Navštívte svoje bezplatné ",
|
||||
"Sign up": "Prihlásiť sa",
|
||||
"Learn more in our {{helpCenterLink}}.": "Zistiť viac v našom {{helpCenterLink}}."
|
||||
"Learn more in our {{helpCenterLink}}.": "Zistiť viac v našom {{helpCenterLink}}.",
|
||||
"Only show documents": "Dokumenty iba ukázať"
|
||||
},
|
||||
"Importer": {
|
||||
"Merge rows that match these fields:": "Zlúčiť riadky, ktoré zodpovedajú týmto poliam:",
|
||||
@ -755,7 +769,8 @@
|
||||
"TOOLS": "NÁSTROJE"
|
||||
},
|
||||
"TopBar": {
|
||||
"Manage Team": "Riadiť Tím"
|
||||
"Manage Team": "Riadiť Tím",
|
||||
"Manage team": "Riadiť tím"
|
||||
},
|
||||
"TriggerFormulas": {
|
||||
"Any field": "Akékoľvek pole",
|
||||
@ -876,7 +891,9 @@
|
||||
"Account deleted{{suffix}}": "Účet odstránený {{suffix}}",
|
||||
"Your account has been deleted.": "Váš účet bol odstránený.",
|
||||
"An unknown error occurred.": "Vyskytla sa neznáma chyba.",
|
||||
"Build your own form": "Vytvorte si vlastný formulár"
|
||||
"Build your own form": "Vytvorte si vlastný formulár",
|
||||
"Sign-in failed{{suffix}}": "Prihlásenie zlyhalo{{suffix}}",
|
||||
"Failed to log in.{{separator}}Please try again or contact support.": "Nepodarilo sa prihlásiť.{{separator}}Prosím skúste to znova alebo kontaktujte podporu."
|
||||
},
|
||||
"menus": {
|
||||
"Select fields": "Vybrať polia",
|
||||
@ -1025,12 +1042,12 @@
|
||||
"Nested Filtering": "Vnorené Filtrovanie",
|
||||
"Updates every 5 minutes.": "Aktualizácie každých 5 minút.",
|
||||
"Use the \\u{1D6BA} icon to create summary (or pivot) tables, for totals or subtotals.": "Pomocou ikony \\u{1D6BA} vytvorte súhrnné (alebo kontingenčné) tabuľky pre súčty alebo medzisúčty.",
|
||||
"Custom Widgets": "Vlastná Miniaplikácia",
|
||||
"Custom Widgets": "Vlastné Miniaplikácie",
|
||||
"Use the 𝚺 icon to create summary (or pivot) tables, for totals or subtotals.": "Pomocou ikony 𝚺 vytvorte súhrnné (alebo kontingenčné) tabuľky pre súčty alebo medzisúčty.",
|
||||
"Anchor Links": "Odkaz na Kotvu",
|
||||
"Can't find the right columns? Click 'Change Widget' to select the table with events data.": "Neviete nájsť správne stĺpce? Kliknite na „Zmeniť miniaplikáciu“ a vyberte tabuľku s údajmi udalostí.",
|
||||
"Can't find the right columns? Click 'Change Widget' to select the table with events data.": "Neviete nájsť správne stĺpce? Kliknite na „Zmeniť Miniaplikáciu“ a vyberte tabuľku s údajmi udalostí.",
|
||||
"Example: {{example}}": "Príklad: {{example}}",
|
||||
"Build simple forms right in Grist and share in a click with our new widget. {{learnMoreButton}}": "Vytvárajte jednoduché formuláre priamo v Grist a zdieľajte ich jediným kliknutím pomocou nášho nového widgetu. {{learnMoreButton}}",
|
||||
"Build simple forms right in Grist and share in a click with our new widget. {{learnMoreButton}}": "Vytvárajte jednoduché formuláre priamo v Grist a zdieľajte ich jediným kliknutím pomocou našej novej miniaplikácie. {{learnMoreButton}}",
|
||||
"Clicking {{EyeHideIcon}} in each cell hides the field from this view without deleting it.": "Kliknutím na {{EyeHideIcon}} v každej bunke skryjete pole v tomto zobrazení bez toho, aby ste ho odstránili.",
|
||||
"Link your new widget to an existing widget on this page.": "Prepojte svoju novú miniaplikáciu s existujúcou miniaplikáciou na tejto stránke.",
|
||||
"Select the table containing the data to show.": "Vyberte tabuľku obsahujúcu údaje, ktoré chcete zobraziť.",
|
||||
@ -1077,7 +1094,14 @@
|
||||
"Forms are here!": "Formuláre sú tu!",
|
||||
"Learn more": "Naučiť sa viac",
|
||||
"Linking Widgets": "Prepojenie Miniaplikácií",
|
||||
"The total size of all data in this document, excluding attachments.": "Celková veľkosť všetkých údajov v tomto dokumente okrem príloh."
|
||||
"The total size of all data in this document, excluding attachments.": "Celková veľkosť všetkých údajov v tomto dokumente okrem príloh.",
|
||||
"Community widgets are created and maintained by Grist community members.": "Miniaplikácie komunity vytvárajú a udržiavajú členovia komunity Grist.",
|
||||
"To allow multiple assignments, change the type of the Reference column to Reference List.": "Ak chcete povoliť viacero priradení, zmeňte typ stĺpca Referencie na Zoznam referencií.",
|
||||
"Two-way references are not currently supported for Formula or Trigger Formula columns": "Obojsmerné referencie nie sú v súčasnosti podporované pre stĺpce Formula alebo Trigger Formula",
|
||||
"Creates a reverse column in target table that can be edited from either end.": "Vytvorí náprotivný stĺpec v cieľovej tabuľke, ktorý možno upravovať z oboch strán.",
|
||||
"This limitation occurs when one end of a two-way reference is configured as a single Reference.": "Toto obmedzenie nastáva, keď je jedna strana obojsmernej referencie nakonfigurovaná ako jedna referencia.",
|
||||
"This limitation occurs when one column in a two-way reference has the Reference type.": "Toto obmedzenie nastáva, keď jeden stĺpec v obojsmernej referencii má typ referencie.",
|
||||
"To allow multiple assignments, change the referenced column's type to Reference List.": "Ak chcete povoliť viacero priradení, zmeňte typ odkazovaného stĺpca na Referenčný zoznam."
|
||||
},
|
||||
"ColumnTitle": {
|
||||
"Add description": "Pridať popis",
|
||||
@ -1311,7 +1335,8 @@
|
||||
"Key to sign sessions with": "Kľúč na podpisovanie relácií",
|
||||
"Session Secret": "Tajomstvo Relácie",
|
||||
"Enable Grist Enterprise": "Povoliť Grist Enterprise",
|
||||
"Enterprise": "Enterprise"
|
||||
"Enterprise": "Enterprise",
|
||||
"checking": "kontrola"
|
||||
},
|
||||
"TimingPage": {
|
||||
"Table ID": "ID Tabuľky",
|
||||
@ -1650,7 +1675,8 @@
|
||||
"Complete our basics tutorial": "Náš kompletný základný návod",
|
||||
"Complete the tutorial": "Dokončiť tutoriál",
|
||||
"Learn the basic of reference columns, linked widgets, column types, & cards.": "Naučiť sa základ referenčných stĺpcov, prepojených miniaplikácií, typov stĺpcov a kariet.",
|
||||
"3 minute video tour": "3 minútová videoprehliadka"
|
||||
"3 minute video tour": "3 minútová videoprehliadka",
|
||||
"Learn the basics of reference columns, linked widgets, column types, & cards.": "Naučte sa základy referenčných stĺpcov, prepojených miniaplikácií, typov stĺpcov a kariet."
|
||||
},
|
||||
"OnboardingPage": {
|
||||
"Back": "Späť",
|
||||
@ -1677,9 +1703,91 @@
|
||||
},
|
||||
"ViewLayout": {
|
||||
"Delete": "Odstrániť",
|
||||
"Delete data and this widget.": "Odstrániť dáta a tento widget.",
|
||||
"Keep data and delete widget. Table will remain available in {{rawDataLink}}": "Uchovať údaje a odstrániť widget. Tabuľka zostane dostupná v {{rawDataLink}}",
|
||||
"Delete data and this widget.": "Odstrániť dáta a túto miniaplikáciu.",
|
||||
"Keep data and delete widget. Table will remain available in {{rawDataLink}}": "Uchovať údaje a odstrániť miniaplikáciu. Tabuľka zostane dostupná v {{rawDataLink}}",
|
||||
"raw data page": "stránka nespracovaných údajov",
|
||||
"Table {{tableName}} will no longer be visible": "Tabuľka {{tableName}} nebude dlho viditeľná"
|
||||
},
|
||||
"AdminPanelName": {
|
||||
"Admin Panel": "Admin Panel"
|
||||
},
|
||||
"CustomWidgetGallery": {
|
||||
"(Missing info)": "(Chýbajúce informácie)",
|
||||
"Add Your Own Widget": "Pridať svoju vlastnú Miniaplikáciu",
|
||||
"Add Widget": "Pridať Miniaplikáciu",
|
||||
"Add a widget from outside this gallery.": "Pridajte miniaplikáciu mimo tejto galérie.",
|
||||
"Cancel": "Zrušiť",
|
||||
"Choose Custom Widget": "Zvoliť Vlastnú Miniaplikáciu",
|
||||
"Community Widget": "Komunitná Miniaplikácia",
|
||||
"Custom URL": "Vlastná URL",
|
||||
"Developer:": "Vývojár:",
|
||||
"Grist Widget": "Grist Miniaplikácia",
|
||||
"Last updated:": "Posledná aktualizácia:",
|
||||
"Learn more about Custom Widgets": "Ďalšie informácie o Vlastných Miniaplikáciách",
|
||||
"No matching widgets": "Žiadne zodpovedajúce miniaplikácie",
|
||||
"Search": "Vyhľadať",
|
||||
"Widget URL": "URL Miniaplikácie",
|
||||
"Change Widget": "Zmeniť Miniaplikáciu"
|
||||
},
|
||||
"SupportGristButton": {
|
||||
"Thank you! Your trust and support is greatly appreciated. Opt out any time from the {{link}} in the user menu.": "Ďakujem! Vaša dôvera a podpora je veľmi cenená. Odhlásiť sa kedykoľvek cez {{link}} v užívateľskom menu.",
|
||||
"Opt in to Telemetry": "Pripojiť sa k Telemetrii",
|
||||
"Admin Panel": "Administračný Panel",
|
||||
"Close": "Zavrieť",
|
||||
"Help Center": "Centrum Pomoci",
|
||||
"Opted In": "Pripojený",
|
||||
"Support Grist": "Podporiť Grist"
|
||||
},
|
||||
"markdown": {
|
||||
"The toggle is **on**": "Prepínač je **on**",
|
||||
"# New Markdown Function\n *\n * We can _write_ [the usual Markdown](https:": {
|
||||
"": {
|
||||
"markdownguide.org) *inside*\n * a Grainjs element.": "# Nová funkcia Markdown\n *\n * Môžete použiť _write_ [the usual Markdown](https://markdownguide.org) *vo vnútri*\n * elementu Grainjs."
|
||||
}
|
||||
},
|
||||
"The toggle is **off**": "Prepínač je **off**"
|
||||
},
|
||||
"HomeIntroCards": {
|
||||
"Find solutions and explore more resources {{helpCenterLink}}": "Nájdite riešenia a preskúmajte viac zdrojov {{helpCenterLink}}",
|
||||
"3 minute video tour": "3 minútová videoprehliadka",
|
||||
"Blank document": "Prázdny dokument",
|
||||
"Finish our basics tutorial": "Dokončiť náš základný tutorial",
|
||||
"Help center": "Centrum pomoci",
|
||||
"Import file": "Importovať súbor",
|
||||
"Learn more {{webinarsLinks}}": "Dozvedieť sa viac {{webinarsLinks}}",
|
||||
"Start a new document": "Spustiť nový dokument",
|
||||
"Templates": "Šablóny",
|
||||
"Tutorial": "Tutorial",
|
||||
"Webinars": "Webináre"
|
||||
},
|
||||
"markdown.d": {
|
||||
"# New Markdown Function\n *\n * We can _write_ [the usual Markdown](https:": {
|
||||
"": {
|
||||
"markdownguide.org) *inside*\n * a Grainjs element.": "# Nová funkcia Markdown\n *\n * Môžete použiť _write_ [the usual Markdown](https://markdownguide.org) *vo vnútri*\n * elementu Grainjs."
|
||||
}
|
||||
},
|
||||
"The toggle is **off**": "Prepínač je **off**",
|
||||
"The toggle is **on**": "Prepínač je **on**"
|
||||
},
|
||||
"ReverseReferenceConfig": {
|
||||
"Add two-way reference": "Pridať obojsmernú referenciu",
|
||||
"Column": "Stĺpec",
|
||||
"Delete": "Odstrániť",
|
||||
"Delete column {{column}} in table {{table}}?": "Odstrániť stĺpec {{column}} v tabuľke {{table}}?",
|
||||
"It is the reverse of the reference column {{column}} in table {{table}}.": "Je to rub referenčného stĺpca {{column}} v tabuľke {{table}}.",
|
||||
"Table": "Tabuľka",
|
||||
"Two-way Reference": "Obojsmerná Referencia",
|
||||
"Delete two-way reference?": "Odstrániť obojsmernú referenciu?",
|
||||
"Target table": "Cieľová tabuľka"
|
||||
},
|
||||
"buildReassignModal": {
|
||||
"Cancel": "Zrušiť",
|
||||
"Each {{targetTable}} record may only be assigned to a single {{sourceTable}} record.": "Každý záznam {{targetTable}} môže byť priradený iba jednému záznamu {{sourceTable}}.",
|
||||
"Reassign": "Preradiť",
|
||||
"Reassign to new {{sourceTable}} records.": "Preradiť do nových záznamov {{sourceTable}}.",
|
||||
"Reassign to {{sourceTable}} record {{sourceName}}.": "Preradiť na {{sourceTable}} záznam {{sourceName}}.",
|
||||
"Record already assigned_one": "Záznam je už priradený",
|
||||
"Record already assigned_other": "Záznam je už priradený",
|
||||
"{{targetTable}} record {{targetName}} is already assigned to {{sourceTable}} record {{oldSourceName}}.": "{{targetTable}} záznam {{targetName}} je už priradený k {{sourceTable}} záznam {{oldSourceName}}."
|
||||
}
|
||||
}
|
||||
|
1
static/locales/sk.server.json
Normal file
1
static/locales/sk.server.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
1
static/locales/sl.server.json
Normal file
1
static/locales/sl.server.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
1
static/locales/th.server.json
Normal file
1
static/locales/th.server.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
1
static/locales/tr.server.json
Normal file
1
static/locales/tr.server.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
1
static/locales/uk.server.json
Normal file
1
static/locales/uk.server.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
1
static/locales/vi.server.json
Normal file
1
static/locales/vi.server.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
1
static/locales/zh_Hans.server.json
Normal file
1
static/locales/zh_Hans.server.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
1
static/locales/zh_Hant.server.json
Normal file
1
static/locales/zh_Hant.server.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
@ -6,7 +6,17 @@ describe("NumberFormat", function() {
|
||||
locale: 'en-US'
|
||||
};
|
||||
|
||||
// useGrouping became more nuanced in recent node.
|
||||
// Its old 'true' value may now be 'always' or 'auto'.
|
||||
const useGroupingAlways = buildNumberFormat(
|
||||
{numMode: 'decimal'},
|
||||
defaultDocSettings
|
||||
).resolvedOptions().useGrouping as boolean|string;
|
||||
const useGroupingAuto = (useGroupingAlways === 'always') ? 'auto' : true;
|
||||
|
||||
it("should convert Grist options into Intr.NumberFormat", function() {
|
||||
assert.include([true, 'always'], String(useGroupingAlways));
|
||||
|
||||
assert.ownInclude(buildNumberFormat({}, defaultDocSettings).resolvedOptions(), {
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 10,
|
||||
@ -17,21 +27,21 @@ describe("NumberFormat", function() {
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 3,
|
||||
style: 'decimal',
|
||||
useGrouping: true,
|
||||
useGrouping: useGroupingAlways,
|
||||
});
|
||||
assert.ownInclude(buildNumberFormat({numMode: 'percent'}, defaultDocSettings).resolvedOptions(), {
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 0,
|
||||
// style: 'percent', // In node v14.17.0 style is 'decimal' (unclear why)
|
||||
// so we check final formatting instead in this case.
|
||||
useGrouping: true,
|
||||
useGrouping: useGroupingAuto,
|
||||
});
|
||||
assert.equal(buildNumberFormat({numMode: 'percent'}, defaultDocSettings).format(0.5), '50%');
|
||||
assert.ownInclude(buildNumberFormat({numMode: 'currency'}, defaultDocSettings).resolvedOptions(), {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
style: 'currency',
|
||||
useGrouping: true,
|
||||
useGrouping: useGroupingAuto,
|
||||
currency: 'USD',
|
||||
});
|
||||
assert.ownInclude(buildNumberFormat({numMode: 'scientific'}, defaultDocSettings).resolvedOptions(), {
|
||||
@ -73,42 +83,42 @@ describe("NumberFormat", function() {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
style: 'currency',
|
||||
useGrouping: true,
|
||||
useGrouping: useGroupingAuto,
|
||||
currency: 'EUR',
|
||||
});
|
||||
assert.ownInclude(buildNumberFormat({numMode: 'currency'}, {locale: 'en-NZ'}).resolvedOptions(), {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
style: 'currency',
|
||||
useGrouping: true,
|
||||
useGrouping: useGroupingAuto,
|
||||
currency: 'NZD',
|
||||
});
|
||||
assert.ownInclude(buildNumberFormat({numMode: 'currency'}, {locale: 'de-CH'}).resolvedOptions(), {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
style: 'currency',
|
||||
useGrouping: true,
|
||||
useGrouping: useGroupingAuto,
|
||||
currency: 'CHF',
|
||||
});
|
||||
assert.ownInclude(buildNumberFormat({numMode: 'currency'}, {locale: 'es-AR'}).resolvedOptions(), {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
style: 'currency',
|
||||
useGrouping: true,
|
||||
useGrouping: useGroupingAuto,
|
||||
currency: 'ARS',
|
||||
});
|
||||
assert.ownInclude(buildNumberFormat({numMode: 'currency'}, {locale: 'zh-TW'}).resolvedOptions(), {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
style: 'currency',
|
||||
useGrouping: true,
|
||||
useGrouping: useGroupingAuto,
|
||||
currency: 'TWD',
|
||||
});
|
||||
assert.ownInclude(buildNumberFormat({numMode: 'currency'}, {locale: 'en-AU'}).resolvedOptions(), {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
style: 'currency',
|
||||
useGrouping: true,
|
||||
useGrouping: useGroupingAuto,
|
||||
currency: 'AUD',
|
||||
});
|
||||
});
|
||||
|
@ -149,6 +149,82 @@ describe("AccessRules3", function() {
|
||||
await assertSaved();
|
||||
});
|
||||
|
||||
it('can have a SeedRule special that refers to columns', async function() {
|
||||
// Open Access Rules page.
|
||||
const mainSession = await gu.session().teamSite.user('user1').login();
|
||||
await mainSession.loadDoc(`/doc/${docId}`);
|
||||
await driver.find('.test-tools-access-rules').click();
|
||||
await driver.findWait('.test-rule-set', 2000);
|
||||
|
||||
// Check seed rule checkbox is unselected.
|
||||
const seedRule = await driver.find('div.test-rule-special-SeedRule');
|
||||
const checkbox = seedRule.find('input[type=checkbox]');
|
||||
assert.equal(await checkbox.isSelected(), false);
|
||||
|
||||
// Now check the box, and see we get the default rule we expect.
|
||||
await checkbox.click();
|
||||
await assertChanged();
|
||||
await driver.find('.test-rule-special-SeedRule .test-rule-special-expand').click();
|
||||
assert.deepEqual(await getRules(seedRule),
|
||||
[{ formula: 'user.Access in [OWNER]', perm: '+R+U+C+D' }]);
|
||||
assert.equal(await hasExtraAdd(seedRule), true);
|
||||
|
||||
// Tweak the seed rule to refer to a column.
|
||||
await seedRule.find('.test-rule-part .test-rule-add').click();
|
||||
await enterRulePart(seedRule, 1, 'rec.Year == 1', 'Deny All', 'memo1');
|
||||
assert.equal(await checkbox.getAttribute('disabled'), 'true');
|
||||
|
||||
// New table rules should include the seed rule.
|
||||
await driver.findContentWait('button', /Add Table Rules/, 2000).click();
|
||||
await driver.findContentWait('.grist-floating-menu li', /FinancialsTable/, 3000).click();
|
||||
let fin = findTable(/FinancialsTable/);
|
||||
assert.deepEqual(await getRules(fin),
|
||||
[{ formula: 'rec.Year == 1', perm: '-R-U-C-D', res: 'All', memo: 'memo1'},
|
||||
{ formula: 'user.Access in [OWNER]', perm: '+R+U+C+D', res: 'All' },
|
||||
{ formula: 'Everyone Else', perm: '', res: 'All' }]);
|
||||
assert.equal(await hasExtraAdd(fin), false);
|
||||
await removeTable(/FinancialsTable/);
|
||||
|
||||
// Tweak the seed rule to refer to a column that won't exist.
|
||||
await enterRulePart(seedRule, 1, 'rec.Unreal == 1', 'Deny All', 'memo1');
|
||||
assert.equal(await checkbox.getAttribute('disabled'), 'true');
|
||||
|
||||
// New table rules should include the seed rule, and show an error.
|
||||
await driver.findContentWait('button', /Add Table Rules/, 2000).click();
|
||||
await driver.findContentWait('.grist-floating-menu li', /FinancialsTable/, 3000).click();
|
||||
fin = findTable(/FinancialsTable/);
|
||||
assert.deepEqual(await getRules(fin),
|
||||
[{ formula: 'rec.Unreal == 1', perm: '-R-U-C-D', res: 'All', memo: 'memo1',
|
||||
error: 'Invalid columns: Unreal' },
|
||||
{ formula: 'user.Access in [OWNER]', perm: '+R+U+C+D', res: 'All' },
|
||||
{ formula: 'Everyone Else', perm: '', res: 'All' }]);
|
||||
assert.equal(await hasExtraAdd(fin), false);
|
||||
await removeTable(/FinancialsTable/);
|
||||
|
||||
// Check that returning to the single OWNER rule gets us back to an uncomplicated
|
||||
// selected checkbox.
|
||||
await assertChanged();
|
||||
assert.equal(await checkbox.getAttribute('disabled'), 'true');
|
||||
assert.equal(await checkbox.isSelected(), false);
|
||||
await seedRule.find('.test-rule-part .test-rule-remove').click();
|
||||
assert.equal(await checkbox.getAttribute('disabled'), null);
|
||||
assert.equal(await checkbox.isSelected(), true);
|
||||
|
||||
// Check that removing that rule deselected the checkbox and collapses rule list.
|
||||
await seedRule.find('.test-rule-part .test-rule-remove').click();
|
||||
assert.equal(await checkbox.getAttribute('disabled'), null);
|
||||
assert.equal(await checkbox.isSelected(), false);
|
||||
await assertSaved();
|
||||
assert.lengthOf(await seedRule.findAll('.test-rule-set'), 0);
|
||||
|
||||
// Expand again, and make sure we are back to default.
|
||||
await driver.find('.test-rule-special-SeedRule .test-rule-special-expand').click();
|
||||
assert.lengthOf(await seedRule.findAll('.test-rule-set'), 1);
|
||||
assert.deepEqual(await getRules(seedRule),
|
||||
[{ formula: 'Everyone', perm: '' }]);
|
||||
await assertSaved();
|
||||
});
|
||||
|
||||
it('can save and reload SeedRule special', async function() {
|
||||
const mainSession = await gu.session().teamSite.user('user1').login();
|
||||
await mainSession.loadDoc(`/doc/${docId}`);
|
||||
|
@ -19,6 +19,10 @@ describe('Importer', function() {
|
||||
// have tests go faster. Each successful test case should leave the document unchanged.
|
||||
if (!docUrl || !await gu.testCurrentUrl(docUrl)) {
|
||||
const session = await gu.session().teamSite.login();
|
||||
// TODO: tests check colors literally, so need to be in
|
||||
// light theme - but calling gu.setGristTheme results in
|
||||
// some problems so right now if you are a dev you just
|
||||
// need to run these tests in light mode, sorry.
|
||||
await session.tempDoc(cleanup, 'Hello.grist');
|
||||
docUrl = await driver.getCurrentUrl();
|
||||
}
|
||||
@ -450,9 +454,9 @@ describe('Importer', function() {
|
||||
assert.equal(await driver.findWait('.test-importer-preview', 2000).isPresent(), true);
|
||||
|
||||
// Check that the merge field select button has a red outline.
|
||||
assert.equal(
|
||||
assert.match(
|
||||
await driver.find('.test-importer-merge-fields-select').getCssValue('border'),
|
||||
'1px solid rgb(208, 2, 27)'
|
||||
/solid rgb\(208, 2, 27\)/
|
||||
);
|
||||
|
||||
// Select a merge field, and check that the red outline is gone.
|
||||
@ -461,9 +465,9 @@ describe('Importer', function() {
|
||||
'.test-multi-select-menu .test-multi-select-menu-option',
|
||||
/Name/
|
||||
).click();
|
||||
assert.equal(
|
||||
assert.match(
|
||||
await driver.find('.test-importer-merge-fields-select').getCssValue('border'),
|
||||
'1px solid rgb(217, 217, 217)'
|
||||
/solid rgb\(217, 217, 217\)/
|
||||
);
|
||||
// Hide dropdown
|
||||
await gu.sendKeys(Key.ESCAPE);
|
||||
@ -584,9 +588,9 @@ describe('Importer', function() {
|
||||
await driver.findContent('.test-importer-source', /UploadedData2Extended.csv/).click();
|
||||
|
||||
// Check that it failed, and that the merge fields select button is outlined in red.
|
||||
assert.equal(
|
||||
assert.match(
|
||||
await driver.find('.test-importer-merge-fields-select').getCssValue('border'),
|
||||
'1px solid rgb(208, 2, 27)'
|
||||
/solid rgb\(208, 2, 27\)/
|
||||
);
|
||||
assert.equal(
|
||||
await driver.find('.test-importer-source-selected .test-importer-from').getText(),
|
||||
|
@ -139,7 +139,8 @@ namespace gristUtils {
|
||||
export async function getRules(el: WebElement): Promise<Array<{
|
||||
formula: string, perm: string,
|
||||
res?: string,
|
||||
memo?: string}>> {
|
||||
memo?: string,
|
||||
error?: string}>> {
|
||||
const ruleSets = await el.findAll('.test-rule-set');
|
||||
const results: Array<{formula: string, perm: string,
|
||||
res?: string,
|
||||
@ -162,9 +163,12 @@ namespace gristUtils {
|
||||
}
|
||||
const hasMemo = await part.find('.test-rule-memo').isPresent();
|
||||
const memo = hasMemo ? await part.find('.test-rule-memo input').value() : undefined;
|
||||
const hasError = await part.find('.test-rule-error').isPresent();
|
||||
const error = hasError ? await part.find('.test-rule-error').getText() : undefined;
|
||||
results.push({formula, perm: permParts.join(''),
|
||||
...(memo ? {memo} : {}),
|
||||
...(res ? {res} : {})
|
||||
...(res ? {res} : {}),
|
||||
...(error ? {error} : {}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1464,6 +1464,7 @@ export function revertChanges(test: () => Promise<void>, invariant: () => any =
|
||||
export async function redo(optCount: number = 1, optTimeout?: number) {
|
||||
for (let i = 0; i < optCount; ++i) {
|
||||
await driver.find('.test-redo').doClick();
|
||||
await waitForServer(optTimeout);
|
||||
}
|
||||
await waitForServer(optTimeout);
|
||||
}
|
||||
@ -2781,11 +2782,11 @@ export function addSamplesForSuite(includeTutorial = false) {
|
||||
}
|
||||
|
||||
export async function openAccountMenu() {
|
||||
await driver.findWait('.test-dm-account', 1000).click();
|
||||
await driver.findWait('.test-dm-account', 2000).click();
|
||||
// Since the AccountWidget loads orgs and the user data asynchronously, the menu
|
||||
// can expand itself causing the click to land on a wrong button.
|
||||
await waitForServer();
|
||||
await driver.findWait('.test-site-switcher-org', 1000);
|
||||
await driver.findWait('.test-site-switcher-org', 2000);
|
||||
await driver.sleep(250); // There's still some jitter (scroll-bar? other user accounts?)
|
||||
}
|
||||
|
||||
|
@ -35,7 +35,14 @@ export const getPreviewDiffCellValues = stackWrapFunc(async (cols: number[], row
|
||||
|
||||
// Helper that waits for the diff preview to finish loading.
|
||||
export const waitForDiffPreviewToLoad = stackWrapFunc(async (): Promise<void> => {
|
||||
await gu.waitForServer();
|
||||
await driver.wait(() => driver.find('.test-importer-preview').isPresent(), 5000);
|
||||
await gu.waitToPass(async () => {
|
||||
const preview = (await getPreviewDiffCellValues([0], [1]))[0];
|
||||
if (preview[0] === undefined && preview[1] === undefined) {
|
||||
throw new Error('sometimes data is a little slow to show up?');
|
||||
}
|
||||
}, 2000);
|
||||
});
|
||||
|
||||
// Helper that gets the list of visible column matching rows to the left of the preview.
|
||||
|
96
test/server/lib/AppSettings.ts
Normal file
96
test/server/lib/AppSettings.ts
Normal file
@ -0,0 +1,96 @@
|
||||
import { AppSettings } from 'app/server/lib/AppSettings';
|
||||
import { EnvironmentSnapshot } from '../testUtils';
|
||||
|
||||
import { assert } from 'chai';
|
||||
|
||||
describe('AppSettings', () => {
|
||||
let appSettings: AppSettings;
|
||||
let env: EnvironmentSnapshot;
|
||||
beforeEach(() => {
|
||||
appSettings = new AppSettings('test');
|
||||
env = new EnvironmentSnapshot();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
env.restore();
|
||||
});
|
||||
|
||||
describe('for integers', () => {
|
||||
function testIntMethod(method: 'readInt' | 'requireInt') {
|
||||
it('should throw an error if the value is less than the minimum', () => {
|
||||
process.env.TEST = '4';
|
||||
assert.throws(() => {
|
||||
appSettings[method]({ envVar: 'TEST', minValue: 5 });
|
||||
}, 'value 4 is less than minimum 5');
|
||||
});
|
||||
|
||||
it('should throw an error if the value is greater than the maximum', () => {
|
||||
process.env.TEST = '6';
|
||||
assert.throws(() => {
|
||||
appSettings[method]({ envVar: 'TEST', maxValue: 5 });
|
||||
}, 'value 6 is greater than maximum 5');
|
||||
});
|
||||
|
||||
it('should throw if the value is NaN', () => {
|
||||
process.env.TEST = 'not a number';
|
||||
assert.throws(() => appSettings[method]({ envVar: 'TEST' }), 'not a number does not look like a number');
|
||||
});
|
||||
|
||||
it('should throw if the default value is not finite', () => {
|
||||
assert.throws(
|
||||
() => appSettings[method]({ envVar: 'TEST', defaultValue: Infinity }),
|
||||
'Infinity does not look like a number'
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw if the default value is not within the range', () => {
|
||||
assert.throws(
|
||||
() => appSettings[method]({
|
||||
envVar: 'TEST',
|
||||
defaultValue: 6,
|
||||
minValue: 7,
|
||||
maxValue: 9,
|
||||
}),
|
||||
'value 6 is less than minimum 7'
|
||||
);
|
||||
});
|
||||
|
||||
it('should return the default value if it is within the range', () => {
|
||||
const result = appSettings[method]({
|
||||
envVar: 'TEST',
|
||||
defaultValue: 5,
|
||||
minValue: 5,
|
||||
maxValue: 12
|
||||
});
|
||||
assert.strictEqual(result, 5);
|
||||
});
|
||||
|
||||
it('should return the value if it is within the range', () => {
|
||||
process.env.TEST = '5';
|
||||
assert.strictEqual(appSettings[method]({ envVar: 'TEST', minValue: 5 }), 5);
|
||||
});
|
||||
|
||||
it('should return the integer value of a float', () => {
|
||||
process.env.TEST = '5.9';
|
||||
assert.strictEqual(appSettings[method]({ envVar: 'TEST' }), 5);
|
||||
});
|
||||
}
|
||||
|
||||
describe('readInt()', () => {
|
||||
testIntMethod('readInt');
|
||||
|
||||
it('should return undefined when no value nor default value is passed', () => {
|
||||
const result = appSettings.readInt({ envVar: 'TEST', maxValue: 5 });
|
||||
assert.isUndefined(result);
|
||||
});
|
||||
});
|
||||
|
||||
describe('requireInt()', () => {
|
||||
testIntMethod('requireInt');
|
||||
|
||||
it('should throw if env variable is not set and no default value is passed', () => {
|
||||
assert.throws(() => appSettings.requireInt({ envVar: 'TEST' }), 'missing environment variable: TEST');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -76,6 +76,8 @@ function makeConfig(username: string): AxiosRequestConfig {
|
||||
}
|
||||
|
||||
describe('DocApi', function () {
|
||||
const webhooksTestPort = Number(process.env.WEBHOOK_TEST_PORT || 34365);
|
||||
|
||||
this.timeout(30000);
|
||||
testUtils.setTmpLogLevel('error');
|
||||
let oldEnv: testUtils.EnvironmentSnapshot;
|
||||
@ -121,7 +123,7 @@ describe('DocApi', function () {
|
||||
homeUrl = serverUrl = home.serverUrl;
|
||||
hasHomeApi = true;
|
||||
});
|
||||
testDocApi();
|
||||
testDocApi({webhooksTestPort});
|
||||
});
|
||||
|
||||
describe('With GRIST_ANON_PLAYGROUND disabled', async () => {
|
||||
@ -157,7 +159,7 @@ describe('DocApi', function () {
|
||||
homeUrl = serverUrl = home.serverUrl;
|
||||
hasHomeApi = true;
|
||||
});
|
||||
testDocApi();
|
||||
testDocApi({webhooksTestPort});
|
||||
});
|
||||
|
||||
describe('behind a reverse-proxy', function () {
|
||||
@ -206,7 +208,7 @@ describe('DocApi', function () {
|
||||
|
||||
after(() => tearDown(proxy, [home, docs]));
|
||||
|
||||
testDocApi();
|
||||
testDocApi({webhooksTestPort});
|
||||
});
|
||||
|
||||
async function testCompareDocs(proxy: TestServerReverseProxy, home: TestServer) {
|
||||
@ -261,7 +263,7 @@ describe('DocApi', function () {
|
||||
serverUrl = docs.serverUrl;
|
||||
hasHomeApi = false;
|
||||
});
|
||||
testDocApi();
|
||||
testDocApi({webhooksTestPort});
|
||||
});
|
||||
}
|
||||
|
||||
@ -323,7 +325,10 @@ describe('DocApi', function () {
|
||||
});
|
||||
|
||||
// Contains the tests. This is where you want to add more test.
|
||||
function testDocApi() {
|
||||
function testDocApi(settings: {
|
||||
webhooksTestPort: number,
|
||||
}) {
|
||||
const { webhooksTestPort } = settings;
|
||||
let chimpy: AxiosRequestConfig, kiwi: AxiosRequestConfig,
|
||||
charon: AxiosRequestConfig, nobody: AxiosRequestConfig, support: AxiosRequestConfig;
|
||||
|
||||
@ -3478,13 +3483,20 @@ function testDocApi() {
|
||||
});
|
||||
|
||||
describe('webhooks related endpoints', async function () {
|
||||
const serving: Serving = await serveSomething(app => {
|
||||
app.use(express.json());
|
||||
app.post('/200', ({body}, res) => {
|
||||
res.sendStatus(200);
|
||||
res.end();
|
||||
});
|
||||
}, webhooksTestPort);
|
||||
let serving: Serving;
|
||||
before(async function () {
|
||||
serving = await serveSomething(app => {
|
||||
app.use(express.json());
|
||||
app.post('/200', ({body}, res) => {
|
||||
res.sendStatus(200);
|
||||
res.end();
|
||||
});
|
||||
}, webhooksTestPort);
|
||||
});
|
||||
|
||||
after(async function () {
|
||||
await serving.shutdown();
|
||||
});
|
||||
|
||||
/*
|
||||
Regression test for old _subscribe endpoint. /docs/{did}/webhooks should be used instead to subscribe
|
||||
@ -3577,7 +3589,8 @@ function testDocApi() {
|
||||
}
|
||||
}]
|
||||
},
|
||||
403, /Column not found notExisting/);
|
||||
// this check was previously just wrong, was the test not running somehow??
|
||||
404, /Column not found "notExisting"/);
|
||||
|
||||
});
|
||||
|
||||
@ -5385,8 +5398,6 @@ async function getWorkspaceId(api: UserAPIImpl, name: string) {
|
||||
return workspaces.find((w) => w.name === name)!.id;
|
||||
}
|
||||
|
||||
const webhooksTestPort = Number(process.env.WEBHOOK_TEST_PORT || 34365);
|
||||
|
||||
async function setupDataDir(dir: string) {
|
||||
// we'll be serving Hello.grist content for various document ids, so let's make copies of it in
|
||||
// tmpDir
|
||||
|
@ -84,15 +84,17 @@ describe("ProxyAgent", function () {
|
||||
it("should report error when proxy fails", async function() {
|
||||
// if the proxy isn't listening, fetches produces error messages.
|
||||
await testProxyServer.dispose();
|
||||
// Error message depends a little on node version.
|
||||
const logMessages2 = await captureLog('warn', async () => {
|
||||
await assert.isRejected(testFetch('/200'), /ECONNREFUSED/);
|
||||
await assert.isRejected(testFetch('/404'), /ECONNREFUSED/);
|
||||
await assert.isRejected(testFetch('/200'), /(request.*failed)|(ECONNREFUSED)/);
|
||||
await assert.isRejected(testFetch('/404'), /(request.*failed)|(ECONNREFUSED)/);
|
||||
});
|
||||
|
||||
// We rely on "ProxyAgent error" message to detect issues with the proxy server.
|
||||
// Error message depends a little on node version.
|
||||
assertMatchArray(logMessages2, [
|
||||
/warn: ProxyAgent error.*ECONNREFUSED/,
|
||||
/warn: ProxyAgent error.*ECONNREFUSED/,
|
||||
/warn: ProxyAgent error.*((request.*failed)|(ECONNREFUSED)|(AggregateError))/,
|
||||
/warn: ProxyAgent error.*((request.*failed)|(ECONNREFUSED)|(AggregateError))/,
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
@ -51,7 +51,8 @@ describe('UnhandledErrors', function() {
|
||||
}, 1000, 100);
|
||||
|
||||
// We expect the server to be dead now.
|
||||
await assert.isRejected(fetch(`${server.serverUrl}/status`), /failed.*ECONNREFUSED/);
|
||||
// Error message depends a little on node version.
|
||||
await assert.isRejected(fetch(`${server.serverUrl}/status`), /(request.*failed)|(ECONNREFUSED)/);
|
||||
|
||||
} finally {
|
||||
await server.stop();
|
||||
|
Loading…
Reference in New Issue
Block a user