diff --git a/.env.sample b/.env.sample index f1629c896..e4c505d05 100644 --- a/.env.sample +++ b/.env.sample @@ -18,7 +18,7 @@ PORT=3000 FORCE_HTTPS=true ENABLE_UPDATES=true -DEBUG=cache,presenters,events +DEBUG=cache,presenters,events,emails,mailer,utils,multiplayer,server,services # Third party signin credentials (at least one is required) SLACK_KEY=get_a_key_from_slack diff --git a/app/scenes/UserDelete.js b/app/scenes/UserDelete.js index c03e004c2..89ba2265a 100644 --- a/app/scenes/UserDelete.js +++ b/app/scenes/UserDelete.js @@ -28,7 +28,6 @@ class UserDelete extends React.Component { this.props.auth.logout(); } catch (error) { this.props.ui.showToast(error.message); - throw error; } finally { this.isDeleting = false; } diff --git a/app/utils/ApiClient.js b/app/utils/ApiClient.js index 0967d3fd4..dbc62949f 100644 --- a/app/utils/ApiClient.js +++ b/app/utils/ApiClient.js @@ -5,10 +5,12 @@ import stores from "stores"; import download from "./download"; import { AuthorizationError, + BadRequestError, NetworkError, NotFoundError, OfflineError, RequestError, + ServiceUnavailableError, UpdateRequiredError, } from "./errors"; @@ -141,6 +143,10 @@ class ApiClient { throw new UpdateRequiredError(error.message); } + if (response.status === 400) { + throw new BadRequestError(error.message); + } + if (response.status === 403) { throw new AuthorizationError(error.message); } @@ -149,6 +155,10 @@ class ApiClient { throw new NotFoundError(error.message); } + if (response.status === 503) { + throw new ServiceUnavailableError(error.message); + } + throw new RequestError(error.message); }; diff --git a/app/utils/errors.js b/app/utils/errors.js index 662a57c7f..db71a7c3e 100644 --- a/app/utils/errors.js +++ b/app/utils/errors.js @@ -2,8 +2,10 @@ import ExtendableError from "es6-error"; export class AuthorizationError extends ExtendableError {} +export class BadRequestError extends ExtendableError {} export class NetworkError extends ExtendableError {} export class NotFoundError extends ExtendableError {} export class OfflineError extends ExtendableError {} +export class ServiceUnavailableError extends ExtendableError {} export class RequestError extends ExtendableError {} export class UpdateRequiredError extends ExtendableError {} diff --git a/server/api/attachments.js b/server/api/attachments.js index 6ff93e63e..a2d34d547 100644 --- a/server/api/attachments.js +++ b/server/api/attachments.js @@ -98,11 +98,18 @@ router.post("attachments.delete", auth(), async (ctx) => { const user = ctx.state.user; const attachment = await Attachment.findByPk(id); - const document = await Document.findByPk(attachment.documentId, { - userId: user.id, - }); - authorize(user, "update", document); + if (!attachment) { + throw new NotFoundError(); + } + if (attachment.documentId) { + const document = await Document.findByPk(attachment.documentId, { + userId: user.id, + }); + authorize(user, "update", document); + } + + authorize(user, "delete", attachment); await attachment.destroy(); await Event.create({ diff --git a/server/api/attachments.test.js b/server/api/attachments.test.js index e77c59274..3a6e47b41 100644 --- a/server/api/attachments.test.js +++ b/server/api/attachments.test.js @@ -43,6 +43,71 @@ describe("#attachments.delete", () => { expect(await Attachment.count()).toEqual(0); }); + it("should allow deleting an attachment without a document created by user", async () => { + const user = await buildUser(); + const attachment = await buildAttachment({ + teamId: user.teamId, + userId: user.id, + }); + + attachment.documentId = null; + await attachment.save(); + + const res = await server.post("/api/attachments.delete", { + body: { token: user.getJwtToken(), id: attachment.id }, + }); + + expect(res.status).toEqual(200); + expect(await Attachment.count()).toEqual(0); + }); + + it("should allow deleting an attachment without a document if admin", async () => { + const user = await buildUser({ isAdmin: true }); + const attachment = await buildAttachment({ + teamId: user.teamId, + }); + + attachment.documentId = null; + await attachment.save(); + + const res = await server.post("/api/attachments.delete", { + body: { token: user.getJwtToken(), id: attachment.id }, + }); + + expect(res.status).toEqual(200); + expect(await Attachment.count()).toEqual(0); + }); + + it("should not allow deleting an attachment in another team", async () => { + const user = await buildUser({ isAdmin: true }); + const attachment = await buildAttachment(); + + attachment.documentId = null; + await attachment.save(); + + const res = await server.post("/api/attachments.delete", { + body: { token: user.getJwtToken(), id: attachment.id }, + }); + + expect(res.status).toEqual(403); + }); + + it("should not allow deleting an attachment without a document", async () => { + const user = await buildUser(); + const attachment = await buildAttachment({ + teamId: user.teamId, + }); + + attachment.documentId = null; + await attachment.save(); + + const res = await server.post("/api/attachments.delete", { + body: { token: user.getJwtToken(), id: attachment.id }, + }); + + expect(res.status).toEqual(403); + }); + it("should not allow deleting an attachment belonging to a document user does not have access to", async () => { const user = await buildUser(); const collection = await buildCollection({ diff --git a/server/api/index.js b/server/api/index.js index 8fd0f9303..1b273afaf 100644 --- a/server/api/index.js +++ b/server/api/index.js @@ -31,13 +31,13 @@ const api = new Koa(); const router = new Router(); // middlewares +api.use(errorHandling()); api.use( bodyParser({ multipart: true, formidable: { maxFieldsSize: 10 * 1024 * 1024 }, }) ); -api.use(errorHandling()); api.use(methodOverride()); api.use(validation()); api.use(apiWrapper()); diff --git a/server/auth/email.js b/server/auth/email.js index 1f277d1d2..defa94594 100644 --- a/server/auth/email.js +++ b/server/auth/email.js @@ -45,7 +45,11 @@ router.post("email", async (ctx) => { user.lastSigninEmailSentAt && user.lastSigninEmailSentAt > subMinutes(new Date(), 2) ) { - ctx.redirect(`${team.url}?notice=email-auth-ratelimit`); + ctx.body = { + redirect: `${team.url}?notice=email-auth-ratelimit`, + message: "Rate limit exceeded", + success: false, + }; return; } diff --git a/server/mailer.js b/server/mailer.js index 2d89b4366..ee0c14a8c 100644 --- a/server/mailer.js +++ b/server/mailer.js @@ -26,6 +26,8 @@ import { baseStyles } from "./emails/components/EmailLayout"; import { createQueue } from "./utils/queue"; const log = debug("emails"); +const useTestEmailService = + process.env.NODE_ENV !== "production" && !process.env.SMTP_USERNAME; type Emails = "welcome" | "export"; @@ -73,7 +75,7 @@ export class Mailer { try { log(`Sending email "${data.title}" to ${data.to}`); - await transporter.sendMail({ + const info = await transporter.sendMail({ from: process.env.SMTP_FROM_EMAIL, replyTo: process.env.SMTP_REPLY_EMAIL || process.env.SMTP_FROM_EMAIL, to: data.to, @@ -82,6 +84,10 @@ export class Mailer { text: data.text, attachments: data.attachments, }); + + if (useTestEmailService) { + log("Email Preview URL: %s", nodemailer.getTestMessageUrl(info)); + } } catch (err) { if (process.env.SENTRY_DSN) { Sentry.captureException(err); @@ -159,6 +165,10 @@ export class Mailer { }; constructor() { + this.loadTransport(); + } + + async loadTransport() { if (process.env.SMTP_HOST) { let smtpConfig = { host: process.env.SMTP_HOST, @@ -174,6 +184,24 @@ export class Mailer { }; } + this.transporter = nodemailer.createTransport(smtpConfig); + return; + } + + if (useTestEmailService) { + log("SMTP_USERNAME not provided, generating test account…"); + let testAccount = await nodemailer.createTestAccount(); + + const smtpConfig = { + host: "smtp.ethereal.email", + port: 587, + secure: false, + auth: { + user: testAccount.user, + pass: testAccount.pass, + }, + }; + this.transporter = nodemailer.createTransport(smtpConfig); } } diff --git a/server/policies/attachment.js b/server/policies/attachment.js new file mode 100644 index 000000000..d7105402d --- /dev/null +++ b/server/policies/attachment.js @@ -0,0 +1,14 @@ +// @flow +import { Attachment, User } from "../models"; +import policy from "./policy"; + +const { allow } = policy; + +allow(User, "create", Attachment); + +allow(User, "delete", Attachment, (actor, attachment) => { + if (!attachment || attachment.teamId !== actor.teamId) return false; + if (actor.isAdmin) return true; + if (actor.id === attachment.userId) return true; + return false; +}); diff --git a/server/policies/index.js b/server/policies/index.js index 7be7f01e7..cb5df353f 100644 --- a/server/policies/index.js +++ b/server/policies/index.js @@ -1,7 +1,8 @@ // @flow -import { Team, User, Collection, Document, Group } from "../models"; +import { Attachment, Team, User, Collection, Document, Group } from "../models"; import policy from "./policy"; import "./apiKey"; +import "./attachment"; import "./collection"; import "./document"; import "./integration"; @@ -24,7 +25,7 @@ type Policy = { */ export function serialize( model: User, - target: Team | Collection | Document | Group + target: Attachment | Team | Collection | Document | Group ): Policy { let output = {}; diff --git a/server/routes.js b/server/routes.js index 2dfa71b77..b17920196 100644 --- a/server/routes.js +++ b/server/routes.js @@ -8,7 +8,6 @@ import sendfile from "koa-sendfile"; import serve from "koa-static"; import { languages } from "../shared/i18n"; import environment from "./env"; -import { NotFoundError } from "./errors"; import apexRedirect from "./middlewares/apexRedirect"; import { opensearchResponse } from "./utils/opensearch"; import { robotsResponse } from "./utils/robots"; @@ -78,7 +77,8 @@ router.get("/locales/:lng.json", async (ctx) => { let { lng } = ctx.params; if (!languages.includes(lng)) { - throw new NotFoundError(); + ctx.status = 404; + return; } if (process.env.NODE_ENV === "production") { diff --git a/server/static/index.html b/server/static/index.html index 2f79916fb..db53cc129 100644 --- a/server/static/index.html +++ b/server/static/index.html @@ -54,9 +54,11 @@ ignoreErrors: [ "ResizeObserver loop limit exceeded", "AuthorizationError", + "BadRequestError", "NetworkError", "NotFoundError", "OfflineError", + "ServiceUnavailableError", "UpdateRequiredError", "ChunkLoadError", ], diff --git a/shared/i18n/locales/de_DE/translation.json b/shared/i18n/locales/de_DE/translation.json index 637d787b8..4e3f182ef 100644 --- a/shared/i18n/locales/de_DE/translation.json +++ b/shared/i18n/locales/de_DE/translation.json @@ -66,7 +66,7 @@ "Placeholder": "Platzhalter", "Quote": "Zitat", "Remove link": "Link entfernen", - "Search or paste a link": "Suchen oder Einfügen eines Links", + "Search or paste a link": "Suchen oder fügen Sie ein Link ein", "Strikethrough": "Durchstreichen", "Bold": "Fett", "Subheading": "Untertitel", @@ -82,6 +82,8 @@ "Change Language": "Sprache ändern", "Dismiss": "Ablehnen", "Keyboard shortcuts": "Tastaturkürzel", + "Expand": "Ausklappen", + "Collapse": "Zusammenklappen", "New collection": "Neue Sammlung", "Collections": "Sammlungen", "Untitled": "Ohne Titel", @@ -212,7 +214,7 @@ "Invited": "Eingeladen", "Admin": "Administrator", "Collections are for grouping your knowledge base. They work best when organized around a topic or internal team — Product or Engineering for example.": "Sammlungen dienen zur Gruppierung Ihrer Wissensbasis. Sie funktionieren am besten, wenn sie nach einem Thema oder einem internen Team organisiert sind - beispielsweise nach Produkt oder Engineering.", - "Creating": "Erstellen", + "Creating": "Wird erstellt", "Create": "Erstellen", "Recently viewed": "Zuletzt angesehen", "Created by me": "Von mir erstellt", @@ -227,7 +229,7 @@ "New from template": "Neu aus Vorlage", "Publish": "Veröffentlichen", "Publish document": "Dokument veröffentlichen", - "Publishing": "Am Veröffentlichen", + "Publishing": "Wird publiziert", "No documents found for your filters.": "Keine Dokumente anhand Ihre Filter gefunden.", "You’ve not got any drafts at the moment.": "Sie haben im Moment keine Entwürfe.", "Not found": "Nicht gefunden", diff --git a/shared/i18n/locales/es_ES/translation.json b/shared/i18n/locales/es_ES/translation.json index cd6cad592..4734b27cf 100644 --- a/shared/i18n/locales/es_ES/translation.json +++ b/shared/i18n/locales/es_ES/translation.json @@ -73,8 +73,8 @@ "Table": "Tabla", "Tip": "Sugerencia", "Tip notice": "Aviso de sugerencia", - "Warning": "Advertencia", - "Warning notice": "Aviso de advertencia", + "Warning": "Atención", + "Warning notice": "Aviso", "Icon": "Ícono", "Loading": "Cargando", "Search": "Buscar", @@ -82,6 +82,8 @@ "Change Language": "Cambiar Idioma", "Dismiss": "Descartar", "Keyboard shortcuts": "Atajos del teclado", + "Expand": "Expandir", + "Collapse": "Colapso", "New collection": "Nueva colección", "Collections": "Colecciones", "Untitled": "Sin título", @@ -122,7 +124,7 @@ "Edit collection": "Editar colección", "Delete collection": "Eliminar colección", "Export collection": "Exportar colección", - "Document duplicated": "Documento Duplicado", + "Document duplicated": "Documento duplicado", "Document archived": "Documento archivado", "Document restored": "Documento restaurado", "Document unpublished": "Documento no publicado", @@ -166,13 +168,13 @@ "Make {{ userName }} an admin…": "Hacer a {{ userName }} un administrador…", "Revoke invite": "Revocar Invitación", "Activate account": "Activar cuenta", - "Suspend account": "Suspender Cuenta", + "Suspend account": "Suspender cuenta", "Documents": "Documentos", "The document archive is empty at the moment.": "El archivo de documento está vacío en este momento.", "Search in collection": "Buscar en colección", "<0>{{collectionName}} doesn’t contain any documents yet.": "<0>{{collectionName}} todavía no contiene ningún documento.", "Get started by creating a new one!": "¡Empiece creando uno nuevo!", - "Create a document": "Crear Documento", + "Create a document": "Crear documento", "Manage members": "Administrar miembros", "Pinned": "Fijado", "Recently updated": "Recientemente actualizado", @@ -268,7 +270,7 @@ "Blockquote": "Bloque de cita", "Horizontal divider": "Divisor horizontal", "Inline code": "Código en línea", - "Not Found": "No Encontrado", + "Not Found": "No encontrado", "We were unable to find the page you’re looking for.": "No pudimos encontrar la página que buscabas.", "Use the <1>{{meta}}+K shortcut to search from anywhere in your knowledge base": "Usa el atajo <1>{{meta}}+K para buscar desde cualquier lugar de tu base de conocimientos", "No documents found for your search filters. <1>Create a new document?": "No se encontraron documentos para sus filtros de búsqueda. <1> ¿Crear un nuevo documento?", diff --git a/shared/i18n/locales/fr_FR/translation.json b/shared/i18n/locales/fr_FR/translation.json index a88f303a1..507618a24 100644 --- a/shared/i18n/locales/fr_FR/translation.json +++ b/shared/i18n/locales/fr_FR/translation.json @@ -82,6 +82,8 @@ "Change Language": "Changer de Langue", "Dismiss": "Rejeter", "Keyboard shortcuts": "Raccourcis clavier", + "Expand": "Expand", + "Collapse": "Collapse", "New collection": "Nouvelle collection", "Collections": "Collections", "Untitled": "Sans titre", diff --git a/shared/i18n/locales/ja_JP/translation.json b/shared/i18n/locales/ja_JP/translation.json index 1cc7284b6..c03a8e69e 100644 --- a/shared/i18n/locales/ja_JP/translation.json +++ b/shared/i18n/locales/ja_JP/translation.json @@ -1,11 +1,11 @@ { - "currently editing": "currently editing", - "currently viewing": "currently viewing", - "viewed {{ timeAgo }} ago": "viewed {{ timeAgo }} ago", - "You": "You", - "Trash": "Trash", + "currently editing": "現在編集中", + "currently viewing": "閲覧中", + "viewed {{ timeAgo }} ago": "{{ timeAgo }}前に見た", + "You": "あなた", + "Trash": "ゴミ箱", "Archive": "Archive", - "Drafts": "Drafts", + "Drafts": "下書き", "Templates": "Templates", "Deleted Collection": "Deleted Collection", "deleted": "deleted", @@ -15,12 +15,12 @@ "saved": "saved", "updated": "updated", "Never viewed": "Never viewed", - "Viewed": "Viewed", + "Viewed": "見た", "in": "in", "New": "New", - "Only visible to you": "Only visible to you", + "Only visible to you": "あなたにしか見えない", "Draft": "Draft", - "Template": "Template", + "Template": "テンプレート", "New doc": "New doc", "More options": "More options", "Insert column after": "Insert column after", @@ -30,268 +30,270 @@ "Align center": "Align center", "Align left": "Align left", "Align right": "Align right", - "Bulleted list": "Bulleted list", - "Todo list": "Todo list", + "Bulleted list": "順不同リスト", + "Todo list": "チェックリスト", "Code block": "Code block", "Copied to clipboard": "Copied to clipboard", - "Code": "Code", + "Code": "コード", "Create link": "Create link", - "Sorry, an error occurred creating the link": "Sorry, an error occurred creating the link", + "Sorry, an error occurred creating the link": "エラーのため、リンクの作成に失敗しました。", "Create a new doc": "Create a new doc", "Delete column": "Delete column", "Delete row": "Delete row", "Delete table": "Delete table", - "Italic": "Italic", - "Sorry, that link won’t work for this embed type": "Sorry, that link won’t work for this embed type", + "Italic": "斜体", + "Sorry, that link won’t work for this embed type": "申し訳ありませんが、この埋め込みタイプではリンクが機能しません", "Find or create a doc": "Find or create a doc", "Big heading": "Big heading", - "Medium heading": "Medium heading", - "Small heading": "Small heading", - "Heading": "Heading", + "Medium heading": "中見出し", + "Small heading": "小見出し", + "Heading": "見出し", "Divider": "Divider", - "Image": "Image", - "Sorry, an error occurred uploading the image": "Sorry, an error occurred uploading the image", + "Image": "画像", + "Sorry, an error occurred uploading the image": "エラーにより画像のアップロードに失敗しました。", "Info": "Info", "Info notice": "Info notice", - "Link": "Link", - "Link copied to clipboard": "Link copied to clipboard", - "Highlight": "Highlight", - "Type '/' to insert": "Type '/' to insert", + "Link": "リンク", + "Link copied to clipboard": "リンクをクリップボードにコピーしました。", + "Highlight": "ハイライト", + "Type '/' to insert": "タイプ 「/」…", "Keep typing to filter": "Keep typing to filter", - "No results": "No results", + "No results": "検索結果はありません", "Open link": "Open link", "Ordered list": "Ordered list", "Paste a link": "Paste a link", - "Paste a {{service}} link…": "Paste a {{service}} link…", + "Paste a {{service}} link…": "{{service}}のリンクに貼り付けます。", "Placeholder": "Placeholder", "Quote": "Quote", "Remove link": "Remove link", "Search or paste a link": "Search or paste a link", - "Strikethrough": "Strikethrough", - "Bold": "Bold", + "Strikethrough": "取り消し線", + "Bold": "太字", "Subheading": "Subheading", "Table": "Table", - "Tip": "Tip", + "Tip": "ヒント", "Tip notice": "Tip notice", - "Warning": "Warning", + "Warning": "警告", "Warning notice": "Warning notice", - "Icon": "Icon", - "Loading": "Loading", - "Search": "Search", - "Outline is available in your language {{optionLabel}}, would you like to change?": "Outline is available in your language {{optionLabel}}, would you like to change?", - "Change Language": "Change Language", + "Icon": "アイコン", + "Loading": "ローディング...", + "Search": "検索", + "Outline is available in your language {{optionLabel}}, would you like to change?": "Outlineは、{{optionLabel}}で利用できます。言語を切り替えてみませんか?", + "Change Language": "言語の変更", "Dismiss": "Dismiss", "Keyboard shortcuts": "Keyboard shortcuts", + "Expand": "Expand", + "Collapse": "Collapse", "New collection": "New collection", "Collections": "Collections", - "Untitled": "Untitled", - "Home": "Home", - "Starred": "Starred", + "Untitled": "文書", + "Home": "ホーム", + "Starred": "スター文書", "Invite people…": "Invite people…", "Invite people": "Invite people", - "Create a collection": "Create a collection", + "Create a collection": "コレクションを作る", "Return to App": "Return to App", "Profile": "Profile", - "Notifications": "Notifications", - "API Tokens": "API Tokens", + "Notifications": "通知", + "API Tokens": "APIトークン", "Details": "Details", "Security": "Security", - "People": "People", - "Groups": "Groups", + "People": "人々", + "Groups": "グループ", "Share Links": "Share Links", - "Export Data": "Export Data", + "Export Data": "データのエクスポート", "Integrations": "Integrations", "Installation": "Installation", - "Settings": "Settings", - "API documentation": "API documentation", - "Changelog": "Changelog", - "Send us feedback": "Send us feedback", - "Report a bug": "Report a bug", - "Appearance": "Appearance", - "System": "System", - "Light": "Light", - "Dark": "Dark", - "Log out": "Log out", - "Collection permissions": "Collection permissions", + "Settings": "設定", + "API documentation": "API ドキュメント", + "Changelog": "変更履歴", + "Send us feedback": "フィードバック", + "Report a bug": "バグ報告", + "Appearance": "外観", + "System": "システム", + "Light": "ライトモード", + "Dark": "ダークモード", + "Log out": "ログアウト", + "Collection permissions": "コレクションの権限", "New document": "New document", - "Import document": "Import document", - "Edit": "Edit", + "Import document": "ドキュメントのインポート", + "Edit": "編集", "Permissions": "Permissions", - "Export": "Export", - "Delete": "Delete", - "Edit collection": "Edit collection", - "Delete collection": "Delete collection", - "Export collection": "Export collection", - "Document duplicated": "Document duplicated", + "Export": "エクスポート", + "Delete": "削除", + "Edit collection": "編集コレクション", + "Delete collection": "コレクションの削除", + "Export collection": "エクスポート(コレクション)", + "Document duplicated": "ドキュメントの重複", "Document archived": "Document archived", "Document restored": "Document restored", "Document unpublished": "Document unpublished", "Restore": "Restore", - "Choose a collection": "Choose a collection", - "Unpin": "Unpin", + "Choose a collection": "コレクションを選ぶ", + "Unpin": "固定しない", "Pin to collection": "Pin to collection", - "Unstar": "Unstar", - "Star": "Star", + "Unstar": "スター解除", + "Star": "スター", "Share link": "Share link", "Enable embeds": "Enable embeds", "Disable embeds": "Disable embeds", - "New nested document": "New nested document", - "Create template": "Create template", - "Duplicate": "Duplicate", - "Unpublish": "Unpublish", + "New nested document": "新しい入れ子になった文書", + "Create template": "テンプレートの作成", + "Duplicate": "文書の複製", + "Unpublish": "未発表", "Move": "Move", - "History": "History", - "Download": "Download", - "Print": "Print", - "Delete {{ documentName }}": "Delete {{ documentName }}", - "Share document": "Share document", + "History": "時間旅行", + "Download": "ダウンロード", + "Print": "プリント", + "Delete {{ documentName }}": "{{ documentName }}を削除します。", + "Share document": "共有文書", "Edit group": "Edit group", "Delete group": "Delete group", - "Members": "Members", - "collection": "collection", - "New document in <1>{{collectionName}}": "New document in <1>{{collectionName}}", + "Members": "メンバー", + "collection": "コレクション", + "New document in <1>{{collectionName}}": "<1>「{{collectionName}}」コレクションに新規ドキュメントを作成しました。", "New template": "New template", "Link copied": "Link copied", - "Restore version": "Restore version", + "Restore version": "復元", "Copy link": "Copy link", "Share link revoked": "Share link revoked", "Share link copied": "Share link copied", "Go to document": "Go to document", "Revoke link": "Revoke link", "By {{ author }}": "By {{ author }}", - "Are you sure you want to make {{ userName }} an admin? Admins can modify team and billing information.": "Are you sure you want to make {{ userName }} an admin? Admins can modify team and billing information.", - "Are you sure you want to make {{ userName }} a member?": "Are you sure you want to make {{ userName }} a member?", - "Are you sure you want to suspend this account? Suspended users will be prevented from logging in.": "Are you sure you want to suspend this account? Suspended users will be prevented from logging in.", - "Make {{ userName }} a member…": "Make {{ userName }} a member…", - "Make {{ userName }} an admin…": "Make {{ userName }} an admin…", + "Are you sure you want to make {{ userName }} an admin? Admins can modify team and billing information.": "本当に{{ userName }}を管理者にしていいのか?管理者は課金情報を修正することができます。また、チームを変更することもできます。", + "Are you sure you want to make {{ userName }} a member?": "本当に{{ userName }}をメンバーにしていいのか?", + "Are you sure you want to suspend this account? Suspended users will be prevented from logging in.": "このアカウントを停止してもよろしいですか? これにより、ユーザーのログインが停止されます", + "Make {{ userName }} a member…": "{{ userName }}がチームの一員になる...", + "Make {{ userName }} an admin…": "{{ userName }}に管理者権限を与えてください。", "Revoke invite": "Revoke invite", - "Activate account": "Activate account", - "Suspend account": "Suspend account", + "Activate account": "アカウントの有効化", + "Suspend account": "アカウントの一時停止", "Documents": "Documents", "The document archive is empty at the moment.": "The document archive is empty at the moment.", "Search in collection": "Search in collection", - "<0>{{collectionName}} doesn’t contain any documents yet.": "<0>{{collectionName}} doesn’t contain any documents yet.", + "<0>{{collectionName}} doesn’t contain any documents yet.": "<0>{{collectionName}}には文書は含まれていません。", "Get started by creating a new one!": "Get started by creating a new one!", - "Create a document": "Create a document", + "Create a document": "ドキュメントの作成", "Manage members": "Manage members", - "Pinned": "Pinned", - "Recently updated": "Recently updated", + "Pinned": "固定", + "Recently updated": "最近の更新", "Recently published": "Recently published", "Least recently updated": "Least recently updated", "A–Z": "A–Z", - "The collection was updated": "The collection was updated", + "The collection was updated": "コレクションを更新しました。", "You can edit the name and other details at any time, however doing so often might confuse your team mates.": "You can edit the name and other details at any time, however doing so often might confuse your team mates.", - "Name": "Name", + "Name": "名前", "Description": "Description", "More details about this collection…": "More details about this collection…", "Private collection": "Private collection", "A private collection will only be visible to invited team members.": "A private collection will only be visible to invited team members.", - "Saving": "Saving", - "Save": "Save", - "{{ groupName }} was added to the collection": "{{ groupName }} was added to the collection", + "Saving": "保存文書...", + "Save": "セーブ", + "{{ groupName }} was added to the collection": "コレクションに{{ groupName }}を追加しました。", "Could not add user": "Could not add user", - "Can’t find the group you’re looking for?": "Can’t find the group you’re looking for?", + "Can’t find the group you’re looking for?": "お探しのグループが見つかりませんか?", "Create a group": "Create a group", "Search by group name": "Search by group name", "Search groups": "Search groups", "No groups matching your search": "No groups matching your search", "No groups left to add": "No groups left to add", "Add": "Add", - "{{ userName }} was added to the collection": "{{ userName }} was added to the collection", + "{{ userName }} was added to the collection": "コレクションに{{ userName }}を追加しました。", "Need to add someone who’s not yet on the team yet?": "Need to add someone who’s not yet on the team yet?", "Invite people to {{ teamName }}": "Invite people to {{ teamName }}", - "Search by name": "Search by name", + "Search by name": "名前から探す", "Search people": "Search people", "No people matching your search": "No people matching your search", "No people left to add": "No people left to add", "Read only": "Read only", "Read & Edit": "Read & Edit", - "Remove": "Remove", + "Remove": "削除", "Active <1> ago": "Active <1> ago", "Never signed in": "Never signed in", "Invited": "Invited", - "Admin": "Admin", - "Collections are for grouping your knowledge base. They work best when organized around a topic or internal team — Product or Engineering for example.": "Collections are for grouping your knowledge base. They work best when organized around a topic or internal team — Product or Engineering for example.", + "Admin": "アドミン", + "Collections are for grouping your knowledge base. They work best when organized around a topic or internal team — Product or Engineering for example.": "コレクションは、知識ベースを整理するために使用されます。ドキュメントをトピックにグループ化します。e.g. 1つのコレクションは、研究開発に関連するすべての文書を保持することができます。", "Creating": "Creating", - "Create": "Create", + "Create": "作る", "Recently viewed": "Recently viewed", "Created by me": "Created by me", "Hide contents": "Hide contents", "Show contents": "Show contents", - "Archived": "Archived", + "Archived": "アーカイブ", "Anyone with the link <1>can view this document": "Anyone with the link <1>can view this document", - "Share": "Share", + "Share": "共有", "Save Draft": "Save Draft", "Done Editing": "Done Editing", "Edit {{noun}}": "Edit {{noun}}", "New from template": "New from template", - "Publish": "Publish", - "Publish document": "Publish document", + "Publish": "公開", + "Publish document": "ドキュメントの公開", "Publishing": "Publishing", "No documents found for your filters.": "No documents found for your filters.", - "You’ve not got any drafts at the moment.": "You’ve not got any drafts at the moment.", - "Not found": "Not found", - "We were unable to find the page you’re looking for. Go to the <2>homepage?": "We were unable to find the page you’re looking for. Go to the <2>homepage?", - "Offline": "Offline", - "We were unable to load the document while offline.": "We were unable to load the document while offline.", - "Your account has been suspended": "Your account has been suspended", - "A team admin (<1>{{suspendedContactEmail}}) has suspended your account. To re-activate your account, please reach out to them directly.": "A team admin (<1>{{suspendedContactEmail}}) has suspended your account. To re-activate your account, please reach out to them directly.", - "{{userName}} was added to the group": "{{userName}} was added to the group", + "You’ve not got any drafts at the moment.": "この時は下書きがない。", + "Not found": "見つかりません", + "We were unable to find the page you’re looking for. Go to the <2>homepage?": "お探しのページが見つかりませんでした。<2>ホームページに戻りますか?", + "Offline": "インターネットなし", + "We were unable to load the document while offline.": "インターネットに接続していない状態でドキュメントを読み込むことができません。", + "Your account has been suspended": "あなたのアカウントは停止されています。", + "A team admin (<1>{{suspendedContactEmail}}) has suspended your account. To re-activate your account, please reach out to them directly.": "チームの管理者が(<1>{{suspendedContactEmail}})あなたのアカウントを停止しました。アカウントを再アクティベートするには、直接連絡する必要があります。", + "{{userName}} was added to the group": "{{userName}}が加わりました。", "Add team members below to give them access to the group. Need to add someone who’s not yet on the team yet?": "Add team members below to give them access to the group. Need to add someone who’s not yet on the team yet?", "Invite them to {{teamName}}": "Invite them to {{teamName}}", - "{{userName}} was removed from the group": "{{userName}} was removed from the group", - "Could not remove user": "Could not remove user", + "{{userName}} was removed from the group": "{{userName}}はグループから外された。", + "Could not remove user": "このユーザーを削除することはできません。", "Add people": "Add people", - "This group has no members.": "This group has no members.", - "Outline is designed to be fast and easy to use. All of your usual keyboard shortcuts work here, and there’s Markdown too.": "Outline is designed to be fast and easy to use. All of your usual keyboard shortcuts work here, and there’s Markdown too.", + "This group has no members.": "このグループにはメンバーがいません。", + "Outline is designed to be fast and easy to use. All of your usual keyboard shortcuts work here, and there’s Markdown too.": "Outlineは、高速で使いやすいように設計されていました。すでに慣れ親しんだキーボードショートカットは完璧に機能するはずです。Markdownにも対応しています。", "Navigation": "Navigation", "New document in current collection": "New document in current collection", "Edit current document": "Edit current document", "Move current document": "Move current document", "Jump to search": "Jump to search", "Jump to dashboard": "Jump to dashboard", - "Table of contents": "Table of contents", + "Table of contents": "目次", "Open this guide": "Open this guide", - "Editor": "Editor", + "Editor": "エディタ", "Save and exit document edit mode": "Save and exit document edit mode", "Publish and exit document edit mode": "Publish and exit document edit mode", "Save document and continue editing": "Save document and continue editing", "Cancel editing": "Cancel editing", - "Underline": "Underline", - "Undo": "Undo", - "Redo": "Redo", + "Underline": "下線", + "Undo": "元に戻す", + "Redo": "やり直し", "Markdown": "Markdown", - "Large header": "Large header", - "Medium header": "Medium header", - "Small header": "Small header", - "Numbered list": "Numbered list", - "Blockquote": "Blockquote", - "Horizontal divider": "Horizontal divider", - "Inline code": "Inline code", + "Large header": "大見出し", + "Medium header": "中見出し", + "Small header": "小見出し", + "Numbered list": "順序付きのリスト", + "Blockquote": "引用", + "Horizontal divider": "区切り線", + "Inline code": "インラインコード", "Not Found": "Not Found", - "We were unable to find the page you’re looking for.": "We were unable to find the page you’re looking for.", - "Use the <1>{{meta}}+K shortcut to search from anywhere in your knowledge base": "Use the <1>{{meta}}+K shortcut to search from anywhere in your knowledge base", + "We were unable to find the page you’re looking for.": "お探しのページが見つかりませんでした。", + "Use the <1>{{meta}}+K shortcut to search from anywhere in your knowledge base": "<1>{{meta}}+K ショートカットを使用して、いつでも検索バーを開くことができます。", "No documents found for your search filters. <1>Create a new document?": "No documents found for your search filters. <1>Create a new document?", "Clear filters": "Clear filters", "Profile saved": "Profile saved", - "Profile picture updated": "Profile picture updated", - "Unable to upload new profile picture": "Unable to upload new profile picture", - "Photo": "Photo", + "Profile picture updated": "プロフィール写真の更新に成功しました。", + "Unable to upload new profile picture": "プロフィール写真をアップロードできません。", + "Photo": "写真", "Upload": "Upload", - "Full name": "Full name", - "Language": "Language", - "Please note that translations are currently in early access.<1>Community contributions are accepted though our <4>translation portal": "Please note that translations are currently in early access.<1>Community contributions are accepted though our <4>translation portal", - "Delete Account": "Delete Account", - "You may delete your account at any time, note that this is unrecoverable": "You may delete your account at any time, note that this is unrecoverable", - "Delete account": "Delete account", + "Full name": "氏名", + "Language": "言語", + "Please note that translations are currently in early access.<1>Community contributions are accepted though our <4>translation portal": "翻訳は早期アクセスであることをご理解ください。<1> 翻訳への貢献は、<4>コミュニティポータルで受け付けています。", + "Delete Account": "アカウントの終了", + "You may delete your account at any time, note that this is unrecoverable": "アカウントはいつでも削除することができます。これは取り返しのつかないことです。", + "Delete account": "アカウントの削除", "Alphabetical": "Alphabetical", - "You’ve not starred any documents yet.": "You’ve not starred any documents yet.", - "There are no templates just yet. You can create templates to help your team create consistent and accurate documentation.": "There are no templates just yet. You can create templates to help your team create consistent and accurate documentation.", - "Trash is empty at the moment.": "Trash is empty at the moment.", + "You’ve not starred any documents yet.": "あなたは文書に「スター」を付けていません。", + "There are no templates just yet. You can create templates to help your team create consistent and accurate documentation.": "ここにはテンプレートはありません。テンプレートを作成することで、ドキュメントの一貫性と正確性を高めることができます。", + "Trash is empty at the moment.": "ゴミ箱には何もない", "You joined": "You joined", "Joined": "Joined", "{{ time }} ago.": "{{ time }} ago.", "Suspended": "Suspended", "Edit Profile": "Edit Profile", - "{{ userName }} hasn’t updated any documents yet.": "{{ userName }} hasn’t updated any documents yet." + "{{ userName }} hasn’t updated any documents yet.": "{{ userName }}はまだ文書を更新していません" } diff --git a/shared/i18n/locales/ko_KR/translation.json b/shared/i18n/locales/ko_KR/translation.json index 79fa51019..834c3d56a 100644 --- a/shared/i18n/locales/ko_KR/translation.json +++ b/shared/i18n/locales/ko_KR/translation.json @@ -82,6 +82,8 @@ "Change Language": "언어 변경", "Dismiss": "닫기", "Keyboard shortcuts": "단축키", + "Expand": "Expand", + "Collapse": "Collapse", "New collection": "새 컬렉션", "Collections": "컬렉션", "Untitled": "제목없음", diff --git a/shared/i18n/locales/pt_BR/translation.json b/shared/i18n/locales/pt_BR/translation.json index cf6cc2103..d649d231d 100644 --- a/shared/i18n/locales/pt_BR/translation.json +++ b/shared/i18n/locales/pt_BR/translation.json @@ -82,6 +82,8 @@ "Change Language": "Mudar Idioma", "Dismiss": "Descartar", "Keyboard shortcuts": "Atalhos de teclado", + "Expand": "Expand", + "Collapse": "Collapse", "New collection": "New collection", "Collections": "Coleções", "Untitled": "Sem título", @@ -130,8 +132,8 @@ "Choose a collection": "Escolha uma coleção", "Unpin": "Desafixar", "Pin to collection": "Fixar à coleção", - "Unstar": "Desfavoritar", - "Star": "Favoritar", + "Unstar": "Remover estrela", + "Star": "Adicionar aos favoritos", "Share link": "Share link", "Enable embeds": "Enable embeds", "Disable embeds": "Disable embeds", diff --git a/shared/i18n/locales/pt_PT/translation.json b/shared/i18n/locales/pt_PT/translation.json index c9b4eb801..5214a1833 100644 --- a/shared/i18n/locales/pt_PT/translation.json +++ b/shared/i18n/locales/pt_PT/translation.json @@ -43,7 +43,7 @@ "Delete table": "Eliminar tabela", "Italic": "Itálico", "Sorry, that link won’t work for this embed type": "Desculpe, esse link não funcionará para este tipo de incorporação", - "Find or create a doc": "Encontre ou crie um documento", + "Find or create a doc": "Pesquise ou crie um documento", "Big heading": "Cabeçalho grande", "Medium heading": "Cabeçalho médio", "Small heading": "Cabeçalho pequeno", @@ -57,7 +57,7 @@ "Link copied to clipboard": "Copiado para o clipboard", "Highlight": "Destaque", "Type '/' to insert": "Digite '/' para inserir", - "Keep typing to filter": "Continue digitando para filtrar", + "Keep typing to filter": "Continue a escrever para filtrar", "No results": "Sem resultados", "Open link": "Abrir link", "Ordered list": "Lista ordenada", @@ -82,6 +82,8 @@ "Change Language": "Alterar língua", "Dismiss": "Dispensar", "Keyboard shortcuts": "Atalhos do teclado", + "Expand": "Expandir", + "Collapse": "Fechar", "New collection": "Nova coleção", "Collections": "Coleções", "Untitled": "Sem título", diff --git a/shared/i18n/locales/ru_RU/translation.json b/shared/i18n/locales/ru_RU/translation.json index 998e4f51d..458a85602 100644 --- a/shared/i18n/locales/ru_RU/translation.json +++ b/shared/i18n/locales/ru_RU/translation.json @@ -82,6 +82,8 @@ "Change Language": "Сменить язык", "Dismiss": "Убрать", "Keyboard shortcuts": "Горячие клавиши", + "Expand": "Expand", + "Collapse": "Collapse", "New collection": "New collection", "Collections": "Коллекции", "Untitled": "Без названия", diff --git a/shared/i18n/locales/zh_CN/translation.json b/shared/i18n/locales/zh_CN/translation.json index cb44353d4..20986b8a6 100644 --- a/shared/i18n/locales/zh_CN/translation.json +++ b/shared/i18n/locales/zh_CN/translation.json @@ -82,6 +82,8 @@ "Change Language": "更改界面语言", "Dismiss": "忽略", "Keyboard shortcuts": "快捷键", + "Expand": "Expand", + "Collapse": "Collapse", "New collection": "New collection", "Collections": "文档集", "Untitled": "无标题文档", diff --git a/yarn.lock b/yarn.lock index e7797da37..52397eef8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8524,9 +8524,9 @@ node-modules-regexp@^1.0.0: integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA= node-notifier@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-8.0.0.tgz#a7eee2d51da6d0f7ff5094bc7108c911240c1620" - integrity sha512-46z7DUmcjoYdaWyXouuFNNfUo6eFa94t23c53c+lG/9Cvauk4a98rAUp9672X5dxGdQmLpPzTxzu8f/OeEPaFA== + version "8.0.1" + resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-8.0.1.tgz#f86e89bbc925f2b068784b31f382afdc6ca56be1" + integrity sha512-BvEXF+UmsnAfYfoapKM9nGxnP+Wn7P91YfXmrKnfcYCx6VBeoN5Ez5Ogck6I8Bi5k4RlpqRYaw75pAwzX9OphA== dependencies: growly "^1.3.0" is-wsl "^2.2.0" @@ -10667,9 +10667,11 @@ semver@^6.0.0, semver@^6.3.0: integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== semver@^7.2.1, semver@^7.3.2: - version "7.3.2" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" - integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== + version "7.3.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97" + integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw== + dependencies: + lru-cache "^6.0.0" sequelize-cli@^6.2.0: version "6.2.0" @@ -12293,9 +12295,9 @@ uuid@^3.3.2: integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== uuid@^8.1.0, uuid@^8.3.0: - version "8.3.1" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.1.tgz#2ba2e6ca000da60fce5a196954ab241131e05a31" - integrity sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg== + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== v8-compile-cache@^2.0.3, v8-compile-cache@^2.1.1: version "2.2.0"