Merge pull request #4883 from matrix-org/t3chguy/ts123
Convert things to Typescript, including languageHandler
This commit is contained in:
commit
2c1eb07768
12 changed files with 139 additions and 48 deletions
|
@ -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",
|
||||||
|
|
6
src/@types/global.d.ts
vendored
6
src/@types/global.d.ts
vendored
|
@ -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};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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':
|
||||||
|
|
|
@ -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;
|
|
@ -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";
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
|
@ -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) {
|
|
@ -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,
|
||||||
|
|
|
@ -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 },
|
|
@ -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); }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Loading…
Reference in a new issue