Skip to content

Commit

Permalink
feat(helper): add locales helper (#44)
Browse files Browse the repository at this point in the history
  • Loading branch information
Mister-Hope authored Jan 31, 2024
1 parent a692d7a commit f1be46f
Show file tree
Hide file tree
Showing 25 changed files with 837 additions and 73 deletions.
16 changes: 8 additions & 8 deletions plugins/plugin-feed/src/node/feed/item.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import {
getPageExcerpt,
getPageText,
isAbsoluteUrl,
isArray,
isFunction,
isLinkAbsolute,
isLinkWithProtocol,
isPlainObject,
isUrl,
} from '@vuepress/helper/node'
import type { GitData } from '@vuepress/plugin-git'
import type { App, Page } from 'vuepress/core'
Expand Down Expand Up @@ -213,23 +213,23 @@ export class FeedItem {
const { banner, cover } = this.frontmatter

if (banner) {
if (isAbsoluteUrl(banner)) return getUrl(hostname, base, banner)
if (isLinkAbsolute(banner)) return getUrl(hostname, base, banner)

if (isUrl(banner)) return banner
if (isLinkWithProtocol(banner)) return banner
}

if (cover) {
if (isAbsoluteUrl(cover)) return getUrl(hostname, base, cover)
if (isLinkAbsolute(cover)) return getUrl(hostname, base, cover)

if (isUrl(cover)) return cover
if (isLinkWithProtocol(cover)) return cover
}

const result = /!\[.*?\]\((.*?)\)/iu.exec(this.page.content)

if (result) {
if (isAbsoluteUrl(result[1])) return getUrl(hostname, base, result[1])
if (isLinkAbsolute(result[1])) return getUrl(hostname, base, result[1])

if (isUrl(result[1])) return result[1]
if (isLinkWithProtocol(result[1])) return result[1]
}

return null
Expand Down
4 changes: 2 additions & 2 deletions plugins/plugin-feed/src/node/generator/rss/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isUrl } from '@vuepress/helper/node'
import { isLinkWithProtocol } from '@vuepress/helper/node'
import { js2xml } from 'xml-js'
import type { FeedCategory, FeedEnclosure } from '../../../typings/index.js'
import type { FeedItem } from '../../feed/item.js'
Expand All @@ -25,7 +25,7 @@ const getRSSGuid = (item: FeedItem): RSSGuid => {
const guid = item.guid || item.link

return {
...(isUrl(guid)
...(isLinkWithProtocol(guid)
? {}
: {
_attributes: {
Expand Down
12 changes: 5 additions & 7 deletions plugins/plugin-seo/src/node/utils/getCover.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isAbsoluteUrl, isUrl } from '@vuepress/helper/node'
import { isLinkAbsolute, isLinkWithProtocol } from '@vuepress/helper/node'
import type { App } from 'vuepress/core'
import type { ExtendPage } from '../../typings/index.js'
import type { SeoPluginOptions } from '../options.js'
Expand All @@ -12,15 +12,13 @@ export const getCover = (
const { banner, cover } = frontmatter

if (banner) {
if (isAbsoluteUrl(banner)) return getUrl(hostname, base, banner)

if (isUrl(banner)) return banner
if (isLinkAbsolute(banner)) return getUrl(hostname, base, banner)
if (isLinkWithProtocol(banner)) return banner
}

if (cover) {
if (isAbsoluteUrl(cover)) return getUrl(hostname, base, cover)

if (isUrl(cover)) return cover
if (isLinkAbsolute(cover)) return getUrl(hostname, base, cover)
if (isLinkWithProtocol(cover)) return cover
}

return null
Expand Down
7 changes: 3 additions & 4 deletions plugins/plugin-seo/src/node/utils/getImages.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isAbsoluteUrl, isUrl } from '@vuepress/helper/node'
import { isLinkAbsolute, isLinkWithProtocol } from '@vuepress/helper/node'
import type { App } from 'vuepress/core'
import type { ExtendPage } from '../../typings/index.js'
import type { SeoPluginOptions } from '../options.js'
Expand All @@ -13,9 +13,8 @@ export const getImages = (
): string[] =>
Array.from(content.matchAll(IMAGE_REG_EXP))
.map(([, link]) => {
if (isAbsoluteUrl(link)) return getUrl(hostname, base, link)

if (isUrl(link)) return link
if (isLinkAbsolute(link)) return getUrl(hostname, base, link)
if (isLinkWithProtocol(link)) return link

return null
})
Expand Down
3 changes: 1 addition & 2 deletions tools/helper/src/client/composables/useLocaleConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import { computed } from 'vue'
import type { ComputedRef } from 'vue'
import { useRouteLocale } from 'vuepress/client'
import type { LocaleData } from 'vuepress/shared'

export type ExactLocaleConfig<T extends LocaleData> = Record<string, T>
import type { ExactLocaleConfig } from '../../shared/index.js'

/**
* Get current locale config
Expand Down
1 change: 1 addition & 0 deletions tools/helper/src/node/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export const noopComponent = '@vuepress/helper/noopComponent'
export const noopModule = '@vuepress/helper/noopModule'

export * from './bundler/index.js'
export * from './locales/index.js'
export * from './page/index.js'
export * from './utils/index.js'
export * from '../shared/index.js'
34 changes: 34 additions & 0 deletions tools/helper/src/node/locales/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { fromEntries, keys } from '../../shared/index.js'
import type { KnownLangCode } from './types.js'

export const lang2PathConfig = {
'de-AT': '/de-at/',
'de-DE': '/de/',
'en-US': '/en/',
'es-ES': '/es/',
'fi-FI': '/fi/',
'fr-FR': '/fr/',
'hu-HU': '/hu/',
'id-ID': '/id/',
'ja-JP': '/ja/',
'ko-KR': '/ko/',
'nl-NL': '/nl/',
'pl-PL': '/pl/',
'pt-BR': '/br/',
'ru-RU': '/ru/',
'sk-SK': '/sk/',
'tr-TR': '/tr/',
'uk-UA': '/uk/',
'vi-VN': '/vi/',
'zh-CN': '/zh/',
'zh-TW': '/zh-tw/',
}

export const supportedLangs = keys(lang2PathConfig)

export const path2langConfig = fromEntries(
(supportedLangs as KnownLangCode[]).map((lang) => [
lang2PathConfig[lang],
lang,
]),
)
122 changes: 122 additions & 0 deletions tools/helper/src/node/locales/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import type { App } from 'vuepress/core'
import type { LocaleConfig, LocaleData } from 'vuepress/shared'
import type { ExactLocaleConfig } from '../../shared/index.js'
import { deepAssign, fromEntries, keys } from '../../shared/index.js'
import { Logger } from '../utils/index.js'
import { lang2PathConfig, path2langConfig } from './config.js'
import type { KnownLangCode } from './types.js'

/** Get language from path */
export const path2Lang = (path = '', debug = false): KnownLangCode => {
if (path in path2langConfig) return path2langConfig[path]

if (debug)
console.warn(
`${path} isn’t assign with a lang, and will return "en-US" instead.`,
)

return 'en-US'
}

/** Get path from language */
export const lang2Path = (lang = '', debug = false): string => {
if (lang in lang2PathConfig) return lang2PathConfig[lang as KnownLangCode]

if (debug)
console.warn(`${lang} has no path config, and will return "/" instead.`)

return '/'
}

/**
* Get language of root directory
*
* @param app VuePress Node App
* @returns root language
*/
export const getRootLang = (app: App): string => {
// infer from siteLocale
const siteLocales = app.siteData.locales

if (siteLocales?.['/'] && siteLocales['/']?.lang) return siteLocales['/'].lang

return app.siteData.lang
}

/**
* Get the infer language path from root directory language
*
* @param app VuePress Node App
* @returns infer language
*/
export const getRootLangPath = (app: App): string =>
lang2Path(getRootLang(app), app.env.isDebug)

/**
* Get locale paths
*
* @param app VuePress Node app
* @returns locale paths
*/
export const getLocalePaths = (app: App): string[] =>
Array.from(new Set(keys(app.siteData.locales)))

export interface LocaleConfigOptions<T extends LocaleData> {
/** VuePress Node app */
app: App
/** Default locale config */
default: ExactLocaleConfig<T>
/** user locale config */
config?: LocaleConfig<T> | undefined
/** plugin name */
name?: string
}

/**
* Get final locale config for client
*
* @returns final locale config
*/
export const getLocaleConfig = <T extends LocaleData>({
app,
name,
default: defaultLocalesConfig,
config: userLocalesConfig = {},
}: LocaleConfigOptions<T>): ExactLocaleConfig<T> => {
const rootPath = getRootLangPath(app)
const logger = new Logger(name)

return fromEntries([
...getLocalePaths(app)
.filter((localePath) => localePath !== '/')
.map<[string, T]>((localePath) => {
const defaultLocaleData =
defaultLocalesConfig[localePath] ||
(lang2Path(app.options.locales[localePath].lang) === '/'
? null
: defaultLocalesConfig[
lang2Path(app.options.locales[localePath].lang)
])

if (!defaultLocaleData)
logger.warn(`Locale ${localePath} is missing it's i18n config`)

return [
localePath,
deepAssign(
{},
defaultLocaleData || defaultLocalesConfig[rootPath] || {},
userLocalesConfig[localePath] || {},
),
]
}),
[
'/',
deepAssign(
{},
defaultLocalesConfig[rootPath],
userLocalesConfig['/'] || userLocalesConfig[rootPath] || {},
),
],
])
}
2 changes: 2 additions & 0 deletions tools/helper/src/node/locales/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './helpers.js'
export * from './types.js'
4 changes: 4 additions & 0 deletions tools/helper/src/node/locales/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import type { lang2PathConfig } from './config.js'

/** Types for supported lang codes */
export type KnownLangCode = keyof typeof lang2PathConfig
4 changes: 2 additions & 2 deletions tools/helper/src/node/page/excerpt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { load } from 'cheerio'
import matter from 'gray-matter'
import type { App, Page } from 'vuepress/core'
import { isLinkHttp, removeEndingSlash } from 'vuepress/shared'
import { isAbsoluteUrl, isArray } from '../../shared/index.js'
import { isArray, isLinkAbsolute } from '../../shared/index.js'

const HEADING_TAGS = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']

Expand Down Expand Up @@ -79,7 +79,7 @@ const handleNode = (
const { src } = node.attribs

// this is not a resolvable image link
if (!isLinkHttp(src) && !isAbsoluteUrl(src)) return null
if (!isLinkHttp(src) && !isLinkAbsolute(src)) return null
}

// toc should be dropped
Expand Down
14 changes: 14 additions & 0 deletions tools/helper/src/node/utils/getInstalledStatus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { createRequire } from 'node:module'

export const getInstalledStatus = (
pkg: string,
currentUrl: string,
): boolean => {
try {
pkg && createRequire(currentUrl).resolve(pkg)

return true
} catch (error) {
return false
}
}
8 changes: 8 additions & 0 deletions tools/helper/src/node/utils/getRealPath.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { createRequire } from 'node:module'
import { path } from 'vuepress/utils'

export const getRealPath = (fileUrl: string, currentUrl: string): string => {
const require = createRequire(currentUrl)

return path.normalize(require.resolve(fileUrl))
}
2 changes: 2 additions & 0 deletions tools/helper/src/node/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
export * from './getInstalledStatus.js'
export * from './getRealPath.js'
export * from './logger.js'
export * from './packageManager.js'
34 changes: 34 additions & 0 deletions tools/helper/src/shared/deepAssign.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { entries, isArray, isPlainObject } from './helper.js'

type IAnyObject = Record<string, any>

/** Deep merge objects to the first one */
export const deepAssign = <
T extends IAnyObject,
U extends IAnyObject = T,
V extends Partial<T> & Partial<U> = T & U,
>(
originObject: T,
...overrideObjects: (U | null | undefined)[]
): V => {
if (overrideObjects.length === 0) return originObject as unknown as V

/** Object being merged */
const assignObject = overrideObjects.shift() || null

if (assignObject)
entries(assignObject).forEach(([property, value]) => {
if (property === '__proto__' || property === 'constructor') return
if (isPlainObject(originObject[property]) && isPlainObject(value))
deepAssign(originObject[property], value)
else if (isArray(value))
(originObject as IAnyObject)[property] = [...value]
else if (isPlainObject(value))
(originObject as IAnyObject)[property] = {
...value,
}
else (originObject as IAnyObject)[property] = assignObject[property]
})

return deepAssign(originObject, ...overrideObjects)
}
2 changes: 2 additions & 0 deletions tools/helper/src/shared/helper.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { isString } from 'vuepress/shared'

export { isFunction, isString, isPlainObject } from 'vuepress/shared'

/* Type helper */
export const isDef = <T = any>(val?: T | undefined): val is T =>
typeof val !== 'undefined'
Expand Down
5 changes: 3 additions & 2 deletions tools/helper/src/shared/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from 'vuepress/shared'
export * from './deepAssign.js'
export * from './date.js'
export * from './helper.js'
export * from './url.js'
export * from './locales.js'
export * from './link.js'
8 changes: 8 additions & 0 deletions tools/helper/src/shared/link.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { startsWith } from './helper.js'

export { isLinkExternal, isLinkHttp, isLinkWithProtocol } from 'vuepress/shared'

/**
* Whether a variable is a valid absolute url
*/
export const isLinkAbsolute = (test: unknown): boolean => startsWith(test, '/')
Loading

0 comments on commit f1be46f

Please sign in to comment.