(core) updates from grist-core

pull/758/head
Paul Fitzpatrick 7 months ago
commit cea0404a22

@ -1,23 +1,12 @@
# Grist # Grist
Grist is a modern relational spreadsheet. It combines the flexibility of a spreadsheet with the Grist is a modern relational spreadsheet. It combines the flexibility of a spreadsheet with the robustness of a database to organize your data and make you more productive.
robustness of a database to organize your data and make you more productive.
This repository, `grist-core`, is the heart of Grist, and has what you need to run a powerful spreadsheet hosting server. If you wish to view and edit spreadsheets stored locally, another option is to use the [`grist-electron`](https://github.com/gristlabs/grist-electron) desktop app for Linux, Mac, and Windows. And to show Grist spreadsheets on a website without any special back-end support, you can use [`grist-static`](https://github.com/gristlabs/grist-static), a fully in-browser build of Grist.
This repository, `grist-core`, is the heart of Grist, and has what you
need to run a powerful spreadsheet hosting server. If you wish to view and edit The `grist-core` repository is the basis for all these options, and for the hosted spreadsheet services offered by [Grist Labs](https://getgrist.com), an NYC-based company 🇺🇸 that is the main developer of Grist, and by [ANCT Données et Territoires](https://donnees.incubateur.anct.gouv.fr/toolbox/grist), a French government agency 🇫🇷 whose developers have made many contributions to the codebase.
spreadsheets stored locally, another option is to use the
[`grist-electron`](https://github.com/gristlabs/grist-electron) desktop app for Linux, Mac, and Windows. And to show Grist spreadsheets on a website The `grist-core`, `grist-electron`, and `grist-static` repositories are all open source (Apache License, Version 2.0).
without any special back-end support, your options include
[`grist-static`](https://github.com/gristlabs/grist-static),
a fully in-browser build of Grist.
The `grist-core` repository is the basis for all these options, and
for the hosted spreadsheet services offered by
[`Grist Labs`](https://getgrist.com), an NYC-based company 🇺🇸 that is the main developer of Grist, and by
[`ANCT Données et Territoires`](https://donnees.incubateur.anct.gouv.fr/toolbox/grist),
a French government agency 🇫🇷 whose developers have made many
contributions to the code-base.
The `grist-core`, `grist-electron`, and `grist-static` repositories
are all open-source (Apache License, Version 2.0).
https://user-images.githubusercontent.com/118367/151245587-892e50a6-41f5-4b74-9786-fe3566f6b1fb.mp4 https://user-images.githubusercontent.com/118367/151245587-892e50a6-41f5-4b74-9786-fe3566f6b1fb.mp4
@ -25,81 +14,69 @@ https://user-images.githubusercontent.com/118367/151245587-892e50a6-41f5-4b74-97
Grist is a hybrid database/spreadsheet, meaning that: Grist is a hybrid database/spreadsheet, meaning that:
- Columns work like they do in databases. They are named, and hold one kind of data. - Columns work like they do in databases: they are named, and they hold one kind of data.
- Columns can be filled by formula, spreadsheet-style, with automatic updates when referenced cells change. - Columns can be filled by formula, spreadsheet-style, with automatic updates when referenced cells change.
This difference can confuse people coming directly from Excel or Google Sheets. Give it a chance! This difference can confuse people coming directly from Excel or Google Sheets. Give it a chance! If you are coming from Airtable, you'll find the model familiar though (and there's a [Grist vs Airtable](https://www.getgrist.com/blog/grist-v-airtable/) article that might interest you).
If you are coming from Airtable, you'll find the model familiar though (and there's a
[Grist vs Airtable](https://www.getgrist.com/blog/grist-v-airtable/) article that might interest you).
Here are some specific feature highlights of Grist: Here are some specific feature highlights of Grist:
* Python formulas. * Python formulas.
- Full [Python syntax is supported](https://support.getgrist.com/formulas/#python), and the standard library. - Full [Python syntax is supported](https://support.getgrist.com/formulas/#python), including the standard library.
- Many [Excel functions](https://support.getgrist.com/functions/) also available. - Many [Excel functions](https://support.getgrist.com/functions/) also available.
- An [AI Assistant](https://www.getgrist.com/ai-formula-assistant/) specifically tuned for formula generation (using OpenAI gpt-3.5-turbo or [Llama](https://ai.meta.com/llama/) via <a href="https://github.com/abetlen/llama-cpp-python">llama-cpp-python</a>).
* A portable, self-contained format. * A portable, self-contained format.
- Based on SQLite, the most widely deployed database engine. - Based on SQLite, the most widely deployed database engine.
- Any tool that can read SQLite can read numeric and text data from a Grist file. - Any tool that can read SQLite can read numeric and text data from a Grist file.
- Great format for [backups](https://support.getgrist.com/exports/#backing-up-an-entire-document) that you can be confident you can restore in full. - Enables [backups](https://support.getgrist.com/exports/#backing-up-an-entire-document) that you can confidently restore in full.
- Great format for moving between different hosts. - Great for moving between different hosts.
- Can be displayed on a static website with [grist-static](https://github.com/gristlabs/grist-static), no special server needed. * Can be displayed on a static website with [`grist-static`](https://github.com/gristlabs/grist-static) no special server needed.
- There's a self-contained desktop app available for viewing and editing: [grist-electron](https://github.com/gristlabs/grist-electron). * A self-contained desktop app for viewing and editing locally: [`grist-electron`](https://github.com/gristlabs/grist-electron).
* Convenient editing and formatting features. * Convenient editing and formatting features.
- Choices and [choice lists](https://support.getgrist.com/col-types/#choice-list-columns), for adding colorful tags to records without fuss. - Choices and [choice lists](https://support.getgrist.com/col-types/#choice-list-columns), for adding colorful tags to records.
- [References](https://support.getgrist.com/col-refs/#creating-a-new-reference-list-column) and reference lists, for cross-referencing records in other tables. - [References](https://support.getgrist.com/col-refs/#creating-a-new-reference-list-column) and reference lists, for cross-referencing records in other tables.
- [Attachments](https://support.getgrist.com/col-types/#attachment-columns), to include media or document files in records. - [Attachments](https://support.getgrist.com/col-types/#attachment-columns), to include media or document files in records.
- Dates and times, toggles, and special numerics such as currency all have specialized editors and formatting options. - Dates and times, toggles, and special numerics such as currency all have specialized editors and formatting options.
- [Conditional Formatting](https://support.getgrist.com/conditional-formatting/), letting you control the style of cells with formulas, to draw attention to important information. - [Conditional Formatting](https://support.getgrist.com/conditional-formatting/), letting you control the style of cells with formulas to draw attention to important information.
* Great for dashboards, visualizations, and data entry. * Drag-and-drop dashboards.
- [Charts](https://support.getgrist.com/widget-chart/) for visualization. - [Charts](https://support.getgrist.com/widget-chart/) for visualization.
- [Summary tables](https://support.getgrist.com/summary-tables/) for summing and counting across groups. - [Summary tables](https://support.getgrist.com/summary-tables/) for summing and counting across groups.
- [Widget linking](https://support.getgrist.com/linking-widgets/) streamlines filtering and editing data. - [Widget linking](https://support.getgrist.com/linking-widgets/) streamlines filtering and editing data.
Grist has a unique approach to visualization, where you can lay out and link distinct widgets to show together, Grist has a unique approach to visualization, where you can lay out and link distinct widgets to show together,
without cramming mixed material into a table. without cramming mixed material into a table.
- The [Filter bar](https://support.getgrist.com/search-sort-filter/#filter-buttons) is great for quick slicing and dicing. - [Filter bar](https://support.getgrist.com/search-sort-filter/#filter-buttons) for quick slicing and dicing.
* [Incremental imports](https://support.getgrist.com/imports/#updating-existing-records). * [Incremental imports](https://support.getgrist.com/imports/#updating-existing-records).
- So you can import a CSV of the last three months activity from your bank... - Import a CSV of the last three months activity from your bank...
- ... and import new activity a month later without fuss or duplicates. - ...and import new activity a month later without fuss or duplication.
* Integrations. * Integrations.
- A [REST API](https://support.getgrist.com/api/), [Zapier actions/triggers](https://support.getgrist.com/integrators/#integrations-via-zapier), and support from similar [integrators](https://support.getgrist.com/integrators/). - A [REST API](https://support.getgrist.com/api/), [Zapier actions/triggers](https://support.getgrist.com/integrators/#integrations-via-zapier), and support from similar [integrators](https://support.getgrist.com/integrators/).
- Import/export to Google drive, Excel format, CSV. - Import/export to Google drive, Excel format, CSV.
- Can link data with custom widgets hosted externally. - Link data with [custom widgets](https://support.getgrist.com/widget-custom/#_top), hosted externally.
- You can set up outgoing webhooks. - Configurable outgoing webhooks.
* [Many templates](https://templates.getgrist.com/) to get you started, from investment research to organizing treasure hunts. * [Many templates](https://templates.getgrist.com/) to get you started, from investment research to organizing treasure hunts.
* Access control options. * Access control options.
- (You'll need SSO logins set up to make use of these options; [grist-omnibus](https://github.com/gristlabs/grist-omnibus) has a prepackaged solution if configuring this feels daunting) - (You'll need SSO logins set up to make use of these options; [`grist-omnibus`](https://github.com/gristlabs/grist-omnibus) has a prepackaged solution if configuring this feels daunting)
- Share [individual documents](https://support.getgrist.com/sharing/), or workspaces, or [team sites](https://support.getgrist.com/team-sharing/). - Share [individual documents](https://support.getgrist.com/sharing/), workspaces, or [team sites](https://support.getgrist.com/team-sharing/).
- Control access to [individual rows, columns, and tables](https://support.getgrist.com/access-rules/). - Control access to [individual rows, columns, and tables](https://support.getgrist.com/access-rules/).
- Control access based on cell values and user attributes. - Control access based on cell values and user attributes.
* Can be self-maintained. * Self-maintainable.
- Useful for intranet operation and specific compliance requirements. - Useful for intranet operation and specific compliance requirements.
* Sandboxing options for untrusted documents. * Sandboxing options for untrusted documents.
- On Linux or with docker, you can enable - On Linux or with Docker, you can enable [gVisor](https://github.com/google/gvisor) sandboxing at the individual document level.
[gVisor](https://github.com/google/gvisor) sandboxing at the individual - On macOS, you can use native sandboxing.
document level.
- On OSX, you can use native sandboxing.
- On any OS, including Windows, you can use a wasm-based sandbox. - On any OS, including Windows, you can use a wasm-based sandbox.
* Translated to many languages. * Translated to many languages.
* Support for an AI Formula Assistant (using OpenAI gpt-3.5-turbo or comparable models). * `F1` key brings up some quick help. This used to go without saying, but in general Grist has good keyboard support.
* `F1` key brings up some quick help. This used to go without saying. In general Grist has good keyboard support. * We post progress on [𝕏 or Twitter or whatever](https://twitter.com/getgrist) and publish [monthly newsletters](https://support.getgrist.com/newsletters/).
* We post progress on [𝕏 or Twitter or whatever](https://twitter.com/getgrist).
If you are curious about where Grist is going heading,
see [our roadmap](https://github.com/gristlabs/grist-core/projects/1), drop a
question in [our forum](https://community.getgrist.com),
or browse [our extensive documentation](https://support.getgrist.com).
If you are curious about where Grist is heading, see [our roadmap](https://github.com/gristlabs/grist-core/projects/1), drop a question in [our forum](https://community.getgrist.com), or browse [our extensive documentation](https://support.getgrist.com).
## Using Grist ## Using Grist
If you just want a quick demo of Grist: If you just want a quick demo of Grist:
* You can try Grist out at the hosted service run * You can try Grist out at the hosted service run by Grist Labs at [docs.getgrist.com](https://docs.getgrist.com) (no registration needed).
by Grist Labs at [docs.getgrist.com](https://docs.getgrist.com) * Or you can see a fully in-browser build of Grist at [gristlabs.github.io/grist-static](https://gristlabs.github.io/grist-static/).
(no registration needed).
* Or you can see an experimental fully in-browser build of Grist
at [gristlabs.github.io/grist-static](https://gristlabs.github.io/grist-static/).
* Or you can download Grist as a desktop app from [github.com/gristlabs/grist-electron](https://github.com/gristlabs/grist-electron). * Or you can download Grist as a desktop app from [github.com/gristlabs/grist-electron](https://github.com/gristlabs/grist-electron).
To get `grist-core` running on your computer with [Docker](https://www.docker.com/get-started), do: To get `grist-core` running on your computer with [Docker](https://www.docker.com/get-started), do:
@ -150,7 +127,7 @@ Grist formulas in documents will be run using Python executed directly on your
machine. You can configure sandboxing using a `GRIST_SANDBOX_FLAVOR` machine. You can configure sandboxing using a `GRIST_SANDBOX_FLAVOR`
environment variable. environment variable.
* On OSX, `export GRIST_SANDBOX_FLAVOR=macSandboxExec` * On macOS, `export GRIST_SANDBOX_FLAVOR=macSandboxExec`
uses the native `sandbox-exec` command for sandboxing. uses the native `sandbox-exec` command for sandboxing.
* On Linux with [gVisor's runsc](https://github.com/google/gvisor) * On Linux with [gVisor's runsc](https://github.com/google/gvisor)
installed, `export GRIST_SANDBOX_FLAVOR=gvisor` is an option. installed, `export GRIST_SANDBOX_FLAVOR=gvisor` is an option.
@ -195,18 +172,15 @@ did the hard work of making a good chunk of the application localizable. Merci b
This repository, [grist-core](https://github.com/gristlabs/grist-core), is maintained by Grist This repository, [grist-core](https://github.com/gristlabs/grist-core), is maintained by Grist
Labs. Our flagship product available at [getgrist.com](https://www.getgrist.com) is built from the code you see Labs. Our flagship product available at [getgrist.com](https://www.getgrist.com) is built from the code you see
here, combined with business-specific software designed to scale it to many users, handle billing, here, combined with business-specific software designed to scale to many users, handle billing,
etc. etc.
Grist Labs is an open-core company. We offer Grist hosting as a Grist Labs is an open-core company. We offer Grist hosting as a
service, with free and paid plans. We also develop and sell service, with free and paid plans. We also develop and sell
features related to Grist using a proprietary license, targeted at the features related to Grist using a proprietary license, targeted at the
needs of enterprises with large self-managed installations. We see needs of enterprises with large self-managed installations.
data portability and autonomy as a key value Grist can bring to our
users, and `grist-core` as an essential means to deliver that. We are We see data portability and autonomy as a key value, and `grist-core` is an essential part of that. We are committed to maintaining and improving the `grist-core` codebase, and to be thoughtful about how proprietary offerings impact data portability and autonomy.
committed to maintaining and improving the `grist-core` codebase, and
to be thoughtful about how proprietary offerings impact data portability
and autonomy.
By opening its source code and offering an [OSI](https://opensource.org/)-approved free license, By opening its source code and offering an [OSI](https://opensource.org/)-approved free license,
Grist benefits its users: Grist benefits its users:
@ -223,10 +197,9 @@ Grist benefits its users:
- **Price flexibility.** If you are low on funds but have time to invest, self-hosting is a great - **Price flexibility.** If you are low on funds but have time to invest, self-hosting is a great
option to have. And DIY users may have the technical savvy and motivation to delve in and make improvements, option to have. And DIY users may have the technical savvy and motivation to delve in and make improvements,
which can benefit all users of Grist. which can benefit all users of Grist.
- **Extensibility.** For developers, having the source open makes it easier to build extensions (such as the - **Extensibility.** For developers, having the source open makes it easier to build extensions (such as [Custom Widgets](https://support.getgrist.com/widget-custom/)). You can more easily include Grist in your pipeline. And if a feature is missing, you can just take the source code and build on top of it.
experimental [Custom Widget](https://support.getgrist.com/widget-custom/)). You can more easily
include Grist in your pipeline. And if a feature is missing, you can just take the source code and For more on Grist Labs' history and principles, see our [About Us](https://www.getgrist.com/about/) page.
build on top of it.
## Sponsors ## Sponsors

@ -72,6 +72,8 @@ export interface SessionObj {
oidc?: { oidc?: {
// codeVerifier is used during OIDC authentication, to protect against attacks like CSRF. // codeVerifier is used during OIDC authentication, to protect against attacks like CSRF.
codeVerifier?: string; codeVerifier?: string;
state?: string;
targetUrl?: string;
} }
} }

@ -36,7 +36,7 @@ export async function collectTableSchemaInFrictionlessFormat(
req: express.Request, req: express.Request,
options: DownloadOptions options: DownloadOptions
): Promise<FrictionlessFormat> { ): Promise<FrictionlessFormat> {
const {tableId} = options; const {tableId, header} = options;
if (!activeDoc.docData) { if (!activeDoc.docData) {
throw new Error('No docData in active document'); throw new Error('No docData in active document');
} }
@ -50,24 +50,15 @@ export async function collectTableSchemaInFrictionlessFormat(
throw new ApiError(`Table ${tableId} not found.`, 404); throw new ApiError(`Table ${tableId} not found.`, 404);
} }
const data = await exportTable(activeDoc, tableRef, req); const {tableName, columns} = await exportTable(activeDoc, tableRef, req);
const tableSchema = columnsToTableSchema(tableId, data, settings.locale);
return tableSchema;
}
function columnsToTableSchema(
tableId: string,
{tableName, columns}: {tableName: string, columns: ExportColumn[]},
locale: string,
): FrictionlessFormat {
return { return {
name: tableId.toLowerCase().replace(/_/g, '-'), name: tableId.toLowerCase().replace(/_/g, '-'),
title: tableName, title: tableName,
schema: { schema: {
fields: columns.map(col => ({ fields: columns.map(col => ({
name: col.label, name: col[header || "label"],
...(col.description ? {description: col.description} : {}), ...(col.description ? {description: col.description} : {}),
...buildTypeField(col, locale), ...buildTypeField(col, settings.locale),
})), })),
} }
}; };

@ -78,6 +78,7 @@ export class OIDCConfig {
redirect_uris: [ this._redirectUrl ], redirect_uris: [ this._redirectUrl ],
response_types: [ 'code' ], response_types: [ 'code' ],
}); });
log.info(`OIDCConfig: initialized with issuer ${issuerUrl}`);
} }
public addEndpoints(app: express.Application, sessions: Sessions): void { public addEndpoints(app: express.Application, sessions: Sessions): void {
@ -85,15 +86,18 @@ export class OIDCConfig {
} }
public async handleCallback(sessions: Sessions, req: express.Request, res: express.Response): Promise<void> { public async handleCallback(sessions: Sessions, req: express.Request, res: express.Response): Promise<void> {
const mreq = req as RequestWithLogin;
try { try {
const params = this._client.callbackParams(req); const params = this._client.callbackParams(req);
const { state } = params; const { state, targetUrl } = mreq.session?.oidc ?? {};
if (!state) { if (!state) {
throw new Error('Login or logout failed to complete'); throw new Error('Login or logout failed to complete');
} }
const codeVerifier = await this._retrieveCodeVerifierFromSession(req); const codeVerifier = await this._retrieveCodeVerifierFromSession(req);
// The callback function will compare the state present in the params and the one we retrieved from the session.
// If they don't match, it will throw an error.
const tokenSet = await this._client.callback( const tokenSet = await this._client.callback(
this._redirectUrl, this._redirectUrl,
params, params,
@ -102,23 +106,29 @@ export class OIDCConfig {
const userInfo = await this._client.userinfo(tokenSet); const userInfo = await this._client.userinfo(tokenSet);
const profile = this._makeUserProfileFromUserInfo(userInfo); const profile = this._makeUserProfileFromUserInfo(userInfo);
log.info(`OIDCConfig: got OIDC response for ${profile.email} (${profile.name}) redirecting to ${targetUrl}`);
const scopedSession = sessions.getOrCreateSessionFromRequest(req); const scopedSession = sessions.getOrCreateSessionFromRequest(req);
await scopedSession.operateOnScopedSession(req, async (user) => Object.assign(user, { await scopedSession.operateOnScopedSession(req, async (user) => Object.assign(user, {
profile, profile,
})); }));
res.redirect('/'); delete mreq.session.oidc;
res.redirect(targetUrl ?? '/');
} catch (err) { } catch (err) {
log.error(`OIDC callback failed: ${err.message}`); log.error(`OIDC callback failed: ${err.stack}`);
res.status(500).send(`OIDC callback failed: ${err.message}`); // Delete the session data even if the login failed.
// This way, we prevent several login attempts.
//
// Also session deletion must be done before sending the response.
delete mreq.session.oidc;
res.status(500).send(`OIDC callback failed.`);
} }
} }
public async getLoginRedirectUrl(req: express.Request): Promise<string> { public async getLoginRedirectUrl(req: express.Request, targetUrl: URL): Promise<string> {
const codeVerifier = await this._generateAndStoreCodeVerifier(req); const { codeVerifier, state } = await this._generateAndStoreConnectionInfo(req, targetUrl.href);
const codeChallenge = generators.codeChallenge(codeVerifier); const codeChallenge = generators.codeChallenge(codeVerifier);
const state = generators.state();
const authUrl = this._client.authorizationUrl({ const authUrl = this._client.authorizationUrl({
scope: process.env.GRIST_OIDC_IDP_SCOPES || 'openid email profile', scope: process.env.GRIST_OIDC_IDP_SCOPES || 'openid email profile',
@ -135,15 +145,18 @@ export class OIDCConfig {
}); });
} }
private async _generateAndStoreCodeVerifier(req: express.Request) { private async _generateAndStoreConnectionInfo(req: express.Request, targetUrl: string) {
const mreq = req as RequestWithLogin; const mreq = req as RequestWithLogin;
if (!mreq.session) { throw new Error('no session available'); } if (!mreq.session) { throw new Error('no session available'); }
const codeVerifier = generators.codeVerifier(); const codeVerifier = generators.codeVerifier();
const state = generators.state();
mreq.session.oidc = { mreq.session.oidc = {
codeVerifier, codeVerifier,
state,
targetUrl
}; };
return codeVerifier; return { codeVerifier, state };
} }
private async _retrieveCodeVerifierFromSession(req: express.Request) { private async _retrieveCodeVerifierFromSession(req: express.Request) {
@ -151,7 +164,6 @@ export class OIDCConfig {
if (!mreq.session) { throw new Error('no session available'); } if (!mreq.session) { throw new Error('no session available'); }
const codeVerifier = mreq.session.oidc?.codeVerifier; const codeVerifier = mreq.session.oidc?.codeVerifier;
if (!codeVerifier) { throw new Error('Login is stale'); } if (!codeVerifier) { throw new Error('Login is stale'); }
delete mreq.session.oidc?.codeVerifier;
return codeVerifier; return codeVerifier;
} }

@ -1,6 +1,6 @@
{ {
"name": "grist-core", "name": "grist-core",
"version": "1.1.7", "version": "1.1.8",
"license": "Apache-2.0", "license": "Apache-2.0",
"description": "Grist is the evolution of spreadsheets", "description": "Grist is the evolution of spreadsheets",
"homepage": "https://github.com/gristlabs/grist-core", "homepage": "https://github.com/gristlabs/grist-core",

@ -17,7 +17,9 @@
"Everyone": "Všichni", "Everyone": "Všichni",
"Allow everyone to copy the entire document, or view it in full in fiddle mode.\nUseful for examples and templates, but not for sensitive data.": "Umožni všem kopírovat celý dokument, nebo zobrazit plně v \"fiddle\" režimu.\nUžiitečné pro ukázky a šablony, ale ne pro citlivá data.", "Allow everyone to copy the entire document, or view it in full in fiddle mode.\nUseful for examples and templates, but not for sensitive data.": "Umožni všem kopírovat celý dokument, nebo zobrazit plně v \"fiddle\" režimu.\nUžiitečné pro ukázky a šablony, ale ne pro citlivá data.",
"Add Default Rule": "Přidej Základní Pravidlo", "Add Default Rule": "Přidej Základní Pravidlo",
"Checking...": "Kontroluji…" "Checking...": "Kontroluji…",
"Permissions": "Povolení",
"Permission to view Access Rules": "Povolení na zobrazení Přistupových Pravidel"
}, },
"ACUserManager": { "ACUserManager": {
"Invite new member": "Pozvi nového uživatele", "Invite new member": "Pozvi nového uživatele",

@ -1055,7 +1055,8 @@
"Can't find the right columns? Click 'Change Widget' to select the table with events data.": "Не можете найти нужные столбцы? Нажмите «Изменить виджет», чтобы выбрать таблицу с данными о событиях.", "Can't find the right columns? Click 'Change Widget' to select the table with events data.": "Не можете найти нужные столбцы? Нажмите «Изменить виджет», чтобы выбрать таблицу с данными о событиях.",
"A UUID is a randomly-generated string that is useful for unique identifiers and link keys.": "UUID - это случайно сгенерированная строка, которая полезна для уникальных идентификаторов и ключевых ссылок.", "A UUID is a randomly-generated string that is useful for unique identifiers and link keys.": "UUID - это случайно сгенерированная строка, которая полезна для уникальных идентификаторов и ключевых ссылок.",
"Lookups return data from related tables.": "Lookups возвращают данные из связанных таблиц.", "Lookups return data from related tables.": "Lookups возвращают данные из связанных таблиц.",
"Use reference columns to relate data in different tables.": "Используйте ссылочные столбцы для сопоставления данных в разных таблицах." "Use reference columns to relate data in different tables.": "Используйте ссылочные столбцы для сопоставления данных в разных таблицах.",
"You can choose from widgets available to you in the dropdown, or embed your own by providing its full URL.": "Вы можете выбрать виджеты, доступные вам в раскрывающемся списке, или встроить свой собственный, указав его полный URL-адрес."
}, },
"DescriptionConfig": { "DescriptionConfig": {
"DESCRIPTION": "ОПИСАНИЕ" "DESCRIPTION": "ОПИСАНИЕ"

@ -19,19 +19,19 @@
"Type a message...": "Vnesite sporočilo…", "Type a message...": "Vnesite sporočilo…",
"User Attributes": "Atributi uporabnika", "User Attributes": "Atributi uporabnika",
"View As": "Poglej kot", "View As": "Poglej kot",
"When adding table rules, automatically add a rule to grant OWNER full access.": "Pri dodajanju pravil za tabele samodejno dodajte pravilo, ki lastniku omogoča popoln dostop.", "When adding table rules, automatically add a rule to grant OWNER full access.": "Pri dodajanju pravil za tabele samodejno dodaj pravilo, ki lastniku omogoča popoln dostop.",
"Permission to edit document structure": "Dovoljenje za urejanje strukture dokumenta", "Permission to edit document structure": "Dovoljenje za urejanje strukture dokumenta",
"Everyone": "Vsi", "Everyone": "Vsi",
"Everyone Else": "Vsi ostali", "Everyone Else": "Vsi ostali",
"Checking...": "Preverjanje…", "Checking...": "Preverjanje…",
"Condition": "Stanje", "Condition": "Stanje",
"Enter Condition": "Vnesite pogoj", "Enter Condition": "Vnesite pogoj",
"Add Column Rule": "Dodajanje pravila za stolpce", "Add Column Rule": "Dodaj pravila za stolpec",
"Add Default Rule": "Dodaj privzeto pravilo", "Add Default Rule": "Dodaj privzeto pravilo",
"Add Table Rules": "Dodajanje pravil tabele", "Add Table Rules": "Dodaj pravila za tabelo",
"Add User Attributes": "Dodajanje atributov uporabnika", "Add User Attributes": "Dodaj atribute za uporabnika",
"Allow everyone to copy the entire document, or view it in full in fiddle mode.\nUseful for examples and templates, but not for sensitive data.": "Vsakomur omogočite kopiranje celotnega dokumenta ali pa si ga oglejte v celoti v načinu fiddle.\nUporabno za primere in predloge, ne pa za občutljive podatke.", "Allow everyone to copy the entire document, or view it in full in fiddle mode.\nUseful for examples and templates, but not for sensitive data.": "Vsakomur omogočite kopiranje celotnega dokumenta ali pa si ga oglejte v celoti v načinu fiddle.\nUporabno za primere in predloge, ne pa za občutljive podatke.",
"Allow everyone to view Access Rules.": "Vsakomur omogočite ogled pravil za dostop.", "Allow everyone to view Access Rules.": "Omogoči vsakomur ogled pravil za dostop.",
"Attribute name": "Ime atributa", "Attribute name": "Ime atributa",
"Attribute to Look Up": "Atribut za iskanje", "Attribute to Look Up": "Atribut za iskanje",
"Lookup Table": "Preglednica za iskanje", "Lookup Table": "Preglednica za iskanje",
@ -40,8 +40,8 @@
}, },
"ACUserManager": { "ACUserManager": {
"We'll email an invite to {{email}}": "Vabilo bomo poslali po e-pošti {{email}}", "We'll email an invite to {{email}}": "Vabilo bomo poslali po e-pošti {{email}}",
"Enter email address": "Vnesite e-poštni naslov", "Enter email address": "Vnesi e-poštni naslov",
"Invite new member": "Povabite novega člana" "Invite new member": "Povabi novega člana"
}, },
"AccountPage": { "AccountPage": {
"API": "API", "API": "API",
@ -110,7 +110,7 @@
"Translators: please translate this only when your language is ready to be offered to users": "Prevajalci: prosimo, prevedite to šele, ko bo vaš jezik pripravljen, da se ponudi uporabnikom" "Translators: please translate this only when your language is ready to be offered to users": "Prevajalci: prosimo, prevedite to šele, ko bo vaš jezik pripravljen, da se ponudi uporabnikom"
}, },
"CellContextMenu": { "CellContextMenu": {
"Delete {{count}} columns_one": "Brisanje stolpca", "Delete {{count}} columns_one": "Briši stolpec",
"Delete {{count}} columns_other": "Brisanje stolpcev {{count}}", "Delete {{count}} columns_other": "Brisanje stolpcev {{count}}",
"Delete {{count}} rows_one": "Brisanje vrstice", "Delete {{count}} rows_one": "Brisanje vrstice",
"Delete {{count}} rows_other": "Brisanje vrstic {{count}}", "Delete {{count}} rows_other": "Brisanje vrstic {{count}}",
@ -118,12 +118,12 @@
"Copy anchor link": "Kopiranje sidrne povezave", "Copy anchor link": "Kopiranje sidrne povezave",
"Duplicate rows_one": "Podvoji vrstico", "Duplicate rows_one": "Podvoji vrstico",
"Duplicate rows_other": "Podvoji vrstice", "Duplicate rows_other": "Podvoji vrstice",
"Insert column to the right": "Vstavi stolpec na desno", "Insert column to the right": "Vstavi stolpec na desno stran",
"Insert column to the left": "Vstavi stolpec na levo", "Insert column to the left": "Vstavi stolpec na levo stran",
"Insert row": "Vstavljanje vrstice", "Insert row": "Vstavljanje vrstice",
"Insert row above": "Vstavite vrstico zgoraj", "Insert row above": "Vstavite vrstico zgoraj",
"Insert row below": "Vstavi vrstico spodaj", "Insert row below": "Vstavi vrstico spodaj",
"Reset {{count}} columns_one": "Ponastavitev stolpca", "Reset {{count}} columns_one": "Ponastavi stolpec",
"Reset {{count}} columns_other": "Ponastavit {{count}} stolpcev", "Reset {{count}} columns_other": "Ponastavit {{count}} stolpcev",
"Reset {{count}} entire columns_one": "Ponastavi celote stolpec", "Reset {{count}} entire columns_one": "Ponastavi celote stolpec",
"Reset {{count}} entire columns_other": "Ponastavi {{count}} celotnih stolpcev", "Reset {{count}} entire columns_other": "Ponastavi {{count}} celotnih stolpcev",
@ -177,7 +177,7 @@
}, },
"GridViewMenus": { "GridViewMenus": {
"Rename column": "Preimenuj stolpec", "Rename column": "Preimenuj stolpec",
"Delete {{count}} columns_one": "Brisanje stolpca", "Delete {{count}} columns_one": "Briši stolpec",
"Delete {{count}} columns_other": "Brisanje stolpcev {{count}}", "Delete {{count}} columns_other": "Brisanje stolpcev {{count}}",
"Unfreeze {{count}} columns_one": "Odmrzni ta stolpec", "Unfreeze {{count}} columns_one": "Odmrzni ta stolpec",
"Sorted (#{{count}})_one": "Razvrščeno (#{{count}})", "Sorted (#{{count}})_one": "Razvrščeno (#{{count}})",
@ -189,7 +189,7 @@
"Filter Data": "Filtriranje podatkov", "Filter Data": "Filtriranje podatkov",
"Hide {{count}} columns_other": "Skrij {{count}} stolpcev", "Hide {{count}} columns_other": "Skrij {{count}} stolpcev",
"Add Column": "Dodaj stolpec", "Add Column": "Dodaj stolpec",
"Reset {{count}} columns_one": "Ponastavitev stolpca", "Reset {{count}} columns_one": "Ponastavi stolpec",
"Freeze {{count}} columns_one": "Zamrzni stolpec", "Freeze {{count}} columns_one": "Zamrzni stolpec",
"More sort options ...": "Več možnosti razvrščanja…", "More sort options ...": "Več možnosti razvrščanja…",
"Freeze {{count}} more columns_one": "Zamrznite še en stolpec", "Freeze {{count}} more columns_one": "Zamrznite še en stolpec",
@ -200,10 +200,10 @@
"Freeze {{count}} more columns_other": "Zamrznite še {{count}} stolpcev", "Freeze {{count}} more columns_other": "Zamrznite še {{count}} stolpcev",
"Hide {{count}} columns_one": "Skrij stolpec", "Hide {{count}} columns_one": "Skrij stolpec",
"Sorted (#{{count}})_other": "Razvrščeno (#{{count}})", "Sorted (#{{count}})_other": "Razvrščeno (#{{count}})",
"Insert column to the {{to}}": "Vstavi stolpec v {{to}}", "Insert column to the {{to}}": "Vstavi stolpec na {{to}}",
"Reset {{count}} entire columns_other": "Ponastavi {{count}} stolpcev", "Reset {{count}} entire columns_other": "Ponastavi {{count}} stolpcev",
"Unfreeze {{count}} columns_other": "Odmrznite {{count}} stolpcev", "Unfreeze {{count}} columns_other": "Odmrznite {{count}} stolpcev",
"Insert column to the right": "Vstavi stolpec na desno", "Insert column to the right": "Vstavi stolpec na desno stran",
"Reset {{count}} entire columns_one": "Ponastavi celoten stolpec", "Reset {{count}} entire columns_one": "Ponastavi celoten stolpec",
"Insert column to the left": "Vstavi stolpec na levo", "Insert column to the left": "Vstavi stolpec na levo",
"Shortcuts": "Bližnjice", "Shortcuts": "Bližnjice",
@ -270,7 +270,7 @@
"Access Rules": "Pravila dostopa", "Access Rules": "Pravila dostopa",
"Code View": "Pogled kode", "Code View": "Pogled kode",
"Raw Data": "Neobdelani podatki", "Raw Data": "Neobdelani podatki",
"Document History": "Zgodovina dokumentov", "Document History": "Zgodovina Dokumentov",
"Validate Data": "Potrdi podatke", "Validate Data": "Potrdi podatke",
"How-to Tutorial": "Vadnica kako narediti", "How-to Tutorial": "Vadnica kako narediti",
"Tour of this Document": "Ogled tega dokumenta", "Tour of this Document": "Ogled tega dokumenta",
@ -278,7 +278,7 @@
}, },
"pages": { "pages": {
"Rename": "Preimenuj", "Rename": "Preimenuj",
"Duplicate Page": "Podvojena stran", "Duplicate Page": "Podvoji stran",
"You do not have edit access to this document": "Nimate dovoljenja za urejanje tega dokumenta", "You do not have edit access to this document": "Nimate dovoljenja za urejanje tega dokumenta",
"Remove": "Odstrani" "Remove": "Odstrani"
}, },
@ -293,7 +293,7 @@
"Add New": "Dodaj" "Add New": "Dodaj"
}, },
"DataTables": { "DataTables": {
"Delete {{formattedTableName}} data, and remove it from all pages?": "Izbrišite podatke {{formattedTableName}} in jih odstranite z vseh strani?", "Delete {{formattedTableName}} data, and remove it from all pages?": "Izbriši podatke {{formattedTableName}} in jih odstrani z vseh strani?",
"Click to copy": "Kliknite za kopiranje", "Click to copy": "Kliknite za kopiranje",
"Duplicate Table": "Podvojena tabela", "Duplicate Table": "Podvojena tabela",
"Table ID copied to clipboard": "ID tabele kopiran v odložišče", "Table ID copied to clipboard": "ID tabele kopiran v odložišče",
@ -305,8 +305,8 @@
"Delete widget": "Izbriši gradnik", "Delete widget": "Izbriši gradnik",
"Advanced Sort & Filter": "Napredno razvrščanje in filtriranje", "Advanced Sort & Filter": "Napredno razvrščanje in filtriranje",
"Data selection": "Izbira podatkov", "Data selection": "Izbira podatkov",
"Download as XLSX": "Prenesite kot XLSX", "Download as XLSX": "Prenesi kot XLSX",
"Download as CSV": "Prenesite kot CSV", "Download as CSV": "Prenesi kot CSV",
"Widget options": "Možnosti gradnika", "Widget options": "Možnosti gradnika",
"Print widget": "Gradnik za tiskanje", "Print widget": "Gradnik za tiskanje",
"Open configuration": "Odpri konfiguracijo", "Open configuration": "Odpri konfiguracijo",
@ -423,13 +423,13 @@
"Formula Columns_other": "Stolpci formule", "Formula Columns_other": "Stolpci formule",
"Formula Columns_one": "Stolpec formule", "Formula Columns_one": "Stolpec formule",
"Enter formula": "Vnesite formulo", "Enter formula": "Vnesite formulo",
"Clear and make into formula": "Brišite in pretvorite v formulo", "Clear and make into formula": "Briši in pretvori v formulo",
"Mixed Behavior": "Mešano vedenje", "Mixed Behavior": "Mešano vedenje",
"Convert to trigger formula": "Pretvori v sprožitveno formulo", "Convert to trigger formula": "Pretvori v sprožitveno formulo",
"Data Columns_one": "Stolpec podatkov", "Data Columns_one": "Stolpec podatkov",
"TRIGGER FORMULA": "SPROŽILNA FORMULA", "TRIGGER FORMULA": "SPROŽILNA FORMULA",
"Set trigger formula": "Nastavite sprožitveno formulo", "Set trigger formula": "Nastavi sprožitveno formulo",
"Make into data column": "Spremenite v podatkovni stolpec", "Make into data column": "Spremeni v podatkovni stolpec",
"COLUMN BEHAVIOR": "OBNAŠANJE STOLPCA" "COLUMN BEHAVIOR": "OBNAŠANJE STOLPCA"
}, },
"DuplicateTable": { "DuplicateTable": {
@ -440,7 +440,7 @@
}, },
"DocPageModel": { "DocPageModel": {
"Sorry, access to this document has been denied. [{{error}}]": "Žal je bil dostop do tega dokumenta zavrnjen. [{{error}}]", "Sorry, access to this document has been denied. [{{error}}]": "Žal je bil dostop do tega dokumenta zavrnjen. [{{error}}]",
"Add Empty Table": "Dodajte prazno tabelo", "Add Empty Table": "Dodaj prazno tabelo",
"You do not have edit access to this document": "Nimate dostopa za urejanje tega dokumenta", "You do not have edit access to this document": "Nimate dostopa za urejanje tega dokumenta",
"Add Widget to Page": "Dodaj widget na stran", "Add Widget to Page": "Dodaj widget na stran",
"Add Page": "Dodaj stran", "Add Page": "Dodaj stran",
@ -552,7 +552,7 @@
"SOURCE DATA": "IZVORNI PODATKI", "SOURCE DATA": "IZVORNI PODATKI",
"CHART TYPE": "VRSTA DIAGRAMA", "CHART TYPE": "VRSTA DIAGRAMA",
"Detach": "Odklopi", "Detach": "Odklopi",
"Change Widget": "Spremite widget", "Change Widget": "Spremeni Pripomoček",
"Columns_one": "Stolpec", "Columns_one": "Stolpec",
"Series_other": "Serija", "Series_other": "Serija",
"Fields_other": "Polja", "Fields_other": "Polja",
@ -661,8 +661,8 @@
"Apply conditional formatting to cells in this column when formula conditions are met.": "Uporabi pogojno oblikovanje za celice v tem stolpcu, ko so izpolnjeni pogoji formule.", "Apply conditional formatting to cells in this column when formula conditions are met.": "Uporabi pogojno oblikovanje za celice v tem stolpcu, ko so izpolnjeni pogoji formule.",
"To make an anchor link that takes the user to a specific cell, click on a row and press {{shortcut}}.": "Če želite narediti sidrno povezavo, ki uporabnika pripelje do določene celice, kliknite vrstico in pritisnite {{shortcut}}.", "To make an anchor link that takes the user to a specific cell, click on a row and press {{shortcut}}.": "Če želite narediti sidrno povezavo, ki uporabnika pripelje do določene celice, kliknite vrstico in pritisnite {{shortcut}}.",
"Unpin to hide the the button while keeping the filter.": "Odpnite, da skrijete gumb in obdržite filter.", "Unpin to hide the the button while keeping the filter.": "Odpnite, da skrijete gumb in obdržite filter.",
"Apply conditional formatting to rows based on formulas.": "Uporabite pogojno oblikovanje za vrstice na podlagi formul.", "Apply conditional formatting to rows based on formulas.": "Uporabi pogojno oblikovanje za vrstice na podlagi formul.",
"Click on “Open row styles” to apply conditional formatting to rows.": "Kliknite »Odpri sloge vrstic«, da za vrstice uporabite pogojno oblikovanje.", "Click on “Open row styles” to apply conditional formatting to rows.": "Klikni »Odpri sloge vrstic«, da za vrstice uporabiš pogojno oblikovanje.",
"Pinned filters are displayed as buttons above the widget.": "Pripeti filtri so prikazani kot gumbi nad pripomočkom.", "Pinned filters are displayed as buttons above the widget.": "Pripeti filtri so prikazani kot gumbi nad pripomočkom.",
"Link your new widget to an existing widget on this page.": "Povežite svoj novi pripomoček z obstoječim pripomočkom na tej strani.", "Link your new widget to an existing widget on this page.": "Povežite svoj novi pripomoček z obstoječim pripomočkom na tej strani.",
"Use the \\u{1D6BA} icon to create summary (or pivot) tables, for totals or subtotals.": "Uporabite ikono \\u{1D6BA} za ustvarjanje povzetkov (ali vrtilnih) tabel za vsote ali delne vsote.", "Use the \\u{1D6BA} icon to create summary (or pivot) tables, for totals or subtotals.": "Uporabite ikono \\u{1D6BA} za ustvarjanje povzetkov (ali vrtilnih) tabel za vsote ali delne vsote.",
@ -688,7 +688,7 @@
"User has view access to {{resource}} resulting from manually-set access to resources inside. If removed here, this user will lose access to resources inside.": "Uporabnik ima vpogled v {{resource}}, ki je posledica ročno nastavljenega dostopa do virov v njem. Če ga tukaj odstranite, bo ta uporabnik izgubil dostop do notranjih virov.", "User has view access to {{resource}} resulting from manually-set access to resources inside. If removed here, this user will lose access to resources inside.": "Uporabnik ima vpogled v {{resource}}, ki je posledica ročno nastavljenega dostopa do virov v njem. Če ga tukaj odstranite, bo ta uporabnik izgubil dostop do notranjih virov.",
"User may not modify their own access.": "Uporabnik ne more spreminjati lastnega dostopa.", "User may not modify their own access.": "Uporabnik ne more spreminjati lastnega dostopa.",
"member": "član", "member": "član",
"Add {{member}} to your team": "Dodajte {{member}} v svojo ekipo", "Add {{member}} to your team": "Dodaj {{member}} v svojo ekipo",
"Collaborator": "Sodelavec", "Collaborator": "Sodelavec",
"Link copied to clipboard": "Povezava kopirana v odložišče", "Link copied to clipboard": "Povezava kopirana v odložišče",
"team site": "spletno mesto ekipe", "team site": "spletno mesto ekipe",
@ -758,7 +758,7 @@
}, },
"ColumnTitle": { "ColumnTitle": {
"Column ID copied to clipboard": "ID stolpca kopiran v odložišče", "Column ID copied to clipboard": "ID stolpca kopiran v odložišče",
"Add description": "Dodajte opis", "Add description": "Dodaj opis",
"Column description": "Opis stolpca", "Column description": "Opis stolpca",
"Provide a column label": "Navedite oznako stolpca", "Provide a column label": "Navedite oznako stolpca",
"Close": "Zapri", "Close": "Zapri",
@ -870,7 +870,7 @@
}, },
"RecordLayoutEditor": { "RecordLayoutEditor": {
"Show field {{- label}}": "Prikaži polje {{- label}}", "Show field {{- label}}": "Prikaži polje {{- label}}",
"Add Field": "Dodajte polje", "Add Field": "Dodaj polje",
"Save Layout": "Shrani postavitev", "Save Layout": "Shrani postavitev",
"Cancel": "Prekliči", "Cancel": "Prekliči",
"Create New Field": "Ustvari novo polje" "Create New Field": "Ustvari novo polje"
@ -926,7 +926,7 @@
"Switch appearance automatically to match system": "Samodejno preklopite videz, da se ujema s sistemom" "Switch appearance automatically to match system": "Samodejno preklopite videz, da se ujema s sistemom"
}, },
"SiteSwitcher": { "SiteSwitcher": {
"Switch Sites": "Preklopite mesta", "Switch Sites": "Preklopi mesta",
"Create new team site": "Ustvarite novo spletno mesto ekipe" "Create new team site": "Ustvarite novo spletno mesto ekipe"
}, },
"WebhookPage": { "WebhookPage": {
@ -1016,7 +1016,7 @@
"Reference List": "Referenčni seznam", "Reference List": "Referenčni seznam",
"* Workspaces are available on team plans. ": "* Delovni prostori so na voljo v skupinskih načrtih. ", "* Workspaces are available on team plans. ": "* Delovni prostori so na voljo v skupinskih načrtih. ",
"Upgrade now": "Nadgradi zdaj", "Upgrade now": "Nadgradi zdaj",
"Toggle": "Preklopi", "Toggle": "Preklop",
"Choice List": "Izbirni seznam", "Choice List": "Izbirni seznam",
"Any": "Katerikoli", "Any": "Katerikoli",
"DateTime": "Datum čas", "DateTime": "Datum čas",
@ -1170,8 +1170,8 @@
"ConditionalStyle": { "ConditionalStyle": {
"Rule must return True or False": "Pravilo mora vrniti True ali False", "Rule must return True or False": "Pravilo mora vrniti True ali False",
"Row Style": "Slog vrstice", "Row Style": "Slog vrstice",
"Add another rule": "Dodajte še eno pravilo", "Add another rule": "Dodaj dodatno pravilo",
"Add conditional style": "Dodajte pogojni slog", "Add conditional style": "Dodaj pogojni slog",
"Error in style rule": "Napaka v slogovnem pravilu" "Error in style rule": "Napaka v slogovnem pravilu"
}, },
"EditorTooltip": { "EditorTooltip": {
@ -1219,7 +1219,7 @@
}, },
"duplicatePage": { "duplicatePage": {
"Note that this does not copy data, but creates another view of the same data.": "Upoštevajte, da s tem ne kopirate podatkov, ampak ustvarite drug pogled istih podatkov.", "Note that this does not copy data, but creates another view of the same data.": "Upoštevajte, da s tem ne kopirate podatkov, ampak ustvarite drug pogled istih podatkov.",
"Duplicate page {{pageName}}": "Podvojena stran {{pageName}}" "Duplicate page {{pageName}}": "Podvoji stran {{pageName}}"
}, },
"CurrencyPicker": { "CurrencyPicker": {
"Invalid currency": "Neveljavna valuta" "Invalid currency": "Neveljavna valuta"

@ -151,7 +151,27 @@
"Unfreeze {{count}} columns_one": "Відкріпити цей стовпець", "Unfreeze {{count}} columns_one": "Відкріпити цей стовпець",
"Unfreeze {{count}} columns_other": "Відкріпити {{count}} стовпців", "Unfreeze {{count}} columns_other": "Відкріпити {{count}} стовпців",
"Insert column to the right": "Вставити стовпець праворуч", "Insert column to the right": "Вставити стовпець праворуч",
"Insert column to the left": "Вставити стовпець ліворуч" "Insert column to the left": "Вставити стовпець ліворуч",
"Detect Duplicates in...": "Виявлення дублікатів у...",
"UUID": "UUID",
"Shortcuts": "Ярлики",
"Show hidden columns": "Показати приховані стовпці",
"Created At": "Дата створення",
"Authorship": "Авторство",
"Last Updated By": "Автор останньої зміни",
"Hidden Columns": "Приховані стовпці",
"Lookups": "Lookups",
"No reference columns.": "Немає довідкових стовпців.",
"Apply on record changes": "Застосовувати при зміні записів",
"Duplicate in {{- label}}": "Дублікат у {{- label}}\"",
"Created By": "Автор створення",
"Last Updated At": "Дата останньої зміни",
"Apply to new records": "Застосувати до нових записів",
"Search columns": "Шукати стовпці",
"Timestamp": "Часова мітка",
"no reference column": "немає довідкових стовпців",
"Adding UUID column": "Adding UUID column",
"Adding duplicates column": "Adding duplicates column"
}, },
"ThemeConfig": { "ThemeConfig": {
"Switch appearance automatically to match system": "Автоматично змінювати оформлення відповідно до системи", "Switch appearance automatically to match system": "Автоматично змінювати оформлення відповідно до системи",
@ -820,7 +840,8 @@
"Reference": "Посилання", "Reference": "Посилання",
"DateTime": "Дата і час", "DateTime": "Дата і час",
"Reference List": "Список посилань", "Reference List": "Список посилань",
"Attachment": "Вкладення" "Attachment": "Вкладення",
"Search columns": "Шукати стовпці"
}, },
"modals": { "modals": {
"Cancel": "Відмінити", "Cancel": "Відмінити",
@ -913,14 +934,15 @@
}, },
"FieldBuilder": { "FieldBuilder": {
"CELL FORMAT": "ФОРМАТ КЛІТИНКИ", "CELL FORMAT": "ФОРМАТ КЛІТИНКИ",
"Changing multiple column types": "Зміна кількох типів стовпців", "Changing multiple column types": "Changing multiple column types",
"DATA FROM TABLE": "ДАНІ З ТАБЛИЦІ", "DATA FROM TABLE": "ДАНІ З ТАБЛИЦІ",
"Mixed format": "Змішаний формат", "Mixed format": "Змішаний формат",
"Mixed types": "Змішані типи", "Mixed types": "Змішані типи",
"Use separate field settings for {{colId}}": "Використати окремі налаштування поля для {{colId}}", "Use separate field settings for {{colId}}": "Використати окремі налаштування поля для {{colId}}",
"Apply Formula to Data": "Застосувати формулу до даних", "Apply Formula to Data": "Застосувати формулу до даних",
"Revert field settings for {{colId}} to common": "Змінити налаштування поля для {{colId}} на загальні", "Revert field settings for {{colId}} to common": "Змінити налаштування поля для {{colId}} на загальні",
"Save field settings for {{colId}} as common": "Зберегти налаштування поля для {{colId}} як загальні" "Save field settings for {{colId}} as common": "Зберегти налаштування поля для {{colId}} як загальні",
"Changing column type": "Changing column type"
}, },
"FieldEditor": { "FieldEditor": {
"Unable to finish saving edited cell": "Неможливо зберегти відредаговану клітинку", "Unable to finish saving edited cell": "Неможливо зберегти відредаговану клітинку",
@ -931,7 +953,11 @@
"Error in the cell": "Помилка в клітинці", "Error in the cell": "Помилка в клітинці",
"Errors in all {{numErrors}} cells": "Помилки в усіх клітинках {{numErrors}}", "Errors in all {{numErrors}} cells": "Помилки в усіх клітинках {{numErrors}}",
"editingFormula is required": "editingFormula обов'язкове", "editingFormula is required": "editingFormula обов'язкове",
"Errors in {{numErrors}} of {{numCells}} cells": "Помилки в {{numErrors}} в {{numCells}} клітинках" "Errors in {{numErrors}} of {{numCells}} cells": "Помилки в {{numErrors}} в {{numCells}} клітинках",
"Enter formula or {{button}}.": "Введіть формулу або {{button}}.",
"Enter formula.": "Введіть формулу.",
"Expand Editor": "Розгорнути редактор",
"use AI Assistant": "Використати AI Помічника"
}, },
"HyperLinkEditor": { "HyperLinkEditor": {
"[link label] url": "[мітка посилання] URL" "[link label] url": "[мітка посилання] URL"
@ -1017,9 +1043,14 @@
"Useful for storing the timestamp or author of a new record, data cleaning, and more.": "Корисно для збереження позначки часу або автора нового запису, очищення даних тощо.", "Useful for storing the timestamp or author of a new record, data cleaning, and more.": "Корисно для збереження позначки часу або автора нового запису, очищення даних тощо.",
"You can filter by more than one column.": "Ви можете фільтрувати за кількома стовпцями.", "You can filter by more than one column.": "Ви можете фільтрувати за кількома стовпцями.",
"Try out changes in a copy, then decide whether to replace the original with your edits.": "Спробуйте змінити копію, а потім вирішіть, чи слід замінювати оригінал своїми правками.", "Try out changes in a copy, then decide whether to replace the original with your edits.": "Спробуйте змінити копію, а потім вирішіть, чи слід замінювати оригінал своїми правками.",
"Access rules give you the power to create nuanced rules to determine who can see or edit which parts of your document.": "Правила доступу дають вам можливість створювати детальні правила, щоб визначити, хто може бачити або редагувати (та які) частини вашого документа." "Access rules give you the power to create nuanced rules to determine who can see or edit which parts of your document.": "Правила доступу дають вам можливість створювати детальні правила, щоб визначити, хто може бачити або редагувати (та які) частини вашого документа.",
"To make an anchor link that takes the user to a specific cell, click on a row and press {{shortcut}}.": "Щоб створити якірне посилання, яке перенаправляє користувача до певної комірки, клацніть на рядку та натисніть {{shortcut}}.",
"Anchor Links": "Якірні посилання"
}, },
"DescriptionConfig": { "DescriptionConfig": {
"DESCRIPTION": "ОПИС" "DESCRIPTION": "ОПИС"
},
"FieldContextMenu": {
"Copy anchor link": "Скопіювати якірне посилання"
} }
} }

@ -2700,6 +2700,43 @@ function testDocApi() {
assert.equal(resp2.data, 'A,B\nSanta,1\nBob,11\nAlice,2\nFelix,22\n'); assert.equal(resp2.data, 'A,B\nSanta,1\nBob,11\nAlice,2\nFelix,22\n');
}); });
it("GET /docs/{did}/download/table-schema serves table-schema-encoded document with header=colId", async function () {
const { docUrl, tableUrl } = await generateDocAndUrl('tableSchemaWithColIdAsHeader');
const columns = [
{
id: 'Some_ID',
fields: {
label: 'Some Label',
type: 'Text',
}
},
];
const setupColResp = await axios.put(`${tableUrl}/columns`, { columns }, {...chimpy, params: { replaceall: true }});
assert.equal(setupColResp.status, 200);
const resp = await axios.get(`${docUrl}/download/table-schema?tableId=Table1&header=colId`, chimpy);
assert.equal(resp.status, 200);
const expected = {
format: "csv",
mediatype: "text/csv",
encoding: "utf-8",
dialect: {
delimiter: ",",
doubleQuote: true,
},
name: 'table1',
title: 'Table1',
schema: {
fields: [{
name: 'Some_ID',
type: 'string',
format: 'default',
}]
}
};
assert.deepInclude(resp.data, expected);
});
it("GET /docs/{did}/download/table-schema respects permissions", async function () { it("GET /docs/{did}/download/table-schema respects permissions", async function () {
// kiwi has no access to TestDoc // kiwi has no access to TestDoc
const resp = await axios.get(`${serverUrl}/api/docs/${docIds.TestDoc}/download/table-schema?tableId=Table1`, kiwi); const resp = await axios.get(`${serverUrl}/api/docs/${docIds.TestDoc}/download/table-schema?tableId=Table1`, kiwi);

Loading…
Cancel
Save