diff --git a/src/camera/retriever.ts b/src/camera/retriever.ts index 227cae8f..d0878490 100644 --- a/src/camera/retriever.ts +++ b/src/camera/retriever.ts @@ -1,12 +1,12 @@ /** * @fileoverview * Libraries associated with retrieving cameras. - * + * * @author mebjas */ import { CameraDevice } from "./core"; -import { Html5QrcodeStrings } from "../strings"; +import { t } from "../strings"; /** Class for retrieving cameras on the device. */ export class CameraRetriever { @@ -16,7 +16,7 @@ export class CameraRetriever { if (navigator.mediaDevices) { return CameraRetriever.getCamerasFromMediaDevices(); } - + // Using deprecated api to support really old browsers. var mst = MediaStreamTrack; if (MediaStreamTrack && mst.getSources) { @@ -28,9 +28,9 @@ export class CameraRetriever { private static rejectWithError(): Promise> { // This can potentially happen if the page is loaded without SSL. - let errorMessage = Html5QrcodeStrings.unableToQuerySupportedDevices(); + let errorMessage = t('common.unableToQuerySupportedDevices'); if (!CameraRetriever.isHttpsOrLocalhost()) { - errorMessage = Html5QrcodeStrings.insecureContextCameraQueryError(); + errorMessage = t('common.insecureContextCameraQueryError'); } return Promise.reject(errorMessage); } @@ -71,7 +71,7 @@ export class CameraRetriever { } private static getCamerasFromMediaStreamTrack() - : Promise> { + : Promise> { return new Promise((resolve, _) => { const callback = (sourceInfos: Array) => { const results: Array = []; diff --git a/src/html5-qrcode-scanner.ts b/src/html5-qrcode-scanner.ts index 028262fe..3ddf2360 100644 --- a/src/html5-qrcode-scanner.ts +++ b/src/html5-qrcode-scanner.ts @@ -2,9 +2,9 @@ * @module * Complete Scanner build on top of {@link Html5Qrcode}. * - Decode QR Code using web cam or smartphone camera - * + * * @author mebjas - * + * * The word "QR Code" is registered trademark of DENSO WAVE INCORPORATED * http://www.denso-wave.com/qrcode/faqpatent-e.html */ @@ -33,9 +33,7 @@ import { Html5QrcodeFullConfig, } from "./html5-qrcode"; -import { - Html5QrcodeScannerStrings, -} from "./strings"; +import { t } from "./strings"; import { ASSET_FILE_SCAN, @@ -94,14 +92,14 @@ export interface Html5QrcodeScannerConfig * were previously granted and what camera was last used. If the permissions * is already granted for "camera", QR code scanning will automatically * start for previously used camera. - * + * * Note: default value is `true`. */ rememberLastUsedCamera?: boolean | undefined; /** * Sets the desired scan types to be supported in the scanner. - * + * * - Not setting a value will follow the default order supported by * library. * - First value would be used as the default value. Example: @@ -119,7 +117,7 @@ export interface Html5QrcodeScannerConfig /** * If `true` the rendered UI will have button to turn flash on or off * based on device + browser support. - * + * * Note: default value is `false`. */ showTorchButtonIfSupported?: boolean | undefined; @@ -127,18 +125,18 @@ export interface Html5QrcodeScannerConfig /** * If `true` the rendered UI will have slider to zoom camera based on * device + browser support. - * + * * Note: default value is `false`. - * + * * TODO(minhazav): Document this API, currently hidden. */ showZoomSliderIfSupported?: boolean | undefined; /** * Default zoom value if supported. - * + * * Note: default value is 1x. - * + * * TODO(minhazav): Document this API, currently hidden. */ defaultZoomValueIfSupported?: number | undefined; @@ -168,10 +166,10 @@ function toHtml5QrcodeFullConfig( /** * End to end web based QR and Barcode Scanner. - * + * * Use this class for setting up QR scanner in your web application with * few lines of codes. - * + * * - Supports camera as well as file based scanning. * - Depending on device supports camera selection, zoom and torch features. * - Supports different kind of 2D and 1D codes {@link Html5QrcodeSupportedFormats}. @@ -203,7 +201,7 @@ export class Html5QrcodeScanner { * * @param elementId Id of the HTML element. * @param config Extra configurations to tune the code scanner. - * @param verbose - If true, all logs would be printed to console. + * @param verbose - If true, all logs would be printed to console. */ public constructor( elementId: string, @@ -232,7 +230,7 @@ export class Html5QrcodeScanner { /** * Renders the User Interface. - * + * * @param qrCodeSuccessCallback Callback called when an instance of a QR * code or any other supported bar code is found. * @param qrCodeErrorCallback optional, callback called in cases where no @@ -255,7 +253,7 @@ export class Html5QrcodeScanner { this.lastMatchFound = decodedText; this.setHeaderMessage( - Html5QrcodeScannerStrings.lastMatch(decodedText), + t('scanner.lastMatch', { decodedText }), Html5QrcodeScannerStatus.STATUS_SUCCESS); } }; @@ -282,13 +280,13 @@ export class Html5QrcodeScanner { //#region State related public APIs /** * Pauses the ongoing scan. - * + * * Notes: * - Should only be called if camera scan is ongoing. - * + * * @param shouldPauseVideo (Optional, default = false) If `true` * the video will be paused. - * + * * @throws error if method is called when scanner is not in scanning state. */ public pause(shouldPauseVideo?: boolean) { @@ -298,19 +296,19 @@ export class Html5QrcodeScanner { this.getHtml5QrcodeOrFail().pause(shouldPauseVideo); } - + /** * Resumes the paused scan. - * + * * If the video was previously paused by setting `shouldPauseVideo` * to `true` in {@link Html5QrcodeScanner#pause(shouldPauseVideo)}, * calling this method will resume the video. - * + * * Notes: * - Should only be called if camera scan is ongoing. * - With this caller will start getting results in success and error * callbacks. - * + * * @throws error if method is called when scanner is not in paused state. */ public resume() { @@ -328,7 +326,7 @@ export class Html5QrcodeScanner { /** * Removes the QR Code scanner UI. - * + * * @returns Promise which succeeds if the cleanup is complete successfully, * fails otherwise. */ @@ -380,11 +378,11 @@ export class Html5QrcodeScanner { //#region Beta APIs to modify running stream state. /** * Returns the capabilities of the running video track. - * + * * Read more: https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack/getConstraints - * + * * Note: Should only be called if {@link Html5QrcodeScanner#getState()} - * returns {@link Html5QrcodeScannerState#SCANNING} or + * returns {@link Html5QrcodeScannerState#SCANNING} or * {@link Html5QrcodeScannerState#PAUSED}. * * @returns the capabilities of a running video track. @@ -397,11 +395,11 @@ export class Html5QrcodeScanner { /** * Returns the object containing the current values of each constrainable * property of the running video track. - * + * * Read more: https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack/getSettings - * + * * Note: Should only be called if {@link Html5QrcodeScanner#getState()} - * returns {@link Html5QrcodeScannerState#SCANNING} or + * returns {@link Html5QrcodeScannerState#SCANNING} or * {@link Html5QrcodeScannerState#PAUSED}. * * @returns the supported settings of the running video track. @@ -415,7 +413,7 @@ export class Html5QrcodeScanner { * Apply a video constraints on running video track from camera. * * Note: Should only be called if {@link Html5QrcodeScanner#getState()} - * returns {@link Html5QrcodeScannerState#SCANNING} or + * returns {@link Html5QrcodeScannerState#SCANNING} or * {@link Html5QrcodeScannerState#PAUSED}. * * @param {MediaTrackConstraints} specifies a variety of video or camera @@ -545,7 +543,7 @@ export class Html5QrcodeScanner { const $this = this; $this.showHideScanTypeSwapLink(false); $this.setHeaderMessage( - Html5QrcodeScannerStrings.cameraPermissionRequesting()); + t('scanner.cameraPermissionRequesting')); const createPermissionButtonIfNotExists = () => { if (!requestPermissionButton) { @@ -565,14 +563,14 @@ export class Html5QrcodeScanner { $this.renderCameraSelection(cameras); } else { $this.setHeaderMessage( - Html5QrcodeScannerStrings.noCameraFound(), + t('scanner.noCameraFound'), Html5QrcodeScannerStatus.STATUS_WARNING); createPermissionButtonIfNotExists(); } }).catch((error) => { $this.persistedDataManager.setHasPermission( /* hasPermission */ false); - + if (requestPermissionButton) { requestPermissionButton.disabled = false; } else { @@ -598,7 +596,7 @@ export class Html5QrcodeScanner { .createElement( "button", this.getCameraPermissionButtonId()); requestPermissionButton.innerText - = Html5QrcodeScannerStrings.cameraPermissionTitle(); + = t('scanner.cameraPermissionTitle'); requestPermissionButton.addEventListener("click", function () { requestPermissionButton.disabled = true; @@ -687,7 +685,7 @@ export class Html5QrcodeScanner { return; } - $this.setHeaderMessage(Html5QrcodeScannerStrings.loadingImage()); + $this.setHeaderMessage(t('scanner.loadingImage')); $this.html5Qrcode.scanFileV2(file, /* showImage= */ true) .then((html5qrcodeResult: Html5QrcodeResult) => { $this.resetHeaderMessage(); @@ -751,14 +749,14 @@ export class Html5QrcodeScanner { = BaseUiElementFactory.createElement( "button", PublicUiElementIdAndClasses.CAMERA_START_BUTTON_ID); cameraActionStartButton.innerText - = Html5QrcodeScannerStrings.scanButtonStartScanningText(); + = t('scanner.scanButtonStartScanningText'); cameraActionContainer.appendChild(cameraActionStartButton); const cameraActionStopButton = BaseUiElementFactory.createElement( "button", PublicUiElementIdAndClasses.CAMERA_STOP_BUTTON_ID); cameraActionStopButton.innerText - = Html5QrcodeScannerStrings.scanButtonStopScanningText(); + = t('scanner.scanButtonStopScanningText'); cameraActionStopButton.style.display = "none"; cameraActionStopButton.disabled = true; cameraActionContainer.appendChild(cameraActionStopButton); @@ -801,8 +799,7 @@ export class Html5QrcodeScanner { cameraActionStartButton.style.display = "none"; } cameraActionStartButton.innerText - = Html5QrcodeScannerStrings - .scanButtonStartScanningText(); + = t('scanner.scanButtonStartScanningText'); cameraActionStartButton.style.opacity = "1"; cameraActionStartButton.disabled = false; if (shouldShow) { @@ -813,7 +810,7 @@ export class Html5QrcodeScanner { cameraActionStartButton.addEventListener("click", (_) => { // Update the UI. cameraActionStartButton.innerText - = Html5QrcodeScannerStrings.scanButtonScanningStarting(); + = t('scanner.scanButtonScanningStarting'); cameraSelectUi.disable(); cameraActionStartButton.disabled = true; cameraActionStartButton.style.opacity = "0.5"; @@ -821,7 +818,7 @@ export class Html5QrcodeScanner { if (this.scanTypeSelector.hasMoreThanOneScanType()) { $this.showHideScanTypeSwapLink(false); } - $this.resetHeaderMessage(); + $this.resetHeaderMessage(); // Attempt starting the camera. const cameraId = cameraSelectUi.getValue(); @@ -875,7 +872,7 @@ export class Html5QrcodeScanner { if(this.scanTypeSelector.hasMoreThanOneScanType()) { $this.showHideScanTypeSwapLink(true); } - + cameraSelectUi.enable(); cameraActionStartButton.disabled = false; cameraActionStopButton.style.display = "none"; @@ -909,9 +906,9 @@ export class Html5QrcodeScanner { private createSectionSwap() { const $this = this; const TEXT_IF_CAMERA_SCAN_SELECTED - = Html5QrcodeScannerStrings.textIfCameraScanSelected(); + = t('scanner.textIfCameraScanSelected'); const TEXT_IF_FILE_SCAN_SELECTED - = Html5QrcodeScannerStrings.textIfFileScanSelected(); + = t('scanner.textIfFileScanSelected'); // TODO(minhaz): Export this as an UI element. const section = document.getElementById(this.getDashboardSectionId())!; @@ -939,7 +936,7 @@ export class Html5QrcodeScanner { $this.resetHeaderMessage(); $this.fileSelectionUi!.resetValue(); $this.sectionSwapAllowed = false; - + if (ScanTypeSelector.isCameraScanType($this.currentScanType)) { // Swap to file based scanning. $this.clearScanRegion(); @@ -1060,7 +1057,7 @@ export class Html5QrcodeScanner { this.cameraScanImage.width = 64; this.cameraScanImage.style.opacity = "0.8"; this.cameraScanImage.src = ASSET_CAMERA_SCAN; - this.cameraScanImage.alt = Html5QrcodeScannerStrings.cameraScanAltText(); + this.cameraScanImage.alt = t('scanner.cameraScanAltText'); } private insertFileScanImageToScanRegion() { @@ -1082,7 +1079,7 @@ export class Html5QrcodeScanner { this.fileScanImage.width = 64; this.fileScanImage.style.opacity = "0.8"; this.fileScanImage.src = ASSET_FILE_SCAN; - this.fileScanImage.alt = Html5QrcodeScannerStrings.fileScanAltText(); + this.fileScanImage.alt = t('scanner.fileScanAltText'); } private clearScanRegion() { diff --git a/src/html5-qrcode.ts b/src/html5-qrcode.ts index b3fcbdab..4471b1c1 100644 --- a/src/html5-qrcode.ts +++ b/src/html5-qrcode.ts @@ -28,7 +28,7 @@ import { QrDimensions, QrDimensionFunction } from "./core"; -import { Html5QrcodeStrings } from "./strings"; +import { t } from "./strings"; import { VideoConstraintsUtil } from "./utils"; import { Html5QrcodeShim } from "./code-decoder"; import { CameraFactory } from "./camera/factories"; @@ -72,7 +72,7 @@ class Constants extends Html5QrcodeConstants { export interface Html5QrcodeConfigs { /** * Array of formats to support of type {@link Html5QrcodeSupportedFormats}. - * + * * All invalid values would be ignored. If null or underfined all supported * formats will be used for scanning. Unless you want to limit the scan to * only certain formats or want to improve performance, you should not set @@ -84,10 +84,10 @@ export interface Html5QrcodeConfigs { * {@link BarcodeDetector} is being implemented by browsers at the moment. * It has very limited browser support but as it gets available it could * enable faster native code scanning experience. - * + * * Set this flag to true, to enable using {@link BarcodeDetector} if * supported. This is true by default. - * + * * Documentations: * - https://developer.mozilla.org/en-US/docs/Web/API/BarcodeDetector * - https://web.dev/shape-detection/#barcodedetector @@ -96,7 +96,7 @@ export interface Html5QrcodeConfigs { /** * Config for experimental features. - * + * * Everything is false by default. */ experimentalFeatures?: ExperimentalFeaturesConfig | undefined; @@ -104,7 +104,7 @@ export interface Html5QrcodeConfigs { /** * Interface for full configuration of {@link Html5Qrcode}. - * + * * Notes: Ideally we don't need to have two interfaces for this purpose, but * since the public APIs before version 2.0.8 allowed passing a boolean verbose * flag to constructor we need to allow users to pass Html5QrcodeFullConfig or @@ -132,7 +132,7 @@ export interface Html5QrcodeCameraScanConfig { * Optional, edge size, dimension or calculator function for QR scanning * box, the value or computed value should be smaller than the width and * height of the full region. - * + * * This would make the scanner look like this: * ---------------------- * |********************| @@ -143,12 +143,12 @@ export interface Html5QrcodeCameraScanConfig { * |********************| * |********************| * ---------------------- - * + * * Instance of {@link QrDimensions} can be passed to construct a non * square rendering of scanner box. You can also pass in a function of type * {@link QrDimensionFunction} that takes in the width and height of the * video stream and return QR box size of type {@link QrDimensions}. - * + * * If this value is not set, no shaded QR box will be rendered and the * scanner will scan the entire area of video stream. */ @@ -185,7 +185,7 @@ export interface Html5QrcodeCameraScanConfig { /** * Internal implementation of {@link Html5QrcodeConfig} with util & factory * methods. - * + * * @hidden */ class InternalHtml5QrcodeConfig implements Html5QrcodeCameraScanConfig { @@ -234,7 +234,7 @@ class InternalHtml5QrcodeConfig implements Html5QrcodeCameraScanConfig { /** * Create instance of {@link Html5QrcodeCameraScanConfig}. - * + * * Create configuration by merging default and input settings. */ static create(config: Html5QrcodeCameraScanConfig | undefined, logger: Logger) @@ -253,9 +253,9 @@ interface QrcodeRegionBounds { /** * Low level APIs for building web based QR and Barcode Scanner. - * + * * Supports APIs for camera as well as file based scanning. - * + * * Depending of the configuration, the class will help render code * scanning UI on the provided parent HTML container. */ @@ -302,13 +302,13 @@ export class Html5Qrcode { * compatibility). If nothing is passed, default values would be used. * If a boolean value is used, it'll be used to set verbosity. Pass a * config value to configure the Html5Qrcode scanner as per needs. - * + * * Use of `configOrVerbosityFlag` as a boolean value is being * deprecated since version 2.0.7. - * + * * TODO(mebjas): Deprecate the verbosity boolean flag completely. */ - public constructor(elementId: string, + public constructor(elementId: string, configOrVerbosityFlag?: boolean | Html5QrcodeFullConfig | undefined) { if (!document.getElementById(elementId)) { throw `HTML Element with id=${elementId} not found`; @@ -316,7 +316,7 @@ export class Html5Qrcode { this.elementId = elementId; this.verbose = false; - + let experimentalFeatureConfig : ExperimentalFeaturesConfig | undefined; let configObject: Html5QrcodeFullConfig | undefined; if (typeof configOrVerbosityFlag == "boolean") { @@ -326,7 +326,7 @@ export class Html5Qrcode { this.verbose = configObject.verbose === true; experimentalFeatureConfig = configObject.experimentalFeatures; } - + this.logger = new BaseLoggger(this.verbose); this.qrcode = new Html5QrcodeShim( this.getSupportedFormats(configOrVerbosityFlag), @@ -342,7 +342,7 @@ export class Html5Qrcode { //#region start() /** * Start scanning QR codes or bar codes for a given camera. - * + * * @param cameraIdOrConfig Identifier of the camera, it can either be the * camera id retrieved from {@link Html5Qrcode#getCameras()} method or * object with facing mode constraint. @@ -351,7 +351,7 @@ export class Html5Qrcode { * code or any other supported bar code is found. * @param qrCodeErrorCallback Callback called in cases where no instance of * QR code or any other supported bar code is found. - * + * * @returns Promise for starting the scan. The Promise can fail if the user * doesn't grant permission or some API is not supported by the browser. */ @@ -456,11 +456,11 @@ export class Html5Qrcode { }); }).catch((error) => { toScanningStateChangeTransaction.cancel(); - reject(Html5QrcodeStrings.errorGettingUserMedia(error)); + reject(t('common.errorGettingUserMedia', {error})); }); }).catch((_) => { toScanningStateChangeTransaction.cancel(); - reject(Html5QrcodeStrings.cameraStreamingNotSupported()); + reject(t('common.cameraStreamingNotSupported')); }); }); } @@ -469,10 +469,10 @@ export class Html5Qrcode { //#region Other state related public APIs /** * Pauses the ongoing scan. - * + * * @param shouldPauseVideo (Optional, default = false) If true the * video will be paused. - * + * * @throws error if method is called when scanner is not in scanning state. */ public pause(shouldPauseVideo?: boolean) { @@ -493,14 +493,14 @@ export class Html5Qrcode { /** * Resumes the paused scan. - * + * * If the video was previously paused by setting `shouldPauseVideo`` * to `true` in {@link Html5Qrcode#pause(shouldPauseVideo)}, calling * this method will resume the video. - * + * * Note: with this caller will start getting results in success and error * callbacks. - * + * * @throws error if method is called when scanner is not in paused state. */ public resume() { @@ -628,7 +628,7 @@ export class Html5Qrcode { * * @returns Promise which resolves with result of type * {@link Html5QrcodeResult}. - * + * * @beta This is a WIP method, it's available as a public method but not * documented. * TODO(mebjas): Replace scanFile with ScanFileV2 @@ -757,7 +757,7 @@ export class Html5Qrcode { this.clearElement(); } - /** + /** * Returns list of {@link CameraDevice} supported by the device. * * @returns array of camera devices on success. @@ -768,9 +768,9 @@ export class Html5Qrcode { /** * Returns the capabilities of the running video track. - * + * * Read more: https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack/getConstraints - * + * * Important: * 1. Must be called only if the camera based scanning is in progress. * @@ -784,9 +784,9 @@ export class Html5Qrcode { /** * Returns the object containing the current values of each constrainable * property of the running video track. - * + * * Read more: https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack/getSettings - * + * * Important: * 1. Must be called only if the camera based scanning is in progress. * @@ -800,9 +800,9 @@ export class Html5Qrcode { /** * Returns {@link CameraCapabilities} of the running video track. - * + * * TODO(minhazav): Document this API, currently hidden. - * + * * @returns capabilities of the running camera. * @throws error if the scanning is not in running state. */ @@ -853,10 +853,10 @@ export class Html5Qrcode { * compatibility). If nothing is passed, default values would be used. * If a boolean value is used, it'll be used to set verbosity. Pass a * config value to configure the Html5Qrcode scanner as per needs. - * + * * Use of `configOrVerbosityFlag` as a boolean value is being * deprecated since version 2.0.7. - * + * * TODO(mebjas): Deprecate the verbosity boolean flag completely. */ private getSupportedFormats( @@ -882,7 +882,7 @@ export class Html5Qrcode { Html5QrcodeSupportedFormats.UPC_EAN_EXTENSION, ]; - if (!configOrVerbosityFlag + if (!configOrVerbosityFlag || typeof configOrVerbosityFlag == "boolean") { return allFormats; } @@ -969,9 +969,9 @@ export class Html5Qrcode { /** * The 'config.qrbox.width' shall be overriden if it's larger than the * width of the root element. - * + * * Based on the verbosity settings, this will be logged to the logger. - * + * * @param configWidth the width of qrbox set by users in the config. */ const correctWidthBasedOnRootElementSize = (configWidth: number) => { @@ -996,7 +996,7 @@ export class Html5Qrcode { /** * Validates if the `qrboxSize` is a valid value. - * + * * It's expected to be either a number or of type {@link QrDimensions}. */ private validateQrboxConfig( @@ -1058,7 +1058,7 @@ export class Html5Qrcode { // If `qrbox` size is not set, it will default to the dimensions of the // viewfinder. - const qrboxSize = isNullOrUndefined(internalConfig.qrbox) ? + const qrboxSize = isNullOrUndefined(internalConfig.qrbox) ? {width: viewfinderWidth, height: viewfinderHeight}: internalConfig.qrbox!; this.validateQrboxConfig(qrboxSize); @@ -1068,7 +1068,7 @@ export class Html5Qrcode { + "greater than the height of the video stream. Shading will be" + " ignored"); } - + const shouldShadingBeApplied = internalConfig.isShadedBoxEnabled() && qrDimensions.height <= viewfinderHeight; @@ -1082,7 +1082,7 @@ export class Html5Qrcode { const qrRegion = shouldShadingBeApplied ? this.getShadedRegionBounds(viewfinderWidth, viewfinderHeight, qrDimensions) : defaultQrRegion; - + const canvasElement = this.createCanvasElement( qrRegion.width, qrRegion.height); // Tell user agent that this canvas will be read frequently. @@ -1104,7 +1104,7 @@ export class Html5Qrcode { } this.createScannerPausedUiElement(this.element!); - + // Update local states this.qrRegion = qrRegion; this.context = context; @@ -1114,7 +1114,7 @@ export class Html5Qrcode { // TODO(mebjas): Convert this to a standard message viewer. private createScannerPausedUiElement(rootElement: HTMLElement) { const scannerPausedUiElement = document.createElement("div"); - scannerPausedUiElement.innerText = Html5QrcodeStrings.scannerPaused(); + scannerPausedUiElement.innerText = t('common.scannerPaused'); scannerPausedUiElement.style.display = "none"; scannerPausedUiElement.style.position = "absolute"; scannerPausedUiElement.style.top = "0px"; @@ -1126,7 +1126,7 @@ export class Html5Qrcode { rootElement.appendChild(scannerPausedUiElement); this.scannerPausedUiElement = scannerPausedUiElement; } - + /** * Scans current context using the qrcode library. * @@ -1153,7 +1153,7 @@ export class Html5Qrcode { return true; }).catch((error) => { this.possiblyUpdateShaders(/* qrMatch= */ false); - let errorMessage = Html5QrcodeStrings.codeParseError(error); + let errorMessage = t('common.codeParseError', {error}); qrCodeErrorCallback( errorMessage, Html5QrcodeErrorFactory.createFrom(errorMessage)); return false; @@ -1465,10 +1465,10 @@ export class Html5Qrcode { shadingElement.style.left = "0px"; shadingElement.style.right = "0px"; shadingElement.id = `${Constants.SHADED_REGION_ELEMENT_ID}`; - + // Check if div is too small for shadows. As there are two 5px width // borders the needs to have a size above 10px. - if ((width - qrboxSize.width) < 11 + if ((width - qrboxSize.width) < 11 || (height - qrboxSize.height) < 11) { this.hasBorderShaders = false; } else { @@ -1476,7 +1476,7 @@ export class Html5Qrcode { const largeSize = 40; this.insertShaderBorders( shadingElement, - /* width= */ largeSize, + /* width= */ largeSize, /* height= */ smallSize, /* top= */ -smallSize, /* bottom= */ null, diff --git a/src/index.ts b/src/index.ts index b9854263..07bacb1f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -26,7 +26,8 @@ export { } from "./core"; export { Html5QrcodeScannerState } from "./state-manager"; export { Html5QrcodeScanType } from "./core"; -export { +export { CameraCapabilities, CameraDevice } from "./camera/core"; +export { setTranslations } from './strings'; diff --git a/src/strings.ts b/src/strings.ts index 3f701c1e..ae33e16e 100644 --- a/src/strings.ts +++ b/src/strings.ts @@ -1,200 +1,108 @@ /** * @fileoverview * Strings used by {@class Html5Qrcode} & {@class Html5QrcodeScanner} - * + * * @author mebjas - * + * * The word "QR Code" is registered trademark of DENSO WAVE INCORPORATED * http://www.denso-wave.com/qrcode/faqpatent-e.html */ -/** - * Strings used in {@class Html5Qrcode}. - * - * TODO(mebjas): Support internalization. - */ -export class Html5QrcodeStrings { - - public static codeParseError(exception: any): string { - return `QR code parse error, error = ${exception}`; - } - - public static errorGettingUserMedia(error: any): string { - return `Error getting userMedia, error = ${error}`; - } - - public static onlyDeviceSupportedError(): string { - return "The device doesn't support navigator.mediaDevices , only " - + "supported cameraIdOrConfig in this case is deviceId parameter " - + "(string)."; - } - - public static cameraStreamingNotSupported(): string { - return "Camera streaming not supported by the browser."; - } - - public static unableToQuerySupportedDevices(): string { - return "Unable to query supported devices, unknown error."; - } - - public static insecureContextCameraQueryError(): string { - return "Camera access is only supported in secure context like https " - + "or localhost."; - } - - public static scannerPaused(): string { - return "Scanner paused"; - } +interface LocalizedStrings { + [key: string]: string | LocalizedStrings; } -/** - * Strings used in {@class Html5QrcodeScanner}. - * - * TODO(mebjas): Support internalization. - */ -export class Html5QrcodeScannerStrings { - - public static scanningStatus(): string { - return "Scanning"; - } - - public static idleStatus(): string { - return "Idle"; - } - - public static errorStatus(): string { - return "Error"; - } - - public static permissionStatus(): string { - return "Permission"; - } - - public static noCameraFoundErrorStatus(): string { - return "No Cameras"; - } - - public static lastMatch(decodedText: string): string { - return `Last Match: ${decodedText}`; - } - - public static codeScannerTitle(): string { - return "Code Scanner"; - } - - public static cameraPermissionTitle(): string { - return "Request Camera Permissions"; - } - - public static cameraPermissionRequesting(): string { - return "Requesting camera permissions..."; - } - - public static noCameraFound(): string { - return "No camera found"; - } - - public static scanButtonStopScanningText(): string { - return "Stop Scanning"; - } - - public static scanButtonStartScanningText(): string { - return "Start Scanning"; - } - - public static torchOnButton(): string { - return "Switch On Torch"; - } - - public static torchOffButton(): string { - return "Switch Off Torch"; - } - - public static torchOnFailedMessage(): string { - return "Failed to turn on torch"; - } - - public static torchOffFailedMessage(): string { - return "Failed to turn off torch"; - } - - public static scanButtonScanningStarting(): string { - return "Launching Camera..."; - } - +const DEFAULT_STRINGS = { /** - * Text to show when camera scan is selected. - * - * This will be used to switch to file based scanning. + * Strings used in {@class Html5Qrcode}. */ - public static textIfCameraScanSelected(): string { - return "Scan an Image File"; - } - + common: { + codeParseError: 'QR code parse error, error = {{exception}}', + errorGettingUserMedia: 'Error getting userMedia, error = {{error}}', + cameraStreamingNotSupported: 'Camera streaming not supported by the browser.', + unableToQuerySupportedDevices: 'Unable to query supported devices, unknown error.', + insecureContextCameraQueryError: 'Camera access is only supported in secure context like https or localhost.', + scannerPaused: 'Scanner paused' + }, /** - * Text to show when file based scan is selected. - * - * This will be used to switch to camera based scanning. + * Strings used in {@class Html5QrcodeScanner}. */ - public static textIfFileScanSelected(): string { - return "Scan using camera directly"; - } - - public static selectCamera(): string { - return "Select Camera"; - } - - public static fileSelectionChooseImage(): string { - return "Choose Image"; - } - - public static fileSelectionChooseAnother(): string { - return "Choose Another"; - } - - public static fileSelectionNoImageSelected(): string { - return "No image choosen"; - } - - /** Prefix to be given to anonymous cameras. */ - public static anonymousCameraPrefix(): string { - return "Anonymous Camera"; - } - - public static dragAndDropMessage(): string { - return "Or drop an image to scan"; - } - - public static dragAndDropMessageOnlyImages(): string { - return "Or drop an image to scan (other files not supported)"; - } - - /** Value for zoom. */ - public static zoom(): string { - return "zoom"; - } - - public static loadingImage(): string { - return "Loading image..."; + scanner: { + scanningStatus: 'Scanning', + idleStatus: 'Idle', + errorStatus: 'Error', + permissionStatus: 'Permission', + noCameraFoundErrorStatus: 'No Cameras', + lastMatch: 'Last Match: {{decodedText}}', + codeScannerTitle: 'Code Scanner', + cameraPermissionTitle: 'Request Camera Permissions', + cameraPermissionRequesting: 'Requesting camera permissions...', + noCameraFound: 'No camera found', + scanButtonStopScanningText: 'Stop Scanning', + scanButtonStartScanningText: 'Start Scanning', + torchOnButton: 'Switch On Torch', + torchOffButton: 'Switch Off Torch', + torchOnFailedMessage: 'Failed to turn on torch', + scanButtonScanningStarting: 'Launching Camera...', + /** + * Text to show when camera scan is selected. + * + * This will be used to switch to file based scanning. + */ + textIfCameraScanSelected: "Scan an Image File", + /** + * Text to show when file based scan is selected. + * + * This will be used to switch to camera based scanning. + */ + textIfFileScanSelected: "Scan using camera directly", + selectCamera: 'Select Camera', + fileSelectionChooseImage: 'Choose Image', + fileSelectionChooseAnother: 'Choose Another', + fileSelectionNoImageSelected: 'No image choosen', + /** Prefix to be given to anonymous cameras. */ + anonymousCameraPrefix: "Anonymous Camera", + dragAndDropMessage: 'Or drop an image to scan', + dragAndDropMessageOnlyImages: 'Or drop an image to scan (other files not supported)', + /** Value for zoom. */ + zoom: "zoom", + loadingImage: 'Loading image...', + cameraScanAltText: 'Camera based scan', + fileScanAltText: 'File based scan' + }, + /** Strings used in {@class LibraryInfoDiv} */ + libraryInfo: { + poweredBy: 'Powered by ', + reportIssues: 'Report issues' } +} - public static cameraScanAltText(): string { - return "Camera based scan"; - } +let localizedStrings: LocalizedStrings = DEFAULT_STRINGS; - public static fileScanAltText(): string { - return "Fule based scan"; - } +export function setTranslations(translations: Partial) { + localizedStrings = translations; } -/** Strings used in {@class LibraryInfoDiv} */ -export class LibraryInfoStrings { - - public static poweredBy(): string { - return "Powered by "; - } +function findValueByKey(key: string, lookup: LocalizedStrings | string) { + const keyChain = key.split('.'); + let result = lookup; + for (let key of keyChain) { + if (typeof result === 'string') { + break; + } + result = result[key]; + if (!result) { + return result; + } + } + return result; +} - public static reportIssues(): string { - return "Report issues"; +export function t(key: string, values?: Record): string { + let result = findValueByKey(key, localizedStrings) || findValueByKey(key, DEFAULT_STRINGS); + if (typeof result !== 'string') throw new Error('Incorrect localization key provided: ' + key); + for (let valueKey in values) { + result = result.replace(new RegExp(`{{${valueKey}}}`, 'g'), values[valueKey]); } + return result; } diff --git a/src/ui.ts b/src/ui.ts index ee331d55..aee91c97 100644 --- a/src/ui.ts +++ b/src/ui.ts @@ -1,7 +1,7 @@ /** * @fileoverview * All structured UI classes. - * + * * TODO(mebjas): Migrate all UI components to modular UI classes so they are * easy to improve. * TODO(mebjas): Add tests for all UI components. @@ -10,7 +10,7 @@ import { ASSET_CLOSE_ICON_16PX, ASSET_INFO_ICON_16PX } from "./image-assets"; -import { LibraryInfoStrings } from "./strings"; +import { t } from "./strings"; type OnClickListener0 = () => void; @@ -38,7 +38,7 @@ class LibraryInfoDiv { this.infoDiv.style.fontWeight = "400"; this.infoDiv.style.color = "white"; - this.infoDiv.innerText = LibraryInfoStrings.poweredBy(); + this.infoDiv.innerText = t('libraryInfo.poweredBy'); const projectLink = document.createElement("a"); projectLink.innerText = "ScanApp"; projectLink.href = "https://scanapp.org"; @@ -52,7 +52,7 @@ class LibraryInfoDiv { this.infoDiv.appendChild(breakElemSecond); const reportIssueLink = document.createElement("a"); - reportIssueLink.innerText = LibraryInfoStrings.reportIssues(); + reportIssueLink.innerText = t('libraryInfo.reportIssues'); reportIssueLink.href = "https://github.com/mebjas/html5-qrcode/issues"; reportIssueLink.target = "new"; reportIssueLink.style.color = "white"; @@ -83,7 +83,7 @@ class LibraryInfoIcon { this.infoIcon = document.createElement("img"); } - + public renderInto(parent: HTMLElement) { this.infoIcon.alt = "Info icon"; this.infoIcon.src = ASSET_INFO_ICON_16PX; @@ -143,7 +143,7 @@ export class LibraryInfoContainer { this.infoDiv.hide(); }); } - + public renderInto(parent: HTMLElement) { this.infoDiv.renderInto(parent); this.infoIcon.renderInto(parent); diff --git a/src/ui/scanner/camera-selection-ui.ts b/src/ui/scanner/camera-selection-ui.ts index cda2b9b6..5aa8aef4 100644 --- a/src/ui/scanner/camera-selection-ui.ts +++ b/src/ui/scanner/camera-selection-ui.ts @@ -1,9 +1,9 @@ /** * @fileoverview * File for camera selection UI. - * + * * @author mebjas - * + * * The word "QR Code" is registered trademark of DENSO WAVE INCORPORATED * http://www.denso-wave.com/qrcode/faqpatent-e.html */ @@ -13,9 +13,7 @@ import { BaseUiElementFactory, PublicUiElementIdAndClasses } from "./base"; -import { - Html5QrcodeScannerStrings -} from "../../strings"; +import { t } from "../../strings"; /** Class for rendering and handling camera selection UI. */ export class CameraSelectionUi { @@ -30,7 +28,7 @@ export class CameraSelectionUi { "select", PublicUiElementIdAndClasses.CAMERA_SELECTION_SELECT_ID); this.cameras = cameras; - this.options = []; + this.options = []; } /*eslint complexity: ["error", 10]*/ @@ -47,7 +45,7 @@ export class CameraSelectionUi { cameraSelectionContainer.style.display = "none"; } else { // Otherwise, show the number of cameras found as well. - const selectCameraString = Html5QrcodeScannerStrings.selectCamera(); + const selectCameraString = t('scanner.selectCamera'); cameraSelectionContainer.innerText = `${selectCameraString} (${this.cameras.length}) `; } @@ -61,7 +59,7 @@ export class CameraSelectionUi { // camera label with a count. if (!name || name === "") { name = [ - Html5QrcodeScannerStrings.anonymousCameraPrefix(), + t('scanner.anonymousCameraPrefix'), anonymousCameraId++ ].join(" "); } diff --git a/src/ui/scanner/camera-zoom-ui.ts b/src/ui/scanner/camera-zoom-ui.ts index c49eecc2..2ded6ae0 100644 --- a/src/ui/scanner/camera-zoom-ui.ts +++ b/src/ui/scanner/camera-zoom-ui.ts @@ -1,9 +1,9 @@ /** * @fileoverview * File for camera zooming UI. - * + * * @author mebjas - * + * * The word "QR Code" is registered trademark of DENSO WAVE INCORPORATED * http://www.denso-wave.com/qrcode/faqpatent-e.html */ @@ -13,7 +13,7 @@ PublicUiElementIdAndClasses } from "./base"; -import { Html5QrcodeScannerStrings } from "../../strings"; +import { t } from "../../strings"; /** Callback when zoom value changes with the slider UI. */ export type OnCameraZoomValueChangeCallback = (zoomValue: number) => void; @@ -59,7 +59,7 @@ export class CameraZoomUi { this.rangeInput.style.outline = "none"; this.rangeInput.style.opacity = "0.7"; - let zoomString = Html5QrcodeScannerStrings.zoom(); + let zoomString = t('scanner.zoom'); this.rangeText.innerText = `${this.rangeInput.value}x ${zoomString}`; this.rangeText.style.marginRight = "10px"; @@ -73,7 +73,7 @@ export class CameraZoomUi { } private onValueChange() { - let zoomString = Html5QrcodeScannerStrings.zoom(); + let zoomString = t('scanner.zoom'); this.rangeText.innerText = `${this.rangeInput.value}x ${zoomString}`; if (this.onChangeCallback) { this.onChangeCallback(parseFloat(this.rangeInput.value)); @@ -113,7 +113,7 @@ export class CameraZoomUi { //#endregion /** - * Creates and renders the zoom slider if {@code renderOnCreate} is + * Creates and renders the zoom slider if {@code renderOnCreate} is * {@code true}. */ public static create( diff --git a/src/ui/scanner/file-selection-ui.ts b/src/ui/scanner/file-selection-ui.ts index ebf68f72..ddc3b5fb 100644 --- a/src/ui/scanner/file-selection-ui.ts +++ b/src/ui/scanner/file-selection-ui.ts @@ -1,14 +1,14 @@ /** * @fileoverview * File for file selection UI handling. - * + * * @author mebjas - * + * * The word "QR Code" is registered trademark of DENSO WAVE INCORPORATED * http://www.denso-wave.com/qrcode/faqpatent-e.html */ -import {Html5QrcodeScannerStrings} from "../../strings"; +import { t } from "../../strings"; import { BaseUiElementFactory, PublicUiElementIdAndClasses @@ -41,7 +41,7 @@ export class FileSelectionUi { fileScanLabel.style.display = "inline-block"; this.fileBasedScanRegion.appendChild(fileScanLabel); - + this.fileSelectionButton = BaseUiElementFactory.createElement( "button", @@ -61,7 +61,7 @@ export class FileSelectionUi { this.fileScanInput.accept = "image/*"; this.fileScanInput.style.display = "none"; fileScanLabel.appendChild(this.fileScanInput); - + let $this = this; /*eslint complexity: ["error", 5]*/ this.fileScanInput.addEventListener("change", (e: Event) => { @@ -141,15 +141,14 @@ export class FileSelectionUi { onFileSelected(file); dragAndDropMessage.innerText - = Html5QrcodeScannerStrings.dragAndDropMessage(); + = t('scanner.dragAndDropMessage'); break; } - + // None of the files were images. if (!isAnyFileImage) { dragAndDropMessage.innerText - = Html5QrcodeScannerStrings - .dragAndDropMessageOnlyImages(); + = t('scanner.dragAndDropMessageOnlyImages'); } } @@ -207,7 +206,7 @@ export class FileSelectionUi { private createDragAndDropMessage(): HTMLDivElement { let dragAndDropMessage = document.createElement("div"); dragAndDropMessage.innerText - = Html5QrcodeScannerStrings.dragAndDropMessage(); + = t('scanner.dragAndDropMessage'); dragAndDropMessage.style.fontWeight = "400"; return dragAndDropMessage; } @@ -224,16 +223,16 @@ export class FileSelectionUi { imageFileName = `${start8Chars}....${last8Chars}`; } - let newText = Html5QrcodeScannerStrings.fileSelectionChooseAnother() + let newText = t('scanner.fileSelectionChooseAnother') + " - " + imageFileName; this.fileSelectionButton.innerText = newText; } private setInitialValueToButton() { - let initialText = Html5QrcodeScannerStrings.fileSelectionChooseImage() + let initialText = t('scanner.fileSelectionChooseImage') + " - " - + Html5QrcodeScannerStrings.fileSelectionNoImageSelected(); + + t('scanner.fileSelectionNoImageSelected'); this.fileSelectionButton.innerText = initialText; } @@ -244,12 +243,12 @@ export class FileSelectionUi { /** * Creates a file selection UI and renders. - * + * * @param parentElement parent div element to render the UI to. * @param showOnRender if {@code true}, the UI will be shown upon render * else hidden. * @param onFileSelected callback to be called when file selection changes. - * + * * @returns Instance of {@code FileSelectionUi}. */ public static create( @@ -260,4 +259,4 @@ export class FileSelectionUi { parentElement, showOnRender, onFileSelected); return button; } -} +} diff --git a/src/ui/scanner/torch-button.ts b/src/ui/scanner/torch-button.ts index f39cbe7e..473eeb87 100644 --- a/src/ui/scanner/torch-button.ts +++ b/src/ui/scanner/torch-button.ts @@ -1,15 +1,15 @@ /** * @fileoverview * File for torch related UI components and handling. - * + * * @author mebjas - * + * * The word "QR Code" is registered trademark of DENSO WAVE INCORPORATED * http://www.denso-wave.com/qrcode/faqpatent-e.html */ import { BooleanCameraCapability } from "../../camera/core"; -import { Html5QrcodeScannerStrings } from "../../strings"; +import { t } from "../../strings"; import { BaseUiElementFactory, PublicUiElementIdAndClasses @@ -32,7 +32,7 @@ class TorchController { private readonly torchCapability: BooleanCameraCapability; private readonly buttonController: TorchButtonController; private readonly onTorchActionFailureCallback: OnTorchActionFailureCallback; - + // Mutable states. private isTorchOn: boolean = false; @@ -52,10 +52,10 @@ class TorchController { /** * Flips the state of the torch. - * + * *

Turns torch On if current state is Off and vice-versa. *

Modifies the UI state accordingly. - * + * * @returns Promise that finishes when the async action is done. */ public async flipState(): Promise { @@ -77,8 +77,8 @@ class TorchController { if (isTorchOn === isTorchOnExpected) { // Action succeeded, flip the state. this.buttonController.setText(isTorchOnExpected - ? Html5QrcodeScannerStrings.torchOffButton() - : Html5QrcodeScannerStrings.torchOnButton()); + ? t('scanner.torchOffButton') + : t('scanner.torchOnButton')); this.isTorchOn = isTorchOnExpected; } else { // Torch didn't get set as expected. @@ -91,8 +91,8 @@ class TorchController { private propagateFailure( isTorchOnExpected: boolean, error?: any) { let errorMessage = isTorchOnExpected - ? Html5QrcodeScannerStrings.torchOnFailedMessage() - : Html5QrcodeScannerStrings.torchOffFailedMessage(); + ? t('scanner.torchOnFailedMessage') + : t('scanner.torchOffFailedMessage'); if (error) { errorMessage += "; Error = " + error; } @@ -101,7 +101,7 @@ class TorchController { /** * Resets the state. - * + * *

Note: Doesn't turn off the torch implicitly. */ public reset() { @@ -121,7 +121,7 @@ export class TorchButton implements TorchButtonController { private readonly onTorchActionFailureCallback: OnTorchActionFailureCallback; private torchController: TorchController; - + private constructor( torchCapability: BooleanCameraCapability, onTorchActionFailureCallback: OnTorchActionFailureCallback) { @@ -139,7 +139,7 @@ export class TorchButton implements TorchButtonController { private render( parentElement: HTMLElement, torchButtonOptions: TorchButtonOptions) { this.torchButton.innerText - = Html5QrcodeScannerStrings.torchOnButton(); + = t('scanner.torchOnButton'); this.torchButton.style.display = torchButtonOptions.display; this.torchButton.style.marginLeft = torchButtonOptions.marginLeft; @@ -150,7 +150,7 @@ export class TorchButton implements TorchButtonController { $this.torchButton.classList.remove( PublicUiElementIdAndClasses.TORCH_BUTTON_CLASS_TORCH_OFF); $this.torchButton.classList.add( - PublicUiElementIdAndClasses.TORCH_BUTTON_CLASS_TORCH_ON); + PublicUiElementIdAndClasses.TORCH_BUTTON_CLASS_TORCH_ON); } else { $this.torchButton.classList.remove( PublicUiElementIdAndClasses.TORCH_BUTTON_CLASS_TORCH_ON); @@ -196,17 +196,17 @@ export class TorchButton implements TorchButtonController { /** * Resets the state. - * + * *

Note: Doesn't turn off the torch implicitly. */ public reset() { - this.torchButton.innerText = Html5QrcodeScannerStrings.torchOnButton(); + this.torchButton.innerText = t('scanner.torchOnButton'); this.torchController.reset(); } /** * Factory method for creating torch button. - * + * * @param parentElement parent HTML element to render torch button into * @param torchCapability torch capability of the camera * @param torchButtonOptions options for creating torch