mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
(core) updates from grist-core
This commit is contained in:
commit
65a1863015
@ -6,8 +6,8 @@
|
||||
* IdP is the "Identity Provider", somewhere users log into, e.g. Okta or Google Apps.
|
||||
*
|
||||
* We also use optional attributes for the user's name, for which we accept any of:
|
||||
* given_name
|
||||
* family_name
|
||||
* given_name + family_name
|
||||
* name
|
||||
*
|
||||
* Expected environment variables:
|
||||
* env GRIST_OIDC_SP_HOST=https://<your-domain>
|
||||
@ -21,6 +21,17 @@
|
||||
* The client secret for the application, as registered with the IdP.
|
||||
* env GRIST_OIDC_IDP_SCOPES
|
||||
* The scopes to request from the IdP, as a space-separated list. Defaults to "openid email profile".
|
||||
* env GRIST_OIDC_SP_PROFILE_NAME_ATTR
|
||||
* The key of the attribute to use for the user's name.
|
||||
* If omitted, the name will either be the concatenation of "given_name" + "family_name" or the "name" attribute.
|
||||
* env GRIST_OIDC_SP_PROFILE_EMAIL_ATTR
|
||||
* The key of the attribute to use for the user's email. Defaults to "email".
|
||||
* env GRIST_OIDC_IDP_SKIP_END_SESSION_ENDPOINT
|
||||
* If set to "true", on logout, there won't be any attempt to call the IdP's end_session_endpoint
|
||||
* (the user will remain logged in in the IdP).
|
||||
* env GRIST_OIDC_SP_IGNORE_EMAIL_VERIFIED
|
||||
* If set to "true", the user will be allowed to login even if the email is not verified by the IDP.
|
||||
* Defaults to false.
|
||||
*
|
||||
* This version of OIDCConfig has been tested with Keycloak OIDC IdP following the instructions
|
||||
* at:
|
||||
@ -43,12 +54,17 @@ import { Sessions } from './Sessions';
|
||||
import log from 'app/server/lib/log';
|
||||
import { appSettings } from './AppSettings';
|
||||
import { RequestWithLogin } from './Authorizer';
|
||||
import { UserProfile } from 'app/common/LoginSessionAPI';
|
||||
|
||||
const CALLBACK_URL = '/oauth2/callback';
|
||||
|
||||
export class OIDCConfig {
|
||||
private _client: Client;
|
||||
private _redirectUrl: string;
|
||||
private _namePropertyKey?: string;
|
||||
private _emailPropertyKey: string;
|
||||
private _skipEndSessionEndpoint: boolean;
|
||||
private _ignoreEmailVerified: boolean;
|
||||
|
||||
public constructor() {
|
||||
}
|
||||
@ -69,6 +85,24 @@ export class OIDCConfig {
|
||||
envVar: 'GRIST_OIDC_IDP_CLIENT_SECRET',
|
||||
censor: true,
|
||||
});
|
||||
this._namePropertyKey = section.flag('namePropertyKey').readString({
|
||||
envVar: 'GRIST_OIDC_SP_PROFILE_NAME_ATTR',
|
||||
});
|
||||
|
||||
this._emailPropertyKey = section.flag('emailPropertyKey').requireString({
|
||||
envVar: 'GRIST_OIDC_SP_PROFILE_EMAIL_ATTR',
|
||||
defaultValue: 'email',
|
||||
});
|
||||
|
||||
this._skipEndSessionEndpoint = section.flag('endSessionEndpoint').readBool({
|
||||
envVar: 'GRIST_OIDC_IDP_SKIP_END_SESSION_ENDPOINT',
|
||||
defaultValue: false,
|
||||
})!;
|
||||
|
||||
this._ignoreEmailVerified = section.flag('ignoreEmailVerified').readBool({
|
||||
envVar: 'GRIST_OIDC_SP_IGNORE_EMAIL_VERIFIED',
|
||||
defaultValue: false,
|
||||
})!;
|
||||
|
||||
const issuer = await Issuer.discover(issuerUrl);
|
||||
this._redirectUrl = new URL(CALLBACK_URL, spHost).href;
|
||||
@ -78,6 +112,10 @@ export class OIDCConfig {
|
||||
redirect_uris: [ this._redirectUrl ],
|
||||
response_types: [ 'code' ],
|
||||
});
|
||||
if (this._client.issuer.metadata.end_session_endpoint === undefined && !this._skipEndSessionEndpoint) {
|
||||
throw new Error('The Identity provider does not propose end_session_endpoint. ' +
|
||||
'If that is expected, please set GRIST_OIDC_IDP_SKIP_END_SESSION_ENDPOINT=true');
|
||||
}
|
||||
log.info(`OIDCConfig: initialized with issuer ${issuerUrl}`);
|
||||
}
|
||||
|
||||
@ -105,6 +143,11 @@ export class OIDCConfig {
|
||||
);
|
||||
|
||||
const userInfo = await this._client.userinfo(tokenSet);
|
||||
|
||||
if (!this._ignoreEmailVerified && userInfo.email_verified !== true) {
|
||||
throw new Error(`OIDCConfig: email not verified for ${userInfo.email}`);
|
||||
}
|
||||
|
||||
const profile = this._makeUserProfileFromUserInfo(userInfo);
|
||||
log.info(`OIDCConfig: got OIDC response for ${profile.email} (${profile.name}) redirecting to ${targetUrl}`);
|
||||
|
||||
@ -140,6 +183,10 @@ export class OIDCConfig {
|
||||
}
|
||||
|
||||
public async getLogoutRedirectUrl(req: express.Request, redirectUrl: URL): Promise<string> {
|
||||
// For IdPs that don't have end_session_endpoint, we just redirect to the logout page.
|
||||
if (this._skipEndSessionEndpoint) {
|
||||
return redirectUrl.href;
|
||||
}
|
||||
return this._client.endSessionUrl({
|
||||
post_logout_redirect_uri: redirectUrl.href
|
||||
});
|
||||
@ -167,14 +214,21 @@ export class OIDCConfig {
|
||||
return codeVerifier;
|
||||
}
|
||||
|
||||
private _makeUserProfileFromUserInfo(userInfo: UserinfoResponse) {
|
||||
const email = userInfo.email;
|
||||
private _makeUserProfileFromUserInfo(userInfo: UserinfoResponse): Partial<UserProfile> {
|
||||
return {
|
||||
email: String(userInfo[ this._emailPropertyKey ]),
|
||||
name: this._extractName(userInfo)
|
||||
};
|
||||
}
|
||||
|
||||
private _extractName(userInfo: UserinfoResponse): string|undefined {
|
||||
if (this._namePropertyKey) {
|
||||
return (userInfo[ this._namePropertyKey ] as any)?.toString();
|
||||
}
|
||||
const fname = userInfo.given_name ?? '';
|
||||
const lname = userInfo.family_name ?? '';
|
||||
return {
|
||||
email,
|
||||
name: `${fname} ${lname}`.trim(),
|
||||
};
|
||||
|
||||
return `${fname} ${lname}`.trim() || userInfo.name;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -217,7 +217,13 @@
|
||||
"Duplicate Table": "Tabelle duplizieren",
|
||||
"Raw Data Tables": "Rohdaten-Tabellen",
|
||||
"Table ID copied to clipboard": "Tabellen-ID in die Zwischenablage kopiert",
|
||||
"You do not have edit access to this document": "Sie haben keinen Bearbeitungszugriff auf dieses Dokument"
|
||||
"You do not have edit access to this document": "Sie haben keinen Bearbeitungszugriff auf dieses Dokument",
|
||||
"Edit Record Card": "Karteikarte bearbeiten",
|
||||
"Rename Table": "Tabelle umbenennen",
|
||||
"{{action}} Record Card": "{{action}} Karteikarte",
|
||||
"Record Card": "Karteikarte",
|
||||
"Remove Table": "Tabelle entfernen",
|
||||
"Record Card Disabled": "Karteikarte Deaktiviert"
|
||||
},
|
||||
"DocHistory": {
|
||||
"Activity": "Aktivität",
|
||||
@ -663,7 +669,8 @@
|
||||
"Insert row above": "Zeile oben einfügen",
|
||||
"Insert row below": "Zeile unten einfügen",
|
||||
"Duplicate rows_one": "Zeile duplizieren",
|
||||
"Duplicate rows_other": "Zeilen duplizieren"
|
||||
"Duplicate rows_other": "Zeilen duplizieren",
|
||||
"View as card": "Ansicht als Karte"
|
||||
},
|
||||
"SelectionSummary": {
|
||||
"Copied to clipboard": "In die Zwischenablage kopiert"
|
||||
@ -1295,5 +1302,13 @@
|
||||
},
|
||||
"searchDropdown": {
|
||||
"Search": "Suchen"
|
||||
},
|
||||
"CardContextMenu": {
|
||||
"Insert card above": "Karte oben einfügen",
|
||||
"Duplicate card": "Karte duplizieren",
|
||||
"Insert card below": "Karte unten einfügen",
|
||||
"Delete card": "Karte löschen",
|
||||
"Copy anchor link": "Ankerlink kopieren",
|
||||
"Insert card": "Karte einfügen"
|
||||
}
|
||||
}
|
||||
|
@ -543,7 +543,8 @@
|
||||
"Insert row above": "Insertar fila arriba",
|
||||
"Insert row below": "Insertar fila debajo",
|
||||
"Duplicate rows_one": "Duplicar fila",
|
||||
"Duplicate rows_other": "Duplicar filas"
|
||||
"Duplicate rows_other": "Duplicar filas",
|
||||
"View as card": "Ver como tarjeta"
|
||||
},
|
||||
"ShareMenu": {
|
||||
"Access Details": "Detalles de Acceso",
|
||||
@ -718,7 +719,13 @@
|
||||
"Duplicate Table": "Duplicar tabla",
|
||||
"Raw Data Tables": "Tablas de datos brutos",
|
||||
"Table ID copied to clipboard": "ID de tabla copiado al portapapeles",
|
||||
"Click to copy": "Haga clic para copiar"
|
||||
"Click to copy": "Haga clic para copiar",
|
||||
"Edit Record Card": "Editar la ficha del registro",
|
||||
"Rename Table": "Cambiar el nombre de la tabla",
|
||||
"{{action}} Record Card": "{{action}} Ficha",
|
||||
"Record Card": "Ficha de registro",
|
||||
"Remove Table": "Quitar la tabla",
|
||||
"Record Card Disabled": "Tarjeta de registro desactivada"
|
||||
},
|
||||
"DocPageModel": {
|
||||
"Add Empty Table": "Agregar tabla vacía",
|
||||
@ -1285,5 +1292,13 @@
|
||||
"FloatingPopup": {
|
||||
"Maximize": "Maximizar",
|
||||
"Minimize": "Minimizar"
|
||||
},
|
||||
"CardContextMenu": {
|
||||
"Insert card above": "Inserte la tarjeta arriba",
|
||||
"Duplicate card": "Tarjeta duplicada",
|
||||
"Insert card below": "Inserte la tarjeta a continuación",
|
||||
"Delete card": "Borrar la tarjeta",
|
||||
"Copy anchor link": "Copiar enlace fijado",
|
||||
"Insert card": "Insertar la tarjeta"
|
||||
}
|
||||
}
|
||||
|
@ -217,7 +217,13 @@
|
||||
"Duplicate Table": "Duplicar a Tabela",
|
||||
"Raw Data Tables": "Tabelas de Dados Primários",
|
||||
"Table ID copied to clipboard": "ID da Tabela copiada para a área de transferência",
|
||||
"You do not have edit access to this document": "Você não tem permissão de edição desse documento"
|
||||
"You do not have edit access to this document": "Você não tem permissão de edição desse documento",
|
||||
"Edit Record Card": "Editar cartão de registro",
|
||||
"Rename Table": "Renomear tabela",
|
||||
"{{action}} Record Card": "{{action}} Cartão de registro",
|
||||
"Record Card": "Cartão de registro",
|
||||
"Remove Table": "Remover tabela",
|
||||
"Record Card Disabled": "Cartão de registro desabilitado"
|
||||
},
|
||||
"DocHistory": {
|
||||
"Activity": "Atividade",
|
||||
@ -663,7 +669,8 @@
|
||||
"Insert row above": "Inserir linha acima",
|
||||
"Insert row below": "Inserir linha abaixo",
|
||||
"Duplicate rows_one": "Duplicar linha",
|
||||
"Duplicate rows_other": "Duplicar linhas"
|
||||
"Duplicate rows_other": "Duplicar linhas",
|
||||
"View as card": "Ver como cartão"
|
||||
},
|
||||
"SelectionSummary": {
|
||||
"Copied to clipboard": "Copiado para a área de transferência"
|
||||
@ -1295,5 +1302,13 @@
|
||||
"FloatingPopup": {
|
||||
"Maximize": "Maximizar",
|
||||
"Minimize": "Minimizar"
|
||||
},
|
||||
"CardContextMenu": {
|
||||
"Insert card above": "Inserir cartão acima",
|
||||
"Duplicate card": "Duplicar o cartão",
|
||||
"Insert card below": "Inserir cartão abaixo",
|
||||
"Delete card": "Excluir cartão",
|
||||
"Copy anchor link": "Copiar link de ancoragem",
|
||||
"Insert card": "Inserir cartão"
|
||||
}
|
||||
}
|
||||
|
@ -334,7 +334,13 @@
|
||||
"Duplicate Table": "Дублировать таблицу",
|
||||
"Table ID copied to clipboard": "Идентификатор таблицы скопирован в буфер обмена",
|
||||
"You do not have edit access to this document": "У вас нет доступа к редактированию этого документа",
|
||||
"Delete {{formattedTableName}} data, and remove it from all pages?": "Удалить {{formattedTableName}} данные, и удалить их со всех страниц?"
|
||||
"Delete {{formattedTableName}} data, and remove it from all pages?": "Удалить {{formattedTableName}} данные, и удалить их со всех страниц?",
|
||||
"Edit Record Card": "Редактировать карточку записи",
|
||||
"Rename Table": "Переименовать Таблицу",
|
||||
"{{action}} Record Card": "{{action}} Карточка записи",
|
||||
"Record Card": "Карточка записи",
|
||||
"Remove Table": "Удалить Таблицу",
|
||||
"Record Card Disabled": "Карточка записи отключена"
|
||||
},
|
||||
"DocHistory": {
|
||||
"Snapshots": "Снимки",
|
||||
@ -603,7 +609,8 @@
|
||||
"Insert row below": "Вставить строку ниже",
|
||||
"Insert row": "Вставить строку",
|
||||
"Insert row above": "Вставить строку выше",
|
||||
"Delete": "Удалить"
|
||||
"Delete": "Удалить",
|
||||
"View as card": "Посмотреть как карточку"
|
||||
},
|
||||
"RecordLayout": {
|
||||
"Updating record layout.": "Обновление макета записи."
|
||||
@ -1231,5 +1238,13 @@
|
||||
"FloatingPopup": {
|
||||
"Maximize": "Максимизировать",
|
||||
"Minimize": "Минимизировать"
|
||||
},
|
||||
"CardContextMenu": {
|
||||
"Insert card above": "Вставить карточку выше",
|
||||
"Duplicate card": "Дублировать карточку",
|
||||
"Insert card below": "Вставить карточку ниже",
|
||||
"Delete card": "Удалить карточку",
|
||||
"Copy anchor link": "Скопировать якорную ссылку",
|
||||
"Insert card": "Вставить карточку"
|
||||
}
|
||||
}
|
||||
|
@ -146,7 +146,7 @@
|
||||
"Deleted {{at}}": "Izbrisano {{at}}",
|
||||
"Delete {{name}}": "Izbriši {{name}}",
|
||||
"Document will be permanently deleted.": "Dokument bo trajno izbrisan.",
|
||||
"Permanently Delete \"{{name}}\"?": "Trajno izbrisati \"{{name}}\"?",
|
||||
"Permanently Delete \"{{name}}\"?": "Trajno izbrišem \"{{name}}\"?",
|
||||
"(The organization needs a paid plan)": "(Organizacija potrebuje plačljiv načrt)",
|
||||
"Access Details": "Podrobnosti o dostopu",
|
||||
"All Documents": "Vsi dokumenti",
|
||||
@ -168,7 +168,7 @@
|
||||
"Requires edit permissions": "Zahteva dovoljenja za urejanje",
|
||||
"Other Sites": "Druga spletna mesta",
|
||||
"Pinned Documents": "Pripeti dokumenti",
|
||||
"To restore this document, restore the workspace first.": "Če želite obnoviti ta dokument, najprej obnovite delovni prostor.",
|
||||
"To restore this document, restore the workspace first.": "Če želiš obnoviti ta dokument, najprej obnovi delovni prostor.",
|
||||
"You are on your personal site. You also have access to the following sites:": "Nahajate se na svojem osebnem spletnem mestu. Prav tako imate dostop do naslednjih spletnih mest:",
|
||||
"Restore": "Obnovi",
|
||||
"Move {{name}} to workspace": "Premakni {{name}} v delovni prostor",
|
||||
@ -260,7 +260,8 @@
|
||||
"Copy anchor link": "Kopiraj sidrno povezavo",
|
||||
"Duplicate rows_one": "Podvoji vrstico",
|
||||
"Duplicate rows_other": "Podvoji vrstice",
|
||||
"Insert row above": "Vstavi vrstico zgoraj"
|
||||
"Insert row above": "Vstavi vrstico zgoraj",
|
||||
"View as card": "Kartični pogled"
|
||||
},
|
||||
"Tools": {
|
||||
"Delete": "Izbriši",
|
||||
@ -298,7 +299,13 @@
|
||||
"Duplicate Table": "Podvojena tabela",
|
||||
"Table ID copied to clipboard": "ID tabele kopiran v odložišče",
|
||||
"You do not have edit access to this document": "Nimate dostopa za urejanje tega dokumenta",
|
||||
"Raw Data Tables": "Neobdelana tabela"
|
||||
"Raw Data Tables": "Neobdelana tabela",
|
||||
"Edit Record Card": "Uredi evidenčno kartico",
|
||||
"Rename Table": "Preimenuj Tabelo",
|
||||
"{{action}} Record Card": "{{action}} Evidenčno Kartico",
|
||||
"Record Card": "Evidenčna kartica",
|
||||
"Remove Table": "Odstrani Tabelo",
|
||||
"Record Card Disabled": "Evidenčna kartica onemogočena"
|
||||
},
|
||||
"ViewLayoutMenu": {
|
||||
"Delete record": "Brisanje zapisa",
|
||||
@ -590,7 +597,7 @@
|
||||
"Organization": "Organizacija",
|
||||
"Replacing the original requires editing rights on the original document.": "Za zamenjavo izvirnika so potrebne pravice za urejanje izvirnega dokumenta.",
|
||||
"Remove document history (can significantly reduce file size)": "Odstranitev zgodovine dokumenta (lahko znatno zmanjša velikost datoteke)",
|
||||
"To save your changes, please sign up, then reload this page.": "Če želite shraniti spremembe, se prijavite in nato ponovno naložite to stran.",
|
||||
"To save your changes, please sign up, then reload this page.": "Če želiš shraniti spremembe, se prijavi in nato ponovno naloži to stran.",
|
||||
"The original version of this document will be updated.": "Prvotna različica tega dokumenta bo posodobljena.",
|
||||
"However, it appears to be already identical.": "Vendar se zdi, da je že identična.",
|
||||
"Update Original": "Posodobitev izvirnika",
|
||||
@ -642,7 +649,7 @@
|
||||
"Access rules give you the power to create nuanced rules to determine who can see or edit which parts of your document.": "Pravila dostopa vam omogočajo, da ustvarite podrobna pravila, s katerimi določite, kdo lahko vidi ali ureja posamezne dele dokumenta.",
|
||||
"Rearrange the fields in your card by dragging and resizing cells.": "Z vlečenjem in spreminjanjem velikosti polj na kartici spremenite njihovo razporeditev.",
|
||||
"Useful for storing the timestamp or author of a new record, data cleaning, and more.": "Uporabno za shranjevanje časovnega žiga ali avtorja novega zapisa, čiščenje podatkov in drugo.",
|
||||
"Click the Add New button to create new documents or workspaces, or import data.": "Če želite ustvariti nove dokumente ali delovne prostore ali uvoziti podatke, kliknite gumb Dodaj.",
|
||||
"Click the Add New button to create new documents or workspaces, or import data.": "Klikni gumb dodaj novega, če želiš ustvariti nove dokumente, delovne prostore ali uvoziti podatke,",
|
||||
"Nested Filtering": "Vgnezdeno filtriranje",
|
||||
"Only those rows will appear which match all of the filters.": "Prikazane bodo samo tiste vrstice, ki ustrezajo vsem filtrom.",
|
||||
"Editing Card Layout": "Urejanje postavitve kartice",
|
||||
@ -655,11 +662,11 @@
|
||||
"They allow for one record to point (or refer) to another.": "Omogočajo, da en zapis kaže (ali se sklicuje) na drugega.",
|
||||
"Reference Columns": "Referenčni stolpci",
|
||||
"To configure your calendar, select columns for start": {
|
||||
"end dates and event titles. Note each column's type.": "Če želite konfigurirati svoj koledar, izberite stolpce za začetne/končne datume in naslove dogodkov. Upoštevajte vrsto vsakega stolpca."
|
||||
"end dates and event titles. Note each column's type.": "Če želiš konfigurirati svoj koledar, izberi stolpce za začetne/končne datume in naslove dogodkov. Upoštevaj vrsto vsakega stolpca."
|
||||
},
|
||||
"Calendar": "Koledar",
|
||||
"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 želiš narediti sidrno povezavo, ki uporabnika pripelje do določene celice, klikni vrstico in pritisni {{shortcut}}.",
|
||||
"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.": "Uporabi pogojno oblikovanje za vrstice na podlagi formul.",
|
||||
"Click on “Open row styles” to apply conditional formatting to rows.": "Klikni »Odpri sloge vrstic«, da za vrstice uporabiš pogojno oblikovanje.",
|
||||
@ -707,14 +714,14 @@
|
||||
"Close": "Zapri",
|
||||
"Allow anyone with the link to open.": "Omogočite odprtje vsakomur, ki ima povezavo.",
|
||||
"Invite people to {{resourceType}}": "Povabite ljudi k {{resourceType}}",
|
||||
"Public access inherited from {{parent}}. To remove, set 'Inherit access' option to 'None'.": "Uporabnik podeduje dovoljenja od {{parent}}. Če jih želite odstraniti, nastavite možnost \"Podeduje dostop\" na \"Ne\".",
|
||||
"Public access inherited from {{parent}}. To remove, set 'Inherit access' option to 'None'.": "Uporabnik podeduje dovoljenja od {{parent}}. Če jih želiš odstraniti, nastavi možnost \"Podeduje dostop\" na \"Ne\".",
|
||||
"Remove my access": "Odstranitev mojega dostopa",
|
||||
"Public access": "Javni dostop",
|
||||
"Public Access": "Javni dostop",
|
||||
"Cancel": "Prekliči",
|
||||
"Grist support": "Grist podpora",
|
||||
"You are about to remove your own access to this {{resourceType}}": "Odstranili boste svoj dostop do tega {{resourceType}}",
|
||||
"User inherits permissions from {{parent}}. To remove, set 'Inherit access' option to 'None'.": "Uporabnik podeduje dovoljenja od {{parent}}. Če jih želite odstraniti, nastavite možnost \"Podeduje dostop\" na \"Ni\".",
|
||||
"User inherits permissions from {{parent}}. To remove, set 'Inherit access' option to 'None'.": "Uporabnik podeduje dovoljenja od {{parent}}. Če jih želiš odstraniti, nastavi možnost \"Podeduje dostop\" na \"Ni\".",
|
||||
"Guest": "Gost",
|
||||
"Invite multiple": "Povabite več",
|
||||
"Confirm": "Potrdite",
|
||||
@ -813,7 +820,7 @@
|
||||
"Welcome to Grist!": "Dobrodošli v Gristu!",
|
||||
"Visit our {{link}} to learn more about Grist.": "Obiščite našo spletno stran {{link}} da izveste več o Grisstu.",
|
||||
"Sign in": "Prijavi se",
|
||||
"To use Grist, please either sign up or sign in.": "Če želite uporabljati Grist, se prijavite ali prvič prijavite."
|
||||
"To use Grist, please either sign up or sign in.": "Če želiš uporabljati Grist, se prijavi ali prvič prijavi."
|
||||
},
|
||||
"WelcomeSitePicker": {
|
||||
"You have access to the following Grist sites.": "Imate dostop do naslednjih Grist spletnih mest .",
|
||||
@ -1231,5 +1238,13 @@
|
||||
},
|
||||
"sendToDrive": {
|
||||
"Sending file to Google Drive": "Pošiljanje datoteke v Google Drive"
|
||||
},
|
||||
"CardContextMenu": {
|
||||
"Insert card above": "Vstavi kartico zgoraj",
|
||||
"Duplicate card": "Podvoji kartico",
|
||||
"Insert card below": "Vstavi kartico spodaj",
|
||||
"Delete card": "Briši kartico",
|
||||
"Copy anchor link": "Kopiraj sidrno povezavo",
|
||||
"Insert card": "Vstavi kartico"
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user