translate-shield 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +221 -0
- package/dist/dom-crorgc8a.js +71 -0
- package/dist/dom-dauvp8pk.cjs +76 -0
- package/dist/index.cjs +508 -0
- package/dist/index.d.cts +21 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +505 -0
- package/dist/react.cjs +78 -0
- package/dist/react.d.cts +37 -0
- package/dist/react.d.ts +37 -0
- package/dist/react.js +75 -0
- package/dist/types-fjbg60f8.d.ts +28 -0
- package/package.json +112 -0
package/dist/react.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx } from 'react/jsx-runtime';
|
|
3
|
+
import { useState, useEffect } from 'react';
|
|
4
|
+
import { M as MARKER_ATTRIBUTES, d as detectEngine } from './dom-crorgc8a.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Marks a value the browser translator must leave alone.
|
|
8
|
+
*
|
|
9
|
+
* Prices, counters, order numbers and codes do not need translating, and every
|
|
10
|
+
* engine measured — Chrome, Edge, Firefox, Yandex and the Google bundle —
|
|
11
|
+
* honours both `translate="no"` and `.notranslate`. Because the translator never
|
|
12
|
+
* touches the node, the framework can keep updating it: no crash, no frozen
|
|
13
|
+
* value, and no risk of a merged number breaking the grammar of a sentence.
|
|
14
|
+
*
|
|
15
|
+
* Wrap the value, never the whole sentence, or the reader loses the translation.
|
|
16
|
+
*/ const NoTranslate = ({ children, as: Tag = 'span', className })=>/*#__PURE__*/ jsx(Tag, {
|
|
17
|
+
translate: "no",
|
|
18
|
+
className: className ? `notranslate ${className}` : 'notranslate',
|
|
19
|
+
children: children
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Reports when a translator starts working on the document, without patching
|
|
24
|
+
* anything. Apps that only want to warn the user, or to switch a formatter, can
|
|
25
|
+
* use this on its own; the shield uses the heavier observer instead.
|
|
26
|
+
*/ const observeTranslation = (onDetected)=>{
|
|
27
|
+
if (typeof document === 'undefined') return ()=>undefined;
|
|
28
|
+
const detectAndReport = ()=>{
|
|
29
|
+
const engine = detectEngine(document);
|
|
30
|
+
if (!engine) return false;
|
|
31
|
+
onDetected({
|
|
32
|
+
lang: document.documentElement.lang,
|
|
33
|
+
engine,
|
|
34
|
+
wrapperTag: ''
|
|
35
|
+
});
|
|
36
|
+
return true;
|
|
37
|
+
};
|
|
38
|
+
if (detectAndReport()) return ()=>undefined;
|
|
39
|
+
const observer = new MutationObserver(()=>{
|
|
40
|
+
if (detectAndReport()) observer.disconnect();
|
|
41
|
+
});
|
|
42
|
+
observer.observe(document.documentElement, {
|
|
43
|
+
childList: true,
|
|
44
|
+
subtree: true,
|
|
45
|
+
attributes: true,
|
|
46
|
+
attributeFilter: [
|
|
47
|
+
'lang',
|
|
48
|
+
...MARKER_ATTRIBUTES
|
|
49
|
+
]
|
|
50
|
+
});
|
|
51
|
+
return ()=>observer.disconnect();
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const IDLE = {
|
|
55
|
+
isTranslated: false,
|
|
56
|
+
engine: null,
|
|
57
|
+
lang: ''
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Tells a component whether the page is being machine translated, and by which
|
|
61
|
+
* engine. Always reports the idle state on the server and on the first client
|
|
62
|
+
* render, so it cannot cause a hydration mismatch.
|
|
63
|
+
*/ const useTranslationDetected = ()=>{
|
|
64
|
+
const [state, setState] = useState(IDLE);
|
|
65
|
+
useEffect(()=>observeTranslation((info)=>{
|
|
66
|
+
setState({
|
|
67
|
+
isTranslated: true,
|
|
68
|
+
engine: info.engine,
|
|
69
|
+
lang: info.lang
|
|
70
|
+
});
|
|
71
|
+
}), []);
|
|
72
|
+
return state;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export { NoTranslate, useTranslationDetected };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
type PatchedSurface = 'Node.prototype.removeChild' | 'Node.prototype.insertBefore' | 'Node.prototype.replaceChild' | 'Node.prototype.nodeValue' | 'Node.prototype.textContent' | 'CharacterData.prototype.data' | 'Element.prototype.attachShadow';
|
|
2
|
+
|
|
3
|
+
type TranslatorEngine = 'google' | 'yandex' | 'edge' | 'firefox';
|
|
4
|
+
interface TranslationInfo {
|
|
5
|
+
lang: string;
|
|
6
|
+
engine: TranslatorEngine | null;
|
|
7
|
+
wrapperTag: string;
|
|
8
|
+
}
|
|
9
|
+
interface RecoveredError {
|
|
10
|
+
method: 'removeChild' | 'insertBefore' | 'replaceChild';
|
|
11
|
+
redirected: boolean;
|
|
12
|
+
}
|
|
13
|
+
interface ShieldOptions {
|
|
14
|
+
root?: Element;
|
|
15
|
+
wrapperTags?: ReadonlyArray<string>;
|
|
16
|
+
onTranslationDetected?: (info: TranslationInfo) => void;
|
|
17
|
+
onRecoveredError?: (error: RecoveredError) => void;
|
|
18
|
+
onConflict?: (surfaces: PatchedSurface[]) => void;
|
|
19
|
+
debug?: boolean;
|
|
20
|
+
}
|
|
21
|
+
interface ShieldHandle {
|
|
22
|
+
stop: () => void;
|
|
23
|
+
isTranslated: () => boolean;
|
|
24
|
+
engine: () => TranslatorEngine | null;
|
|
25
|
+
conflicts: () => PatchedSurface[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type { PatchedSurface as P, RecoveredError as R, ShieldOptions as S, TranslationInfo as T, ShieldHandle as a, TranslatorEngine as b };
|
package/package.json
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "translate-shield",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Keeps React and Next.js apps correct when a browser translator rewrites the DOM: no crash, no frozen values, and no flash of the original language.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"google-translate",
|
|
7
|
+
"browser-translation",
|
|
8
|
+
"react",
|
|
9
|
+
"nextjs",
|
|
10
|
+
"removechild",
|
|
11
|
+
"notfounderror",
|
|
12
|
+
"mutationobserver",
|
|
13
|
+
"i18n",
|
|
14
|
+
"dom",
|
|
15
|
+
"accessibility"
|
|
16
|
+
],
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"type": "module",
|
|
19
|
+
"sideEffects": false,
|
|
20
|
+
"files": [
|
|
21
|
+
"dist"
|
|
22
|
+
],
|
|
23
|
+
"exports": {
|
|
24
|
+
".": {
|
|
25
|
+
"import": {
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"default": "./dist/index.js"
|
|
28
|
+
},
|
|
29
|
+
"require": {
|
|
30
|
+
"types": "./dist/index.d.cts",
|
|
31
|
+
"default": "./dist/index.cjs"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"./react": {
|
|
35
|
+
"import": {
|
|
36
|
+
"types": "./dist/react.d.ts",
|
|
37
|
+
"default": "./dist/react.js"
|
|
38
|
+
},
|
|
39
|
+
"require": {
|
|
40
|
+
"types": "./dist/react.d.cts",
|
|
41
|
+
"default": "./dist/react.cjs"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"./package.json": "./package.json"
|
|
45
|
+
},
|
|
46
|
+
"main": "./dist/index.cjs",
|
|
47
|
+
"module": "./dist/index.js",
|
|
48
|
+
"types": "./dist/index.d.ts",
|
|
49
|
+
"peerDependencies": {
|
|
50
|
+
"@types/react": "*",
|
|
51
|
+
"react": "^18.0.0 || ^19.0.0"
|
|
52
|
+
},
|
|
53
|
+
"peerDependenciesMeta": {
|
|
54
|
+
"react": {
|
|
55
|
+
"optional": true
|
|
56
|
+
},
|
|
57
|
+
"@types/react": {
|
|
58
|
+
"optional": true
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"scripts": {
|
|
62
|
+
"build": "bunchee",
|
|
63
|
+
"dev": "npm run dev -w tests/app",
|
|
64
|
+
"recorder": "vite --port 5200 --strictPort research/recorder",
|
|
65
|
+
"test": "vitest run",
|
|
66
|
+
"test:e2e": "playwright test",
|
|
67
|
+
"typecheck": "tsc --noEmit",
|
|
68
|
+
"summarise": "node scripts/summarise-fingerprints.mjs",
|
|
69
|
+
"verify": "npm run typecheck && npm run build && npm run smoke && npx publint --strict && npx @arethetypeswrong/cli --pack .",
|
|
70
|
+
"prepublishOnly": "npm run typecheck && npm run build && npm run smoke && npx publint --strict",
|
|
71
|
+
"postbuild": "node scripts/assert-directives.mjs",
|
|
72
|
+
"smoke": "node scripts/smoke.mjs"
|
|
73
|
+
},
|
|
74
|
+
"devDependencies": {
|
|
75
|
+
"@arethetypeswrong/cli": "^0.18.0",
|
|
76
|
+
"@playwright/test": "^1.55.0",
|
|
77
|
+
"@types/node": "^26.4.1",
|
|
78
|
+
"@types/react": "^19.0.0",
|
|
79
|
+
"bunchee": "^7.0.1",
|
|
80
|
+
"esbuild": "^0.28.2",
|
|
81
|
+
"publint": "^0.3.0",
|
|
82
|
+
"react": "^19.0.0",
|
|
83
|
+
"translation-resilience": "^0.3.0",
|
|
84
|
+
"typescript": "^5.6.0",
|
|
85
|
+
"vitest": "^2.1.0"
|
|
86
|
+
},
|
|
87
|
+
"workspaces": [
|
|
88
|
+
"tests/app"
|
|
89
|
+
],
|
|
90
|
+
"engines": {
|
|
91
|
+
"node": ">=18"
|
|
92
|
+
},
|
|
93
|
+
"author": "Davlatbek Aliev",
|
|
94
|
+
"repository": {
|
|
95
|
+
"type": "git",
|
|
96
|
+
"url": "git+https://github.com/alievdavlat/translate-shield.git"
|
|
97
|
+
},
|
|
98
|
+
"bugs": {
|
|
99
|
+
"url": "https://github.com/alievdavlat/translate-shield/issues"
|
|
100
|
+
},
|
|
101
|
+
"homepage": "https://github.com/alievdavlat/translate-shield#readme",
|
|
102
|
+
"publishConfig": {
|
|
103
|
+
"access": "public"
|
|
104
|
+
},
|
|
105
|
+
"typesVersions": {
|
|
106
|
+
"*": {
|
|
107
|
+
"react": [
|
|
108
|
+
"./dist/react.d.ts"
|
|
109
|
+
]
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|