fix(web): escaping html-encoded symbols like apostrophes in translations (#1002)

e.g. end user would see `'` from translations
This commit is contained in:
Pujit Mehrotra
2025-01-06 14:59:36 -05:00
committed by GitHub
parent 91de6e6c1e
commit 4ee42a6cf6
3 changed files with 34 additions and 3 deletions

View File

@@ -1,8 +1,9 @@
<script lang="ts" setup>
import en_US from '~/locales/en_US.json';
import { provide } from 'vue';
import { createI18n, I18nInjectionKey } from 'vue-i18n';
import { createHtmlEntityDecoder } from '~/helpers/i18n-utils';
import en_US from '~/locales/en_US.json';
// import ja from '~/locales/ja.json';
const defaultLocale = 'en_US'; // ja, en_US
@@ -34,7 +35,9 @@ const i18n = createI18n<false>({
en_US,
// ja,
...(nonDefaultLocale ? parsedMessages : {}),
}
},
/** safely decodes html-encoded symbols like &amp; and &apos; */
postTranslation: createHtmlEntityDecoder(),
});
provide(I18nInjectionKey, i18n);

26
web/helpers/i18n-utils.ts Normal file
View File

@@ -0,0 +1,26 @@
/**
* Creates a post-translation function that decodes HTML entities in translated strings.
* This function is typically used with createI18n to handle HTML-encoded translations.
*
* @returns A function that takes a translated value and decodes any HTML entities if it's a string.
* If the input is not a string, it returns the original value unchanged.
*
* @example
* const decode = createHtmlEntityDecoder();
* decode("&amp;"); // Returns "&"
* decode(123); // Returns 123
* const i18n = createI18n({
* // ... other options
* postTranslation(translated) {
* return decode(translated);
* },
* });
*/
export const createHtmlEntityDecoder = () => {
const parser = new DOMParser();
return <T>(translated: T) => {
if (typeof translated !== 'string') return translated;
const decoded = parser.parseFromString(translated, 'text/html').documentElement.textContent;
return decoded ?? translated;
};
};

View File

@@ -1,6 +1,7 @@
import { createI18n } from 'vue-i18n';
import en_US from '@/locales/en_US.json';
import en_US from '@/locales/en_US.json';
import { createHtmlEntityDecoder } from '~/helpers/i18n-utils';
export default defineNuxtPlugin(({ vueApp }) => {
const i18n = createI18n({
@@ -11,6 +12,7 @@ export default defineNuxtPlugin(({ vueApp }) => {
messages: {
en_US,
},
postTranslation: createHtmlEntityDecoder(),
});
vueApp.use(i18n);