Skip to main content

Webpage translation JSSDK

Description

This page translation plugin, powered by a large translation model, adds one-click multilingual transformation to your website.

How it works

image

Integrate the JSSDK

Step 1: Add the script

<html>
  <head></head>
  <body>
    <!-- Insert the translation script before the closing </body> tag -->
    <script src="https://g.alicdn.com/translate-js-sdk/translate-js-sdk-stable/2.0.6/light.js"></script>
  </body>
</html>

Step 2: Configure the JSSDK

interface ITokenResponseData {
  code: 200;
  data: {
    url: string,
    host: string,
    method: "POST",
    headers: {
      "host": string,
      "x-acs-action": "BatchTranslateForHtml",
      "x-acs-version": string,
      "x-acs-date": string;
      "x-acs-signature-nonce": string;
      "content-type": "application/json",
      "x-acs-content-sha256": string;
      Authorization: string;
    };
    body: string;
  };
}
interface ITokenRequestData {
  sourceLanguage: string;
  targetLanguage: string;
  streaming: false,
  text: {[index: number]: string}
  scene: 'mt-turbo',
  fallbackTimeoutMs: number;
}
interface ISetupConfig {
  getToken: (data: ITokenRequestData) => Promise<ITokenResponseData>;
}
// Initialize only once.
__AliTranslate.setup({
  getToken: async (data) => {
    const res = await fetch('YOUR_GET_TOKEN_URL', { method: 'post', body: JSON.stringify(data) });
    return (await res.json()).data;
  }
});

Step 3: Call page translation

Display translation only
interface IPageTranslate {
  srcLanguage?: string; // The source language. The default value is 'auto'.
  tgtLanguage?: string; // The target language. The default value is 'en'.
  lazyload?: boolean; // Specifies whether to translate only the visible area. The default value is false.
  lazyOffset?: number; // The offset for extending the visible area.
  target?: HTMLElement; // The target area to translate. The default value is body.
  except?: string; // The areas to exclude from translation. Default value is an empty string.
}
const instance = __AliTranslate.pageTranslate({
  lazyload: true,
  lazyOffset: 500
});
Side-by-side translation
interface IParagraphTranslate {
  srcLanguage?: string; // The source language. The default value is 'auto'.
  tgtLanguage?: string; // The target language. The default value is 'en'.
  target?: HTMLElement; // The target area to translate. The default value is body.
  dynamic?: boolean; // Specifies whether to enable dynamic translation. The default value is false.
}
const instance = __AliTranslate.paragraphTranslate({
  srcLanguage: 'zh',
  tgtLanguage: 'en'
});

Step 4: Cancel translation

// Cancels the translation and restores the original page content.
instance.destroy();

PageTranslate parameters

Parameter

Description

Type

Default

srcLanguage

The source language.

String

auto (automatically detected from page content)

tgtLanguage

The target language.

String

en

lazyload

Specifies whether to translate only the visible area.

Boolean

false

lazyOffset

The pixel offset for extending the visible area. This parameter only applies when lazyload is true.

Number

-1 (No offset is applied.)

target

The target area to translate.

HTMLElement | HTMLElement[]

body

except

A CSS selector for areas to exclude from translation.

Selector(String)

'' (empty string)

ParagraphTranslate parameters

Parameter

Description

Type

Default

srcLanguage

The source language.

String

auto (automatically detected from page content)

tgtLanguage

The target language.

String

en

target

The target area to translate.

HTMLElement | HTMLElement[]

body

dynamic

If true, the page is automatically re-translated when its content changes. If false, the page is translated only once.

Boolean

false

lazyload

Specifies whether to translate only the visible area.

Boolean

false

lazyOffset

The pixel offset for extending the visible area. This parameter only applies when lazyload is true.

Number

300

Implement a token service

