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/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
+ }