Skip to content

Commit 2e040e8

Browse files
author
Sebi Nemeth
committed
add build process
1 parent ff0af90 commit 2e040e8

File tree

6 files changed

+499
-5
lines changed

6 files changed

+499
-5
lines changed

dist/index.d.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import Vue, { VueConstructor } from 'vue';
2+
import VueI18n from 'vue-i18n';
3+
export type TranslationMeta = {
4+
locale: string;
5+
message: string;
6+
values: unknown;
7+
path: string;
8+
};
9+
type LiveTranslatorPluginOptions = {
10+
i18n: VueI18n;
11+
translationLink: (meta: TranslationMeta) => string;
12+
persist?: boolean;
13+
};
14+
export declare const LiveTranslatorPlugin: {
15+
install(app: VueConstructor<Vue>, options: LiveTranslatorPluginOptions): void;
16+
};
17+
export {};

dist/index.js

Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
"use strict";
2+
var __importDefault = (this && this.__importDefault) || function (mod) {
3+
return (mod && mod.__esModule) ? mod : { "default": mod };
4+
};
5+
Object.defineProperty(exports, "__esModule", { value: true });
6+
exports.LiveTranslatorPlugin = void 0;
7+
const throttle_1 = __importDefault(require("lodash/throttle"));
8+
const css = `
9+
.live-translator-enable-button {
10+
position: fixed !important;
11+
top: 0;
12+
left: 0;
13+
z-index: 10000;
14+
padding: 2px;
15+
color: black;
16+
background: rgba(255, 255, 255, 0.6);
17+
font-family: sans-serif;
18+
font-size: 8px;
19+
}
20+
.live-translator-enable-button:hover {
21+
background: white;
22+
}
23+
.live-translator-enable-button-indicator {
24+
display: inline-block;
25+
height: 6px;
26+
width: 6px;
27+
margin-left: 2px;
28+
border-radius: 100%;
29+
background-color: red;
30+
}
31+
.live-translator-badge-container {
32+
position: absolute !important;
33+
display: flex;
34+
z-index: 10000;
35+
}
36+
.live-translator-badge {
37+
width: 10px !important;
38+
height: 10px !important;
39+
border-radius: 10px !important;
40+
box-shadow: 0px 0px 5px black !important;
41+
opacity: 0.5 !important;
42+
}
43+
.live-translator-badge:hover {
44+
opacity: 1 !important;
45+
}
46+
.live-translator-badge.text {
47+
background: green !important;
48+
}
49+
.live-translator-badge.text:hover {
50+
background: lightgreen !important;
51+
box-shadow: 0px 0px 5px lightgreen !important;
52+
}
53+
.live-translator-badge.attribute {
54+
background: blue !important;
55+
}
56+
.live-translator-badge.attribute:hover {
57+
background: #00c0ff !important;
58+
box-shadow: 0px 0px 5px #00c0ff !important;
59+
}
60+
`;
61+
class ZeroWidthEncoder {
62+
constructor() {
63+
this.START = '\u200B';
64+
this.ZERO = '\u200C';
65+
this.ONE = '\u200D';
66+
this.SPACE = '\u200E';
67+
this.END = '\u200F';
68+
}
69+
encode(text) {
70+
const binary = text
71+
.split('')
72+
.map((char) => char.charCodeAt(0).toString(2))
73+
.join(' ');
74+
const zeroWidth = binary
75+
.split('')
76+
.map((binaryNum) => {
77+
const num = parseInt(binaryNum, 10);
78+
if (num === 1) {
79+
return this.ONE;
80+
}
81+
else if (num === 0) {
82+
return this.ZERO;
83+
}
84+
return this.SPACE;
85+
})
86+
.join('');
87+
return this.START + zeroWidth + this.END;
88+
}
89+
decode(zeroWidth) {
90+
const binary = zeroWidth
91+
.split('')
92+
.slice(1, zeroWidth.length - 1) // remove START and END
93+
.map((char) => {
94+
if (char === this.ONE) {
95+
return '1';
96+
}
97+
else if (char === this.ZERO) {
98+
return '0';
99+
}
100+
return ' ';
101+
})
102+
.join('');
103+
const text = binary
104+
.split(' ')
105+
.map((num) => String.fromCharCode(parseInt(num, 2)))
106+
.join('');
107+
return text;
108+
}
109+
}
110+
class LiveTranslatorEnabler {
111+
constructor(persist) {
112+
this._enabled = false;
113+
this.persist = persist;
114+
const savedRaw = localStorage.getItem('live-translator-enabled');
115+
if (persist && savedRaw) {
116+
const saved = JSON.parse(savedRaw);
117+
if (typeof saved === 'boolean') {
118+
this.toggle(saved);
119+
}
120+
}
121+
}
122+
enabled() {
123+
return this._enabled;
124+
}
125+
toggle(enable) {
126+
if (enable !== undefined) {
127+
this._enabled = enable;
128+
}
129+
else {
130+
this._enabled = !this._enabled;
131+
}
132+
if (this.persist) {
133+
localStorage.setItem('live-translator-enabled', JSON.stringify(this._enabled));
134+
}
135+
}
136+
}
137+
const createBadge = (meta, options, attribute) => {
138+
const badge = document.createElement('a');
139+
badge.classList.add('live-translator-badge');
140+
let title = meta.path + ': ' + meta.message;
141+
if (attribute) {
142+
title = `[${attribute}] ${title}`;
143+
badge.classList.add('attribute');
144+
}
145+
else {
146+
badge.classList.add('text');
147+
}
148+
badge.title = title;
149+
badge.href = options.translationLink(meta);
150+
badge.target = 'popup';
151+
badge.addEventListener('click', (e) => {
152+
window.open(badge.href, 'popup', 'width=600,height=600,scrollbars=no,resizable=no');
153+
e.preventDefault();
154+
return false;
155+
});
156+
return badge;
157+
};
158+
exports.LiveTranslatorPlugin = {
159+
install(app, options) {
160+
console.log('LiveTranslator is installed');
161+
const zw = new ZeroWidthEncoder();
162+
const ltEnabler = new LiveTranslatorEnabler(options.persist || false);
163+
const enableButton = document.createElement('button');
164+
enableButton.innerText = 'LT';
165+
enableButton.classList.add('live-translator-enable-button');
166+
const indicator = document.createElement('span');
167+
indicator.classList.add('live-translator-enable-button-indicator');
168+
enableButton.appendChild(indicator);
169+
enableButton.addEventListener('click', () => {
170+
ltEnabler.toggle();
171+
visualize();
172+
// Refresh translations to show immediately
173+
const originalLocale = options.i18n.locale;
174+
options.i18n.locale = '';
175+
options.i18n.locale = originalLocale;
176+
});
177+
document.body.appendChild(enableButton);
178+
const style = document.createElement('style');
179+
style.id = 'live-translator-plugin-style';
180+
style.innerHTML = css;
181+
document.head.appendChild(style);
182+
const visualize = () => {
183+
const badges = document.querySelectorAll('.live-translator-badge');
184+
badges.forEach((badge) => {
185+
badge.remove();
186+
});
187+
indicator.style.background = ltEnabler.enabled() ? 'lightgreen' : 'red';
188+
if (!ltEnabler.enabled()) {
189+
return;
190+
}
191+
const re = new RegExp(`${zw.START}[${zw.ZERO}${zw.ONE}${zw.SPACE}]+${zw.END}`, 'gm');
192+
const queue = [document.documentElement];
193+
while (queue.length > 0) {
194+
const node = queue.pop();
195+
const badges = [];
196+
const parent = node.parentElement;
197+
if (node instanceof Text) {
198+
const matches = node.textContent.match(re);
199+
for (const match of matches !== null && matches !== void 0 ? matches : []) {
200+
const meta = JSON.parse(zw.decode(match));
201+
badges.push(createBadge(meta, options));
202+
}
203+
}
204+
const attributes = (node.attributes ? [...node.attributes] : [])
205+
.map((attribute) => ({ attribute, match: attribute.value.match(re) }))
206+
.filter(({ match }) => !!match);
207+
for (const { attribute, match } of attributes) {
208+
for (const m of match) {
209+
const meta = JSON.parse(zw.decode(m));
210+
badges.push(createBadge(meta, options, attribute.name));
211+
}
212+
}
213+
if (badges.length) {
214+
let container;
215+
if (node.previousElementSibling && node.previousElementSibling.classList.contains('live-translator-badge-container')) {
216+
container = node.previousElementSibling;
217+
}
218+
else {
219+
container = document.createElement('span');
220+
container.classList.add('live-translator-badge-container');
221+
parent.insertBefore(container, node);
222+
}
223+
for (const badge of badges) {
224+
container.appendChild(badge);
225+
}
226+
}
227+
for (const child of node.childNodes) {
228+
queue.push(child);
229+
}
230+
}
231+
};
232+
const originalFormatter = options.i18n.formatter;
233+
options.i18n.formatter = {
234+
interpolate(message, values, path) {
235+
const meta = zw.encode(JSON.stringify({
236+
message,
237+
values,
238+
path,
239+
locale: options.i18n.locale,
240+
}));
241+
const original = originalFormatter.interpolate(message, values, path);
242+
return (original && ltEnabler.enabled()) ? [meta, ...original] : original;
243+
},
244+
};
245+
const throttler = (0, throttle_1.default)(visualize, 800);
246+
const observer = new MutationObserver(throttler);
247+
observer.observe(document.documentElement, {
248+
subtree: true,
249+
attributes: true,
250+
characterData: true,
251+
childList: false,
252+
});
253+
document.documentElement.addEventListener('mousemove', throttler);
254+
},
255+
};

0 commit comments

Comments
 (0)