@crowdin/app-project-module
Version:
Module that generates for you all common endpoints for serving standalone Crowdin App
572 lines (571 loc) • 16.9 kB
TypeScript
import Crowdin, { SourceFilesModel, TranslationStatusModel } from '@crowdin/crowdin-api-client';
import { Request } from 'express';
import { CrowdinClientRequest, ModuleKey } from '../../types';
import { JobClient, JobStoreType } from './util/types';
export interface IntegrationLogic extends ModuleKey {
/**
* Customize your app login form
*/
loginForm?: LoginForm;
/**
* Define login process via OAuth2 protocol
*/
oauthLogin?: OAuthLogin;
/**
* name of the root folder in Crowdin where files from integration will be stored, default your app name, will be ignored in case if {@link withRootFolder} is false
*/
appFolderName?: string;
/**
* flag that defines if the app should have a dedicated root folder in Crowdin files, default 'false'
*/
withRootFolder?: boolean;
/**
* Validate integration settings before saving
*/
validateSettings?: ({ client, credentials, appSettings, }: {
client: Crowdin;
credentials: any;
appSettings: AppSettings;
}) => Promise<{
[key: string]: string;
} | null>;
/**
* function which will be used to check connection with integration service
*/
checkConnection?: (apiCredentials: any) => Promise<void>;
/**
* function to get crowdin files that are related with this integration
*/
getCrowdinFiles?: (projectId: number, client: Crowdin, appRootFolder?: SourceFilesModel.Directory, config?: any, mode?: CrowdinFilesLoadMode) => Promise<TreeItem[]>;
/**
* function to get data from integration
*/
getIntegrationFiles: (apiCredentials: any, config?: any, parentId?: any, search?: any, page?: any, paginationData?: any) => Promise<TreeItem[] | ExtendedResult<TreeItem[]>>;
/**
* function to update crowdin files (e.g. pull integration data to crowdin source files)
*/
updateCrowdin: ({ projectId, client, credentials, request, rootFolder, appSettings, uploadTranslations, job, excludedTargetLanguages, }: {
projectId: number;
client: Crowdin;
credentials: any;
request: IntegrationFile[];
rootFolder?: SourceFilesModel.Directory;
appSettings?: any;
uploadTranslations?: boolean;
job: JobClient;
excludedTargetLanguages?: string[];
}) => Promise<void | ExtendedResult<void>>;
/**
* function to update integration content (e.g. load crowdin translations and push them to integration service)
*/
updateIntegration: ({ projectId, client, credentials, request, rootFolder, appSettings, job, }: {
projectId: number;
client: Crowdin;
credentials: any;
request: UpdateIntegrationRequest;
rootFolder?: SourceFilesModel.Directory;
appSettings?: any;
job: JobClient;
}) => Promise<void | ExtendedResult<void>>;
/**
* Store to use for memorizing job data
*/
jobStoreType?: JobStoreType;
/**
* function to define configuration(settings) modal for you app (by default app will not have any custom settings)
*/
getConfiguration?: (projectId: number, client: Crowdin, apiCredentials: any) => Promise<FormEntity[]>;
/**
* function to normalize saved settings
*/
normalizeSettings?: ({ appSettings, apiCredentials, client, }: {
appSettings: FormEntity[];
apiCredentials: any;
client?: Crowdin;
}) => Promise<FormEntity[]>;
/**
* Logout hook for cleanup logic
*/
onLogout?: (projectId: number, client: Crowdin, apiCredentials: any, config?: any) => Promise<void>;
/**
* flag to turn on auto reload of the tree whenever user updates the configuration
*/
reloadOnConfigSave?: boolean;
/**
* define info modal (help section) for you app (by default app will not have own info section)
*/
infoModal?: {
title: string;
content: string;
};
/**
* background jobs that will be executed for each crowdin project and user
*/
cronJobs?: CronJob[];
/**
* Enable new file sync when syncing via cron or webhook
*/
syncNewElements?: {
crowdin: boolean;
integration: boolean;
};
withCronSync?: {
crowdin: boolean;
integration: boolean;
};
withWebhookSync?: {
crowdin: boolean;
integration: boolean;
};
/**
* Enable file filtering
*/
filtering?: {
crowdinLanguages?: boolean;
/**
* Configuration for integration file filtering
*/
integrationFilterConfig?: any;
/**
* Enable file status filtering
*/
integrationFileStatus?: {
/**
* Enable file filtering by "isNew" status
*/
isNew?: boolean;
/**
* Enable file filtering by "isUpdated" status
*/
isUpdated?: boolean;
/**
* Enable file filtering by "failed" status
*/
failed?: boolean;
/**
* Enable file filtering by "notSynced" status
*/
notSynced?: boolean;
/**
* Enable file filtering by "synced" status
*/
synced?: boolean;
};
};
/**
* Enable integration folder open event
*/
integrationOneLevelFetching?: boolean;
/**
* Enable integration search event
*/
integrationSearchListener?: boolean;
/**
* Enable integration next page event
*/
integrationPagination?: boolean;
/**
* Enable progressive loading for Crowdin files (directories first, then files).
*/
progressiveCrowdinFilesLoading?: boolean;
/**
* Enable the option to upload translations to crowdin that are already present in the integration.
*/
uploadTranslations?: boolean;
/**
* Force sync translations from Crowdin to integration regardless of etag.
*/
forcePushTranslations?: boolean;
/**
* Force sync sources from integration to Crowdin.
*/
forcePushSources?: boolean;
/**
* Enable the option to upload file for translation into selected languages.
*/
excludedTargetLanguages?: boolean;
/**
* Enable the option to add 'Exclude paths' and 'Include paths' text fields to integration settings
*/
filterByPathIntegrationFiles?: boolean;
/**
* function to get crowdin file translation progress
*/
getFileProgress?: (projectId: number, client: Crowdin, fileId: number) => Promise<{
[key: number]: TranslationStatusModel.LanguageProgress[];
}>;
/**
* Register Crowdin webhook to get notified when translations are ready
*/
webhooks?: Webhooks;
/**
* define a notification for your application at the top of the screen
*/
notice?: {
title: string;
content: string;
type: NoticeType;
icon: boolean;
close: boolean;
};
/**
* Skip integration nodes
*/
skipIntegrationNodes?: SkipIntegrationNodes;
/**
* Configuration for toggling skipIntegrationNodes functionality with custom title and description
*/
skipIntegrationNodesToggle?: {
title: string;
description: string;
value: boolean;
};
/**
* Async progress checking time interval to update job progress, im ms.
*
* Default 1000
*/
asyncProgress?: {
checkInterval?: number;
};
/**
* The duration for storing user errors, default is 30 days.
*/
userErrorLifetimeDays?: number;
/**
* When true, folder filtering during automatic translation sync is bypassed, and the file tree is returned unchanged.
*/
skipAutoSyncFoldersFilter?: boolean;
}
export interface LoginForm {
fields: FormEntity[];
/**
* Override to implement request for retrieving access token (and refresh token if 'refresh' is enabled)
*/
performGetTokenRequest?: (fieldsCredentials: any) => Promise<LoginFormTokenResponse>;
/**
* Override to implement request for refreshing token (only if 'refresh' is enabled)
*/
performRefreshTokenRequest?: (currentCredentials: any) => Promise<LoginFormTokenResponse>;
/**
* default 'false' which means that the access token has no expiration date
*/
refresh?: boolean;
}
export interface OAuthLogin {
/**
* Extra field for login form
*/
loginFields?: FormField[];
/**
* Authorization url (e.g. https://github.com/login/oauth/authorize or https://accounts.google.com/o/oauth2/v2/auth)
*/
authorizationUrl?: string;
/**
* Authorization url getter
*/
getAuthorizationUrl?: (redirectUrl: string, loginForm: any) => string;
/**
* Access token url (e.g. https://github.com/login/oauth/access_token)
*/
accessTokenUrl: string;
/**
* Url to refresh token, default will use {@link accessTokenUrl}. Needed when {@link refresh} is enabled
*/
refreshTokenUrl?: string;
mode?: 'standard' | 'polling';
/**
* The scopes of access, usually expressed as a list of space-delimited, case-sensitive strings
*/
scope?: string;
/**
* Client id
*/
clientId: string;
/**
* Client secret
*/
clientSecret: string;
/**
* default '/oauth/code'
*/
redirectUriRoute?: string;
/**
* request/response fields mapping
*/
fieldsMapping?: {
/**
* default 'client_id'
*/
clientId?: string;
/**
* default 'client_secret'
*/
clientSecret?: string;
/**
* default 'scope'
*/
scope?: string;
/**
* default 'redirect_uri'
*/
redirectUri?: string;
/**
* default 'code'
*/
code: string;
/**
* default 'access_token'
*/
accessToken?: string;
/**
* default 'refresh_token'
*/
refreshToken?: string;
/**
* default 'expires_in'
*/
expiresIn?: string;
/**
* default 'state'
*/
state?: string;
};
/**
* default 'false' which means that the access token has no expiration date
*/
refresh?: boolean;
/**
* Additional URL parameters for authorizarion url
*/
extraAutorizationUrlParameters?: {
[key: string]: string;
};
/**
* Additional parameters for access token request
*/
extraAccessTokenParameters?: {
[key: string]: any;
};
/**
* Additional parameters for refresh token request
*/
extraRefreshTokenParameters?: {
[key: string]: any;
};
/**
* Override to implement request for retrieving access token (and refresh token if 'refresh' is enabled)
*/
performGetTokenRequest?: (code: string, query: {
[key: string]: any;
}, url: string, redirectUri: string, loginForm?: any) => Promise<any>;
/**
* Override to implement request for refreshing token (only if 'refresh' is enabled)
*/
performRefreshTokenRequest?: (currentCredentials: any, loginForm?: any) => Promise<any>;
}
export type CrowdinFilesLoadMode = 'directories' | 'files';
export interface BaseTreeItem {
id: string;
name: string;
parentId?: string;
nodeType?: IntegrationTreeElementType;
customContent?: string;
labels?: LabelTreeElement[];
disabled?: boolean;
tooltip?: string;
path?: string;
type?: string;
syncedAt?: string;
}
export interface File extends BaseTreeItem {
type: SourceFilesModel.FileType;
failed?: boolean;
excludedTargetLanguages?: string[];
}
export type Folder = BaseTreeItem;
export type TreeItem = File | Folder;
/**
* 0 - folder
* 1 - file
* 2 - branch
*/
type IntegrationTreeElementType = '0' | '1' | '2';
export type FormEntity = FormField | FormDelimiter;
export interface FormDelimiter {
label?: string;
labelHtml?: string;
category?: string;
}
export interface FormField {
key: string;
helpText?: string;
helpTextHtml?: string;
label: string;
type?: 'text' | 'password' | 'checkbox' | 'select' | 'textarea' | 'file' | 'notice';
defaultValue?: any;
/**
* only for select
*/
isMulti?: boolean;
/**
* only for select
*/
isSearchable?: boolean;
/**
* only for select
*/
options?: {
label: string;
value: string;
}[];
/**
* only for type file
*/
accept?: string;
/**
* field dependency settings
*/
dependencySettings?: string;
/**
* only for notice type
*/
noticeType?: string;
/**
* only for notice type
*/
noIcon?: boolean;
category?: string;
position?: number;
}
type NoticeType = 'info' | 'warning' | 'danger' | 'success' | 'error' | 'dataLostWarning';
export interface IntegrationCredentials {
id: string;
credentials: any;
crowdinId: string;
managers?: any;
}
export interface IntegrationConfig {
id: number;
integrationId: string;
crowdinId: string;
config: any;
}
export interface IntegrationFile {
id: string;
name: string;
type: SourceFilesModel.FileType;
parentId: string;
nodeType?: IntegrationTreeElementType;
}
export interface UpdateIntegrationRequest {
[fileId: string]: string[];
}
export interface IntegrationRequest extends CrowdinClientRequest {
integrationCredentials: any;
integrationSettings?: any;
}
export interface CronJob {
task: (projectId: number, client: Crowdin, apiCredentials: any, appRootFolder?: SourceFilesModel.Directory, config?: any) => Promise<void>;
expression: string;
}
export interface ExtendedResult<T> {
data?: T;
message?: string;
stopPagination?: boolean;
}
type LabelTreeElementType = 'primary' | 'secondary' | 'success' | 'warning' | 'info' | 'danger' | 'dark' | 'light';
export interface LabelTreeElement {
text: string;
type?: LabelTreeElementType;
tooltip?: string;
color?: string;
}
export declare enum Provider {
CROWDIN = "crowdin",
INTEGRATION = "integration"
}
export interface IntegrationSyncSettings {
id: number;
files?: any;
integrationId: string;
crowdinId: string;
provider: Provider;
}
export interface IntegrationFilesSnapshot {
id: number;
files?: any;
integrationId: string;
crowdinId: string;
provider: Provider;
}
export interface IntegrationWebhooks {
id: number;
fileId: number;
integrationId: string;
crowdinId: string;
provider: Provider;
}
export interface SkipIntegrationNodes {
fileNamePattern?: string;
folderNamePattern?: string;
}
export interface Webhooks {
crowdinWebhookUrl?: string;
integrationWebhookUrl?: string;
urlParam?: string;
crowdinWebhooks?: (client: Crowdin, projectId: number, available: boolean, config?: AppSettings) => Promise<void>;
integrationWebhooks?: (apiCredentials: any, urlParam: string, available: boolean, config?: AppSettings, syncSettings?: any) => Promise<void>;
crowdinWebhookInterceptor?: (projectId: number, client: Crowdin, appRootFolder?: SourceFilesModel.Directory, config?: any, syncSettings?: any, webhookRequest?: any) => Promise<UpdateIntegrationRequest>;
integrationWebhookInterceptor?: (projectId: number, client: Crowdin, apiCredentials: any, appRootFolder?: SourceFilesModel.Directory, config?: AppSettings, syncSettings?: any, webhookRequests?: any) => Promise<IntegrationFile[]>;
queueUrl: string;
}
export declare enum SyncCondition {
ALL = 0,
TRANSLATED = 1,
APPROVED = 2
}
export declare enum SyncSchedule {
DISABLED = 0,
ACTIVE
}
export type Payload = {
event: string;
projectId: string;
language: string;
fileId: string;
};
export type WebhookUrlParams = {
projectId: number;
crowdinId: string;
clientId: string;
};
export interface UpdateCrowdinWebhookPayloadsArgs {
integration: IntegrationLogic;
webhookData: any;
req: Request[];
}
export interface FilterSyncFilesArgs {
projectId: number;
crowdinClient: Crowdin;
events: Payload[];
syncFileSettings: UpdateIntegrationRequest;
appSettings: AppSettings;
}
export interface AppSettings {
schedule?: number;
condition?: number;
'new-crowdin-files'?: boolean;
'new-integration-files'?: boolean;
[key: string]: any;
}
interface LoginFormTokenResponse {
access_token: string;
expires_in: number;
refresh_token: string;
}
export declare enum DefaultCategory {
GENERAL = "General Settings",
SYNC = "Sync settings"
}
export {};