Skip to content

Commit 0b8b423

Browse files
authored
add message format debugging tools for internal (#140)
* chore: add message format debugging tools * update readme
1 parent 130bddb commit 0b8b423

File tree

15 files changed

+5344
-1
lines changed

15 files changed

+5344
-1
lines changed

format-explorer/README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# @intlify/message-format-explorer
2+
3+
intlify message format explorer
4+
5+
## :cd: Installation
6+
7+
```sh
8+
$ npm install
9+
```
10+
11+
## :rocket: Run the format explorer
12+
13+
```sh
14+
$ npm run dev
15+
```
16+
17+
## :copyright: License
18+
19+
[MIT](http://opensource.org/licenses/MIT)

format-explorer/package.json

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"name": "@intlify/message-format-explorer",
3+
"description": "intlify message format explorer",
4+
"version": "0.0.0",
5+
"private": true,
6+
"main": "index.js",
7+
"license": "MIT",
8+
"scripts": {
9+
"build": "webpack",
10+
"dev": "webpack-dev-server",
11+
"clean": "rm -rf ./dist"
12+
},
13+
"devDependencies": {
14+
"@vue/compiler-sfc": "^3.0.0",
15+
"css-loader": "^4.3.0",
16+
"file-loader": "^6.1.0",
17+
"ts-loader": "^7.0.5",
18+
"url-loader": "^4.1.0",
19+
"typescript": "^4.0.3",
20+
"vue-loader": "^16.0.0-beta.8",
21+
"webpack": "^4.44.0",
22+
"webpack-cli": "^3.3.12",
23+
"webpack-dev-server": "^3.11.0",
24+
"html-webpack-plugin": "^4.3.0",
25+
"monaco-editor-webpack-plugin": "^2.0.0",
26+
"style-loader": "^1.2.1"
27+
},
28+
"dependencies": {
29+
"vue": "^3.0.0",
30+
"vue-i18n": "link:..",
31+
"monaco-editor": "^0.21.2"
32+
}
33+
}

format-explorer/src/App.vue

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
<script lang="ts">
2+
import { defineComponent, ref } from 'vue'
3+
import Navigation from './components/Navigation.vue'
4+
import Editor from './components/Editor.vue'
5+
import { baseCompile } from 'vue-i18n'
6+
import type { CompileError, CompileOptions } from 'vue-i18n'
7+
8+
interface PersistedState {
9+
src?: string
10+
options: CompileOptions
11+
}
12+
13+
export default defineComponent({
14+
name: 'App',
15+
16+
components: {
17+
Navigation,
18+
Editor
19+
},
20+
21+
setup() {
22+
/**
23+
* states
24+
*/
25+
const genCodes = ref<string>('')
26+
const compileErrors = ref<CompileError[]>([])
27+
const persistedState: PersistedState = JSON.parse(
28+
decodeURIComponent(window.location.hash.slice(1)) ||
29+
localStorage.getItem('state') ||
30+
`{}`
31+
)
32+
const initialCodes = persistedState.src || 'hello {name}!'
33+
34+
/**
35+
* utilties
36+
*/
37+
let lastSuccessCode: string
38+
function compile(message: string): string {
39+
console.clear()
40+
41+
try {
42+
const start = performance.now()
43+
44+
const errors: CompileError[] = []
45+
const { code, ast } = baseCompile(message, {
46+
onError: err => errors.push(err)
47+
})
48+
if (errors.length > 0) {
49+
console.error(errors)
50+
}
51+
52+
console.log(`Compiled in ${(performance.now() - start).toFixed(2)}ms.`)
53+
compileErrors.value = errors
54+
console.log(`AST: `, ast)
55+
56+
const evalCode = new Function(`return ${code}`)()
57+
lastSuccessCode =
58+
evalCode.toString() + `\n\n// Check the console for the AST`
59+
} catch (e) {
60+
lastSuccessCode = `/* ERROR: ${e.message} (see console for more info) */`
61+
console.error(e)
62+
}
63+
64+
return lastSuccessCode
65+
}
66+
67+
/**
68+
* envet handlers
69+
*/
70+
const onChange = (message: string): void => {
71+
const state = JSON.stringify({ src: message } as PersistedState)
72+
localStorage.setItem('state', state)
73+
window.location.hash = encodeURIComponent(state)
74+
genCodes.value = compile(message)
75+
}
76+
77+
// setup context
78+
return {
79+
initialCodes,
80+
genCodes,
81+
compileErrors,
82+
onChange
83+
}
84+
}
85+
})
86+
</script>
87+
88+
<template>
89+
<div class="container">
90+
<div class="navigation">
91+
<Navigation class="navigation" />
92+
</div>
93+
<div class="operation">
94+
<Editor
95+
class="input"
96+
language="intlify"
97+
:code="initialCodes"
98+
:errors="compileErrors"
99+
:debounce="true"
100+
@change="onChange"
101+
/>
102+
<!-- prettier-ignore -->
103+
<Editor
104+
class="output"
105+
:code="genCodes"
106+
/>
107+
</div>
108+
</div>
109+
</template>
110+
111+
<style scoped>
112+
.container {
113+
width: 100%;
114+
height: 100%;
115+
}
116+
.navigation {
117+
width: 100%;
118+
height: 5%;
119+
box-sizing: border-box;
120+
background-color: var(--bg);
121+
border-bottom: 1px solid var(--border);
122+
}
123+
.operation {
124+
width: 100%;
125+
height: 100%;
126+
display: flex;
127+
flex-direction: row;
128+
}
129+
.input {
130+
margin-top: 0.6rem;
131+
flex: 2;
132+
width: 30%;
133+
height: 100%;
134+
}
135+
.output {
136+
margin-top: 0.6rem;
137+
flex: 3;
138+
width: 70%;
139+
height: 100%;
140+
}
141+
</style>
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
<script lang="ts">
2+
import { defineComponent, ref, onMounted, watchEffect } from 'vue'
3+
import theme from '../theme'
4+
import * as monaco from 'monaco-editor'
5+
import type { PropType } from 'vue'
6+
import type { CompileError } from 'vue-i18n'
7+
8+
/* eslint-disable */
9+
function debounce<T extends (...args: any[]) => any>(
10+
fn: T,
11+
delay: number = 300
12+
): T {
13+
let prevTimer: number | null = null
14+
return ((...args: any[]) => {
15+
if (prevTimer) {
16+
clearTimeout(prevTimer)
17+
}
18+
prevTimer = window.setTimeout(() => {
19+
fn(...args)
20+
prevTimer = null
21+
}, delay)
22+
}) as any
23+
}
24+
/* eslint-enable */
25+
26+
export type Foo = number
27+
28+
export default defineComponent({
29+
name: 'Editor',
30+
31+
props: {
32+
code: {
33+
type: String,
34+
default: () => ''
35+
},
36+
debounce: {
37+
type: Boolean,
38+
default: () => false
39+
},
40+
language: {
41+
type: String,
42+
default: () => 'javascript'
43+
},
44+
errors: {
45+
type: Array as PropType<CompileError[]>,
46+
default: () => []
47+
}
48+
},
49+
50+
emits: {
51+
change: null
52+
},
53+
54+
setup(props, { emit }) {
55+
const container = ref<HTMLElement | null>(null)
56+
57+
function formatError(err: CompileError) {
58+
const loc = err.location!
59+
return {
60+
severity: monaco.MarkerSeverity.Error,
61+
startLineNumber: loc.start.line,
62+
startColumn: loc.start.column,
63+
endLineNumber: loc.end.line,
64+
endColumn: loc.end.column,
65+
message: `intlify message compilation error: ${err.message}`,
66+
code: String(err.code)
67+
}
68+
}
69+
70+
onMounted(() => {
71+
if (container.value == null) {
72+
return
73+
}
74+
75+
monaco.editor.defineTheme('my-theme', theme)
76+
monaco.editor.setTheme('my-theme')
77+
78+
const editor = monaco.editor.create(container.value, {
79+
value: [props.code].join('\n'),
80+
wordWrap: 'bounded',
81+
language: props.language,
82+
fontSize: 14,
83+
scrollBeyondLastLine: false,
84+
renderWhitespace: 'selection',
85+
tabSize: 2,
86+
minimap: {
87+
enabled: false
88+
}
89+
})
90+
window.addEventListener('resize', () => editor.layout())
91+
92+
const changeEmitter = props.debounce
93+
? debounce(() => emit('change', editor.getValue()))
94+
: () => emit('change', editor.getValue())
95+
96+
editor.onDidChangeModelContent(changeEmitter)
97+
98+
watchEffect(() => editor.setValue(props.code!))
99+
watchEffect(() => {
100+
monaco.editor.setModelMarkers(
101+
editor.getModel()!,
102+
`vue-i18n`,
103+
props.errors.filter(e => e.location).map(formatError)
104+
)
105+
})
106+
})
107+
108+
return { container }
109+
}
110+
})
111+
</script>
112+
113+
<template>
114+
<div ref="container" />
115+
</template>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<template>
2+
<div class="navigation-root">
3+
<h2>Intlify Message Format Explorer</h2>
4+
</div>
5+
</template>
6+
7+
<style scoped>
8+
h2 {
9+
margin: 0;
10+
padding: 0.4rem 0 0.4rem 2rem;
11+
}
12+
</style>

format-explorer/src/index.css

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
body {
2+
--bg: #1D1F21;
3+
--fg: #fff;
4+
--border: #333;
5+
--in-bg: white;
6+
--in-fg: black;
7+
--in-border: #666;
8+
margin: 0;
9+
background-color: var(--bg);
10+
}
11+
12+
#app {
13+
font-family: Avenir, Helvetica, Arial, sans-serif;
14+
-webkit-font-smoothing: antialiased;
15+
-moz-osx-font-smoothing: grayscale;
16+
color: var(--fg);
17+
margin: 0;
18+
width: 100vw;
19+
height: 100vh;
20+
overflow: hidden;
21+
}

format-explorer/src/index.html

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>intlify message format explorer</title>
7+
</head>
8+
<body>
9+
<div id="app"></div>
10+
</body>
11+
</html>

format-explorer/src/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { createApp } from 'vue'
2+
import App from './App.vue'
3+
import './index.css'
4+
5+
createApp(App).mount('#app')

format-explorer/src/shim-vue.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
declare module '*.vue' {
2+
import { defineComponent } from 'vue'
3+
const component: ReturnType<typeof defineComponent>
4+
export default component
5+
}

0 commit comments

Comments
 (0)