Is Manga Translator🍓 safe?
Manga Translator sends a device fingerprint and base64-encoded image data to ichigoreader.com with every translation request.
When a user triggers image translation, the extension captures the target image as base64 and transmits it to ichigoreader.com/translate along with a detailed hardware fingerprint. The fingerprint includes WebGL renderer info, canvas hash, screen dimensions, hardware concurrency, device memory, connection type, locale, and other browser capability flags. Both the fingerprint and image data are sent on every translation call, not just at install time.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Manga Translator sends a device fingerprint
Translating an image builds a fingerprint from your renderer, screen, hardware, locale, capabilities, touch/orientation, user-agent, and JS heap signals, sent to ichigoreader.com/translate and also as a query parameter to /metrics.
You enable translation on a page or request an image translation.
The content script then finds eligible page images and converts them into translation jobs.
The extension computes a device fingerprint and attaches it to translation traffic.
The same fingerprint function is referenced by the translation POST and the metrics GET.
| Field | Value | Why it matters | |
|---|---|---|---|
Device fingerprint | 3f2a91c0b8a4d12e7c903f1a5e61b02a9d4c8f33 | Lets the service correlate translation requests from the same browser even when the page being translated changes. | |
Client ID | 9b3b3a8e-7d52-4e19-9b8c-91c6f9a2d4b7 | Gives the service another stable identifier for the same extension install. | |
Image being translated | data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+/p9sAAAAASUVORK5CYII= | Sends the selected page image to the translation service so text can be extracted and translated. | |
Target language | en | Shows which translation language you requested for the image. | |
Network address | 198.51.100.23 (illustrative) | The server receiving the request can associate the fingerprint with the connection used for translation. |
Fingerprint creation and request attachment in the shipped background bundle
t.getFingerprint = function() {
if (d) return d;
const e = function() {
try {
if (s) {
const e = s.createElement("canvas").getContext("webgl");
if (!e) return "no-webgl";
const t = e.getExtension("WEBGL_debug_renderer_info");
return t ? e.getParameter(t.UNMASKED_RENDERER_WEBGL) : "unknown"
}
if ("undefined" != typeof OffscreenCanvas) {
const e = new OffscreenCanvas(1, 1).getContext("webgl");
if (!e) return "no-webgl";
const t = e.getExtension("WEBGL_debug_renderer_info");
return t ? e.getParameter(t.UNMASKED_RENDERER_WEBGL) : "unknown"
}
} catch (e) {
return "webgl-error"
}
return "webgl-unavailable"
}(),
t = `${null!==(w=null==l?void 0:l.hardwareConcurrency)&&void 0!==w?w:"unknown"}-${null!==(b=null==l?void 0:l.deviceMemory)&&void 0!==b?b:"unknown"}`,
n = function() {
var e, t, n, o, r;
const i = null == l ? void 0 : l.connection;
return `${null!==(e=null==i?void 0:i.type)&&void 0!==e?e:"unknown"}-${null!==(t=null==i?void 0:i.rtt)&&void 0!==t?t:"unknown"}-${null!==(n=null==i?void 0:i.downlinkMax)&&void 0!==n?n:"unknown"}-${null!==(o=null==i?void 0:i.effectiveType)&&void 0!==o?o:"unknown"}-${null!==(r=null==i?void 0:i.saveData)&&void 0!==r&&r}`
}(),
o = (new Date).getTimezoneOffset(),
r = function() {
var e;
const t = u;
if (!t) return "screen-unavailable";
return `${t.width}x${t.height}-${t.colorDepth}-${t.pixelDepth}-${t.availWidth}x${t.availHeight}-${null!==(e=null==a?void 0:a.devicePixelRatio)&&void 0!==e?e:1}`
}(),
f = function() {
try {
if (!s) return "canvas-unavailable";
const e = s.createElement("canvas"),
t = e.getContext("2d");
if (!t) return "no-2d-context";
t.textBaseline = "top", t.font = "14px Arial", t.fillStyle = "rgba(102, 204, 0, 0.7)", t.fillRect(125, 1, 62, 20), t.fillStyle = "#f60", t.fillText("BrowserLeaks,com <canvas> 1.0", 2, 15), t.fillStyle = "rgba(102, 204, 0, 0.7)", t.fillText("BrowserLeaks,com <canvas> 1.0", 4, 17);
const n = e.toDataURL();
let o = 0;
for (let e = 0; e < n.length; e++) o = (o << 5) - o + n.charCodeAt(e), o |= 0;
return o.toString()
} catch (e) {
return "canvas-error"
}
}(),
g = function() {
const e = [],
t = l,
n = a;
return t && "serviceWorker" in t && e.push("sw"), a && "localStorage" in a && e.push("ls"), a && "sessionStorage" in a && e.push("ss"), "indexedDB" in i && e.push("idb"), t && "geolocation" in t && e.push("geo"), "Notification" in i && e.push("notif"), t && "permissions" in t && e.push("perm"), "boolean" == typeof(null == t ? void 0 : t.cookieEnabled) && t.cookieEnabled && e.push("cookie"), void 0 !== (null == t ? void 0 : t.doNotTrack) && e.push("dnt"), t && "onLine" in t && e.push("online"), t && "deviceMemory" in t && e.push("devmem"), t && "connection" in t && e.push("conn"), "function" == typeof(null == t ? void 0 : t.getBattery) && e.push("bat"), t && "mediaDevices" in t && e.push("media"), n && "webkitRequestFileSystem" in n && e.push("fs"), n && "webkitStorageInfo" in n && e.push("storage"), e.sort().join(",")
}(),
v = function() {
var e, t, n, o;
const r = (null == l ? void 0 : l.languages) && l.languages.length > 0 ? l.languages : (null == l ? void 0 : l.language) ? [l.language] : [];
return `${null!==(e=null==l?void 0:l.language)&&void 0!==e?e:"unknown"}-${null!==(t=null==l?void 0:l.userLanguage)&&void 0!==t?t:"unknown"}-${null!==(n=null==l?void 0:l.browserLanguage)&&void 0!==n?n:"unknown"}-${null!==(o=null==l?void 0:l.systemLanguage)&&void 0!==o?o:"unknown"}-${r.join(",")}`
}(),
m = function() {
var e;
const t = a;
return `${null!==(e=null==l?void 0:l.maxTouchPoints)&&void 0!==e?e:0}-${!(!t||!("ontouchstart"in t))}-${"TouchEvent"in i}-${"PointerEvent"in i}`
}(),
h = function() {
var e, t, n, o, r;
const i = a;
return `${null!==(t=null===(e=null==u?void 0:u.orientation)||void 0===e?void 0:e.type)&&void 0!==t?t:"unknown"}-${null!==(r=null!==(o=null===(n=null==u?void 0:u.orientation)||void 0===n?void 0:n.angle)&&void 0!==o?o:null==i?void 0:i.orientation)&&void 0!==r?r:0}-${!(!a||!("onorientationchange"in a))}`
}(),
p = function() {
var e, t, n, o, r;
const i = null !== (e = null == l ? void 0 : l.userAgent) && void 0 !== e ? e : "unknown";
return `${null!==(t=null==l?void 0:l.platform)&&void 0!==t?t:"unknown"}-${null!==(n=null==l?void 0:l.vendor)&&void 0!==n?n:""}-${!!(null==l?void 0:l.cookieEnabled)}-${"function"==typeof(null==l?void 0:l.javaEnabled)&&l.javaEnabled()}-${/iPad|iPhone|iPod/.test(i)}-${/Safari/.test(i)&&!/Chrome/.test(i)}-${(null===(o=i.match(/Version\/(\d+)/))||void 0===o?void 0:o[1])||"unknown"}-${(null===(r=i.match(/(iPhone|iPad|iPod)/))||void 0===r?void 0:r[1])||"unknown"}`
}(),
y = [e, t, n, o, r, f, g, v, m, h, p, function() {
try {
const e = null != c ? c : null == a ? void 0 : a.performance;
if (!e) return "no-performance";
const t = e.timing,
n = e.memory,
o = null == l ? void 0 : l.connection,
r = (null == t ? void 0 : t.navigationStart) || 0,
i = ((null == t ? void 0 : t.loadEventEnd) || 0) - r,
s = (null == n ? void 0 : n.usedJSHeapSize) || 0,
u = (null == n ? void 0 : n.totalJSHeapSize) || 0,
d = (null == n ? void 0 : n.jsHeapSizeLimit) || 0,
f = (null == o ? void 0 : o.effectiveType) || "unknown";
return `${i}-${s}-${u}-${d}-${f}-${(null==o?void 0:o.downlink)||0}-${(null==o?void 0:o.rtt)||0}`
} catch (e) {
return "performance-error"
}
}()].join("-");
var w, b;
return d = function(e) {
let t = 5381;
for (let n = 0; n < e.length; n++) t = 33 * t ^ e.charCodeAt(n);
let n = (t >>> 0).toString(16).padStart(8, "0");
const o = Math.ceil(e.length / 4);
for (let t = 0; t < 4; t++) {
const r = e.slice(t * o, (t + 1) * o);
let i = 5381;
for (let e = 0; e < r.length; e++) i = 33 * i ^ r.charCodeAt(e);
n += (i >>> 0).toString(16).padStart(8, "0")
}
return n
}(y), d
}t.getCurrentUser = function() {
return o(this, void 0, void 0, (function*() {
yield u();
const e = yield r.appConfig.getClientUuid(), n = f(), o = yield fetch(`${t.baseUrl}/metrics?clientUuid=${e}&fingerprint=${(0,i.getFingerprint)()}`, {
method: "GET",
headers: n
});
if (o.status !== s.Ok) throw new Error("Failed to retrieve user.");
return yield o.json()
}))
}t.translateImage = function(e, n, a) {
return o(this, void 0, void 0, (function*() {
const o = yield r.appConfig.getClientUuid(), l = yield fetch(`${t.baseUrl}/translate`, {
method: "POST",
headers: f(),
body: JSON.stringify({
base64Images: [n],
translationModel: a,
targetLangCode: e,
fingerprint: (0, i.getFingerprint)(),
clientUuid: o
})
});
if (l.status === s.InternalServerError) {
const e = "Server is down or experiencing issues. Sorry for the inconvenience.";
return {
errorMessage: e,
translations: [{
originalLanguage: "Unknown",
translatedText: e,
minX: 0,
minY: 0,
maxX: 200,
maxY: 200
}]
}
}
if (l.status === s.TooManyRequests) {
const e = "Out of translations. Server costs are expensive. Upgrade for more!";
return {
errorMessage: e,
translations: [{
originalLanguage: "Unknown",
translatedText: e,
minX: 0,
minY: 0,
maxX: 200,
maxY: 200
}]
}
}
return {
translations: (yield l.json()).images[0]
}
}))
}- ichigoreader.com
Receives translation POSTs at /translate with fingerprint and clientUuid fields; the code also sends the fingerprint in the /metrics query string.