(core) updates from grist-core

pull/465/head
Paul Fitzpatrick 1 year ago
commit 572995f19a

@ -218,6 +218,20 @@ export class App extends DisposableWithEvents {
this._mostRecentDocPageModel = pageModel;
}
/**
* This method is not called anywhere, it is here just to introduce
* a special translation key. The purpose of this key is to let translators
* control whether a translation is ready to be offered to the user.
*
* If the key has not been translated for a language, and the language
* is not the default language, then the language should not be offered
* or used (unless some flag is set). TODO: implement this once key
* is available in weblate and good translations have been updated.
*/
public checkSpecialTranslationKey() {
return t('Translators: please translate this only when your language is ready to be offered to users');
}
// Get the user profile for testing purposes
public async testGetProfile(): Promise<any> {
const resp = await fetchFromHome('/api/profile/user', {credentials: 'include'});

@ -79,7 +79,7 @@ export function makeViewLayoutMenu(viewSection: ViewSectionRec, isReadonly: bool
menuItemLink({ href: gristDoc.getCsvLink(), target: '_blank', download: ''},
t("Download as CSV"), testId('download-section')),
menuItemLink({ href: gristDoc.getXlsxActiveViewLink(), target: '_blank', download: ''},
t("Download as XLSX"), testId('download-section')),
t("Download as XLSX"), testId('download-section')),
dom.maybe((use) => ['detail', 'single'].includes(use(viewSection.parentKey)), () =>
menuItemCmd(allCommands.editLayout, t("Edit Card Layout"),
dom.cls('disabled', isReadonly))),

@ -370,7 +370,7 @@ export interface UserAPI {
}
/**
* Parameters for the download CSV and XLSX endpoint (/download/csv & /download/csv).
* Parameters for the download CSV and XLSX endpoint (/download/table-schema & /download/csv & /download/csv).
*/
export interface DownloadDocParams {
tableId: string;
@ -418,6 +418,7 @@ export interface DocAPI {
getDownloadUrl(template?: boolean): string;
getDownloadXlsxUrl(params?: DownloadDocParams): string;
getDownloadCsvUrl(params: DownloadDocParams): string;
getDownloadTableSchemaUrl(params: DownloadDocParams): string;
/**
* Exports current document to the Google Drive as a spreadsheet file. To invoke this method, first
* acquire "code" via Google Auth Endpoint (see ShareMenu.ts for an example).
@ -920,6 +921,11 @@ export class DocAPIImpl extends BaseAPI implements DocAPI {
return this._url + '/download/csv?' + encodeQueryParams({...params});
}
public getDownloadTableSchemaUrl(params: DownloadDocParams) {
// We spread `params` to work around TypeScript being overly cautious.
return this._url + '/download/table-schema?' + encodeQueryParams({...params});
}
public async sendToDrive(code: string, title: string): Promise<{url: string}> {
const url = new URL(`${this._url}/send-to-drive`);
url.searchParams.append('title', title);

@ -0,0 +1,11 @@
import {NumberFormatOptions} from 'app/common/NumberFormat';
export interface WidgetOptions extends NumberFormatOptions {
textColor?: 'string';
fillColor?: 'string';
alignment?: 'left' | 'center' | 'right';
dateFormat?: string;
timeFormat?: string;
widget?: 'HyperLink';
choices?: Array<string>;
}

@ -36,6 +36,7 @@ import {IDocWorkerMap} from "app/server/lib/DocWorkerMap";
import {DownloadOptions, parseExportParameters} from "app/server/lib/Export";
import {downloadCSV} from "app/server/lib/ExportCSV";
import {downloadXLSX} from "app/server/lib/ExportXLSX";
import {collectTableSchemaInFrictionlessFormat} from "app/server/lib/ExportTableSchema";
import {expressWrap} from 'app/server/lib/expressWrap';
import {filterDocumentInPlace} from "app/server/lib/filterUtils";
import {googleAuthTokenMiddleware} from "app/server/lib/GoogleAuth";
@ -871,6 +872,26 @@ export class DocWorkerApi {
res.json(result);
}));
this._app.get('/api/docs/:docId/download/table-schema', canView, withDoc(async (activeDoc, req, res) => {
const doc = await this._dbManager.getDoc(req);
const options = this._getDownloadOptions(req, doc.name);
const tableSchema = await collectTableSchemaInFrictionlessFormat(activeDoc, req, options);
const apiPath = await this._grist.getResourceUrl(doc, 'api');
const query = new URLSearchParams(req.query as {[key: string]: string});
const tableSchemaPath = `${apiPath}/download/csv?${query.toString()}`;
res.send({
format: "csv",
mediatype: "text/csv",
encoding: "utf-8",
path: tableSchemaPath,
dialect: {
delimiter: ",",
doubleQuote: true,
},
...tableSchema,
});
}));
this._app.get('/api/docs/:docId/download/csv', canView, withDoc(async (activeDoc, req, res) => {
// Query DB for doc metadata to get the doc title.
const {name: docTitle} = await this._dbManager.getDoc(req);

@ -204,7 +204,8 @@ export async function exportTable(
label: tc.label,
type: displayCol.type,
widgetOptions,
parentPos: tc.parentPos
parentPos: tc.parentPos,
description: displayCol.description,
};
}).filter(tc => tc !== emptyCol);
@ -279,6 +280,7 @@ export async function exportSection(
label: col.label,
type: col.type,
parentPos: col.parentPos,
description: col.description,
widgetOptions: Object.assign(colWidgetOptions, fieldWidgetOptions),
};
};

@ -0,0 +1,151 @@
import * as express from 'express';
import {ApiError} from 'app/common/ApiError';
import {WidgetOptions} from 'app/common/WidgetOptions';
import {ActiveDoc} from 'app/server/lib/ActiveDoc';
import {DownloadOptions, exportTable} from 'app/server/lib/Export';
interface ExportColumn {
id: number;
colId: string;
label: string;
type: string;
widgetOptions: WidgetOptions;
description?: string;
parentPos: number;
}
interface FrictionlessFormat {
name: string;
title: string;
schema: {
fields: {
name: string;
type: string;
description?: string;
format?: string;
bareNumber?: boolean;
groupChar?: string;
decimalChar?: string;
gristFormat?: string;
constraint?: {};
trueValue?: string[];
falseValue?: string[];
}[]
}
}
/**
* Return a table schema for frictionless interoperability
*
* See https://specs.frictionlessdata.io/table-schema/#page-frontmatter-title for spec
* @param {Object} activeDoc - the activeDoc that the table being converted belongs to.
* @param {Object} options - options to get the table ID
* @return {Promise<FrictionlessFormat>} Promise for the resulting schema.
*/
export async function collectTableSchemaInFrictionlessFormat(
activeDoc: ActiveDoc,
req: express.Request,
options: DownloadOptions
): Promise<FrictionlessFormat> {
const {tableId} = options;
if (!activeDoc.docData) {
throw new Error('No docData in active document');
}
// Look up the table to make a CSV from.
const settings = activeDoc.docData.docSettings();
const tables = activeDoc.docData.getMetaTable('_grist_Tables');
const tableRef = tables.findRow('tableId', tableId);
if (tableRef === 0) {
throw new ApiError(`Table ${tableId} not found.`, 404);
}
const data = 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 {
name: tableId.toLowerCase().replace(/_/g, '-'),
title: tableName,
schema: {
fields: columns.map(col => ({
name: col.label,
...(col.description ? {description: col.description} : {}),
...buildTypeField(col, locale),
})),
}
};
}
function buildTypeField(col: ExportColumn, locale: string) {
const type = col.type.split(':', 1)[0];
switch (type) {
case 'Text':
return {
type: 'string',
format: col.widgetOptions.widget === 'HyperLink' ? 'uri' : 'default',
};
case 'Numeric':
return {
type: 'number',
bareNumber: col.widgetOptions?.numMode === 'decimal',
...getNumberSeparators(locale),
};
case 'Integer':
return {
type: 'integer',
bareNumber: col.widgetOptions?.numMode === 'decimal',
groupChar: getNumberSeparators(locale).groupChar,
};
case 'Date':
return {
type: 'date',
format: 'any',
gristFormat: col.widgetOptions?.dateFormat || 'YYYY-MM-DD',
};
case 'DateTime':
return {
type: 'datetime',
format: 'any',
gristFormat: `${col.widgetOptions?.dateFormat} ${col.widgetOptions?.timeFormat}`,
};
case 'Bool':
return {
type: 'boolean',
trueValue: ['TRUE'],
falseValue: ['FALSE'],
};
case 'Choice':
return {
type: 'string',
constraints: {enum: col.widgetOptions?.choices},
};
case 'ChoiceList':
return {
type: 'array',
constraints: {enum: col.widgetOptions?.choices},
};
case 'Reference':
return {type: 'string'};
case 'ReferenceList':
return {type: 'array'};
default:
return {type: 'string'};
}
}
function getNumberSeparators(locale: string) {
const numberWithGroupAndDecimalSeparator = 1000.1;
const parts = Intl.NumberFormat(locale).formatToParts(numberWithGroupAndDecimalSeparator);
return {
groupChar: parts.find(obj => obj.type === 'group')?.value,
decimalChar: parts.find(obj => obj.type === 'decimal')?.value,
};
}

@ -2,7 +2,7 @@
"ACUserManager": {
"Enter email address": "E-Mail Adresse eingeben",
"Invite new member": "Neues Mitglied einladen",
"We'll email an invite to {{email}}": "Wir schicken eine Einladung zu {{email}}"
"We'll email an invite to {{email}}": "Eine Einladung wird per E-Mail an {{email}} gesendet"
},
"AccessRules": {
"Add Column Rule": "Spaltenregel hinzufügen",
@ -95,7 +95,8 @@
"App": {
"Description": "Beschreibung",
"Key": "Schlüssel",
"Memory Error": "Speicherfehler"
"Memory Error": "Speicherfehler",
"Translators: please translate this only when your language is ready to be offered to users": "Übersetzer: Bitte übersetzen Sie dies erst, wenn Ihre Sprache bereit ist, Benutzern angeboten zu werden"
},
"AppHeader": {
"Home Page": "Startseite",
@ -279,10 +280,10 @@
"Time Zone:": "Zeitzone:",
"API": "API",
"Document ID copied to clipboard": "Dokument-ID in die Zwischenablage kopiert",
"Ok": "Ok"
"Ok": "OK"
},
"DocumentUsage": {
"Attachments Size": "Anhänge Größe",
"Attachments Size": "Größe der Anhänge",
"Contact the site owner to upgrade the plan to raise limits.": "Wenden Sie sich an den Eigentümer der Seite, um den Plan zu aktualisieren und die Grenzwerte zu erhöhen.",
"Data Size": "Daten Größe",
"Document limits {{- link}}.": "Dokumentgrenzwerte {{- link}}.",
@ -572,7 +573,7 @@
"Save": "Speichern",
"Select Widget": "Widget auswählen",
"Series": "Serie",
"Sort & Filter": "Sortieren & Filtern",
"Sort & Filter": "Sortieren und Filtern",
"TRANSFORM": "UMWANDELN",
"Theme": "Thema",
"WIDGET TITLE": "WIDGET-TITEL",
@ -687,7 +688,7 @@
"No Default Access": "Kein Standardzugriff",
"None": "Keine",
"Owner": "Eigentümer",
"View & Edit": "Anzeigen & Bearbeiten",
"View & Edit": "Anzeigen und Bearbeiten",
"View Only": "Nur anzeigen",
"Viewer": "Betrachter"
},
@ -825,7 +826,7 @@
},
"modals": {
"Cancel": "Abbrechen",
"Ok": "Ok",
"Ok": "OK",
"Save": "Speichern"
},
"pages": {
@ -943,7 +944,7 @@
"Row ID": "Zeilen-ID"
},
"HyperLinkEditor": {
"[link label] url": "[Linklabel]-URL"
"[link label] url": "[Linkbezeichnung] URL"
},
"welcomeTour": {
"Add New": "Neu hinzufügen",

@ -1,8 +1,8 @@
{
"ACUserManager": {
"Enter email address": "Enter email address",
"Enter email address": "Enter e-mail address",
"Invite new member": "Invite new member",
"We'll email an invite to {{email}}": "We'll email an invite to {{email}}"
"We'll email an invite to {{email}}": "An invite will be e-mailed to {{email}}"
},
"AccessRules": {
"Add Column Rule": "Add Column Rule",
@ -48,7 +48,7 @@
"Allow signing in to this account with Google": "Allow signing in to this account with Google",
"Change Password": "Change Password",
"Edit": "Edit",
"Email": "Email",
"Email": "E-mail",
"Login Method": "Login Method",
"Name": "Name",
"Names only allow letters, numbers and certain special characters": "Names only allow letters, numbers and certain special characters",
@ -99,7 +99,8 @@
"App": {
"Description": "Description",
"Key": "Key",
"Memory Error": "Memory Error"
"Memory Error": "Memory Error",
"Translators: please translate this only when your language is ready to be offered to users": "Translators: please translate this only when your language is ready to be offered to users"
},
"AppHeader": {
"Home Page": "Home Page",
@ -137,7 +138,7 @@
"Each Y series is followed by two series, for top and bottom error bars.": "Each Y series is followed by two series, for top and bottom error bars.",
"Pick a column": "Pick a column",
"Toggle chart aggregation": "Toggle chart aggregation",
"selected new group data columns": "selected new group data columns"
"selected new group data columns": "selected new group-data columns"
},
"CodeEditorPanel": {
"Access denied": "Access denied",
@ -218,7 +219,7 @@
"Document will be permanently deleted.": "Document will be permanently deleted.",
"Documents stay in Trash for 30 days, after which they get deleted permanently.": "Documents stay in Trash for 30 days, after which they get deleted permanently.",
"Edited {{at}}": "Edited {{at}}",
"Examples & Templates": "Examples & Templates",
"Examples & Templates": "Examples and Templates",
"Examples and Templates": "Examples and Templates",
"Featured": "Featured",
"Manage Users": "Manage Users",
@ -271,10 +272,10 @@
"Time Zone:": "Time Zone:",
"API": "API",
"Document ID copied to clipboard": "Document ID copied to clipboard",
"Ok": "Ok"
"Ok": "OK"
},
"DocumentUsage": {
"Attachments Size": "Attachments Size",
"Attachments Size": "Size of Attachments",
"Contact the site owner to upgrade the plan to raise limits.": "Contact the site owner to upgrade the plan to raise limits.",
"Data Size": "Data Size",
"For higher limits, ": "For higher limits, ",
@ -300,7 +301,7 @@
"Check out our related tutorial to learn how to create summary tables and charts, and to link charts dynamically.": "Check out our related tutorial to learn how to create summary tables and charts, and to link charts dynamically.",
"Investment Research": "Investment Research",
"Lightweight CRM": "Lightweight CRM",
"Tutorial: Analyze & Visualize": "Tutorial: Analyze & Visualize",
"Tutorial: Analyze & Visualize": "Tutorial: Analyze and Visualize",
"Tutorial: Create a CRM": "Tutorial: Create a CRM",
"Tutorial: Manage Business Data": "Tutorial: Manage Business Data",
"Welcome to the Afterschool Program template": "Welcome to the Afterschool Program template",
@ -365,7 +366,7 @@
"Hide {{count}} columns_one": "Hide column",
"Hide {{count}} columns_other": "Hide {{count}} columns",
"Insert column to the {{to}}": "Insert column to the {{to}}",
"More sort options ...": "More sort options…",
"More sort options ...": "More sorting options…",
"Rename column": "Rename column",
"Reset {{count}} columns_one": "Reset column",
"Reset {{count}} columns_other": "Reset {{count}} columns",
@ -413,7 +414,7 @@
"Create Workspace": "Create Workspace",
"Delete": "Delete",
"Delete {{workspace}} and all included documents?": "Delete {{workspace}} and all included documents?",
"Examples & Templates": "Examples & Templates",
"Examples & Templates": "Examples and Templates",
"Import Document": "Import Document",
"Manage Users": "Manage Users",
"Rename": "Rename",
@ -533,7 +534,7 @@
"Select Widget": "Select Widget",
"Series_one": "Series",
"Series_other": "Series",
"Sort & Filter": "Sort & Filter",
"Sort & Filter": "Sort and Filter",
"TRANSFORM": "TRANSFORM",
"Theme": "Theme",
"WIDGET TITLE": "WIDGET TITLE",
@ -636,7 +637,7 @@
"No Default Access": "No Default Access",
"None": "None",
"Owner": "Owner",
"View & Edit": "View & Edit",
"View & Edit": "View and Edit",
"View Only": "View Only",
"Viewer": "Viewer"
},
@ -660,7 +661,7 @@
"Unmark On-Demand": "Unmark On-Demand"
},
"ViewLayoutMenu": {
"Advanced Sort & Filter": "Advanced Sort & Filter",
"Advanced Sort & Filter": "Advanced Sorting and Filtering",
"Copy anchor link": "Copy anchor link",
"Data selection": "Data selection",
"Delete record": "Delete record",
@ -698,9 +699,9 @@
},
"WelcomeQuestions": {
"Education": "Education",
"Finance & Accounting": "Finance & Accounting",
"HR & Management": "HR & Management",
"IT & Technology": "IT & Technology",
"Finance & Accounting": "Finance and Accounting",
"HR & Management": "HR and Management",
"IT & Technology": "IT and Technology",
"Marketing": "Marketing",
"Media Production": "Media Production",
"Other": "Other",
@ -743,7 +744,7 @@
"Sign in to access this organization's documents.": "Sign in to access this organization's documents.",
"Signed out{{suffix}}": "Signed out{{suffix}}",
"Something went wrong": "Something went wrong",
"The requested page could not be found.{{separator}}Please check the URL and try again.": "The requested page could not be found.{{separator}}Please check the URL and try again.",
"The requested page could not be found.{{separator}}Please check the URL and try again.": "Could not find the requested page.{{separator}}Please check the URL and try again.",
"There was an error: {{message}}": "There was an error: {{message}}",
"There was an unknown error.": "There was an unknown error.",
"You are now signed out.": "You are now signed out.",
@ -769,7 +770,7 @@
},
"modals": {
"Cancel": "Cancel",
"Ok": "Ok",
"Ok": "OK",
"Save": "Save"
},
"pages": {
@ -878,7 +879,7 @@
"editingFormula is required": "editingFormula is required"
},
"HyperLinkEditor": {
"[link label] url": "[link label] url"
"[link label] url": "[link label] URL"
},
"NumericTextBox": {
"Currency": "Currency",

@ -44,7 +44,7 @@
"Allow signing in to this account with Google": "Permitir iniciar sesión en esta cuenta con Google",
"Change Password": "Cambiar contraseña",
"Edit": "Editar",
"Email": "E-mail",
"Email": "Correo electrónico",
"Login Method": "Método de inicio de sesión",
"Name": "Nombre",
"Names only allow letters, numbers and certain special characters": "Los nombres solo permiten letras, números y ciertos caracteres especiales",
@ -84,7 +84,8 @@
"App": {
"Description": "Descripción",
"Key": "Clave",
"Memory Error": "Error de memoria"
"Memory Error": "Error de memoria",
"Translators: please translate this only when your language is ready to be offered to users": "Traductores: por favor, traduzcan esto sólo cuando su idioma esté listo para ser ofrecido a los usuarios"
},
"AppHeader": {
"Home Page": "Portada",
@ -226,7 +227,7 @@
"Save and Reload": "Guardar y recargar",
"This document's ID (for API use):": "ID de este documento (para uso de API):",
"Time Zone:": "Zona horaria:",
"Ok": "Ok",
"Ok": "OK",
"Document ID copied to clipboard": "ID de documento copiado al portapapeles",
"API": "API"
},
@ -612,7 +613,7 @@
"WelcomeQuestions": {
"Education": "Educación",
"Finance & Accounting": "Finanzas y Contabilidad",
"HR & Management": "Recursos humanos y gestión",
"HR & Management": "RRHH y Gestión",
"IT & Technology": "TI y Tecnología",
"Marketing": "Publicidad",
"Media Production": "Producción audiovisual",
@ -682,7 +683,7 @@
"ACUserManager": {
"Enter email address": "Introduzca la dirección de correo electrónico",
"Invite new member": "Invitar nuevo miembro",
"We'll email an invite to {{email}}": "Enviaremos una invitación a {{email}}"
"We'll email an invite to {{email}}": "Se enviará una invitación por correo electrónico a {{email}}"
},
"ViewAsDropdown": {
"View As": "Ver como",
@ -795,7 +796,7 @@
"No Default Access": "Sin acceso predeterminado",
"None": "Ninguno",
"Owner": "Propietario",
"View & Edit": "Ver y Editar",
"View & Edit": "Ver y editar",
"View Only": "Ver sólo",
"Viewer": "Espectador"
},
@ -830,7 +831,7 @@
},
"modals": {
"Cancel": "Cancelar",
"Ok": "Ok",
"Ok": "OK",
"Save": "Guardar"
},
"pages": {
@ -940,7 +941,7 @@
"convert to card view, select data, and more.": "convertir a la vista de la tarjeta, seleccionar datos y más."
},
"HyperLinkEditor": {
"[link label] url": "[etiqueta de enlace] url"
"[link label] url": "[etiqueta de enlace] URL"
},
"NumericTextBox": {
"Currency": "Moneda",

@ -352,7 +352,8 @@
"App": {
"Memory Error": "Errore di memoria",
"Key": "Chiave",
"Description": "Descrizione"
"Description": "Descrizione",
"Translators: please translate this only when your language is ready to be offered to users": "Traduttori: per favore tradurre questo solo quando la tua lingua è pronta per gli utenti"
},
"AppModel": {
"This team site is suspended. Documents can be read, but not modified.": "Il sito del team è sospeso. Si possono leggere i documenti, ma non modificarli."
@ -386,7 +387,7 @@
"Allow signing in to this account with Google": "Permetti di accedere a questo account con Google",
"Change Password": "Cambia password",
"Edit": "Modifica",
"Email": "Email",
"Email": "E-mail",
"Login Method": "Metodo di accesso",
"Name": "Nome",
"Names only allow letters, numbers and certain special characters": "Per il nome puoi usare solo lettere, numeri e alcuni caratteri speciali",
@ -434,9 +435,9 @@
"When adding table rules, automatically add a rule to grant OWNER full access.": "Quando si mettono regole per la tabella, inserire sempre una che dà pieno accesso a OWNER."
},
"ACUserManager": {
"Enter email address": "Inserisci indirizzo email",
"Enter email address": "Inserisci indirizzo e-mail",
"Invite new member": "Invita un nuovo membro",
"We'll email an invite to {{email}}": "Spediremo un invito a {{email}}"
"We'll email an invite to {{email}}": "Un invito sarà inviato a {{email}}"
},
"ViewAsDropdown": {
"View As": "Vedi come",
@ -598,7 +599,7 @@
"Time Zone:": "Zona oraria:",
"API": "API",
"Document ID copied to clipboard": "ID del documento copiato",
"Ok": "Ok"
"Ok": "OK"
},
"DocumentUsage": {
"Data Size": "Dimensione dei dati",
@ -816,7 +817,9 @@
"Open configuration": "Apri configurazione",
"Print widget": "Widget di stampa",
"Show raw data": "Mostra dati grezzi",
"Widget options": "Opzioni widget"
"Widget options": "Opzioni widget",
"Add to page": "Aggiungi a pagina",
"Collapse widget": "Compatta widget"
},
"ViewSectionMenu": {
"(customized)": "(personalizzato)",
@ -957,5 +960,8 @@
"Access Rules": "Regole di accesso",
"Access rules give you the power to create nuanced rules to determine who can see or edit which parts of your document.": "Le regole di accesso ti danno il potere di creare regole sofisticate per decidere chi può vedere o modificare il tuo documento, e quali parti.",
"Reference columns are the key to {{relational}} data in Grist.": "Le colonne con riferimenti sono il motore {{relational}} dei dati in Grist."
},
"DescriptionConfig": {
"DESCRIPTION": "DESCRIZIONE"
}
}

File diff suppressed because it is too large Load Diff

@ -823,5 +823,144 @@
},
"ChoiceTextBox": {
"CHOICES": "WYBORY"
},
"FormulaEditor": {
"Error in the cell": "Błąd w komórce",
"editingFormula is required": "edycja Formuły jest wymagana",
"Column or field is required": "Kolumna lub pole jest wymagane",
"Errors in {{numErrors}} of {{numCells}} cells": "Błędy w {{numErrors}} komórek {{numCells}}",
"Errors in all {{numErrors}} cells": "Błędy we wszystkich komórkach {{numErrors}}"
},
"FieldEditor": {
"Unable to finish saving edited cell": "Nie można zakończyć zapisywania edytowanej komórki",
"It should be impossible to save a plain data value into a formula column": "Zapisanie zwykłej wartości danych w kolumnie formuły powinno być niemożliwe"
},
"HyperLinkEditor": {
"[link label] url": "[etykieta linku] url"
},
"NumericTextBox": {
"Decimals": "Miejsc dziesiętnych",
"Default currency ({{defaultCurrency}})": "Waluta domyślna ({{defaultCurrency}})",
"Number Format": "Format liczb",
"Currency": "Waluta:"
},
"ConditionalStyle": {
"Add another rule": "Dodaj kolejną regułę",
"Add conditional style": "Dodaj styl warunkowy",
"Error in style rule": "Błąd w regule stylu",
"Row Style": "Styl wiersza",
"Rule must return True or False": "Reguła musi zwracać wartość Prawda lub Fałsz"
},
"CurrencyPicker": {
"Invalid currency": "Nieprawidłowa waluta"
},
"DiscussionEditor": {
"Only current page": "Tylko bieżąca strona",
"Remove": "Usuń",
"Resolve": "Rozwiązać",
"Save": "Zapisać",
"Showing last {{nb}} comments": "Wyświetlanie ostatnich {{nb}} komentarzy",
"Started discussion": "Rozpoczęto dyskusję",
"Write a comment": "Napisz komentarz",
"Only my threads": "Tylko moje wątki",
"Open": "Otwórz",
"Reply to a comment": "Odpowiedz na komentarz",
"Show resolved comments": "Pokaż rozwiązane komentarze",
"Cancel": "Anulować",
"Comment": "Komentarz",
"Edit": "Edytuj",
"Marked as resolved": "Oznaczone jako rozwiązane",
"Reply": "Odpowiedź"
},
"EditorTooltip": {
"Convert column to formula": "Konwertuj kolumnę na formułę"
},
"FieldBuilder": {
"Apply Formula to Data": "Zastosuj formułę do danych",
"CELL FORMAT": "FORMAT KOMÓRKI",
"DATA FROM TABLE": "DANE Z TABELI",
"Mixed format": "Format mieszany",
"Mixed types": "Rodzaje mieszane",
"Save field settings for {{colId}} as common": "Zapisz ustawienia pola dla {{colId}} jako typowe",
"Use separate field settings for {{colId}}": "Użyj oddzielnych ustawień pól dla {{colId}}",
"Revert field settings for {{colId}} to common": "Przywróć ustawienia pola dla {{colId}} do wspólnych",
"Changing multiple column types": "Zmienianie wielu typów kolumn"
},
"Reference": {
"CELL FORMAT": "FORMAT KOMÓRKI",
"Row ID": "Identyfikator wiersza",
"SHOW COLUMN": "POKAŻ KOLUMNĘ"
},
"welcomeTour": {
"Add New": "Dodaj nowy",
"Building up": "Budowanie",
"Flying higher": "Latanie wyżej",
"Help Center": "Centrum pomocy",
"Make it relational! Use the {{ref}} type to link tables. ": "Spraw, aby był relacyjny! Użyj typu {{ref}}, aby połączyć tabele. ",
"Reference": "Odnośnik",
"Set formatting options, formulas, or column types, such as dates, choices, or attachments. ": "Ustaw opcje formatowania, formuły lub typy kolumn, takie jak daty, wybory lub załączniki. ",
"Sharing": "Udostępnianie",
"Start with {{equal}} to enter a formula.": "Zacznij od {{equal}}, aby wprowadzić formułę.",
"convert to card view, select data, and more.": "Konwertuj na widok karty, wybierz Dane i nie tylko.",
"creator panel": "Panel tworzenia",
"template library": "Biblioteka szablonów",
"Configuring your document": "Konfigurowanie dokumentu",
"Customizing columns": "Dostosowywanie kolumn",
"Double-click or hit {{enter}} on a cell to edit it. ": "Kliknij dwukrotnie lub naciśnij {{enter}} na komórkę, aby ją edytować. ",
"Enter": "Wejść",
"Editing Data": "Edycja danych",
"Welcome to Grist!": "Witamy w Grist!",
"Use {{helpCenter}} for documentation or questions.": "Użyj {{helpCenter}} do dokumentacji lub pytań.",
"Browse our {{templateLibrary}} to discover what's possible and get inspired.": "Przeglądaj nasze {{templateLibrary}}, aby odkryć, co jest możliwe i zainspirować się.",
"Toggle the {{creatorPanel}} to format columns, ": "Przełącz {{creatorPanel}}, aby sformatować kolumny, ",
"Share": "Udostępnij",
"Use the Share button ({{share}}) to share the document or export data.": "Użyj przycisku Udostępnij ({{share}}), aby udostępnić dokument lub wyeksportować dane.",
"Use {{addNew}} to add widgets, pages, or import more data. ": "Użyj {{addNew}}, aby dodać widżety, strony lub zaimportować więcej danych. "
},
"GristTooltips": {
"Click on “Open row styles” to apply conditional formatting to rows.": "Kliknij na \"Otwórz style wierszy\", aby zastosować formatowanie warunkowe do wierszy.",
"Click the Add New button to create new documents or workspaces, or import data.": "Kliknij przycisk Dodaj nowy, aby utworzyć nowe dokumenty lub obszary robocze albo zaimportować dane.",
"Learn more.": "Dowiedz się więcej.",
"Link your new widget to an existing widget on this page.": "Połącz swój nowy widget z istniejącym widgetem na tej stronie.",
"Nested Filtering": "Filtrowanie zagnieżdżone",
"Pinned filters are displayed as buttons above the widget.": "Przypięte filtry są wyświetlane jako przyciski nad widżetem.",
"Pinning Filters": "Przypinanie filtrów",
"Raw Data page": "Strona z danymi surowymi",
"Reference Columns": "Kolumny referencyjne",
"Select the table containing the data to show.": "Wybierz tabelę zawierającą dane do wyświetlenia.",
"Select the table to link to.": "Wybierz tabelę, do której chcesz utworzyć łącze.",
"Selecting Data": "Wybieranie danych",
"The Raw Data page lists all data tables in your document, including summary tables and tables not included in page layouts.": "Strona Dane surowe zawiera listę wszystkich tabel danych w dokumencie, w tym tabel podsumowania i tabel nieuwzględnionych w układach stron.",
"The total size of all data in this document, excluding attachments.": "Całkowity rozmiar wszystkich danych w tym dokumencie, z wyłączeniem załączników.",
"Try out changes in a copy, then decide whether to replace the original with your edits.": "Wypróbuj zmiany w kopii, a następnie zdecyduj, czy zastąpić oryginał zmianami.",
"Unpin to hide the the button while keeping the filter.": "Odepnij, aby ukryć przycisk, zachowując filtr.",
"You can filter by more than one column.": "Możesz filtrować według więcej niż jednej kolumny.",
"entire": "cały",
"Editing Card Layout": "Edytuj układ karty",
"Formulas that trigger in certain cases, and store the calculated value as data.": "Formuły, które wyzwalają się w określonych przypadkach i przechowują obliczoną wartość jako dane.",
"Useful for storing the timestamp or author of a new record, data cleaning, and more.": "Przydatne do przechowywania znacznika czasu lub autora nowego rekordu, czyszczenia danych i nie tylko.",
"Apply conditional formatting to cells in this column when formula conditions are met.": "Zastosuj formatowanie warunkowe do komórek w tej kolumnie, gdy spełnione są warunki formuły.",
"Apply conditional formatting to rows based on formulas.": "Zastosuj formatowanie warunkowe do wierszy na podstawie formuł.",
"Cells in a reference column always identify an {{entire}} record in that table, but you may select which column from that record to show.": "Komórki w kolumnie odwołania zawsze identyfikują rekord {{entire}} w tej tabeli, ale można wybrać kolumnę z tego rekordu do wyświetlenia.",
"Clicking {{EyeHideIcon}} in each cell hides the field from this view without deleting it.": "Kliknięcie {{EyeHideIcon}} w każdej komórce powoduje ukrycie pola w tym widoku bez jego usuwania.",
"Only those rows will appear which match all of the filters.": "Pojawią się tylko te wiersze, które pasują do wszystkich filtrów.",
"Linking Widgets": "Połączenie widżetów",
"Rearrange the fields in your card by dragging and resizing cells.": "Zmień kolejność pól na karcie, przeciągając komórki i zmieniając ich rozmiar.",
"Reference columns are the key to {{relational}} data in Grist.": "Kolumny referencyjne są kluczem do {{relational}} danych w Grist.",
"They allow for one record to point (or refer) to another.": "Pozwalają one na to, aby jeden rekord wskazywał (lub odnosił się) do innego.",
"This is the secret to Grist's dynamic and productive layouts.": "To jest sekret dynamicznych i produktywnych układów Grist.",
"Updates every 5 minutes.": "Aktualizacje co 5 minut.",
"Use the 𝚺 icon to create summary (or pivot) tables, for totals or subtotals.": "Użyj ikony Σ, aby utworzyć tabele podsumowujące (lub przestawne) dla sum lub sum częściowych.",
"Use the \\u{1D6BA} icon to create summary (or pivot) tables, for totals or subtotals.": "Użyj ikony \\u{1D6BA}, aby utworzyć tabele podsumowujące (przestawne) dla sum lub sum częściowych.",
"relational": "relacyjny",
"Access Rules": "Zasady dostępu",
"Access rules give you the power to create nuanced rules to determine who can see or edit which parts of your document.": "Reguły dostępu umożliwiają tworzenie reguł szczegółowych w celu określenia, kto może wyświetlać lub edytować poszczególne części dokumentu.",
"Add New": "Dodaj nowy"
},
"DescriptionConfig": {
"DESCRIPTION": "OPIS"
},
"LanguageMenu": {
"Language": "Język"
}
}

@ -1,8 +1,8 @@
{
"ACUserManager": {
"Enter email address": "Digite seu email",
"Enter email address": "Digite o endereço de e-mail",
"Invite new member": "Convidar novo membro",
"We'll email an invite to {{email}}": "Enviaremos um convite por e-mail para {{email}}"
"We'll email an invite to {{email}}": "Um convite será enviado por e-mail para {{email}}"
},
"AccessRules": {
"Add Column Rule": "Adicionar Regra de Coluna",
@ -95,7 +95,8 @@
"App": {
"Description": "Descrição",
"Key": "Chave",
"Memory Error": "Erro de Memória"
"Memory Error": "Erro de Memória",
"Translators: please translate this only when your language is ready to be offered to users": "Tradutores: por favor, traduzam isso apenas quando seu idioma estiver pronto para ser oferecido aos usuários"
},
"AppHeader": {
"Home Page": "Página inicial",
@ -277,7 +278,7 @@
"Save and Reload": "Salvar e Recarregar",
"This document's ID (for API use):": "O ID deste documento (para uso em API):",
"Time Zone:": "Fuso horário:",
"Ok": "Ok",
"Ok": "OK",
"Document ID copied to clipboard": "ID do documento copiado para a área de transferência",
"API": "API"
},
@ -712,7 +713,7 @@
"Unmark table On-Demand?": "Desmarcar a tabela Sob-Demanda?"
},
"ViewLayoutMenu": {
"Advanced Sort & Filter": "Classificação e Filtros Avançados",
"Advanced Sort & Filter": "Classificação e filtragem avançadas",
"Copy anchor link": "Copiar o link de ancoragem",
"Data selection": "Seleção de dados",
"Delete record": "Excluir registro",
@ -825,7 +826,7 @@
},
"modals": {
"Cancel": "Cancelar",
"Ok": "Ok",
"Ok": "OK",
"Save": "Salvar"
},
"pages": {
@ -940,7 +941,7 @@
"Error in the cell": "Erro na célula"
},
"HyperLinkEditor": {
"[link label] url": "[rótulo do link] url"
"[link label] url": "[rótulo do link] URL"
},
"NumericTextBox": {
"Currency": "Moeda",

@ -39,7 +39,7 @@
"ACUserManager": {
"Enter email address": "Введите адрес электронной почты",
"Invite new member": "Пригласить нового участника",
"We'll email an invite to {{email}}": "Мы вышлем приглашение на {{email}}"
"We'll email an invite to {{email}}": "Приглашение будет отправлено на электронную почту {{email}}"
},
"AccountPage": {
"API": "API",
@ -50,7 +50,7 @@
"Account settings": "Настройки аккаунта",
"Edit": "Редактировать",
"Name": "Имя",
"Email": "Email",
"Email": "E-mail",
"Login Method": "Способ входа в систему",
"Names only allow letters, numbers and certain special characters": "В именах допускаются только буквы, цифры и определенные специальные символы",
"Save": "Сохранить",
@ -62,7 +62,8 @@
"App": {
"Memory Error": "Ошибка памяти",
"Description": "Описание",
"Key": "Ключ"
"Key": "Ключ",
"Translators: please translate this only when your language is ready to be offered to users": "Переводчики: пожалуйста, переведите это только тогда, когда ваш язык будет готов для использования пользователями"
},
"ColorSelect": {
"Apply": "Применить",
@ -338,7 +339,7 @@
"Edited {{at}}": "Отредактировано {{at}}",
"Document will be permanently deleted.": "Документ будет удален навсегда.",
"Documents stay in Trash for 30 days, after which they get deleted permanently.": "Документы остаются в Корзине в течение 30 дней, после чего удаляются навсегда.",
"Examples & Templates": "Примеры & Шаблоны",
"Examples & Templates": "Примеры и Шаблоны",
"Featured": "Рекомендуемые",
"Manage Users": "Управление пользователями",
"Move": "Переместить",
@ -369,7 +370,7 @@
"DocumentSettings": {
"Document Settings": "Настройки документа",
"Currency:": "Валюта:",
"Ok": "Ok",
"Ok": "OK",
"Locale:": "Регион:",
"Save and Reload": "Сохранить и Перезагрузить",
"Save": "Сохранить",
@ -394,7 +395,7 @@
},
"DocumentUsage": {
"Data Size": "Размер данных",
"Attachments Size": "Размер вложений",
"Attachments Size": "Размер Вложений",
"For higher limits, ": "Для более высоких пределов, ",
"Rows": "Строки",
"Contact the site owner to upgrade the plan to raise limits.": "Свяжитесь с владельцем сайта для обновления тарифа и увеличения лимитов.",
@ -417,7 +418,7 @@
"Investment Research": "Инвестиционные исследования",
"Check out our related tutorial for how to link data, and create high-productivity layouts.": "Ознакомьтесь с нашим соответствующим учебником, чтобы узнать, как связывать данные и создавать высокопроизводительные макеты.",
"Lightweight CRM": "Легкая CRM",
"Tutorial: Analyze & Visualize": "Учебник: Анализ и визуализация",
"Tutorial: Analyze & Visualize": "Учебник: Анализ и Визуализация",
"Check out our related tutorial to learn how to create summary tables and charts, and to link charts dynamically.": "Ознакомьтесь с нашим соответствующим учебником, чтобы узнать, как создавать сводные таблицы и графики, а также динамически связывать графики.",
"Tutorial: Create a CRM": "Учебник: Создание CRM",
"Tutorial: Manage Business Data": "Учебник: Управление бизнес-данными",
@ -469,7 +470,7 @@
"Access Details": "Детали доступа",
"Create Empty Document": "Создать пустой документ",
"Delete": "Удалить",
"Examples & Templates": "Примеры & Шаблоны",
"Examples & Templates": "Примеры и Шаблоны",
"Rename": "Переименовать",
"Delete {{workspace}} and all included documents?": "Удалить {{workspace}} и все прилагаемые документы?",
"Trash": "Корзина",
@ -590,7 +591,7 @@
"Save": "Сохранить",
"Select Widget": "Выберите виджет",
"Series_one": "Ряд",
"Sort & Filter": "Сортировка & Фильтрация",
"Sort & Filter": "Сортировка и Фильтрация",
"TRANSFORM": "ПРЕОБРАЗОВАНИЕ",
"WIDGET TITLE": "ЗАГОЛОВОК ВИДЖЕТА",
"You do not have edit access to this document": "У вас нет прав на редактирование этого документа",
@ -648,7 +649,7 @@
"UserManagerModel": {
"In Full": "Полный",
"None": "Без доступа",
"View & Edit": "Просмотр & Редактирование",
"View & Edit": "Просмотр и Редактирование",
"Viewer": "Наблюдатель",
"Owner": "Владелец",
"No Default Access": "Нет доступа по умолчанию",
@ -671,7 +672,7 @@
"Compact": "Компактная"
},
"ViewLayoutMenu": {
"Advanced Sort & Filter": "Расширенная Сортировка & Фильтр",
"Advanced Sort & Filter": "Расширенная Сортировка и Фильтрация",
"Delete record": "Удалить запись",
"Delete widget": "Удалить виджет",
"Download as XLSX": "Скачать как XLSX",
@ -682,7 +683,9 @@
"Open configuration": "Открыть конфигурацию",
"Print widget": "Печать виджета",
"Data selection": "Выбор данных",
"Widget options": "Параметры виджета"
"Widget options": "Параметры виджета",
"Add to page": "Добавить на страницу",
"Collapse widget": "Свернуть виджет"
},
"FieldEditor": {
"It should be impossible to save a plain data value into a formula column": "Должно быть невозможно сохранить значение простых данных в столбце формулы",
@ -957,5 +960,8 @@
"Access rules give you the power to create nuanced rules to determine who can see or edit which parts of your document.": "Правила доступа дают вам возможность создавать детальные правила, определяющие, кто может просматривать или редактировать части вашего документа.",
"Use the 𝚺 icon to create summary (or pivot) tables, for totals or subtotals.": "Используйте 𝚺 значок для создания сводных таблиц для итогов или промежуточных итогов.",
"relational": "реляционный"
},
"DescriptionConfig": {
"DESCRIPTION": "ОПИСАНИЕ"
}
}

@ -2138,6 +2138,62 @@ function testDocApi() {
assert.deepEqual(resp.data, { error: 'tableId parameter should be a string: undefined' });
});
it("GET /docs/{did}/download/table-schema serves table-schema-encoded document", async function() {
const resp = await axios.get(`${serverUrl}/api/docs/${docIds.TestDoc}/download/table-schema?tableId=Foo`, chimpy);
assert.equal(resp.status, 200);
const expected = {
format: "csv",
mediatype: "text/csv",
encoding: "utf-8",
dialect: {
delimiter: ",",
doubleQuote: true,
},
name: 'foo',
title: 'Foo',
schema: {
fields: [{
name: 'A',
type: 'string',
format: 'default',
}, {
name: 'B',
type: 'string',
format: 'default',
}]
}
};
assert.deepInclude(resp.data, expected);
const resp2 = await axios.get(resp.data.path, chimpy);
assert.equal(resp2.status, 200);
assert.equal(resp2.data, 'A,B\nSanta,1\nBob,11\nAlice,2\nFelix,22\n');
});
it("GET /docs/{did}/download/table-schema respects permissions", async function() {
// kiwi has no access to TestDoc
const resp = await axios.get(`${serverUrl}/api/docs/${docIds.TestDoc}/download/table-schema?tableId=Table1`, kiwi);
assert.equal(resp.status, 403);
assert.deepEqual(resp.data, {"error":"No view access"});
});
it("GET /docs/{did}/download/table-schema returns 404 if tableId is invalid", async function() {
const resp = await axios.get(
`${serverUrl}/api/docs/${docIds.TestDoc}/download/table-schema?tableId=MissingTableId`,
chimpy,
);
assert.equal(resp.status, 404);
assert.deepEqual(resp.data, { error: 'Table MissingTableId not found.' });
});
it("GET /docs/{did}/download/table-schema returns 400 if tableId is missing", async function() {
const resp = await axios.get(
`${serverUrl}/api/docs/${docIds.TestDoc}/download/table-schema`, chimpy);
assert.equal(resp.status, 400);
assert.deepEqual(resp.data, { error: 'tableId parameter should be a string: undefined' });
});
it("GET /docs/{did}/download/xlsx serves XLSX-encoded document", async function() {
const resp = await axios.get(`${serverUrl}/api/docs/${docIds.Timesheets}/download/xlsx?tableId=Table1`, chimpy);
assert.equal(resp.status, 200);

Loading…
Cancel
Save