Sign requests using the V3 request body and signature mechanism of Alibaba Cloud.
// Generate a signature by using the calculateSignature method.
import * as crypto from 'crypto';
export interface SignatureRequest {
  httpMethod: string;
  canonicalUri: string;
  host: string;
  xAcsAction: string;
  xAcsVersion: string;
  headers: Record<string, string>;
  body: any;
  queryParam: Record<string, any>;
}
export interface BatchTranslateRequest {
  scene: string;
  sourceLanguage: string;
  streaming: boolean;
  targetLanguage: string;
  text: { [key: string]: string };
}
export class Request implements SignatureRequest {
  httpMethod: string;
  canonicalUri: string;
  host: string;
  xAcsAction: string;
  xAcsVersion: string;
  headers: Record<string, string>;
  body: any;
  queryParam: Record<string, any>;
  constructor(httpMethod: string, canonicalUri: string, host: string, xAcsAction: string, xAcsVersion: string) {
    this.httpMethod = httpMethod;
    this.canonicalUri = canonicalUri || '/';
    this.host = host;
    this.xAcsAction = xAcsAction;
    this.xAcsVersion = xAcsVersion;
    this.headers = {};
    this.body = null;
    this.queryParam = {};
    this.initHeader();
  }
  private initHeader() {
    const date = new Date();
    this.headers = {
      'host': this.host,
      'x-acs-action': this.xAcsAction,
      'x-acs-version': this.xAcsVersion,
      'x-acs-date': date.toISOString().replace(/\..+/, 'Z'),
      'x-acs-signature-nonce': crypto.randomBytes(16).toString('hex')
    }
  }
}
const ALGORITHM = 'ACS3-HMAC-SHA256';
const accessKeyId = process.env.ACCESS_KEY_ID; // Your Access Key ID.
const accessKeySecret = process.env.ACCESS_KEY_SECRET; // Your Access Key Secret.
const workspaceId = process.env.WORKSPACE_ID; // Your workspace ID from Model Studio.
const encoder = new TextEncoder();
const httpMethod = 'POST';
const canonicalUri = '/anytrans/translate/batchForHtml';
const xAcsAction = 'BatchTranslateForHtml';
const xAcsVersion = '2025-07-07';
const host = 'anytrans.cn-beijing.aliyuncs.com';
export interface SignedRequest {
  url: string;
  method: string;
  host: string;
  headers: Record<string, string>;
  body?: any; // Can be a Uint8Array or a Base64 string.
}
export function getAuthorization(signRequest: SignatureRequest): SignedRequest {
  try {
    const newQueryParam: Record<string, any> = {};
    processObject(newQueryParam, "", signRequest.queryParam);
    signRequest.queryParam = newQueryParam;
    // Step 1: Build the canonical request string.
    const canonicalQueryString = Object.entries(signRequest.queryParam)
      .sort(([a], [b]) => a.localeCompare(b))
      .map(([key, value]) => `${percentCode(key)}=${percentCode(value)}`)
      .join('&');
    // The request payload. For requests with an empty body, such as GET requests, the RequestPayload is an empty string.
    const requestPayload = signRequest.body || encoder.encode('');
    const hashedRequestPayload = sha256Hex(requestPayload);
    signRequest.headers['x-acs-content-sha256'] = hashedRequestPayload;
    // Convert all keys to lowercase.
    signRequest.headers = Object.fromEntries(
      Object.entries(signRequest.headers).map(([key, value]) => [key.toLowerCase(), value])
    );
    const sortedKeys = Object.keys(signRequest.headers)
      .filter(key => key.startsWith('x-acs-') || key === 'host' || key === 'content-type')
      .sort();
    // A list of signed headers. The header names are in lowercase, sorted alphabetically, and separated by semicolons (;).
    const signedHeaders = sortedKeys.join(";")
    // Construct the canonical headers. The canonical headers are concatenated in ascending order of their lowercase header names.
    const canonicalHeaders = sortedKeys.map(key => `${key}:${signRequest.headers[key]}`).join('\n') + '\n';
    const canonicalRequest = [
      signRequest.httpMethod,
      signRequest.canonicalUri,
      canonicalQueryString,
      canonicalHeaders,
      signedHeaders,
      hashedRequestPayload
    ].join('\n');
    console.log('canonicalRequest=========>\n', canonicalRequest);
    // Step 2: Build the string to sign.
    const hashedCanonicalRequest = sha256Hex(encoder.encode(canonicalRequest));
    const stringToSign = `${ALGORITHM}\n${hashedCanonicalRequest}`;
    console.log('stringToSign=========>', stringToSign);
    // Step 3: Calculate the signature.
    const signature = hmac256(accessKeySecret!, stringToSign);
    console.log('signature=YOUR_SIGNATURE>', signature);
    // Step 4: Build the Authorization header.
    const authorization = `${ALGORITHM} Credential=${accessKeyId},SignedHeaders=${signedHeaders},Signature=YOUR_SIGNATURE
    console.log('authorization=========>', authorization);
    signRequest.headers['Authorization'] = authorization;
    // Build the complete URL.
    let url = `https://${signRequest.host}${signRequest.canonicalUri}`;
    if (signRequest.queryParam && Object.keys(signRequest.queryParam).length > 0) {
      const query = new URLSearchParams(signRequest.queryParam);
      url += '?' + query.toString();
    }
    return {
      url,
      host: signRequest.host,
      method: signRequest.httpMethod.toUpperCase(),
      headers: signRequest.headers,
      body: signRequest.body
    };
  } catch (error) {
    console.error('Failed to get authorization');
    console.error(error);
    throw error;
  }
}
function percentCode(str: string): string {
  return encodeURIComponent(str)
    .replace(/\+/g, '%20')
    .replace(/\*/g, '%2A')
    .replace(/~/g, '%7E');
}
function hmac256(key: string, data: string): string {
  const hmac = crypto.createHmac('sha256', key);
  hmac.update(data, 'utf8');
  return hmac.digest('hex').toLowerCase();
}
function sha256Hex(bytes: any): string {
  const hash = crypto.createHash('sha256');
  const digest = hash.update(bytes).digest('hex');
  return digest.toLowerCase();
}
function processObject(map: Record<string, any>, key: string, value: any): void {
  // If the value is null, no further processing is required.
  if (value === null) {
    return;
  }
  if (key === null) {
    key = "";
  }
  // If the value is an array, iterate over each element and process it recursively.
  if (Array.isArray(value)) {
    value.forEach((item, index) => {
      processObject(map, `${key}.${index + 1}`, item);
    });
  } else if (typeof value === 'object' && value !== null) {
    // If the value is an object, iterate over each key-value pair and process it recursively.
    Object.entries(value).forEach(([subKey, subValue]) => {
      processObject(map, `${key}.${subKey}`, subValue);
    });
  } else {
    // For keys that start with a period (.), remove the leading period to maintain key continuity.
    if (key.startsWith('.')) {
      key = key.slice(1);
    }
    map[key] = String(value);
  }
}
export function formDataToString(formData: Record<string, any>): string {
  const tmp: Record<string, any> = {};
  processObject(tmp, "", formData);
  let queryString = '';
  for (let [key, value] of Object.entries(tmp)) {
    if (queryString !== '') {
      queryString += '&';
    }
    queryString += encodeURIComponent(key) + '=' + encodeURIComponent(value);
  }
  return queryString;
}
// Method for calculating signatures with fixed variables.
export interface FixedSignatureResult {
  httpMethod: string;
  canonicalUri: string;
  host: string;
  xAcsAction: string;
  xAcsVersion: string;
  signature: string;
  date: string;
  nonce: string;
  workspaceId: string;
  authorization: string;
}
export function calculateSignature(req: BatchTranslateRequest): SignedRequest {
  // Create a request instance.
  const signRequest = new Request(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion);
  // Set query parameters.
  signRequest.queryParam = {
    RegionId: 'cn-beijing',
  };
  // Create a Uint8Array for the body.
  const bodyUint8Array = encoder.encode(JSON.stringify({
    ...req,
    workspaceId,
  }));
  // The request body used for signature calculation.
  signRequest.body = bodyUint8Array;
  signRequest.headers['content-type'] = 'application/json';
  // Get the signature.
  const result = getAuthorization(signRequest);
  // Convert the body to a Base64 string for transmission.
  const bodyBase64 = Buffer.from(bodyUint8Array).toString('base64');
  return {
    ...result,
    body: bodyBase64 // Return a Base64 string instead of a Uint8Array.
  };
}

FAQ

1. How to obtain an AccessKey pair?

Obtain an AccessKey pair from the AccessKey Management section of the Alibaba Cloud console.

2. How to find the workspace ID?

Log in to the Alibaba Cloud account that is associated with your AccessKey pair. In the Model Studio console, find the workspace details in the lower-left corner. The Workspace ID in the pop-up window is the required workspaceId. Click the copy icon to the right of the field to copy the ID.

3. API permission error

This error indicates that Tongyi Multimodal Translation is not activated. To resolve this, use your Alibaba Cloud account to activate the service in the Tongyi Multimodal Translation console.

Change log

March 2026

Version

Release date

Description

2.0.6

March 5, 2026

Added the lazyload and lazyOffset parameters for side-by-side translation.

Agent Development
Agent Context
Agent Governance & Evolution
Store
Application Marketplace