Open croxarens opened 4 years ago
Can I help?
https://github.com/mebjas/html5-qrcode/blob/master/src/strings.ts already separates the UI strings (and considers internationalization a TODO :-), so this looks well prepared for.
@mebjas: Have you already thought about how you'd want the internationalization to be triggered/configured? Via an additional config option maybe?
It would of course be nice to just automatically match the first language in navigator.languages
that is supported by html5-qrcode's translations, but I don't know if this can be relied on to be available in all environments supported by html5-qrcode.
@cheweytoo Thanks for the interest. This has been a key issue in my mind.
Have you already thought about how you'd want the internationalisation to be triggered/configured? Via an additional config option maybe?
I am happy to hear your thoughts on this topic. Different languages baked into the JS code with a config deciding the language is definitely one option. In case of missing language support it could fallback to English. I have not worked on projects where this is purely frontend based.
An alternative that comes to mind is generating different JS files for diff languages but that seems like a huge pain for consumers.
The design in my mind is:
| src
......| strings.ts (become an interface only)
......| strings-factory.ts
......| strings
..................| strings.en.ts
..................| strings.fr.ts
..................| (all diff languages)
strings-factory.ts
could either consume argument like this:
enum StringMode {
ENGLISH_ONLY, // default
AUTO_DETECT,
EXPLICIT
}
interface StringsConfig {
stringMode: StringMode;
language?: string; // only honoured when stringMode is 'EXPLICIT'.
}
And return the impl or default to english if the language is not implemented. Based on contributors we can keep adding more strings.
Hi all, nice library !
Is there a way to manually set the texts at render or creation time ? I'd like to use Html5QrcodeScanner but I really need it translated. Some workaround comes to your mind?
Thanks!
Hi @mebjas, I'm mostly a backend developer, and I have just general knowledge of JS.
My suggestion, or at least my point of view, is to allow everyone to easily change/update/amend the language file without the need to compile something.
So, for this reason, I'd stay with some basic JS options, like a file with a single object called lang
and each property as a word/phrase. I think this should allow some sort of easy attribute injection too.
lang = { hello : "ciao" }
Hi @jpaoletti, I just looked for the phrase I want to translate in the codebase, and just changed it in the code. Of course this solution doesn't work with multilanguage applications.
@croxarens Yeah I ended up doing that but it is not ideal. I agree with your proposed solution
@cheweytoo Thanks for the interest. This has been a key issue in my mind.
Have you already thought about how you'd want the internationalisation to be triggered/configured? Via an additional config option maybe?
I am happy to hear your thoughts on this topic. Different languages baked into the JS code with a config deciding the language is definitely one option. In case of missing language support it could fallback to English. I have not worked on projects where this is purely frontend based.
An alternative that comes to mind is generating different JS files for diff languages but that seems like a huge pain for consumers.
The design in my mind is:
| src ......| strings.ts (become an interface only) ......| strings-factory.ts ......| strings ..................| strings.en.ts ..................| strings.fr.ts ..................| (all diff languages)
strings-factory.ts
could either consume argument like this:enum StringMode { ENGLISH_ONLY, // default AUTO_DETECT, EXPLICIT } interface StringsConfig { stringMode: StringMode; language?: string; // only honoured when stringMode is 'EXPLICIT'. }
And return the impl or default to english if the language is not implemented. Based on contributors we can keep adding more strings.
If you start the: ......| strings.ts (become an interface only) ......| strings-factory.ts
I'll be glad to help up create ..................| strings.pt-pt.ts ..................| strings.pt-br.ts ..................| strings.es.ts
Btw thanks for this amazing work @mebjas
Hi @mebjas, I'm mostly a backend developer, and I have just general knowledge of JS. My suggestion, or at least my point of view, is to allow everyone to easily change/update/amend the language file without the need to compile something. So, for this reason, I'd stay with some basic JS options, like a file with a single object called
lang
and each property as a word/phrase. I think this should allow some sort of easy attribute injection too.
lang = { hello : "ciao" }
Yes string injection at runtime is the most versatile, no need to hardcode translation then (but it's possible to do both).
@mebjas
Hi all. I am currently working on a React scanning app. Did you find a way to efficacely change language of elements ? Thank you.
Will look into this soon.
Will look into this soon.
we will be very grateful. :)
One major risk is
We can get the one time effort done for all the current strings.
But thereafter it will add extra task of localisation os every string before release.
Similarly, with string injection let's say you maintain your own strings - new changes to api can break your version because of missing string.
Any ideas on how to address these issues?
On Sat, Nov 19, 2022, 07:26 Juan Cruz Lombardo Bonino < @.***> wrote:
Will look into this soon.
we will be very grateful. :)
— Reply to this email directly, view it on GitHub https://github.com/mebjas/html5-qrcode/issues/132#issuecomment-1320647841, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAW6HBKAKWCI4XMM7YM4L6TWJAGCBANCNFSM4TEGZICA . You are receiving this because you were mentioned.Message ID: @.***>
Similarly, with string injection let's say you maintain your own strings - new changes to api can break your version because of missing string. Any ideas on how to address these issues?
API changes should not break a translated version. At worst, any missing translation should default to English.
The system should fallback to English if there's anything missing, that way nothing breaks and new releases aren't delayed waiting for the update of every translation.
It can be done by javascript (take missing entries from the English version or augment the English version with the entries in the translation), or by copying in every translation file the new English sentences.
let the developer see in the console log the missing string.....
Got it, it makes sense to me.
We would still need a process to keep updating the strings and string changes to as many languages as possible.
Got it, it makes sense to me.
We would still need a process to keep updating the strings and string changes to as many languages as possible.
The most important step is to provide a mean for the lib users to inject the translations. Including the translations in the lib itself is a maintenance burden that you may want to avoid. Also, aside of the many languages, some may want to tweak the messages to better suit their use case (more or less verbose for instance).
Including the translations in the lib itself is a maintenance burden that you may want to avoid.
On the other hand, developers don't tend to speak all the languages. So an ability to overrule or inject own translations is fine, but having existing translations (maybe in a separate repo) would be extremely helpful. It would also avoid an awful lot of duplicated translation work.
We would still need a process to keep updating the strings and string changes to as many languages as possible.
One way to do that is to use versioning (think versioned API endpoints): Loading an outdated translation set would give a console message. This would make developers aware of the issue, and motivate them to contribute updates.
Hi @mebjas ,
Any chance to update https://github.com/scanapp-org/html5-qrcode-react with the language string example
I couldn't do this integration on my own, I think it would be more explanatory
Thank you
I translated it for Turkish and this is how I found a solution for now.
#html5qr-code-full-region {
img[alt="Info icon"] {
display: none;
}
}
#html5-qrcode-button-camera-permission {
text-indent: -9999px;
line-height: 0;
margin-bottom: 10px;
}
#html5-qrcode-button-camera-permission:after {
content: "Kamera izni talep et";
text-indent: 0;
display: block;
line-height: initial;
}
#html5-qrcode-anchor-scan-type-change {
font-size: 0;
}
#html5-qrcode-anchor-scan-type-change:after {
font-size: 1rem;
content: "Tarama tipini değiştir";
cursor: pointer;
}
#html5-qrcode-button-file-selection {
text-indent: -9999px;
line-height: 0;
margin-bottom: 0 !important;
}
#html5-qrcode-button-file-selection:after {
content: "Dosya seç";
text-indent: 0;
display: block;
line-height: initial;
}
#html5qr-code-full-region__dashboard_section {
div:first-of-type {
div:last-of-type {
div {
text-indent: -9999px;
line-height: 0;
}
div:after {
content: "Fotoğrafı sürükleyip bırakabilirsiniz.";
text-indent: 0;
display: block;
line-height: initial;
}
}
}
}
#html5qr-code-full-region__header_message {
text-indent: -9999px;
line-height: 0;
}
#html5qr-code-full-region__header_message:after {
content: "Yüklenen fotoğrafta QR kod okunmuyor lütfen kırpıp yükleyiniz.";
text-indent: 0;
display: block;
line-height: initial;
}
+1 for this! Would love to to discuss sponsoring the work.
Sounds good, that'd be helpful - this issue is pretty high up in my radar.
I would like to learn more about the use cases - please DM at minhazav@gmail.com
Re: sponsorship
Checkout https://ko-fi.com/minhazav/tiers - this would definitely help make maintenance more sustainable!
Based on the workaround of @1sahinomer1 , I developed the following dynamic code that observes the changes that the library does. It is working on Vue, and it should work on vanilla.
/**
* This is a Workaround to translate the interface, because as of 2023-03-31,
* Html5QrcodeScanner the library doesn't support I18N
* Feel free to remove this piece of anti-pattern once the library can translate
* by itself
*
* Note that as there are inner interaction, text must be replaced on the fly
*/
/**
* It observe certain selectors to overwrite with custom texts
* @param {HTMLElement} ref
* @returns {void}
*/
export default function scannerTranslator(ref) {
const mappingArray = [
{
selector: '#html5-qrcode-button-camera-permission',
text: 'Solicitar permiso de cámara'
},
{
selector: '#html5-qrcode-anchor-scan-type-change',
text: 'Cambiar el tipo de escaneo'
},
{
selector: '#html5-qrcode-button-file-selection',
text: 'Seleccionar archivo'
},
{
selector: '#reader__dashboard_section > div:nth-child(1) > div:nth-child(2) > div:nth-child(2)',
text: 'Puedes arrastrar y soltar la foto'
},
{
selector: '#html5qr-code-full-region__header_message',
text: 'No se puede leer el código QR en la foto cargada. Recorta y vuelve a cargar'
},
];
// Options for the observer (which mutations to observe)
const config = { childList: true, subtree: true };
// Create an observer instance linked to the callback function
const observer = new MutationObserver(function(mutationsList) {
for(let mutation of mutationsList) {
if (mutation.type === 'childList') {
mappingArray.forEach((item) => {
const element = ref.querySelector(item.selector);
if (element && element.textContent !== item.text) {
element.textContent = item.text;
}
});
}
}
});
// Start observing the target node for configured mutations
observer.observe(ref, config);
}
Bonjour, Pour ma part, du côté français, j'ai juste traduit les deux boutons ainsi dans ma vue :
<style>
#html5-qrcode-button-camera-start {font-size:0}
#html5-qrcode-button-camera-start::after {
content:"Scanner";
font-size:initial;
}
#html5-qrcode-button-camera-stop {font-size:0}
#html5-qrcode-button-camera-stop::after {
content:"Arrêter le scan";
font-size:initial;
}
</style>
et cela permet d'avoir la traduction comme je souhaite au moins ;)
English: Since the owner does not want to make any changes for the translations I do it without changing the original code. I have created my own code and it works great.
Spanish: En vista que el dueño no quiere realizar ningun cambio para las traducciones yo lo hago sin cambiar el codigo original. He creado mi propio codigo y funciona de maravilla.
function scannerTranslator() {
const traducciones = [
// Html5QrcodeStrings
{original: "QR code parse error, error =", traduccion: "Error al analizar el código QR, error ="},
{original: "Error getting userMedia, error =", traduccion: "Error al obtener userMedia, error ="},
{original: "The device doesn't support navigator.mediaDevices , only supported cameraIdOrConfig in this case is deviceId parameter (string).", traduccion: "El dispositivo no admite navigator.mediaDevices, en este caso sólo se admite cameraIdOrConfig como parámetro deviceId (cadena)."},
{original: "Camera streaming not supported by the browser.", traduccion: "El navegador no admite la transmisión de la cámara."},
{original: "Unable to query supported devices, unknown error.", traduccion: "No se puede consultar los dispositivos compatibles, error desconocido."},
{original: "Camera access is only supported in secure context like https or localhost.", traduccion: "El acceso a la cámara sólo es compatible en un contexto seguro como https o localhost."},
{original: "Scanner paused", traduccion: "Escáner en pausa"},
// Html5QrcodeScannerStrings
{original: "Scanning", traduccion: "Escaneando"},
{original: "Idle", traduccion: "Inactivo"},
{original: "Error", traduccion: "Error"},
{original: "Permission", traduccion: "Permiso"},
{original: "No Cameras", traduccion: "Sin cámaras"},
{original: "Last Match:", traduccion: "Última coincidencia:"},
{original: "Code Scanner", traduccion: "Escáner de código"},
{original: "Request Camera Permissions", traduccion: "Solicitar permisos de cámara"},
{original: "Requesting camera permissions...", traduccion: "Solicitando permisos de cámara..."},
{original: "No camera found", traduccion: "No se encontró ninguna cámara"},
{original: "Stop Scanning", traduccion: "Detener escaneo"},
{original: "Start Scanning", traduccion: "Iniciar escaneo"},
{original: "Switch On Torch", traduccion: "Encender linterna"},
{original: "Switch Off Torch", traduccion: "Apagar linterna"},
{original: "Failed to turn on torch", traduccion: "Error al encender la linterna"},
{original: "Failed to turn off torch", traduccion: "Error al apagar la linterna"},
{original: "Launching Camera...", traduccion: "Iniciando cámara..."},
{original: "Scan an Image File", traduccion: "Escanear un archivo de imagen"},
{original: "Scan using camera directly", traduccion: "Escanear usando la cámara directamente"},
{original: "Select Camera", traduccion: "Seleccionar cámara"},
{original: "Choose Image", traduccion: "Elegir imagen"},
{original: "Choose Another", traduccion: "Elegir otra"},
{original: "No image choosen", traduccion: "Ninguna imagen seleccionada"},
{original: "Anonymous Camera", traduccion: "Cámara anónima"},
{original: "Or drop an image to scan", traduccion: "O arrastra una imagen para escanear"},
{original: "Or drop an image to scan (other files not supported)", traduccion: "O arrastra una imagen para escanear (otros archivos no soportados)"},
{original: "zoom", traduccion: "zoom"},
{original: "Loading image...", traduccion: "Cargando imagen..."},
{original: "Camera based scan", traduccion: "Escaneo basado en cámara"},
{original: "Fule based scan", traduccion: "Escaneo basado en archivo"},
// LibraryInfoStrings
{original: "Powered by ", traduccion: "Desarrollado por "},
{original: "Report issues", traduccion: "Informar de problemas"},
// Others
{original: "NotAllowedError: Permission denied", traduccion: "Permiso denegado para acceder a la cámara"}
];
// Función para traducir un texto
function traducirTexto(texto) {
const traduccion = traducciones.find(t => t.original === texto);
return traduccion ? traduccion.traduccion : texto;
}
// Función para traducir los nodos de texto
function traducirNodosDeTexto(nodo) {
if (nodo.nodeType === Node.TEXT_NODE) {
nodo.textContent = traducirTexto(nodo.textContent.trim());
} else {
for (let i = 0; i < nodo.childNodes.length; i++) {
traducirNodosDeTexto(nodo.childNodes[i]);
}
}
}
// Crear el MutationObserver
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'childList') {
mutation.addedNodes.forEach((nodo) => {
traducirNodosDeTexto(nodo);
});
}
});
});
// Configurar y ejecutar el observer
const config = {childList: true, subtree: true};
observer.observe(document.body, config);
// Traducir el contenido inicial
traducirNodosDeTexto(document.body);
}
document.addEventListener('DOMContentLoaded', function () {
// Utilizando la función scannerTranslator
scannerTranslator(document.querySelector('#qr-reader'));
});
const translates = [
{ en: "QR code parse error, error =", es: "Error al analizar el código QR, error =" },
{ en: "Error getting userMedia, error =", es: "Error al obtener userMedia, error =" },
{ en: "The device doesn't support navigator.mediaDevices , only supported cameraIdOrConfig in this case is deviceId parameter (string).", es: "El dispositivo no admite navigator.mediaDevices, en este caso sólo se admite cameraIdOrConfig como parámetro deviceId (cadena)." },
{ en: "Camera streaming not supported by the browser.", es: "El navegador no admite la transmisión de la cámara." },
{ en: "Unable to query supported devices, unknown error.", es: "No se puede consultar los dispositivos compatibles, error desconocido." },
{ en: "Camera access is only supported in secure context like https or localhost.", es: "El acceso a la cámara sólo es compatible en un contexto seguro como https o localhost." },
{ en: "Scanner paused", es: "Escáner en pausa" },
{ en: "Scanning", es: "Escaneando" },
{ en: "Idle", es: "Inactivo" },
{ en: "Error", es: "Error" },
{ en: "Permission", es: "Permiso" },
{ en: "No Cameras", es: "Sin cámaras" },
{ en: "Last Match:", es: "Última coincidencia:" },
{ en: "Code Scanner", es: "Escáner de código" },
{ en: "Request Camera Permissions", es: "Solicitar permisos de cámara" },
{ en: "Requesting camera permissions...", es: "Solicitando permisos de cámara..." },
{ en: "No camera found", es: "No se encontró ninguna cámara" },
{ en: "Stop Scanning", es: "Detener escáner" },
{ en: "Start Scanning", es: "Iniciar escáner" },
{ en: "Switch On Torch", es: "Encender linterna" },
{ en: "Switch Off Torch", es: "Apagar linterna" },
{ en: "Failed to turn on torch", es: "Error al encender la linterna" },
{ en: "Failed to turn off torch", es: "Error al apagar la linterna" },
{ en: "Launching Camera...", es: "Iniciando cámara..." },
{ en: "Scan an Image File", es: "Escanear un archivo de imagen" },
{ en: "Scan using camera directly", es: "Escanear usando la cámara directamente" },
{ en: "Select Camera", es: "Seleccionar cámara" },
{ en: "Choose Image", es: "Elegir imagen" },
{ en: "Choose Another", es: "Elegir otra" },
{ en: "No image choosen", es: "Ninguna imagen seleccionada" },
{ en: "Anonymous Camera", es: "Cámara anónima" },
{ en: "Or drop an image to scan", es: "O arrastra una imagen para escanear" },
{ en: "Or drop an image to scan (other files not supported)", es: "O arrastra una imagen para escanear (otros archivos no soportados)" },
{ en: "zoom", es: "zoom" },
{ en: "Loading image...", es: "Cargando imagen..." },
{ en: "Camera based scan", es: "Escaneo basado en cámara" },
{ en: "Fule based scan", es: "Escaneo basado en archivo" },
{ en: "Powered by ", es: "Desarrollado por " },
{ en: "Report issues", es: "Informar de problemas" },
{ en: "NotAllowedError: Permission denied", es: "Permiso denegado para acceder a la cámara" }
]
export class Html5QrcodeTranslate {
#observer = null
constructor(elementById) {
this.#observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'childList') {
mutation.addedNodes.forEach((nodo) => {
this.#textNodeTranslate(nodo);
});
}
});
})
const config = { childList: true, subtree: true };
this.#observer.observe(document.querySelector(elementById), config);
this.#textNodeTranslate(document.querySelector(elementById));
return this.#observer
}
disconnect() {
this.#observer !== null && this.#observer.disconnect()
}
#translate(texto) {
const translate = translates.find(t => t.en === texto);
return translate ? translate.es : texto;
}
#textNodeTranslate(nodo) {
if (nodo.nodeType === Node.TEXT_NODE) {
nodo.textContent = this.#translate(nodo.textContent.trim());
} else {
for (let i = 0; i < nodo.childNodes.length; i++) {
this.#textNodeTranslate(nodo.childNodes[i]);
}
}
}
}
@mebjas
- [ ] Start an effort on this with Spanish and French
- [ ] Write a blog post describing how to do this for any language.
- [ ] Request contributors to add support for more languages within the library
I can help with Portuguese if you need
Hi @mebjas, I'm mostly a backend developer, and I have just general knowledge of JS. My suggestion, or at least my point of view, is to allow everyone to easily change/update/amend the language file without the need to compile something. So, for this reason, I'd stay with some basic JS options, like a file with a single object called
lang
and each property as a word/phrase. I think this should allow some sort of easy attribute injection too.
lang = { hello : "ciao" }
Hi @jpaoletti, I just looked for the phrase I want to translate in the codebase, and just changed it in the code. Of course this solution doesn't work with multilanguage applications.
@croxarens How did you translated in the codebase? Directly inside node_modules?
@croxarens How did you translated in the codebase? Directly inside node_modules?
Correct!
@croxarens How did you translated in the codebase? Directly inside node_modules?
Correct!
But if you update the application with npm install or yarn add, won't it be overwritten again?
And I changed inside node_modules but nothing happened :/
Sorry, you are right. It was quite a long time ago. I wasn't really into JS so I didn't want to use NPM. At that time, the library had a compiled version, which I added to my website assets and then updated the values there. Looking at the example here, you could download the https://unpkg.com/html5-qrcode file, change what you need, and use it into your system.
And I changed inside node_modules but nothing happened :/
If you do the same inside the node_modules, I guess you'll need to recompile the code and flush the caches (browser included)
But if you update the application with npm install or yarn add, won't it be overwritten again?
Yes, I guess in that case, it would be better to add into the package.json file the version you are currently at, so that npm will not update to the next version, or just clone the project and create a local path
I hope this is helpful
Hi there,
is there any way I can translate the messages like "Request Camera Permissions", "Scan an Image File"?