Chuyển đổi encoding tiếng Nhật trong Node.js

Chuyển đổi encoding tiếng Nhật trong Node.js
Photo by Gerd Altmann from Pixabay

Vâng, lại là vấn đề encoding với tiếng Nhật 🤣 Dù tôi đã chuyển sang làm việc khác, với một ngôn ngữ khác (cụ thể là Node.js code bằng TypeScript) thì vẫn không chạy đâu cho khỏi nắng. Còn làm với Nhật thì chắc tôi còn phải sống chung với lũ dài dài.

Vấn đề

Tương tự như vấn đề tôi gặp không lâu trước đây, lần này là với ngôn ngữ khác và bối cảnh khác đi một chút.

Số là tôi cần làm một server để nhận request trả về từ các dịch vụ bên ngoài. Server xử lý cũng đơn giản thôi, nhận request (qua HTTP POST), lấy thông tin được gửi cho server rồi gọi API. Server đơn giản nên team quyết định dùng Node.js, code bằng TypeScript với thư viện express 😇.

Mọi chuyện đều ổn cho đến khi gặp một dịch vụ (không tiện nói tên) không chịu dùng utf-8 mà vẫn dùng Shift_JIS 🥲 như “truyền thống” của người Nhật bao lâu nay. Và vì là dịch vụ bên ngoài, mình dùng thì phải theo nó chứ không bắt nó theo mình được 🤬, nên bắt buộc tôi phải tìm cách xử lý được dữ liệu dạng Shift_JIS và chuyển thành utf-8 để xử lý bên hệ thống của mình.

Trước đây, với Python thì việc này không có gì khó, vì Python hỗ trợ chuyển đổi dữ liệu của các bảng mã rất dễ dàng 🥰. Thế nhưng với Node.js thì khó khăn hơn, do việc chuyển đổi này không hề được hỗ trợ sẵn 😤.

Giải quyết

Chuyển đổi encoding

Nói nghe dark deep thế thôi chứ giải quyết vấn đề này không có gì cao siêu cả 🧐. Tôi có thử một số cách “native” của Node.js (dùng escape, unescape, decodeURIComponent) nhưng không thành công. Và thế là tôi phải tìm đến các thư viện bên ngoài.

Sau khi tìm hiểu một hồi (không lâu lắm 🤪) thì tôi lọc ra được 3 thư viện có thể dùng được:

  • encoding-japanese: chuyển đổi encoding tiếng Nhật ổn, tương đối gọn nhẹ nhưng 2 năm rồi không update 😫.
  • iconv-lite: chuyển đổi tiếng Nhật và nhiều loại encoding khác ổn, gọn nhẹ và được update khá thường xuyên 🤤.
  • icon: chuyển đổi rất nhiều encoding khác nhau (đầy đủ nhất trong các thư viện), được update thường xuyên tuy nhiên lại hơi nặng 😵‍💫.

Xét thấy vấn đề đang gặp phải (và nhiều yếu tố khác nữa 😎) thì tôi quyết định xử dụng iconv-lite. Toàn bộ code để chuyển đổi encoding như dưới đây:

import iconv from 'iconv-lite';

const urlEncodedToBytes = (encoded: string) => {
  let decoded = Buffer.from('');
  for (let i = 0; i < encoded.length; i++) {
    if (encoded[i] === '%') {
      const charBuf = Buffer.from(`${encoded[i + 1]}${encoded[i + 2]}`, 'hex');
      decoded = Buffer.concat([decoded, charBuf]);
      i += 2;
    } else {
      const charBuf = Buffer.from(encoded[i]);
      decoded = Buffer.concat([decoded, charBuf]);
    }
  }
  return decoded;
};

const data = '%93%FA%96%7B%8C%EA'; // url encoded của từ 日本語
const buffer = urlEncodedToBytes(data);
console.log(iconv.decode(converted, 'sjis'))
// 日本語

Cấu hình server Node.js

Phần chuyển đổi encoding đã xong, lúc này lại phát sinh một vấn đề mới 😫, đó là làm thế nào để nhận dữ liệu thô (mã hóa bằng Shift_JIS).

Vì tôi có kiểm tra các request đến server thì dữ liệu tiếng Nhật bị chuyển đổi tự động (khá chắc là chuyển đổi trên server) luôn rồi (nhưng chuyển đổi sai mới chết 😡). Tất cả các chữ tiếng Nhật đều được chuyển thành 0xFFFF nên lấy dữ liệu đó ra không dùng được 😹.

Cũng may là vấn đề này không khó lắm, quan trọng là phải Google đúng keyword 🥳. Giờ tôi cũng quên mất tìm như thế nào rồi, nhưng cũng mất kha khá thời gian (lâu hơn nhiều so với tìm hiểu thư viện ở trên).

Kết quả, express hỗ trợ việc này khá đơn giản bằng config như thế này:

import express from 'express';

const app = express();
app.use(express.urlencoded({ extended: true }));
...

Vậy là xong, mọi request đến server sẽ không được chuyển đổi tự động nữa mà nhận luôn dữ liệu thô (dưới dạng url encoded với các dấu %).

works
Nguồn: 9gag

Tôi xin lỗi nếu bài viết có bất kỳ typo nào. Nếu bạn nhận thấy điều gì bất thường, xin hãy cho tôi biết.

Nếu có bất điều gì muốn nói, bạn có thể liên hệ với tôi qua các mạng xã hội, tạo discussion hoặc report issue trên Github.