Skip to content

Commit

Permalink
Vendor in case functions (#3233)
Browse files Browse the repository at this point in the history
* Vendor in case package

* Create a kebabToCamel JS function for locales

* Update the lock file

Signed-off-by: Olga Bulat <[email protected]>

* Add suggestions from code review

Signed-off-by: Olga Bulat <[email protected]>

---------

Signed-off-by: Olga Bulat <[email protected]>
  • Loading branch information
obulat authored Nov 7, 2023
1 parent 7afc91a commit 1eb9f97
Show file tree
Hide file tree
Showing 13 changed files with 132 additions and 29 deletions.
1 change: 0 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@
"async-mutex": "^0.3.2",
"axios": "^0.27.0",
"axios-mock-adapter": "^1.20.0",
"case": "^1.6.3",
"clipboard": "^2.0.11",
"cookie-universal-nuxt": "^2.1.5",
"core-js": "^3.27.2",
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/VLicense/VLicense.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@
</template>

<script lang="ts">
import { camel } from "case"
import { computed, defineComponent, PropType } from "vue"
import type { License } from "~/constants/license"
import { getFullLicenseName, getElements } from "~/utils/license"
import { camelCase } from "~/utils/case"
import { useI18n } from "~/composables/use-i18n"
import VIcon from "~/components/VIcon/VIcon.vue"
Expand Down Expand Up @@ -63,7 +63,7 @@ export default defineComponent({
const iconNames = computed(() => getElements(props.license))
const licenseName = computed(() => {
const licenseKey =
props.license === "sampling+" ? props.license : camel(props.license)
props.license === "sampling+" ? props.license : camelCase(props.license)
return {
readable: i18n.t(`licenseReadableNames.${licenseKey}`).toString(),
full: getFullLicenseName(props.license, "", i18n),
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/VLicense/VLicenseElements.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@

<script lang="ts">
import { computed, defineComponent, PropType } from "vue"
import { camel } from "case"
import type { License } from "~/constants/license"
import { useI18n } from "~/composables/use-i18n"
import { useUiStore } from "~/stores/ui"
import { camelCase } from "~/utils/case"
import { getElements } from "~/utils/license"
import VIcon from "~/components/VIcon/VIcon.vue"
Expand Down Expand Up @@ -62,7 +62,7 @@ export default defineComponent({
const isMobile = computed(() => !uiStore.isDesktopLayout)
const getLicenseDescription = (element: string) => {
return i18n.t(`browsePage.licenseDescription.${camel(element)}`)
return i18n.t(`browsePage.licenseDescription.${camelCase(element)}`)
}
return {
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/components/VSafetyWall/VSafetyWall.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
</p>
<p v-for="reason in media.sensitivity" :key="reason">
{{
$t(`sensitiveContent.reasons.${camel(reason)}`, {
$t(`sensitiveContent.reasons.${camelCase(reason)}`, {
openverse: "Openverse",
})
}}
Expand Down Expand Up @@ -57,10 +57,10 @@

<script lang="ts">
import { PropType, computed, defineComponent } from "@nuxtjs/composition-api"
import { camel } from "case"
import { useSearchStore } from "~/stores/search"
import { useAnalytics } from "~/composables/use-analytics"
import { camelCase } from "~/utils/case"
import type { AudioDetail, ImageDetail } from "~/types/media"
import VLink from "~/components/VLink.vue"
Expand Down Expand Up @@ -99,7 +99,7 @@ export default defineComponent({
backToSearchPath,
handleBack,
handleShow,
camel,
camelCase,
}
},
})
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/meta/VButton.stories.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import {
Story,
} from "@storybook/addon-docs"
import { buttonForms, buttonSizes, buttonVariants } from "~/types/button"
import { capitalCase } from "~/utils/case"
import VButton from "~/components/VButton.vue"
import VIcon from "~/components/VIcon/VIcon.vue"
import { capital } from "case"

<Meta title="Components/VButton" components={VButton} />

Expand Down Expand Up @@ -101,7 +101,7 @@ export const VariantsTemplate = (args) => ({
components: { VButton },
methods: {
capitalize(str) {
return capital(str)
return capitalCase(str)
},
},
setup() {
Expand Down
15 changes: 13 additions & 2 deletions frontend/src/locales/scripts/utils.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
const { writeFile } = require("fs/promises")
const os = require("os")

const { camel } = require("case")
/**
* Convert a kebab-case string (`image-title`) to camel case (`imageTitle`).
*/
function kebabToCamel(input) {
const split = input.split("-")
if (split.length === 1) return input

for (let i = 1; i < split.length; i++) {
split[i] = split[i][0].toUpperCase() + split[i].slice(1)
}
return split.join("")
}

/**
* Mutates an object at the path with the value. If the path
Expand Down Expand Up @@ -34,7 +45,7 @@ function replacer(_, match) {
if (match.includes("-")) {
console.warn("Found kebab-cased key in translation strings:", match)
}
return `{${camel(match)}}`
return `{${kebabToCamel(match)}}`
}

/**
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/stores/provider.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { capital } from "case"
import { defineStore } from "pinia"
import { ssrRef } from "@nuxtjs/composition-api"

import { capitalCase } from "~/utils/case"
import { env } from "~/utils/env"
import { parseFetchingError } from "~/utils/errors"
import {
Expand Down Expand Up @@ -102,7 +102,7 @@ export const useProviderStore = defineStore("provider", {
*/
getProviderName(providerCode: string, mediaType: SupportedMediaType) {
const provider = this._getProvider(providerCode, mediaType)
return provider?.display_name || capital(providerCode)
return provider?.display_name || capitalCase(providerCode)
},

/**
Expand Down
71 changes: 71 additions & 0 deletions frontend/src/utils/case.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copied from https://github.com/blakeembrey/change-case
// Case-related utility functions are vendored in because `case` package
// cannot be used in Nuxt 3 which requires ESM compatibility.

// Regexps involved with splitting words in various case formats.
const SPLIT_LOWER_UPPER_RE = /([\p{Ll}\d])(\p{Lu})/gu
const SPLIT_UPPER_UPPER_RE = /(\p{Lu})([\p{Lu}][\p{Ll}])/gu

// Regexp involved with stripping non-word characters from the result.
const DEFAULT_STRIP_REGEXP = /[^\p{L}\d]+/giu

// The replacement value for splits.
const SPLIT_REPLACE_VALUE = "$1\0$2"

/**
* Split any cased input strings into an array of words.
*/
function split(input: string) {
let result = input.trim()

result = result
.replace(SPLIT_LOWER_UPPER_RE, SPLIT_REPLACE_VALUE)
.replace(SPLIT_UPPER_UPPER_RE, SPLIT_REPLACE_VALUE)
.replace(DEFAULT_STRIP_REGEXP, "\0")

let start = 0
let end = result.length

// Trim the delimiter from around the output string.
while (result.charAt(start) === "\0") start++
if (start === end) return []
while (result.charAt(end - 1) === "\0") end--

return result.slice(start, end).split(/\0/g)
}

function pascalCaseTransformFactory() {
return (word: string, index: number) => {
const char0 = word[0]
const initial =
index > 0 && char0 >= "0" && char0 <= "9"
? "_" + char0
: char0.toUpperCase()
return initial + word.slice(1).toLowerCase()
}
}

function capitalCaseTransformFactory() {
return (word: string) =>
`${word[0].toUpperCase()}${word.slice(1).toLowerCase()}`
}

/**
* Convert a string to camel case (`fooBar`).
*/
export function camelCase(input: string) {
const transform = pascalCaseTransformFactory()
return split(input)
.map((word, index) => {
if (index === 0) return word.toLowerCase()
return transform(word, index)
})
.join("")
}

/**
* Convert a string to capital case (`Foo Bar`).
*/
export function capitalCase(input: string) {
return split(input).map(capitalCaseTransformFactory()).join(" ")
}
5 changes: 2 additions & 3 deletions frontend/src/utils/decode-media-data.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { title as titleCase } from "case"

import { decodeData as decodeString } from "~/utils/decode-data"
import type { ApiMedia, Media, Tag } from "~/types/media"
import { SENSITIVITY_RESPONSE_PARAM } from "~/constants/content-safety"
import type { MediaType } from "~/constants/media"
import { AUDIO, IMAGE, MODEL_3D, VIDEO } from "~/constants/media"
import { useFeatureFlagStore } from "~/stores/feature-flag"
import { capitalCase } from "~/utils/case"
import { getFakeSensitivities } from "~/utils/content-safety"

const mediaTypeExtensions: Record<MediaType, string[]> = {
Expand Down Expand Up @@ -68,7 +67,7 @@ const mediaTitle = (
media: ApiMedia,
mediaType: MediaType
): { title: string; originalTitle: string } => {
const originalTitle = decodeString(media.title) || titleCase(mediaType)
const originalTitle = decodeString(media.title) || capitalCase(mediaType)
return {
originalTitle,
title: stripExtension(originalTitle, mediaType, media),
Expand Down
6 changes: 4 additions & 2 deletions frontend/src/utils/license.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
* For any changes made here, please make the corresponding changes in the
* backend, or open an issue to track it.
*/
import { camel } from "case"

import type {
License,
Expand All @@ -15,6 +14,7 @@ import {
DEPRECATED_CC_LICENSES,
PUBLIC_DOMAIN_MARKS,
} from "~/constants/license"
import { camelCase } from "~/utils/case"

import type VueI18n from "vue-i18n"

Expand All @@ -36,7 +36,9 @@ export const getFullLicenseName = (

// PDM has no abbreviation
if (license === "pdm" && i18n) {
licenseName = i18n.t(`licenseReadableNames.${camel(license)}`).toString()
licenseName = i18n
.t(`licenseReadableNames.${camelCase(license)}`)
.toString()
} else {
licenseName = license.toUpperCase().replace("SAMPLING", "Sampling")
}
Expand Down
5 changes: 2 additions & 3 deletions frontend/src/utils/metadata.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { title } from "case"

import { capitalCase } from "~/utils/case"
import type { AudioDetail, ImageDetail, Metadata } from "~/types/media"
import { AUDIO, IMAGE } from "~/constants/media"

Expand Down Expand Up @@ -95,7 +94,7 @@ export const getMediaMetadata = (
if (media.genres && media.genres.length > 0) {
metadata.push({
label: "audioDetails.table.genre",
value: media.genres.map((genre) => title(genre)).join(", "),
value: media.genres.map((genre) => capitalCase(genre)).join(", "),
})
}

Expand Down
29 changes: 29 additions & 0 deletions frontend/test/unit/specs/utils/case.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { camelCase, capitalCase } from "~/utils/case"

describe("camelCase", () => {
it.each`
input | output
${"media"} | ${"media"}
${"mediaTitle"} | ${"mediaTitle"}
${"Media Title"} | ${"mediaTitle"}
${"media title"} | ${"mediaTitle"}
${"media-title"} | ${"mediaTitle"}
${"media_title"} | ${"mediaTitle"}
`("returns $output for $input", ({ input, output }) => {
expect(camelCase(input)).toBe(output)
})
})

describe("capitalCase", () => {
it.each`
input | output
${"media"} | ${"Media"}
${"mediaTitle"} | ${"Media Title"}
${"Media Title"} | ${"Media Title"}
${"media title"} | ${"Media Title"}
${"media-title"} | ${"Media Title"}
${"media_title"} | ${"Media Title"}
`("returns $output for $input", ({ input, output }) => {
expect(capitalCase(input)).toBe(output)
})
})
7 changes: 0 additions & 7 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 1eb9f97

Please sign in to comment.