Free tool
Locale inspector
A bookmarklet that shows how a page copes with another language before you have any translations. It works on any site, including your local one, and it runs entirely in your browser, so nothing is sent anywhere.
Try it on this page
Click it again, or press the cross, to close it and undo everything.
Install it
Drag this link to your bookmarks bar:
If you can't drag it, on a phone for instance, make a bookmark of any page, edit it, and paste this in place of its address:
javascript:%28function%28%29%7B%0Aconst%20KEY%20%3D%20%27__helloWorldLocaleInspector%27%3B%0Aif%20%28window%5BKEY%5D%29%20%7B%0Awindow%5BKEY%5D.close%28%29%3B%0Areturn%3B%0A%7D%0Aconst%20ACCENTS%20%3D%20%7B%0Aa%3A%20%27%C3%A5%27%2C%20c%3A%20%27%C3%A7%27%2C%20e%3A%20%27%C3%A9%27%2C%20i%3A%20%27%C3%AE%27%2C%20n%3A%20%27%C3%B1%27%2C%20o%3A%20%27%C3%B6%27%2C%20s%3A%20%27%C5%A1%27%2C%20u%3A%20%27%C3%BC%27%2C%20y%3A%20%27%C3%BD%27%2C%0AA%3A%20%27%C3%85%27%2C%20C%3A%20%27%C3%87%27%2C%20E%3A%20%27%C3%89%27%2C%20I%3A%20%27%C3%8E%27%2C%20N%3A%20%27%C3%91%27%2C%20O%3A%20%27%C3%96%27%2C%20S%3A%20%27%C5%A0%27%2C%20U%3A%20%27%C3%9C%27%2C%20Y%3A%20%27%C3%9D%27%2C%0A%7D%3B%0Aconst%20SKIPPED_TAGS%20%3D%20new%20Set%28%5B%27SCRIPT%27%2C%20%27STYLE%27%2C%20%27NOSCRIPT%27%2C%20%27TEMPLATE%27%2C%20%27CODE%27%2C%20%27PRE%27%2C%20%27KBD%27%2C%20%27SAMP%27%2C%20%27TEXTAREA%27%2C%20%27INPUT%27%2C%20%27SELECT%27%2C%20%27OPTION%27%2C%20%27SVG%27%2C%20%27MATH%27%5D%29%3B%0Afunction%20pseudolocalise%28text%29%20%7B%0Aconst%20accented%20%3D%20text%0A.split%28%2F%28%5C%7B%5B%5E%7D%5D%2B%5C%7D%29%2F%29%0A.map%28%28part%29%20%3D%3E%20%28part.startsWith%28%27%7B%27%29%20%3F%20part%20%3A%20part.replace%28%2F%5Ba-zA-Z%5D%2Fg%2C%20%28character%29%20%3D%3E%20ACCENTS%5Bcharacter%5D%20%3F%3F%20character%29%29%29%0A.join%28%27%27%29%3B%0Areturn%20%60%5B%24%7Baccented%7D%20%24%7B%27%21%27.repeat%28Math.ceil%28text.trim%28%29.length%20%2A%200.4%29%29%7D%5D%60%3B%0A%7D%0Aconst%20html%20%3D%20document.documentElement%3B%0Aconst%20originalDir%20%3D%20html.getAttribute%28%27dir%27%29%3B%0Aconst%20originalTexts%20%3D%20new%20Map%28%29%3B%0Aconst%20originalPlaceholders%20%3D%20new%20Map%28%29%3B%0Aconst%20outlined%20%3D%20new%20Map%28%29%3B%0Alet%20badgeFrame%20%3D%200%3B%0Aconst%20host%20%3D%20document.createElement%28%27div%27%29%3B%0Ahost.setAttribute%28%27data-hello-world-locale-inspector%27%2C%20%27%27%29%3B%0Ahost.setAttribute%28%27dir%27%2C%20%27ltr%27%29%3B%0Ahost.setAttribute%28%27lang%27%2C%20%27en%27%29%3B%0Ahost.style.setProperty%28%27all%27%2C%20%27initial%27%29%3B%0Aconst%20shadow%20%3D%20host.attachShadow%28%7B%20mode%3A%20%27open%27%20%7D%29%3B%0Aconst%20css%20%3D%20%60%0A%3Ahost%20%7B%20all%3A%20initial%3B%20%7D%0A.panel%20%7B%20position%3A%20fixed%3B%20right%3A%2016px%3B%20bottom%3A%2016px%3B%20z-index%3A%202147483647%3B%20width%3A%20260px%3B%20box-sizing%3A%20border-box%3B%20padding%3A%2014px%3B%20border-radius%3A%2012px%3B%20background%3A%20%23fff%3B%20color%3A%20%231c1917%3B%20box-shadow%3A%200%2010px%2030px%20rgb%280%200%200%20%2F%200.2%29%2C%200%200%200%201px%20rgb%280%200%200%20%2F%200.08%29%3B%20font%3A%2013px%2F1.45%20system-ui%2C%20-apple-system%2C%20%27Segoe%20UI%27%2C%20sans-serif%3B%20text-align%3A%20left%3B%20direction%3A%20ltr%3B%20%7D%0A.head%20%7B%20display%3A%20flex%3B%20align-items%3A%20center%3B%20justify-content%3A%20space-between%3B%20gap%3A%208px%3B%20margin-bottom%3A%2010px%3B%20%7D%0A.title%20%7B%20font-weight%3A%20600%3B%20font-size%3A%2013px%3B%20margin%3A%200%3B%20%7D%0A.close%20%7B%20all%3A%20unset%3B%20cursor%3A%20pointer%3B%20padding%3A%202px%206px%3B%20border-radius%3A%206px%3B%20color%3A%20%2357534e%3B%20font-size%3A%2016px%3B%20line-height%3A%201%3B%20%7D%0A.close%3Ahover%2C%20.close%3Afocus-visible%20%7B%20background%3A%20%23f5f5f4%3B%20color%3A%20%231c1917%3B%20%7D%0Alabel%20%7B%20display%3A%20flex%3B%20align-items%3A%20flex-start%3B%20gap%3A%208px%3B%20padding%3A%205px%200%3B%20cursor%3A%20pointer%3B%20%7D%0Ainput%20%7B%20margin%3A%202px%200%200%3B%20accent-color%3A%20%23e11d48%3B%20%7D%0A.hint%20%7B%20display%3A%20block%3B%20color%3A%20%2378716c%3B%20font-size%3A%2012px%3B%20%7D%0A.status%20%7B%20margin%3A%2010px%200%200%3B%20padding-top%3A%2010px%3B%20border-top%3A%201px%20solid%20%23e7e5e4%3B%20color%3A%20%2357534e%3B%20font-size%3A%2012px%3B%20%7D%0A.warn%20%7B%20color%3A%20%23b91c1c%3B%20%7D%0A.badges%20%7B%20position%3A%20fixed%3B%20inset%3A%200%3B%20z-index%3A%202147483646%3B%20pointer-events%3A%20none%3B%20overflow%3A%20hidden%3B%20%7D%0A.badge%20%7B%20position%3A%20absolute%3B%20padding%3A%201px%205px%3B%20border-radius%3A%204px%3B%20background%3A%20%231d4ed8%3B%20color%3A%20%23fff%3B%20font%3A%20600%2011px%2F1.4%20ui-monospace%2C%20SFMono-Regular%2C%20Menlo%2C%20monospace%3B%20white-space%3A%20nowrap%3B%20%7D%0A%60%3B%0Atry%20%7B%0Aconst%20sheet%20%3D%20new%20CSSStyleSheet%28%29%3B%0Asheet.replaceSync%28css%29%3B%0Ashadow.adoptedStyleSheets%20%3D%20%5Bsheet%5D%3B%0A%7D%20catch%20%7B%0Aconst%20style%20%3D%20document.createElement%28%27style%27%29%3B%0Astyle.textContent%20%3D%20css%3B%0Ashadow.append%28style%29%3B%0A%7D%0Aconst%20badges%20%3D%20document.createElement%28%27div%27%29%3B%0Abadges.className%20%3D%20%27badges%27%3B%0Aconst%20panel%20%3D%20document.createElement%28%27div%27%29%3B%0Apanel.className%20%3D%20%27panel%27%3B%0Apanel.setAttribute%28%27role%27%2C%20%27dialog%27%29%3B%0Apanel.setAttribute%28%27aria-label%27%2C%20%27Locale%20inspector%27%29%3B%0Apanel.innerHTML%20%3D%20%60%0A%3Cdiv%20class%3D%22head%22%3E%0A%3Cp%20class%3D%22title%22%3ELocale%20inspector%3C%2Fp%3E%0A%3Cbutton%20type%3D%22button%22%20class%3D%22close%22%20aria-label%3D%22Close%20and%20undo%20everything%22%3E%C3%97%3C%2Fbutton%3E%0A%3C%2Fdiv%3E%0A%3Clabel%3E%3Cinput%20type%3D%22checkbox%22%20name%3D%22pseudo%22%3E%3Cspan%3EPseudolocalise%3Cspan%20class%3D%22hint%22%3EAccented%2C%2040%25%20longer%2C%20in%20brackets%3C%2Fspan%3E%3C%2Fspan%3E%3C%2Flabel%3E%0A%3Clabel%3E%3Cinput%20type%3D%22checkbox%22%20name%3D%22rtl%22%3E%3Cspan%3ERight%20to%20left%3Cspan%20class%3D%22hint%22%3ESets%20dir%3D%22rtl%22%20on%20the%20page%3C%2Fspan%3E%3C%2Fspan%3E%3C%2Flabel%3E%0A%3Clabel%3E%3Cinput%20type%3D%22checkbox%22%20name%3D%22clipping%22%3E%3Cspan%3EHighlight%20clipping%3Cspan%20class%3D%22hint%22%3EOutlines%20text%20that%27s%20cut%20off%3C%2Fspan%3E%3C%2Fspan%3E%3C%2Flabel%3E%0A%3Clabel%3E%3Cinput%20type%3D%22checkbox%22%20name%3D%22lang%22%3E%3Cspan%3EShow%20lang%3Cspan%20class%3D%22hint%22%3ELabels%20every%20lang%20attribute%3C%2Fspan%3E%3C%2Fspan%3E%3C%2Flabel%3E%0A%3Cp%20class%3D%22status%22%20aria-live%3D%22polite%22%3E%3C%2Fp%3E%0A%60%3B%0Ashadow.append%28badges%2C%20panel%29%3B%0Aconst%20status%20%3D%20panel.querySelector%28%27.status%27%29%3B%0Aconst%20toggles%20%3D%20Object.fromEntries%28%5B...panel.querySelectorAll%28%27input%27%29%5D.map%28%28input%29%20%3D%3E%20%5Binput.name%2C%20input%5D%29%29%3B%0Afunction%20isOurs%28node%29%20%7B%0Areturn%20node%20%3D%3D%3D%20host%20%7C%7C%20host.contains%28node%29%3B%0A%7D%0Afunction%20isSkipped%28element%29%20%7B%0Afor%20%28let%20current%20%3D%20element%3B%20current%20%26%26%20current%20%21%3D%3D%20document.body%3B%20current%20%3D%20current.parentElement%29%20%7B%0Aif%20%28SKIPPED_TAGS.has%28current.tagName.toUpperCase%28%29%29%20%7C%7C%20current.isContentEditable%20%7C%7C%20current%20%3D%3D%3D%20host%29%20%7B%0Areturn%20true%3B%0A%7D%0A%7D%0Areturn%20false%3B%0A%7D%0Afunction%20setPseudolocalised%28on%29%20%7B%0Aif%20%28%21on%29%20%7B%0AoriginalTexts.forEach%28%28text%2C%20node%29%20%3D%3E%20%7B%0Anode.nodeValue%20%3D%20text%3B%0A%7D%29%3B%0AoriginalPlaceholders.forEach%28%28text%2C%20element%29%20%3D%3E%20element.setAttribute%28%27placeholder%27%2C%20text%29%29%3B%0AoriginalTexts.clear%28%29%3B%0AoriginalPlaceholders.clear%28%29%3B%0Areturn%3B%0A%7D%0Aconst%20walker%20%3D%20document.createTreeWalker%28document.body%2C%20NodeFilter.SHOW_TEXT%2C%20%7B%0AacceptNode%3A%20%28node%29%20%3D%3E%20%28node.nodeValue.trim%28%29%20%3D%3D%3D%20%27%27%20%7C%7C%20isSkipped%28node.parentElement%29%20%3F%20NodeFilter.FILTER_REJECT%20%3A%20NodeFilter.FILTER_ACCEPT%29%2C%0A%7D%29%3B%0Afor%20%28let%20node%20%3D%20walker.nextNode%28%29%3B%20node%3B%20node%20%3D%20walker.nextNode%28%29%29%20%7B%0AoriginalTexts.set%28node%2C%20node.nodeValue%29%3B%0A%7D%0AoriginalTexts.forEach%28%28text%2C%20node%29%20%3D%3E%20%7B%0Aconst%20leading%20%3D%20text.match%28%2F%5E%5Cs%2A%2F%29%5B0%5D%3B%0Aconst%20trailing%20%3D%20text.match%28%2F%5Cs%2A%24%2F%29%5B0%5D%3B%0Anode.nodeValue%20%3D%20leading%20%2B%20pseudolocalise%28text.trim%28%29%29%20%2B%20trailing%3B%0A%7D%29%3B%0Adocument.querySelectorAll%28%27%5Bplaceholder%5D%27%29.forEach%28%28element%29%20%3D%3E%20%7B%0Aif%20%28%21isOurs%28element%29%29%20%7B%0AoriginalPlaceholders.set%28element%2C%20element.getAttribute%28%27placeholder%27%29%29%3B%0Aelement.setAttribute%28%27placeholder%27%2C%20pseudolocalise%28element.getAttribute%28%27placeholder%27%29%29%29%3B%0A%7D%0A%7D%29%3B%0A%7D%0Afunction%20setRightToLeft%28on%29%20%7B%0Aif%20%28on%29%20%7B%0Ahtml.setAttribute%28%27dir%27%2C%20%27rtl%27%29%3B%0A%7D%20else%20if%20%28originalDir%20%3D%3D%3D%20null%29%20%7B%0Ahtml.removeAttribute%28%27dir%27%29%3B%0A%7D%20else%20%7B%0Ahtml.setAttribute%28%27dir%27%2C%20originalDir%29%3B%0A%7D%0A%7D%0Afunction%20clearOutlines%28%29%20%7B%0Aoutlined.forEach%28%28%5Boutline%2C%20offset%5D%2C%20element%29%20%3D%3E%20%7B%0Aelement.style.outline%20%3D%20outline%3B%0Aelement.style.outlineOffset%20%3D%20offset%3B%0A%7D%29%3B%0Aoutlined.clear%28%29%3B%0A%7D%0Afunction%20findClipping%28%29%20%7B%0AclearOutlines%28%29%3B%0Aif%20%28%21toggles.clipping.checked%29%20%7B%0Areturn%3B%0A%7D%0Adocument.body.querySelectorAll%28%27%2A%27%29.forEach%28%28element%29%20%3D%3E%20%7B%0Aif%20%28isOurs%28element%29%20%7C%7C%20isSkipped%28element%29%20%7C%7C%20element.clientWidth%20%3D%3D%3D%200%29%20%7B%0Areturn%3B%0A%7D%0Aconst%20style%20%3D%20getComputedStyle%28element%29%3B%0Aconst%20hidesX%20%3D%20%5B%27hidden%27%2C%20%27clip%27%5D.includes%28style.overflowX%29%20%7C%7C%20style.textOverflow%20%3D%3D%3D%20%27ellipsis%27%3B%0Aconst%20hidesY%20%3D%20%5B%27hidden%27%2C%20%27clip%27%5D.includes%28style.overflowY%29%3B%0Aconst%20clipped%20%3D%20%28hidesX%20%26%26%20element.scrollWidth%20%3E%20element.clientWidth%20%2B%201%29%20%7C%7C%20%28hidesY%20%26%26%20element.scrollHeight%20%3E%20element.clientHeight%20%2B%201%29%3B%0Aif%20%28clipped%29%20%7B%0Aoutlined.set%28element%2C%20%5Belement.style.outline%2C%20element.style.outlineOffset%5D%29%3B%0Aelement.style.outline%20%3D%20%272px%20dashed%20%23e11d48%27%3B%0Aelement.style.outlineOffset%20%3D%20%27-2px%27%3B%0A%7D%0A%7D%29%3B%0A%7D%0Afunction%20drawBadges%28%29%20%7B%0AbadgeFrame%20%3D%200%3B%0Abadges.replaceChildren%28%29%3B%0Aif%20%28%21toggles.lang.checked%29%20%7B%0Areturn%3B%0A%7D%0Adocument.querySelectorAll%28%27%5Blang%5D%27%29.forEach%28%28element%29%20%3D%3E%20%7B%0Aif%20%28isOurs%28element%29%20%7C%7C%20element%20%3D%3D%3D%20html%29%20%7B%0Areturn%3B%0A%7D%0Aconst%20rect%20%3D%20element.getBoundingClientRect%28%29%3B%0Aif%20%28rect.width%20%3D%3D%3D%200%20%7C%7C%20rect.bottom%20%3C%200%20%7C%7C%20rect.top%20%3E%20innerHeight%29%20%7B%0Areturn%3B%0A%7D%0Aconst%20badge%20%3D%20document.createElement%28%27span%27%29%3B%0Abadge.className%20%3D%20%27badge%27%3B%0Abadge.textContent%20%3D%20element.getAttribute%28%27lang%27%29%20%7C%7C%20%27%28empty%29%27%3B%0Abadge.style.left%20%3D%20%60%24%7BMath.max%280%2C%20rect.left%29%7Dpx%60%3B%0Abadge.style.top%20%3D%20%60%24%7BMath.max%280%2C%20rect.top%20-%2018%29%7Dpx%60%3B%0Abadges.append%28badge%29%3B%0A%7D%29%3B%0A%7D%0Afunction%20scheduleBadges%28%29%20%7B%0Aif%20%28toggles.lang.checked%20%26%26%20%21badgeFrame%29%20%7B%0AbadgeFrame%20%3D%20requestAnimationFrame%28drawBadges%29%3B%0A%7D%0A%7D%0Afunction%20updateStatus%28%29%20%7B%0Aconst%20lang%20%3D%20html.getAttribute%28%27lang%27%29%3B%0Aconst%20parts%20%3D%20%5B%5D%3B%0Aparts.push%28lang%20%3F%20%60Page%20language%3A%20%3Cstrong%3E%24%7Blang.replace%28%2F%5B%3C%3E%26%22%5D%2Fg%2C%20%27%27%29%7D%3C%2Fstrong%3E%60%20%3A%20%27%3Cspan%20class%3D%22warn%22%3EThe%20page%20has%20no%20lang%20attribute.%3C%2Fspan%3E%27%29%3B%0Aif%20%28toggles.lang.checked%29%20%7B%0Aparts.push%28%60%24%7Bdocument.querySelectorAll%28%27body%20%5Blang%5D%27%29.length%7D%20other%20elements%20set%20a%20language.%60%29%3B%0A%7D%0Aif%20%28toggles.clipping.checked%29%20%7B%0Aparts.push%28outlined.size%20%3D%3D%3D%200%20%3F%20%27Nothing%20is%20clipped.%27%20%3A%20%60%3Cspan%20class%3D%22warn%22%3E%24%7Boutlined.size%20%3D%3D%3D%201%20%3F%20%271%20element%20clips%20its%20text%27%20%3A%20%60%24%7Boutlined.size%7D%20elements%20clip%20their%20text%60%7D.%3C%2Fspan%3E%60%29%3B%0A%7D%0Astatus.innerHTML%20%3D%20parts.join%28%27%3Cbr%3E%27%29%3B%0A%7D%0Afunction%20refresh%28%29%20%7B%0ArequestAnimationFrame%28%28%29%20%3D%3E%20%7B%0AfindClipping%28%29%3B%0AdrawBadges%28%29%3B%0AupdateStatus%28%29%3B%0A%7D%29%3B%0A%7D%0Atoggles.pseudo.addEventListener%28%27change%27%2C%20%28%29%20%3D%3E%20%7B%0AsetPseudolocalised%28toggles.pseudo.checked%29%3B%0Arefresh%28%29%3B%0A%7D%29%3B%0Atoggles.rtl.addEventListener%28%27change%27%2C%20%28%29%20%3D%3E%20%7B%0AsetRightToLeft%28toggles.rtl.checked%29%3B%0Arefresh%28%29%3B%0A%7D%29%3B%0Atoggles.clipping.addEventListener%28%27change%27%2C%20refresh%29%3B%0Atoggles.lang.addEventListener%28%27change%27%2C%20refresh%29%3B%0AaddEventListener%28%27scroll%27%2C%20scheduleBadges%2C%20%7B%20passive%3A%20true%2C%20capture%3A%20true%20%7D%29%3B%0AaddEventListener%28%27resize%27%2C%20scheduleBadges%2C%20%7B%20passive%3A%20true%20%7D%29%3B%0Afunction%20close%28%29%20%7B%0AsetPseudolocalised%28false%29%3B%0AsetRightToLeft%28false%29%3B%0AclearOutlines%28%29%3B%0AremoveEventListener%28%27scroll%27%2C%20scheduleBadges%2C%20%7B%20capture%3A%20true%20%7D%29%3B%0AremoveEventListener%28%27resize%27%2C%20scheduleBadges%29%3B%0AcancelAnimationFrame%28badgeFrame%29%3B%0Ahost.remove%28%29%3B%0Adelete%20window%5BKEY%5D%3B%0A%7D%0Apanel.querySelector%28%27.close%27%29.addEventListener%28%27click%27%2C%20close%29%3B%0Awindow%5BKEY%5D%20%3D%20%7B%20close%20%7D%3B%0Adocument.body.append%28host%29%3B%0AupdateStatus%28%29%3B%0A%7D%29%28%29%3Bvoid%200%3B
What it shows
Turn on any mix of these. They work best together: pseudolocalise and highlight clipping, then switch to right to left and look again.
- Pseudolocalise
- Swaps the text on the page for an accented version that's 40% longer and wrapped in brackets, the way a pseudo-locale does. Text that stays plain English is hardcoded. A bracket that's missing means the text was cut off, and two bracketed pieces where you expected one sentence means it was glued together. Testing explains how to build this into your app.
- Right to left
- Sets
dir="rtl"on the page, as Arabic and Hebrew would. Anything that doesn't flip, like a margin on the wrong side or an arrow that still points right, uses physical CSS instead of logical properties. Right-to-left design covers what should flip and what shouldn't. - Highlight clipping
- Outlines in red every element that hides its overflow or ends in an ellipsis and has more content than fits. Pseudolocalised text finds these quickly, because German and Finnish do the same to fixed-width buttons and tabs. Layout, type and imagery covers designing for text that grows.
- Show lang
- Labels every element with a
langattribute, and warns when the page itself has none. Screen readers, hyphenation, fonts and translation tools all rely on it. Locales explains how to set it.
What it can't do
- It changes what's on the page when you turn it on. Text that loads afterwards stays as it is, so turn pseudolocalisation off and on again to catch it.
- It can't tell a translated string from a hardcoded one. Pseudolocalising inside your app, from your translation files, can.
- Some sites block bookmarklets with a strict content security policy, and Firefox honours that. If nothing happens, try it in another browser.