@ai4bharat/indic-transliterate
Version:
Transliterate component for React
525 lines (501 loc) • 22.5 kB
JavaScript
var $dWhh5$reactjsxruntime = require("react/jsx-runtime");
var $dWhh5$react = require("react");
var $dWhh5$textareacaret = require("textarea-caret");
function $parcel$interopDefault(a) {
return a && a.__esModule ? a.default : a;
}
function $parcel$export(e, n, v, s) {
Object.defineProperty(e, n, {get: v, set: s, enumerable: true, configurable: true});
}
$parcel$export(module.exports, "IndicTransliterate", () => $3f93eb6d5bf69cb7$export$a62758b764e9e41d);
$parcel$export(module.exports, "TriggerKeys", () => $9090cd5fdacb9284$export$24b0ea3375909d37);
$parcel$export(module.exports, "getTransliterateSuggestions", () => $ad7f01abe8daa1b6$export$27f30d10c00bcc6c);
$parcel$export(module.exports, "getTransliterationLanguages", () => $bb5a013037ca2153$export$58f2e270169de9d3);
function $6a2317d46b969b19$export$e27e3030245d4c9b() {
return "ontouchstart" in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0;
}
function $94c75e1d9a9e24f0$export$8a4ff65f970d59a5(el) {
const start = 0;
const end = 0;
if (!el) return {
start: start,
end: end
};
if (typeof el.selectionStart === "number" && typeof el.selectionEnd === "number") return {
start: el.selectionStart,
end: el.selectionEnd
};
return {
start: start,
end: end
};
}
function $94c75e1d9a9e24f0$export$97ab23b40042f8af(elem, caretPos) {
if (elem) {
if (elem.selectionStart) {
elem.focus();
elem.setSelectionRange(caretPos, caretPos);
} else elem.focus();
}
}
const $9090cd5fdacb9284$export$24b0ea3375909d37 = {
KEY_RETURN: "Enter",
KEY_ENTER: "Enter",
KEY_TAB: "Tab",
KEY_SPACE: " "
};
const $ad7f01abe8daa1b6$var$MAX_CACHE_SIZE = 10000;
const $ad7f01abe8daa1b6$var$SAVE_THRESHOLD = 20;
const $ad7f01abe8daa1b6$var$CACHE_KEY = 'transliterationCache';
const $ad7f01abe8daa1b6$var$cache = $ad7f01abe8daa1b6$var$loadCacheFromLocalStorage();
let $ad7f01abe8daa1b6$var$newEntriesCount = 0;
function $ad7f01abe8daa1b6$var$loadCacheFromLocalStorage() {
const cachedData = localStorage.getItem($ad7f01abe8daa1b6$var$CACHE_KEY);
return cachedData ? JSON.parse(cachedData) : {
};
}
function $ad7f01abe8daa1b6$var$saveCacheToLocalStorage() {
localStorage.setItem($ad7f01abe8daa1b6$var$CACHE_KEY, JSON.stringify($ad7f01abe8daa1b6$var$cache));
}
const $ad7f01abe8daa1b6$var$getWordWithLowestFrequency = (dictionary)=>{
let lowestFreqWord = null;
let lowestFreq = Infinity;
for(const word in dictionary)if (dictionary[word].frequency < lowestFreq) {
lowestFreq = dictionary[word].frequency;
lowestFreqWord = word;
}
return lowestFreqWord;
};
const $ad7f01abe8daa1b6$export$27f30d10c00bcc6c = async (word, customApiURL, apiKey, config)=>{
const { showCurrentWordAsLastSuggestion: // numOptions = 5,
showCurrentWordAsLastSuggestion = true , lang: lang = "hi" , } = config || {
};
// fetch suggestion from api
// const url = `https://www.google.com/inputtools/request?ime=transliteration_en_${lang}&num=5&cp=0&cs=0&ie=utf-8&oe=utf-8&app=jsapi&text=${word}`;
// let myHeaders = new Headers();
// myHeaders.append("Content-Type", "application/json");
if (!$ad7f01abe8daa1b6$var$cache[lang]) $ad7f01abe8daa1b6$var$cache[lang] = {
};
if ($ad7f01abe8daa1b6$var$cache[lang][word.toLowerCase()]) {
$ad7f01abe8daa1b6$var$cache[lang][word.toLowerCase()].frequency += 1;
return $ad7f01abe8daa1b6$var$cache[lang][word.toLowerCase()].suggestions;
}
const requestOptions = {
method: "GET",
headers: {
"Authorization": apiKey
}
};
try {
const res = await fetch(customApiURL + `${lang}/${word === "." || word === ".." ? " " + word.replace(".", "%2E") : encodeURIComponent(word).replace(".", "%2E")}`, requestOptions);
let data = await res.json();
console.log("library data", data);
if (!customApiURL.includes("xlit-api")) data.result = data.output[0].target;
if (data && data.result.length > 0) {
const found = showCurrentWordAsLastSuggestion ? [
...data.result,
word
] : data.result;
if (Object.keys($ad7f01abe8daa1b6$var$cache[lang]).length >= $ad7f01abe8daa1b6$var$MAX_CACHE_SIZE) {
const lowestFreqWord = $ad7f01abe8daa1b6$var$getWordWithLowestFrequency($ad7f01abe8daa1b6$var$cache[lang]);
if (lowestFreqWord) delete $ad7f01abe8daa1b6$var$cache[lang][lowestFreqWord];
}
$ad7f01abe8daa1b6$var$cache[lang][word.toLowerCase()] = {
suggestions: found,
frequency: 1
};
$ad7f01abe8daa1b6$var$newEntriesCount += 1;
if ($ad7f01abe8daa1b6$var$newEntriesCount >= $ad7f01abe8daa1b6$var$SAVE_THRESHOLD) {
$ad7f01abe8daa1b6$var$saveCacheToLocalStorage();
$ad7f01abe8daa1b6$var$newEntriesCount = 0;
}
return found;
} else {
if (showCurrentWordAsLastSuggestion) {
const fallback = [
word
];
return fallback;
}
return [];
}
} catch (e) {
// catch error
console.error("There was an error with transliteration", e);
return [];
}
};
window.addEventListener('beforeunload', $ad7f01abe8daa1b6$var$saveCacheToLocalStorage);
const $905ee54827307cc1$export$ca6dda5263526f75 = "https://xlit-api.ai4bharat.org/";
const $905ee54827307cc1$export$a238c5e20ae27fe7 = "https://xlit-api.ai4bharat.org/tl/";
const $bb5a013037ca2153$export$58f2e270169de9d3 = async ()=>{
if (sessionStorage.getItem("indic_transliterate__supported_languages")) return JSON.parse(sessionStorage.getItem("indic_transliterate__supported_languages") || "");
else {
const apiURL = `${$905ee54827307cc1$export$ca6dda5263526f75}languages`;
const myHeaders = new Headers();
myHeaders.append("Content-Type", "application/json");
const requestOptions = {
method: "GET"
};
try {
const res = await fetch(apiURL, requestOptions);
const data = await res.json();
sessionStorage.setItem("indic_transliterate__supported_languages", JSON.stringify(data));
return data;
} catch (e) {
console.error("There was an error with transliteration", e);
return [];
}
}
};
const $3f93eb6d5bf69cb7$var$KEY_UP = "ArrowUp";
const $3f93eb6d5bf69cb7$var$KEY_DOWN = "ArrowDown";
const $3f93eb6d5bf69cb7$var$KEY_LEFT = "ArrowLeft";
const $3f93eb6d5bf69cb7$var$KEY_RIGHT = "ArrowRight";
const $3f93eb6d5bf69cb7$var$KEY_ESCAPE = "Escape";
const $3f93eb6d5bf69cb7$var$OPTION_LIST_Y_OFFSET = 10;
const $3f93eb6d5bf69cb7$var$OPTION_LIST_MIN_WIDTH = 100;
const $3f93eb6d5bf69cb7$export$a62758b764e9e41d = ({ renderComponent: renderComponent = (props)=>/*#__PURE__*/ $dWhh5$reactjsxruntime.jsx("input", {
...props
})
, lang: lang = "hi" , offsetX: offsetX = 0 , offsetY: offsetY = 10 , onChange: onChange , onChangeText: onChangeText , onBlur: onBlur , value: value , onKeyDown: onKeyDown , containerClassName: containerClassName = "" , containerStyles: containerStyles = {
} , activeItemStyles: activeItemStyles = {
} , maxOptions: maxOptions = 5 , hideSuggestionBoxOnMobileDevices: hideSuggestionBoxOnMobileDevices = false , hideSuggestionBoxBreakpoint: hideSuggestionBoxBreakpoint = 450 , triggerKeys: triggerKeys = [
$9090cd5fdacb9284$export$24b0ea3375909d37.KEY_SPACE,
$9090cd5fdacb9284$export$24b0ea3375909d37.KEY_ENTER,
$9090cd5fdacb9284$export$24b0ea3375909d37.KEY_RETURN,
$9090cd5fdacb9284$export$24b0ea3375909d37.KEY_TAB,
] , insertCurrentSelectionOnBlur: insertCurrentSelectionOnBlur = true , showCurrentWordAsLastSuggestion: showCurrentWordAsLastSuggestion = true , enabled: enabled = true , horizontalView: horizontalView = false , customApiURL: customApiURL = $905ee54827307cc1$export$a238c5e20ae27fe7 , apiKey: apiKey = "" , ...rest })=>{
const [left, setLeft] = $dWhh5$react.useState(0);
const [top, setTop] = $dWhh5$react.useState(0);
const [selection, setSelection] = $dWhh5$react.useState(0);
const [matchStart, setMatchStart] = $dWhh5$react.useState(-1);
const [matchEnd, setMatchEnd] = $dWhh5$react.useState(-1);
const inputRef = $dWhh5$react.useRef(null);
const [windowSize, setWindowSize] = $dWhh5$react.useState({
width: 0,
height: 0
});
const [direction, setDirection] = $dWhh5$react.useState("ltr");
const [googleFont, setGoogleFont] = $dWhh5$react.useState(null);
const [options, setOptions] = $dWhh5$react.useState([]);
const [logJsonArray, setLogJsonArray] = $dWhh5$react.useState([]);
const [numSpaces, setNumSpaces] = $dWhh5$react.useState(0);
const [parentUuid, setParentUuid] = $dWhh5$react.useState("0");
const [uuid, setUuid] = $dWhh5$react.useState(Math.random().toString(36).substr(2, 9));
const [subStrLength, setSubStrLength] = $dWhh5$react.useState(0);
const [restart, setRestart] = $dWhh5$react.useState(true);
const shouldRenderSuggestions = $dWhh5$react.useMemo(()=>hideSuggestionBoxOnMobileDevices ? windowSize.width > hideSuggestionBoxBreakpoint : true
, [
windowSize,
hideSuggestionBoxBreakpoint,
hideSuggestionBoxOnMobileDevices
]);
const reset = ()=>{
// reset the component
setSelection(0);
setOptions([]);
};
const handleSelection = (index)=>{
var ref;
const currentString = value;
// create a new string with the currently typed word
// replaced with the word in transliterated language
const newValue = currentString.substring(0, matchStart) + options[index] + " " + currentString.substring(matchEnd + 1, currentString.length);
if (logJsonArray.length) {
let lastLogJson = logJsonArray[logJsonArray.length - 1];
let logJson = {
keystrokes: lastLogJson.keystrokes,
results: lastLogJson.results,
opted: options[index],
created_at: new Date().toISOString(),
language: lang
};
setLogJsonArray([
...logJsonArray,
logJson
]);
setNumSpaces(numSpaces + 1);
}
// set the position of the caret (cursor) one character after the
// the position of the new word
setTimeout(()=>{
$94c75e1d9a9e24f0$export$97ab23b40042f8af(// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
inputRef.current, matchStart + options[index].length + 1);
}, 1);
// bubble up event to the parent component
const e = {
target: {
value: newValue
}
};
onChangeText(newValue);
onChange && onChange(e);
reset();
return (ref = inputRef.current) === null || ref === void 0 ? void 0 : ref.focus();
};
const renderSuggestions = async (lastWord, wholeText)=>{
if (!shouldRenderSuggestions) return;
// fetch suggestion from api
// const url = `https://www.google.com/inputtools/request?ime=transliteration_en_${lang}&num=5&cp=0&cs=0&ie=utf-8&oe=utf-8&app=jsapi&text=${lastWord}`;
// const numOptions = showCurrentWordAsLastSuggestion
// ? maxOptions - 1
// : maxOptions;
const data = await $ad7f01abe8daa1b6$export$27f30d10c00bcc6c(lastWord, customApiURL, apiKey, {
showCurrentWordAsLastSuggestion: // numOptions,
showCurrentWordAsLastSuggestion,
lang: lang
});
setOptions(data !== null && data !== void 0 ? data : []);
let logJson = {
keystrokes: wholeText,
results: data,
opted: "",
created_at: new Date().toISOString(),
language: lang
};
if (restart) {
setRestart(false);
setLogJsonArray([
logJson
]);
} else setLogJsonArray([
...logJsonArray,
logJson
]);
};
const getDirectionAndFont = async (lang)=>{
const langList = await $bb5a013037ca2153$export$58f2e270169de9d3();
const langObj = langList === null || langList === void 0 ? void 0 : langList.find((l)=>l.LangCode === lang
);
var ref;
return [
(ref = langObj === null || langObj === void 0 ? void 0 : langObj.Direction) !== null && ref !== void 0 ? ref : "ltr",
langObj === null || langObj === void 0 ? void 0 : langObj.GoogleFont,
langObj === null || langObj === void 0 ? void 0 : langObj.FallbackFont,
];
};
const handleChange = (e)=>{
const value = e.currentTarget.value;
if (numSpaces == 0 || restart) {
if (value.length >= 4) setSubStrLength(value.length - 4);
else setSubStrLength(0);
}
if (numSpaces >= 5) {
const finalJson = {
"uuid": uuid,
"parent_uuid": parentUuid,
"word": value,
"source": localStorage.getItem('source') != undefined ? localStorage.getItem('source') : "node-module",
"language": lang,
"steps": logJsonArray
};
setLogJsonArray([]);
setParentUuid(uuid);
setUuid(Math.random().toString(36).substr(2, 9));
setSubStrLength(value.length - 2);
setNumSpaces(0);
setRestart(true);
fetch("https://backend.shoonya.ai4bharat.org/logs/transliteration_selection/", {
method: "POST",
body: JSON.stringify(finalJson),
headers: {
"Content-Type": "application/json"
}
}).then(async (res)=>{
if (!res.ok) throw await res.json();
}).catch((err)=>{
console.log("error", err);
});
}
// bubble up event to the parent component
onChange && onChange(e);
onChangeText(value);
if (!shouldRenderSuggestions) return;
// get the current index of the cursor
const caret = $94c75e1d9a9e24f0$export$8a4ff65f970d59a5(e.target).end;
const input = inputRef.current;
if (!input) return;
const caretPos = $parcel$interopDefault($dWhh5$textareacaret)(input, caret);
// search for the last occurence of the space character from
// the cursor
const indexOfLastSpace = value.lastIndexOf(" ", caret - 1) < value.lastIndexOf("\n", caret - 1) ? value.lastIndexOf("\n", caret - 1) : value.lastIndexOf(" ", caret - 1);
// first character of the currently being typed word is
// one character after the space character
// index of last character is one before the current position
// of the caret
setMatchStart(indexOfLastSpace + 1);
setMatchEnd(caret - 1);
// currentWord is the word that is being typed
const currentWord = value.slice(indexOfLastSpace + 1, caret);
if (currentWord && enabled) {
// make an api call to fetch suggestions
if (numSpaces == 0 || restart) {
if (value.length >= 4) renderSuggestions(currentWord, value.substr(value.length - 4, value.length));
else renderSuggestions(currentWord, value.substr(0, value.length));
} else renderSuggestions(currentWord, value.substr(subStrLength, value.length));
const rect = input.getBoundingClientRect();
// calculate new left and top of the suggestion list
// minimum of the caret position in the text input and the
// width of the text input
const left = Math.min(caretPos.left, rect.width - $3f93eb6d5bf69cb7$var$OPTION_LIST_MIN_WIDTH / 2);
// minimum of the caret position from the top of the input
// and the height of the input
const top = Math.min(caretPos.top + $3f93eb6d5bf69cb7$var$OPTION_LIST_Y_OFFSET, rect.height);
setTop(top);
setLeft(left);
} else reset();
};
const handleKeyDown = (event)=>{
const helperVisible = options.length > 0;
if (helperVisible) {
if (triggerKeys.includes(event.key)) {
event.preventDefault();
handleSelection(selection);
} else switch(event.key){
case $3f93eb6d5bf69cb7$var$KEY_ESCAPE:
event.preventDefault();
reset();
break;
case $3f93eb6d5bf69cb7$var$KEY_UP:
event.preventDefault();
setSelection((options.length + selection - 1) % options.length);
break;
case $3f93eb6d5bf69cb7$var$KEY_DOWN:
event.preventDefault();
setSelection((selection + 1) % options.length);
break;
case $3f93eb6d5bf69cb7$var$KEY_LEFT:
event.preventDefault();
setSelection((options.length + selection - 1) % options.length);
break;
case $3f93eb6d5bf69cb7$var$KEY_RIGHT:
event.preventDefault();
setSelection((selection + 1) % options.length);
break;
default:
onKeyDown && onKeyDown(event);
break;
}
} else onKeyDown && onKeyDown(event);
};
const handleBlur = (event)=>{
if (!$6a2317d46b969b19$export$e27e3030245d4c9b()) {
if (insertCurrentSelectionOnBlur && options[selection]) handleSelection(selection);
else reset();
}
onBlur && onBlur(event);
};
const handleResize = ()=>{
// TODO implement the resize function to resize
// the helper on screen size change
const width = window.innerWidth;
const height = window.innerHeight;
setWindowSize({
width: width,
height: height
});
};
$dWhh5$react.useEffect(()=>{
window.addEventListener("resize", handleResize);
const width = window.innerWidth;
const height = window.innerHeight;
setWindowSize({
width: width,
height: height
});
return ()=>{
window.removeEventListener("resize", handleResize);
};
}, []);
$dWhh5$react.useEffect(()=>{
getDirectionAndFont(lang).then(([direction, googleFont, fallbackFont])=>{
setDirection(direction);
// import google font if not already imported
if (googleFont) {
if (!document.getElementById(`font-${googleFont}`)) {
const link = document.createElement("link");
link.id = `font-${googleFont}`;
link.href = `https://fonts.googleapis.com/css?family=${googleFont}`;
link.rel = "stylesheet";
document.head.appendChild(link);
}
setGoogleFont(`${googleFont}, ${fallbackFont !== null && fallbackFont !== void 0 ? fallbackFont : "sans-serif"}`);
} else setGoogleFont(null);
});
}, [
lang
]);
return(/*#__PURE__*/ $dWhh5$reactjsxruntime.jsxs("div", {
// position relative is required to show the component
// in the correct position
style: {
...containerStyles,
position: "relative"
},
className: containerClassName,
children: [
renderComponent({
onChange: handleChange,
onKeyDown: handleKeyDown,
onBlur: handleBlur,
ref: inputRef,
value: value,
"data-testid": "rt-input-component",
lang: lang,
style: {
direction: direction,
...googleFont && {
fontFamily: googleFont
}
},
...rest
}),
shouldRenderSuggestions && options.length > 0 && /*#__PURE__*/ $dWhh5$reactjsxruntime.jsx("ul", {
style: {
backgroundClip: "padding-box",
backgroundColor: "#fff",
border: "1px solid rgba(0, 0, 0, 0.15)",
boxShadow: "0 6px 12px rgba(0, 0, 0, 0.175)",
display: horizontalView ? "flex" : "block",
fontSize: "14px",
listStyle: "none",
padding: "1px",
textAlign: "center",
zIndex: 20000,
left: `${left + offsetX}px`,
top: `${top + offsetY}px`,
position: "absolute",
width: "auto",
...googleFont && {
fontFamily: googleFont
}
},
"data-testid": "rt-suggestions-list",
lang: lang,
children: Array.from(new Set(options)).map((item, index)=>/*#__PURE__*/ $dWhh5$reactjsxruntime.jsx("li", {
style: index === selection ? {
cursor: "pointer",
padding: "10px",
minWidth: "100px",
backgroundColor: "#65c3d7",
color: "#fff"
} : {
cursor: "pointer",
padding: "10px",
minWidth: "100px",
backgroundColor: "#fff"
},
onMouseEnter: ()=>{
setSelection(index);
},
onClick: ()=>handleSelection(index)
,
children: item
}, item)
)
})
]
}));
};
//# sourceMappingURL=index.js.map