Merge pull request #4883 from matrix-org/t3chguy/ts123

Convert things to Typescript, including languageHandler
This commit is contained in:
Michael Telatynski 2020-07-06 18:54:19 +01:00 committed by GitHub
commit 2c1eb07768
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 139 additions and 48 deletions

View file

@ -120,6 +120,7 @@
"@babel/register": "^7.7.4", "@babel/register": "^7.7.4",
"@peculiar/webcrypto": "^1.0.22", "@peculiar/webcrypto": "^1.0.22",
"@types/classnames": "^2.2.10", "@types/classnames": "^2.2.10",
"@types/counterpart": "^0.18.1",
"@types/flux": "^3.1.9", "@types/flux": "^3.1.9",
"@types/lodash": "^4.14.152", "@types/lodash": "^4.14.152",
"@types/modernizr": "^3.5.3", "@types/modernizr": "^3.5.3",

View file

@ -20,6 +20,7 @@ import { IMatrixClientPeg } from "../MatrixClientPeg";
import ToastStore from "../stores/ToastStore"; import ToastStore from "../stores/ToastStore";
import DeviceListener from "../DeviceListener"; import DeviceListener from "../DeviceListener";
import { RoomListStore2 } from "../stores/room-list/RoomListStore2"; import { RoomListStore2 } from "../stores/room-list/RoomListStore2";
import { PlatformPeg } from "../PlatformPeg";
declare global { declare global {
interface Window { interface Window {
@ -33,6 +34,7 @@ declare global {
mx_ToastStore: ToastStore; mx_ToastStore: ToastStore;
mx_DeviceListener: DeviceListener; mx_DeviceListener: DeviceListener;
mx_RoomListStore2: RoomListStore2; mx_RoomListStore2: RoomListStore2;
mxPlatformPeg: PlatformPeg;
} }
// workaround for https://github.com/microsoft/TypeScript/issues/30933 // workaround for https://github.com/microsoft/TypeScript/issues/30933
@ -45,6 +47,10 @@ declare global {
hasStorageAccess?: () => Promise<boolean>; hasStorageAccess?: () => Promise<boolean>;
} }
interface Navigator {
userLanguage?: string;
}
interface StorageEstimate { interface StorageEstimate {
usageDetails?: {[key: string]: number}; usageDetails?: {[key: string]: number};
} }

View file

@ -53,6 +53,10 @@ export default abstract class BasePlatform {
this.startUpdateCheck = this.startUpdateCheck.bind(this); this.startUpdateCheck = this.startUpdateCheck.bind(this);
} }
abstract async getConfig(): Promise<{}>;
abstract getDefaultDeviceDisplayName(): string;
protected onAction = (payload: ActionPayload) => { protected onAction = (payload: ActionPayload) => {
switch (payload.action) { switch (payload.action) {
case 'on_client_not_viable': case 'on_client_not_viable':

View file

@ -1,5 +1,6 @@
/* /*
Copyright 2016 OpenMarket Ltd Copyright 2016 OpenMarket Ltd
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -14,6 +15,8 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import BasePlatform from "./BasePlatform";
/* /*
* Holds the current Platform object used by the code to do anything * Holds the current Platform object used by the code to do anything
* specific to the platform we're running on (eg. web, electron) * specific to the platform we're running on (eg. web, electron)
@ -21,10 +24,8 @@ limitations under the License.
* This allows the app layer to set a Platform without necessarily * This allows the app layer to set a Platform without necessarily
* having to have a MatrixChat object * having to have a MatrixChat object
*/ */
class PlatformPeg { export class PlatformPeg {
constructor() { platform: BasePlatform = null;
this.platform = null;
}
/** /**
* Returns the current Platform object for the application. * Returns the current Platform object for the application.
@ -39,12 +40,12 @@ class PlatformPeg {
* application. * application.
* This should be an instance of a class extending BasePlatform. * This should be an instance of a class extending BasePlatform.
*/ */
set(plaf) { set(plaf: BasePlatform) {
this.platform = plaf; this.platform = plaf;
} }
} }
if (!global.mxPlatformPeg) { if (!window.mxPlatformPeg) {
global.mxPlatformPeg = new PlatformPeg(); window.mxPlatformPeg = new PlatformPeg();
} }
export default global.mxPlatformPeg; export default window.mxPlatformPeg;

View file

@ -16,7 +16,7 @@ limitations under the License.
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import {_t} from '../../../languageHandler.js'; import {_t} from '../../../languageHandler';
import Field from "./Field"; import Field from "./Field";
import AccessibleButton from "./AccessibleButton"; import AccessibleButton from "./AccessibleButton";

View file

@ -14,13 +14,13 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, {ReactChild} from "react"; import React, {ReactNode} from "react";
import FormButton from "../elements/FormButton"; import FormButton from "../elements/FormButton";
import {XOR} from "../../../@types/common"; import {XOR} from "../../../@types/common";
export interface IProps { export interface IProps {
description: ReactChild; description: ReactNode;
acceptLabel: string; acceptLabel: string;
onAccept(); onAccept();

View file

@ -15,7 +15,8 @@ limitations under the License.
*/ */
import { createContext } from "react"; import { createContext } from "react";
import { MatrixClient } from "matrix-js-sdk/src/client";
const MatrixClientContext = createContext(undefined); const MatrixClientContext = createContext<MatrixClient>(undefined);
MatrixClientContext.displayName = "MatrixClientContext"; MatrixClientContext.displayName = "MatrixClientContext";
export default MatrixClientContext; export default MatrixClientContext;

View file

@ -15,6 +15,9 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import {MatrixClient} from "matrix-js-sdk/src/client";
import {Room} from "matrix-js-sdk/src/models/room";
import {MatrixClientPeg} from './MatrixClientPeg'; import {MatrixClientPeg} from './MatrixClientPeg';
import Modal from './Modal'; import Modal from './Modal';
import * as sdk from './index'; import * as sdk from './index';
@ -26,6 +29,56 @@ import {getAddressType} from "./UserAddress";
const E2EE_WK_KEY = "im.vector.riot.e2ee"; const E2EE_WK_KEY = "im.vector.riot.e2ee";
// TODO move these interfaces over to js-sdk once it has been typescripted enough to accept them
enum Visibility {
Public = "public",
Private = "private",
}
enum Preset {
PrivateChat = "private_chat",
TrustedPrivateChat = "trusted_private_chat",
PublicChat = "public_chat",
}
interface Invite3PID {
id_server: string;
id_access_token?: string; // this gets injected by the js-sdk
medium: string;
address: string;
}
interface IStateEvent {
type: string;
state_key?: string; // defaults to an empty string
content: object;
}
interface ICreateOpts {
visibility?: Visibility;
room_alias_name?: string;
name?: string;
topic?: string;
invite?: string[];
invite_3pid?: Invite3PID[];
room_version?: string;
creation_content?: object;
initial_state?: IStateEvent[];
preset?: Preset;
is_direct?: boolean;
power_level_content_override?: object;
}
interface IOpts {
dmUserId?: string;
createOpts?: ICreateOpts;
spinner?: boolean;
guestAccess?: boolean;
encryption?: boolean;
inlineErrors?: boolean;
andView?: boolean;
}
/** /**
* Create a new room, and switch to it. * Create a new room, and switch to it.
* *
@ -40,11 +93,12 @@ const E2EE_WK_KEY = "im.vector.riot.e2ee";
* Default: False * Default: False
* @param {bool=} opts.inlineErrors True to raise errors off the promise instead of resolving to null. * @param {bool=} opts.inlineErrors True to raise errors off the promise instead of resolving to null.
* Default: False * Default: False
* @param {bool=} opts.andView True to dispatch an action to view the room once it has been created.
* *
* @returns {Promise} which resolves to the room id, or null if the * @returns {Promise} which resolves to the room id, or null if the
* action was aborted or failed. * action was aborted or failed.
*/ */
export default function createRoom(opts) { export default function createRoom(opts: IOpts): Promise<string | null> {
opts = opts || {}; opts = opts || {};
if (opts.spinner === undefined) opts.spinner = true; if (opts.spinner === undefined) opts.spinner = true;
if (opts.guestAccess === undefined) opts.guestAccess = true; if (opts.guestAccess === undefined) opts.guestAccess = true;
@ -59,12 +113,12 @@ export default function createRoom(opts) {
return Promise.resolve(null); return Promise.resolve(null);
} }
const defaultPreset = opts.dmUserId ? 'trusted_private_chat' : 'private_chat'; const defaultPreset = opts.dmUserId ? Preset.TrustedPrivateChat : Preset.PrivateChat;
// set some defaults for the creation // set some defaults for the creation
const createOpts = opts.createOpts || {}; const createOpts = opts.createOpts || {};
createOpts.preset = createOpts.preset || defaultPreset; createOpts.preset = createOpts.preset || defaultPreset;
createOpts.visibility = createOpts.visibility || 'private'; createOpts.visibility = createOpts.visibility || Visibility.Private;
if (opts.dmUserId && createOpts.invite === undefined) { if (opts.dmUserId && createOpts.invite === undefined) {
switch (getAddressType(opts.dmUserId)) { switch (getAddressType(opts.dmUserId)) {
case 'mx-user-id': case 'mx-user-id':
@ -166,7 +220,7 @@ export default function createRoom(opts) {
}); });
} }
export function findDMForUser(client, userId) { export function findDMForUser(client: MatrixClient, userId: string): Room {
const roomIds = DMRoomMap.shared().getDMRoomsForUserId(userId); const roomIds = DMRoomMap.shared().getDMRoomsForUserId(userId);
const rooms = roomIds.map(id => client.getRoom(id)); const rooms = roomIds.map(id => client.getRoom(id));
const suitableDMRooms = rooms.filter(r => { const suitableDMRooms = rooms.filter(r => {
@ -189,7 +243,7 @@ export function findDMForUser(client, userId) {
* NOTE: this assumes you've just created the room and there's not been an opportunity * NOTE: this assumes you've just created the room and there's not been an opportunity
* for other code to run, so we shouldn't miss RoomState.newMember when it comes by. * for other code to run, so we shouldn't miss RoomState.newMember when it comes by.
*/ */
export async function _waitForMember(client, roomId, userId, opts = { timeout: 1500 }) { export async function _waitForMember(client: MatrixClient, roomId: string, userId: string, opts = { timeout: 1500 }) {
const { timeout } = opts; const { timeout } = opts;
let handler; let handler;
return new Promise((resolve) => { return new Promise((resolve) => {
@ -212,7 +266,7 @@ export async function _waitForMember(client, roomId, userId, opts = { timeout: 1
* Ensure that for every user in a room, there is at least one device that we * Ensure that for every user in a room, there is at least one device that we
* can encrypt to. * can encrypt to.
*/ */
export async function canEncryptToAllUsers(client, userIds) { export async function canEncryptToAllUsers(client: MatrixClient, userIds: string[]) {
const usersDeviceMap = await client.downloadKeys(userIds); const usersDeviceMap = await client.downloadKeys(userIds);
// { "@user:host": { "DEVICE": {...}, ... }, ... } // { "@user:host": { "DEVICE": {...}, ... }, ... }
return Object.values(usersDeviceMap).every((userDevices) => return Object.values(usersDeviceMap).every((userDevices) =>
@ -221,7 +275,7 @@ export async function canEncryptToAllUsers(client, userIds) {
); );
} }
export async function ensureDMExists(client, userId) { export async function ensureDMExists(client: MatrixClient, userId: string): Promise<string> {
const existingDMRoom = findDMForUser(client, userId); const existingDMRoom = findDMForUser(client, userId);
let roomId; let roomId;
if (existingDMRoom) { if (existingDMRoom) {

View file

@ -15,7 +15,8 @@ limitations under the License.
*/ */
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { _t } from './languageHandler.js';
import { _t } from './languageHandler';
export const GroupMemberType = PropTypes.shape({ export const GroupMemberType = PropTypes.shape({
userId: PropTypes.string.isRequired, userId: PropTypes.string.isRequired,

View file

@ -1,7 +1,7 @@
/* /*
Copyright 2017 MTRNord and Cooperative EITA Copyright 2017 MTRNord and Cooperative EITA
Copyright 2017 Vector Creations Ltd. Copyright 2017 Vector Creations Ltd.
Copyright 2019 The Matrix.org Foundation C.I.C. Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com> Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
@ -20,10 +20,11 @@ limitations under the License.
import request from 'browser-request'; import request from 'browser-request';
import counterpart from 'counterpart'; import counterpart from 'counterpart';
import React from 'react'; import React from 'react';
import SettingsStore, {SettingLevel} from "./settings/SettingsStore"; import SettingsStore, {SettingLevel} from "./settings/SettingsStore";
import PlatformPeg from "./PlatformPeg"; import PlatformPeg from "./PlatformPeg";
// $webapp is a webpack resolve alias pointing to the output directory, see webpack config // @ts-ignore - $webapp is a webpack resolve alias pointing to the output directory, see webpack config
import webpackLangJsonUrl from "$webapp/i18n/languages.json"; import webpackLangJsonUrl from "$webapp/i18n/languages.json";
const i18nFolder = 'i18n/'; const i18nFolder = 'i18n/';
@ -37,27 +38,31 @@ counterpart.setSeparator('|');
// Fall back to English // Fall back to English
counterpart.setFallbackLocale('en'); counterpart.setFallbackLocale('en');
interface ITranslatableError extends Error {
translatedMessage: string;
}
/** /**
* Helper function to create an error which has an English message * Helper function to create an error which has an English message
* with a translatedMessage property for use by the consumer. * with a translatedMessage property for use by the consumer.
* @param {string} message Message to translate. * @param {string} message Message to translate.
* @returns {Error} The constructed error. * @returns {Error} The constructed error.
*/ */
export function newTranslatableError(message) { export function newTranslatableError(message: string) {
const error = new Error(message); const error = new Error(message) as ITranslatableError;
error.translatedMessage = _t(message); error.translatedMessage = _t(message);
return error; return error;
} }
// Function which only purpose is to mark that a string is translatable // Function which only purpose is to mark that a string is translatable
// Does not actually do anything. It's helpful for automatic extraction of translatable strings // Does not actually do anything. It's helpful for automatic extraction of translatable strings
export function _td(s) { export function _td(s: string): string {
return s; return s;
} }
// Wrapper for counterpart's translation function so that it handles nulls and undefineds properly // Wrapper for counterpart's translation function so that it handles nulls and undefineds properly
// Takes the same arguments as counterpart.translate() // Takes the same arguments as counterpart.translate()
function safeCounterpartTranslate(text, options) { function safeCounterpartTranslate(text: string, options?: object) {
// Horrible hack to avoid https://github.com/vector-im/riot-web/issues/4191 // Horrible hack to avoid https://github.com/vector-im/riot-web/issues/4191
// The interpolation library that counterpart uses does not support undefined/null // The interpolation library that counterpart uses does not support undefined/null
// values and instead will throw an error. This is a problem since everywhere else // values and instead will throw an error. This is a problem since everywhere else
@ -89,6 +94,13 @@ function safeCounterpartTranslate(text, options) {
return translated; return translated;
} }
interface IVariables {
count?: number;
[key: string]: number | string;
}
type Tags = Record<string, (sub: string) => React.ReactNode>;
/* /*
* Translates text and optionally also replaces XML-ish elements in the text with e.g. React components * Translates text and optionally also replaces XML-ish elements in the text with e.g. React components
* @param {string} text The untranslated text, e.g "click <a>here</a> now to %(foo)s". * @param {string} text The untranslated text, e.g "click <a>here</a> now to %(foo)s".
@ -105,7 +117,9 @@ function safeCounterpartTranslate(text, options) {
* *
* @return a React <span> component if any non-strings were used in substitutions, otherwise a string * @return a React <span> component if any non-strings were used in substitutions, otherwise a string
*/ */
export function _t(text, variables, tags) { export function _t(text: string, variables?: IVariables): string;
export function _t(text: string, variables: IVariables, tags: Tags): React.ReactNode;
export function _t(text: string, variables?: IVariables, tags?: Tags): string | React.ReactNode {
// Don't do substitutions in counterpart. We handle it ourselves so we can replace with React components // Don't do substitutions in counterpart. We handle it ourselves so we can replace with React components
// However, still pass the variables to counterpart so that it can choose the correct plural if count is given // However, still pass the variables to counterpart so that it can choose the correct plural if count is given
// It is enough to pass the count variable, but in the future counterpart might make use of other information too // It is enough to pass the count variable, but in the future counterpart might make use of other information too
@ -141,23 +155,25 @@ export function _t(text, variables, tags) {
* *
* @return a React <span> component if any non-strings were used in substitutions, otherwise a string * @return a React <span> component if any non-strings were used in substitutions, otherwise a string
*/ */
export function substitute(text, variables, tags) { export function substitute(text: string, variables?: IVariables): string;
let result = text; export function substitute(text: string, variables: IVariables, tags: Tags): string;
export function substitute(text: string, variables?: IVariables, tags?: Tags): string | React.ReactNode {
let result: React.ReactNode | string = text;
if (variables !== undefined) { if (variables !== undefined) {
const regexpMapping = {}; const regexpMapping: IVariables = {};
for (const variable in variables) { for (const variable in variables) {
regexpMapping[`%\\(${variable}\\)s`] = variables[variable]; regexpMapping[`%\\(${variable}\\)s`] = variables[variable];
} }
result = replaceByRegexes(result, regexpMapping); result = replaceByRegexes(result as string, regexpMapping);
} }
if (tags !== undefined) { if (tags !== undefined) {
const regexpMapping = {}; const regexpMapping: Tags = {};
for (const tag in tags) { for (const tag in tags) {
regexpMapping[`(<${tag}>(.*?)<\\/${tag}>|<${tag}>|<${tag}\\s*\\/>)`] = tags[tag]; regexpMapping[`(<${tag}>(.*?)<\\/${tag}>|<${tag}>|<${tag}\\s*\\/>)`] = tags[tag];
} }
result = replaceByRegexes(result, regexpMapping); result = replaceByRegexes(result as string, regexpMapping);
} }
return result; return result;
@ -172,7 +188,9 @@ export function substitute(text, variables, tags) {
* *
* @return a React <span> component if any non-strings were used in substitutions, otherwise a string * @return a React <span> component if any non-strings were used in substitutions, otherwise a string
*/ */
export function replaceByRegexes(text, mapping) { export function replaceByRegexes(text: string, mapping: IVariables): string;
export function replaceByRegexes(text: string, mapping: Tags): React.ReactNode;
export function replaceByRegexes(text: string, mapping: IVariables | Tags): string | React.ReactNode {
// We initially store our output as an array of strings and objects (e.g. React components). // We initially store our output as an array of strings and objects (e.g. React components).
// This will then be converted to a string or a <span> at the end // This will then be converted to a string or a <span> at the end
const output = [text]; const output = [text];
@ -189,7 +207,7 @@ export function replaceByRegexes(text, mapping) {
// and everything after the match. Insert all three into the output. We need to do this because we can insert objects. // and everything after the match. Insert all three into the output. We need to do this because we can insert objects.
// Otherwise there would be no need for the splitting and we could do simple replacement. // Otherwise there would be no need for the splitting and we could do simple replacement.
let matchFoundSomewhere = false; // If we don't find a match anywhere we want to log it let matchFoundSomewhere = false; // If we don't find a match anywhere we want to log it
for (const outputIndex in output) { for (let outputIndex = 0; outputIndex < output.length; outputIndex++) {
const inputText = output[outputIndex]; const inputText = output[outputIndex];
if (typeof inputText !== 'string') { // We might have inserted objects earlier, don't try to replace them if (typeof inputText !== 'string') { // We might have inserted objects earlier, don't try to replace them
continue; continue;
@ -216,7 +234,7 @@ export function replaceByRegexes(text, mapping) {
let replaced; let replaced;
// If substitution is a function, call it // If substitution is a function, call it
if (mapping[regexpString] instanceof Function) { if (mapping[regexpString] instanceof Function) {
replaced = mapping[regexpString].apply(null, capturedGroups); replaced = (mapping as Tags)[regexpString].apply(null, capturedGroups);
} else { } else {
replaced = mapping[regexpString]; replaced = mapping[regexpString];
} }
@ -277,11 +295,11 @@ export function replaceByRegexes(text, mapping) {
// Allow overriding the text displayed when no translation exists // Allow overriding the text displayed when no translation exists
// Currently only used in unit tests to avoid having to load // Currently only used in unit tests to avoid having to load
// the translations in riot-web // the translations in riot-web
export function setMissingEntryGenerator(f) { export function setMissingEntryGenerator(f: (value: string) => void) {
counterpart.setMissingEntryGenerator(f); counterpart.setMissingEntryGenerator(f);
} }
export function setLanguage(preferredLangs) { export function setLanguage(preferredLangs: string | string[]) {
if (!Array.isArray(preferredLangs)) { if (!Array.isArray(preferredLangs)) {
preferredLangs = [preferredLangs]; preferredLangs = [preferredLangs];
} }
@ -358,8 +376,8 @@ export function getLanguageFromBrowser() {
* @param {string} language The input language string * @param {string} language The input language string
* @return {string[]} List of normalised languages * @return {string[]} List of normalised languages
*/ */
export function getNormalizedLanguageKeys(language) { export function getNormalizedLanguageKeys(language: string) {
const languageKeys = []; const languageKeys: string[] = [];
const normalizedLanguage = normalizeLanguageKey(language); const normalizedLanguage = normalizeLanguageKey(language);
const languageParts = normalizedLanguage.split('-'); const languageParts = normalizedLanguage.split('-');
if (languageParts.length === 2 && languageParts[0] === languageParts[1]) { if (languageParts.length === 2 && languageParts[0] === languageParts[1]) {
@ -380,7 +398,7 @@ export function getNormalizedLanguageKeys(language) {
* @param {string} language The language string to be normalized * @param {string} language The language string to be normalized
* @returns {string} The normalized language string * @returns {string} The normalized language string
*/ */
export function normalizeLanguageKey(language) { export function normalizeLanguageKey(language: string) {
return language.toLowerCase().replace("_", "-"); return language.toLowerCase().replace("_", "-");
} }
@ -396,7 +414,7 @@ export function getCurrentLanguage() {
* @param {string[]} langs List of language codes to pick from * @param {string[]} langs List of language codes to pick from
* @returns {string} The most appropriate language code from langs * @returns {string} The most appropriate language code from langs
*/ */
export function pickBestLanguage(langs) { export function pickBestLanguage(langs: string[]): string {
const currentLang = getCurrentLanguage(); const currentLang = getCurrentLanguage();
const normalisedLangs = langs.map(normalizeLanguageKey); const normalisedLangs = langs.map(normalizeLanguageKey);
@ -408,13 +426,13 @@ export function pickBestLanguage(langs) {
{ {
// Failing that, a different dialect of the same language // Failing that, a different dialect of the same language
const closeLangIndex = normalisedLangs.find((l) => l.substr(0, 2) === currentLang.substr(0, 2)); const closeLangIndex = normalisedLangs.findIndex((l) => l.substr(0, 2) === currentLang.substr(0, 2));
if (closeLangIndex > -1) return langs[closeLangIndex]; if (closeLangIndex > -1) return langs[closeLangIndex];
} }
{ {
// Neither of those? Try an english variant. // Neither of those? Try an english variant.
const enIndex = normalisedLangs.find((l) => l.startsWith('en')); const enIndex = normalisedLangs.findIndex((l) => l.startsWith('en'));
if (enIndex > -1) return langs[enIndex]; if (enIndex > -1) return langs[enIndex];
} }
@ -422,7 +440,7 @@ export function pickBestLanguage(langs) {
return langs[0]; return langs[0];
} }
function getLangsJson() { function getLangsJson(): Promise<object> {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
let url; let url;
if (typeof(webpackLangJsonUrl) === 'string') { // in Jest this 'url' isn't a URL, so just fall through if (typeof(webpackLangJsonUrl) === 'string') { // in Jest this 'url' isn't a URL, so just fall through
@ -443,7 +461,7 @@ function getLangsJson() {
}); });
} }
function weblateToCounterpart(inTrs) { function weblateToCounterpart(inTrs: object): object {
const outTrs = {}; const outTrs = {};
for (const key of Object.keys(inTrs)) { for (const key of Object.keys(inTrs)) {
@ -463,7 +481,7 @@ function weblateToCounterpart(inTrs) {
return outTrs; return outTrs;
} }
function getLanguage(langPath) { function getLanguage(langPath: string): object {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
request( request(
{ method: "GET", url: langPath }, { method: "GET", url: langPath },

View file

@ -15,7 +15,7 @@ limitations under the License.
*/ */
// Returns a promise which resolves with a given value after the given number of ms // Returns a promise which resolves with a given value after the given number of ms
export function sleep<T>(ms: number, value: T): Promise<T> { export function sleep<T>(ms: number, value?: T): Promise<T> {
return new Promise((resolve => { setTimeout(resolve, ms, value); })); return new Promise((resolve => { setTimeout(resolve, ms, value); }));
} }

View file

@ -1257,6 +1257,11 @@
resolved "https://registry.yarnpkg.com/@types/classnames/-/classnames-2.2.10.tgz#cc658ca319b6355399efc1f5b9e818f1a24bf999" resolved "https://registry.yarnpkg.com/@types/classnames/-/classnames-2.2.10.tgz#cc658ca319b6355399efc1f5b9e818f1a24bf999"
integrity sha512-1UzDldn9GfYYEsWWnn/P4wkTlkZDH7lDb0wBMGbtIQc9zXEQq7FlKBdZUn6OBqD8sKZZ2RQO2mAjGpXiDGoRmQ== integrity sha512-1UzDldn9GfYYEsWWnn/P4wkTlkZDH7lDb0wBMGbtIQc9zXEQq7FlKBdZUn6OBqD8sKZZ2RQO2mAjGpXiDGoRmQ==
"@types/counterpart@^0.18.1":
version "0.18.1"
resolved "https://registry.yarnpkg.com/@types/counterpart/-/counterpart-0.18.1.tgz#b1b784d9e54d9879f0a8cb12f2caedab65430fe8"
integrity sha512-PRuFlBBkvdDOtxlIASzTmkEFar+S66Ek48NVVTWMUjtJAdn5vyMSN8y6IZIoIymGpR36q2nZbIYazBWyFxL+IQ==
"@types/fbemitter@*": "@types/fbemitter@*":
version "2.0.32" version "2.0.32"
resolved "https://registry.yarnpkg.com/@types/fbemitter/-/fbemitter-2.0.32.tgz#8ed204da0f54e9c8eaec31b1eec91e25132d082c" resolved "https://registry.yarnpkg.com/@types/fbemitter/-/fbemitter-2.0.32.tgz#8ed204da0f54e9c8eaec31b1eec91e25132d082c"