Skip to content

Commit

Permalink
Fix: correctly handle linebreaks and markdown in translation strings
Browse files Browse the repository at this point in the history
  • Loading branch information
marcelveldt committed Jan 22, 2025
1 parent f206913 commit 1658d39
Show file tree
Hide file tree
Showing 7 changed files with 74 additions and 35 deletions.
1 change: 1 addition & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ module.exports = {
},
],
'vue/multi-word-component-names': 'off',
'vue/no-v-text-v-html-on-component': 'off',
'vue/attribute-hyphenation': ['error', 'always', {ignore: ['onLoad']}],
'no-unused-vars': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
Expand Down
13 changes: 5 additions & 8 deletions src/components/InfoHeader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,7 @@ import MediaItemThumb from "./MediaItemThumb.vue";
import MenuButton from "./MenuButton.vue";
import { getImageThumbForItem } from "./MediaItemThumb.vue";
import { useRouter } from "vue-router";
import { truncateString, parseBool } from "@/helpers/utils";
import { truncateString, parseBool, markdownToHtml } from "@/helpers/utils";
import {
getContextMenuItems,
getPlayMenuItems,
Expand Down Expand Up @@ -485,18 +485,15 @@ const rawDescription = computed(() => {
});
const fullDescription = computed(() => {
return rawDescription.value.replace(/(\r\n|\n|\r)/gm, "<br /><br />");
return markdownToHtml(rawDescription.value);
});
const shortDescription = computed(() => {
const maxChars = mobile.value ? 160 : 300;
if (rawDescription.value.length > maxChars) {
return (
rawDescription.value
.replace(/(\r\n|\n|\r)/gm, " ")
.substring(0, maxChars) + "..."
);
return fullDescription.value.substring(0, maxChars) + "...";
}
return rawDescription.value.replace(/(\r\n|\n|\r)/gm, " ");
return fullDescription.value;
});
const artistLogo = computed(() => {
Expand Down
9 changes: 9 additions & 0 deletions src/helpers/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
} from "@/plugins/api/interfaces";
import { getBreakpointValue } from "@/plugins/breakpoint";
import { api } from "@/plugins/api";
import { marked } from "marked";

import Color from "color";
//@ts-ignore
Expand Down Expand Up @@ -491,3 +492,11 @@ export function isTouchscreenDevice() {
}
return result;
}

export const markdownToHtml = function (text: string): string {
text = text
.replaceAll(/\\n/g, "<br />")
.replaceAll("\n", "<br />")
.replaceAll(" \\", "<br />");
return marked(text) as string;
};
8 changes: 5 additions & 3 deletions src/views/settings/AddPlayerGroup.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
<v-card-title>
{{ $t("settings.add_group_player") }}
</v-card-title>
<v-card-subtitle style="white-space: break-spaces">
{{ $t("settings.add_group_player_desc") }}
</v-card-subtitle>
<v-card-subtitle
style="white-space: break-spaces"
v-html="markdownToHtml($t('settings.add_group_player_desc'))"

Check warning on line 11 in src/views/settings/AddPlayerGroup.vue

View workflow job for this annotation

GitHub Actions / Lint

'v-html' directive can lead to XSS attack
/>
<br />
<v-divider />
<br />
Expand Down Expand Up @@ -84,6 +85,7 @@ import { computed, ref } from "vue";
import { useRouter } from "vue-router";
import { api } from "@/plugins/api";
import { ProviderFeature } from "@/plugins/api/interfaces";
import { markdownToHtml } from "@/helpers/utils";
// global refs
const router = useRouter();
Expand Down
37 changes: 28 additions & 9 deletions src/views/settings/AddProvider.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,17 @@
$t("settings.setup_provider", [api.providerManifests[domain].name])
}}
</v-card-title>
<v-card-subtitle>
{{ api.providerManifests[domain].description }} </v-card-subtitle
><br />
<v-card-subtitle v-if="api.providerManifests[domain].codeowners.length">
<b>{{ $t("settings.codeowners") }}: </b
>{{ api.providerManifests[domain].codeowners.join(" / ") }}
</v-card-subtitle>

<v-card-subtitle
v-html="markdownToHtml(api.providerManifests[domain].description)"

Check warning on line 15 in src/views/settings/AddProvider.vue

View workflow job for this annotation

GitHub Actions / Lint

'v-html' directive can lead to XSS attack
/><br />
<v-card-subtitle
v-if="api.providerManifests[domain].codeowners.length"
v-html="

