Skip to content

Commit

Permalink
refactor: refactored some things
Browse files Browse the repository at this point in the history
  • Loading branch information
gizmo-ds committed Mar 27, 2024
1 parent 3c6839c commit ffe4814
Show file tree
Hide file tree
Showing 12 changed files with 173 additions and 158 deletions.
1 change: 1 addition & 0 deletions src/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const DebugMode = process.env.DEBUG_MODE === 'true'
114 changes: 48 additions & 66 deletions src/injector.ts
Original file line number Diff line number Diff line change
@@ -1,88 +1,70 @@
import { FunctionDeclaration, Identifier } from '@babel/types'
import { InjectFunction } from '@/types/injector'
import { AssignmentExpression, MemberExpression } from '@babel/types'
import { walkAST, babelParse } from 'ast-walker-scope'
import { MagicString } from 'magic-string-ast'

export interface inject_function {
name: string
type: 'jsx' | 'createElement'
}
const propertys = ['jsx', 'createElement']

export async function injector(code: string, functions: inject_function[]): Promise<string> {
export async function injector(code: string, funcs: InjectFunction[]): Promise<string> {
return new Promise<string>(async (resolve, reject) => {
const filters: string[] = [...new Set(funcs.map((f) => f.type))].filter((n) =>
propertys.includes(n)
)
const nodes: Record<string, AssignmentExpression | undefined> = propertys.reduce((acc, key) => {
acc[key] = undefined
return acc
}, {})

const ms = new MagicString(code)
const ast = babelParse(code)

let jsx_func_name: string | undefined
let createElement_func_name: string | undefined
let skip = false
const funcs: FunctionDeclaration[] = []
walkAST(ast, {
enter(node) {
if (skip) return this.skip()
if (node.type === 'FunctionDeclaration' && node.id && node.params.length >= 2) {
funcs.push(node)
return this.skip()
}
if (
node.type === 'AssignmentExpression' &&
node.operator === '=' &&
node.left.type === 'MemberExpression' &&
node.left.property.type === 'Identifier' &&
node.right.type === 'Identifier'
) {
switch (node.left.property.name) {
case 'jsxs':
jsx_func_name = node.right.name
break
case 'createElement':
createElement_func_name = node.right.name
break

if (filters.length > 0) {
if (
node.type === 'ExpressionStatement' &&
node.expression.type === 'AssignmentExpression' &&
node.expression.operator === '=' &&
node.expression.left.type === 'MemberExpression' &&
node.expression.left.property.type === 'Identifier' &&
node.expression.right.type === 'Identifier'
) {
const name = node.expression.left.property.name
if (!filters.includes(name)) return this.skip()
nodes[name] = node.expression
if (propertys.every((n) => nodes[n] !== undefined)) skip = true
return this.skip()
}
if (jsx_func_name && createElement_func_name) skip = true
return this.skip()
}
},
})

if (functions.findIndex((f) => f.type == 'jsx') > -1) {
skip = false
if (!jsx_func_name) return reject(new Error('Cannot find jsx function'))
const func = funcs.find((f) => f.id!.name === jsx_func_name)
if (!func) return reject(new Error('Cannot find jsx function'))
const params = func.params as Identifier[]
walkAST(func, {
enter(node) {
if (skip) return this.skip()
if (node.type === 'VariableDeclaration') {
for (const fn of functions)
fn.type == 'jsx' &&
ms.appendRight(node.start!, `${fn.name}(${params.map((p) => p.name).join(',')});`)
skip = true
return this.skip()
}
},
})
}
if (functions.findIndex((f) => f.type == 'createElement') > -1) {
skip = false
if (!createElement_func_name) return reject(new Error('Cannot find createElement function'))
const func = funcs.find((f) => f.id!.name === createElement_func_name)
if (!func) return reject(new Error('Cannot find createElement function'))
const params = func.params as Identifier[]
walkAST(func, {
enter(node) {
if (skip) return this.skip()
if (node.type === 'VariableDeclaration') {
for (const fn of functions)
fn.type == 'createElement' &&
ms.appendRight(node.start!, `${fn.name}(${params.map((p) => p.name).join(',')});`)
skip = true
return this.skip()
}
},
})
for (const name of filters) {
funcs
.filter((f) => f.type === name)
.forEach((f) => {
const node = nodes[name]!
const obj = (node.left as MemberExpression).object
//@ts-ignore
const n = `${obj.name}.${node.left.property.name}`
ms.appendRight(node.right.end!, `;${n}=__vcc_function_proxy__(${n},${f.name})`)
})
}

resolve(ms.toString())
})
}

export function function_proxy(ofn: Function, fn1: Function, fn2: Function) {
return new Proxy(ofn, {
apply: function (target, thisArg, argumentsList) {
fn1 && fn1.apply(thisArg, argumentsList)
const result = target.apply(thisArg, argumentsList)
if (fn2) return fn2(result)
return result
},
})
}
21 changes: 11 additions & 10 deletions src/patch-loader.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
import { inject_function, injector } from './injector'
import { get as kv_get, set as kv_set, createStore, UseStore } from 'idb-keyval'
import { get as kv_get, set as kv_set } from 'idb-keyval'
import { InjectFunction } from '@/types/injector'
import { LoadingComponent } from './components/loading'

import { injector, function_proxy } from './injector'
import algolia_patch from './patch/algolia'
import translate_patch from './patch/translate'
import console_log_patch from './patch/console_log'
import { store } from './store'

const patchs = [algolia_patch, translate_patch, console_log_patch]

async function main() {
const store = createStore('vcc_auto_translate', 'store')

const index_script_file = document.getElementsByTagName('meta')['index-module'].content
const patched_filename = index_script_file.replace(/\.js$/, '.patched.js')
const local_patched_filename = await kv_get('patched-filename', store)
let patched_code: string | undefined

globalThis['__vcc_function_proxy__'] = function_proxy

if (!local_patched_filename || local_patched_filename !== patched_filename) {
const loading = LoadingComponent({ text: '正在应用翻译补丁...' })
document.querySelector('#root')?.before(loading)
Expand All @@ -24,11 +25,12 @@ async function main() {
u.pathname = index_script_file
const code = await fetch(u.href).then((res) => res.text())

const inject_functions: inject_function[] = []
const inject_functions: InjectFunction[] = []
patchs.forEach((p) => {
if (p.patch_jsx) inject_functions.push({ name: p.patch_jsx.fname, type: 'jsx' })
if (p.patch_createElement)
inject_functions.push({ name: p.patch_createElement.fname, type: 'createElement' })
// if (p.patch_useMemo) inject_functions.push({ name: p.patch_useMemo.fname, type: 'useMemo' })
})
patched_code = await injector(code, inject_functions)

Expand All @@ -41,15 +43,14 @@ async function main() {
}

for (const p of patchs) {
p.patch_jsx?.after && (await p.patch_jsx.after())
p.patch_createElement?.after && (await p.patch_createElement.after())
Object.keys(p).forEach((key) => p[key].after && p[key].after())
p.after && (await p.after())
}

load_patched_code(store, patched_code)
load_patched_code(patched_code)
}

async function load_patched_code(store: UseStore, patched_code?: string) {
async function load_patched_code(patched_code?: string) {
if (!patched_code) patched_code = await kv_get('patched-content', store)!
const e = document.createElement('script')
e.setAttribute('type', 'module')
Expand Down
68 changes: 2 additions & 66 deletions src/patch/algolia.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { language } from '../localization'
import { Config } from './patch'
import { Config } from '@/types/patch'
import { Localization } from '@/types/patch/algolia'

const info = {
apiKey: process.env.ALGOLIA_APIKEY,
Expand All @@ -9,10 +10,6 @@ const info = {
const is_str_set = (v: string | undefined) => v && v != ''
const replace = is_str_set(info.apiKey) && is_str_set(info.appId) && is_str_set(info.indexName)

interface Localization {
translations: DocSearchTranslations
placeholder: string
}
let supported_languages: Record<string, Localization> | undefined

async function get_localization() {
Expand Down Expand Up @@ -57,64 +54,3 @@ const config: Config = {
}

export default config

interface DocSearchTranslations
extends ButtonTranslations,
SearchBoxTranslations,
FooterTranslations,
ErrorScreenTranslations,
StartScreenTranslations,
NoResultsScreenTranslations {
placeholder?: string
}

// https://github.com/algolia/docsearch/blob/2df2e1392fa80e5cc9cafac3437685331b9f07ec/packages/docsearch-react/src/DocSearchButton.tsx#L6-L9
type ButtonTranslations = Partial<{
buttonText: string
buttonAriaLabel: string
}>

// https://github.com/algolia/docsearch/blob/2df2e1392fa80e5cc9cafac3437685331b9f07ec/packages/docsearch-react/src/SearchBox.tsx#L14-L20
type SearchBoxTranslations = Partial<{
resetButtonTitle: string
resetButtonAriaLabel: string
cancelButtonText: string
cancelButtonAriaLabel: string
searchInputLabel: string
}>

// https://github.com/algolia/docsearch/blob/2df2e1392fa80e5cc9cafac3437685331b9f07ec/packages/docsearch-react/src/Footer.tsx#L5-L14
type FooterTranslations = Partial<{
selectText: string
selectKeyAriaLabel: string
navigateText: string
navigateUpKeyAriaLabel: string
navigateDownKeyAriaLabel: string
closeText: string
closeKeyAriaLabel: string
searchByText: string
}>

// https://github.com/algolia/docsearch/blob/2df2e1392fa80e5cc9cafac3437685331b9f07ec/packages/docsearch-react/src/ErrorScreen.tsx#L5-L8
type ErrorScreenTranslations = Partial<{
titleText: string
helpText: string
}>

// https://github.com/algolia/docsearch/blob/2df2e1392fa80e5cc9cafac3437685331b9f07ec/packages/docsearch-react/src/StartScreen.tsx#L8-L15
type StartScreenTranslations = Partial<{
recentSearchesTitle: string
noRecentSearchesText: string
saveRecentSearchButtonTitle: string
removeRecentSearchButtonTitle: string
favoriteSearchesTitle: string
removeFavoriteSearchButtonTitle: string
}>

// https://github.com/algolia/docsearch/blob/2df2e1392fa80e5cc9cafac3437685331b9f07ec/packages/docsearch-react/src/NoResultsScreen.tsx#L7-L12
type NoResultsScreenTranslations = Partial<{
noResultsText: string
suggestedQueryText: string
reportMissingResultsText: string
reportMissingResultsLinkText: string
}>
2 changes: 1 addition & 1 deletion src/patch/console_log.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Config } from './patch'
import { Config } from '@/types/patch'

const trash_logs = [(args: any[]) => args && args.length > 0 && args[0] === 'got backend message']

Expand Down
11 changes: 0 additions & 11 deletions src/patch/patch.d.ts

This file was deleted.

29 changes: 26 additions & 3 deletions src/patch/translate.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { createStore, delMany as kv_del } from 'idb-keyval'
import { supported_languages, language } from '../localization'
import { Config } from './patch'
import { Config } from '@/types/patch'
import { DebugMode } from '../env'

const fname = '__vcc_auto_translate__'
const store = createStore('vcc_auto_translate', 'store')
Expand All @@ -16,6 +17,25 @@ const config: Config = {
globalThis[fname]['restore'] = () => kv_del(['patched-content', 'patched-filename'], store)
},
},
async after() {
if (DebugMode) {
var originHistory = history.pushState
history.pushState = function () {
var rv = originHistory.apply(this, arguments)
var e = new Event('pushState')
//@ts-ignore
e.arguments = arguments
window.dispatchEvent(e)
return rv
}
window.addEventListener('pushState', function (e: Event) {
if ('arguments' in e) {
const args = e.arguments as IArguments
if (args.length === 3) console.log(args[2])
}
})
}
},
}

export default config
Expand Down Expand Up @@ -45,11 +65,14 @@ export class Translater {
: {}
}

translate(e: any, t: any) {
translate(e: any, t: any, r: any) {
if (!this.localization) return t
if (!e) return t
const element_name = typeof e === 'string' ? e : e.displayName

// FIXME: https://github.com/gizmo-ds/vcc-auto-translate/issues/13
if (['Official', 'Curated', 'Local User Packages'].includes(t.children)) return t

if (
// 处理 Symbol(react.fragment)
typeof e === 'symbol' &&
Expand All @@ -65,7 +88,7 @@ export class Translater {
(e) => e === element_name || `Styled(${e})` === element_name
)
) {
if (element_name && process.env.DEBUG_MODE === 'true')
if (DebugMode && element_name)
console.warn('not supported element:', `[${element_name}]`, t.children ?? t.placeholder)
return t
}
Expand Down
3 changes: 3 additions & 0 deletions src/store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { createStore } from 'idb-keyval'

export const store = createStore('vcc_auto_translate', 'store')
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@
"@/*": ["./*"]
}
},
"exclude": ["node_modules", "**/node_modules/*"]
"exclude": ["node_modules", "**/node_modules/*", "test-data", "build"]
}
4 changes: 4 additions & 0 deletions types/injector.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface InjectFunction {
name: string
type: 'jsx' | 'createElement'
}
Loading

0 comments on commit ffe4814

Please sign in to comment.