mirror of
				https://github.com/gristlabs/grist-core.git
				synced 2025-06-13 20:53:59 +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