Check warning on line 19 in src/views/settings/AddProvider.vue

View workflow job for this annotation

GitHub Actions / Lint

'v-html' directive can lead to XSS attack
markdownToHtml(
getAuthorsMarkdown(api.providerManifests[domain].codeowners),
)
"
/>
<v-card-subtitle v-if="api.providerManifests[domain].documentation">
<b>{{ $t("settings.need_help_setup_provider") }} </b>&nbsp;
<a
Expand Down Expand Up @@ -76,7 +79,8 @@ import {
} from "@/plugins/api/interfaces";
import EditConfig from "./EditConfig.vue";
import { watch } from "vue";
import { openLinkInNewTab } from "@/helpers/utils";
import { openLinkInNewTab, markdownToHtml } from "@/helpers/utils";
import { useI18n } from "vue-i18n";
// global refs
const router = useRouter();
Expand Down Expand Up @@ -176,6 +180,21 @@ const onAction = async function (
showAuthLink.value = false;
});
};
const getAuthorsMarkdown = function (authors: string[]) {
const allAuthors: string[] = [];
const { t } = useI18n();
for (const author of authors) {
if (author.includes("@")) {
allAuthors.push(
`[${author.replace("@", "")}](https://github.com/${author.replace("@", "")})`,
);
} else {
allAuthors.push(author);
}
}
return `**${t("settings.codeowners")}**: ` + allAuthors.join(" / ");
};
</script>

<style scoped></style>
11 changes: 1 addition & 10 deletions src/views/settings/EditConfig.vue
Original file line number Diff line number Diff line change
Expand Up @@ -364,9 +364,9 @@ import {
ConfigEntry,
ConfigValueOption,
} from "@/plugins/api/interfaces";
import { markdownToHtml } from "@/helpers/utils";
import { $t } from "@/plugins/i18n";
const router = useRouter();
import { marked } from "marked";
export interface Props {
configEntries: ConfigEntry[];
Expand Down Expand Up @@ -533,15 +533,6 @@ const getTranslatedOptions = function (entry: ConfigEntry) {
return options;
};
const markdownToHtml = function (text: string) {
// text = text.replaceAll(/\\n\\n/g, "<br /><br />").replace(/\\n/g, "<br /> ");
text = text
.replaceAll(/\\n/g, "<br />")
.replaceAll("\n", "<br />")
.replaceAll(" \\", "<br />");
return marked(text);
};
const hasDescriptionOrHelpLink = function (conf_entry: ConfigEntry) {
// overly complicated way to determine we have a description for the entry
// in either the translations (by entry key), on the entry itself as fallback
Expand Down
30 changes: 25 additions & 5 deletions src/views/settings/EditProvider.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,14 @@
><br />
<v-card-subtitle
v-if="api.providerManifests[config.domain].codeowners.length"
>
<b>{{ $t("settings.codeowners") }}: </b
>{{ api.providerManifests[config.domain].codeowners.join(" / ") }}
</v-card-subtitle>
v-html="

Check warning on line 20 in src/views/settings/EditProvider.vue

View workflow job for this annotation

GitHub Actions / Lint

'v-html' directive can lead to XSS attack
markdownToHtml(
getAuthorsMarkdown(
api.providerManifests[config.domain].codeowners,
),
)
"
/>

<v-card-subtitle
v-if="api.providerManifests[config.domain].documentation"
Expand Down Expand Up @@ -102,7 +106,8 @@ import {
} from "@/plugins/api/interfaces";
import EditConfig from "./EditConfig.vue";
import { nanoid } from "nanoid";
import { openLinkInNewTab } from "@/helpers/utils";
import { openLinkInNewTab, markdownToHtml } from "@/helpers/utils";
import { useI18n } from "vue-i18n";
// global refs
const router = useRouter();
Expand Down Expand Up @@ -203,4 +208,19 @@ const onAction = async function (
showAuthLink.value = false;
});
};
const getAuthorsMarkdown = function (authors: string[]) {
const allAuthors: string[] = [];
const { t } = useI18n();
for (const author of authors) {
if (author.includes("@")) {
allAuthors.push(
`[${author.replace("@", "")}](https://github.com/${author.replace("@", "")})`,
);
} else {
allAuthors.push(author);
}
}
return `**${t("settings.codeowners")}**: ` + allAuthors.join(" / ");
};
</script>

0 comments on commit 1658d39

Please sign in to comment.