Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Improve translation keys #1677

Merged
merged 6 commits into from
Dec 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/core/src/processor/colorizer/Colorizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export const ColorTokenTypes = Object.freeze(
'comment',
'enum',
'enumMember',
'escape',
'function',
'keyword',
'modifier',
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/symbol/Symbol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ export const AssetsMiscCategories = Object.freeze(
[
'texture_slot',
'shader_target',
'translation_key',
] as const,
)
export type AssetsMiscCategory = (typeof AssetsMiscCategories)[number]
Expand Down
1 change: 1 addition & 0 deletions packages/discord-bot/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ const ColorTokenTypeLegend: Record<ColorTokenType, Set<RenderFormat>> = {
enum: new Set(['foreground_white']),
enumMember: new Set(['foreground_white']),
error: new Set(['foreground_red', 'underline']),
escape: new Set(['foreground_green']),
function: new Set(['foreground_yellow']),
keyword: new Set(['foreground_pink']),
literal: new Set(['foreground_blue']),
Expand Down
49 changes: 49 additions & 0 deletions packages/java-edition/src/json/binder/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import * as core from '@spyglassmc/core'
import * as json from '@spyglassmc/json'

function bindDeprecated(node: json.JsonObjectNode, ctx: core.BinderContext) {
const renamed = node.children.find(p => p.key?.value === 'renamed')?.value
if (json.JsonObjectNode.is(renamed)) {
for (const pair of renamed.children) {
if (json.JsonStringNode.is(pair.value)) {
const range = core.Range.translate(pair.value.range, 1, -1)
ctx.symbols.query(ctx.doc, 'translation_key', pair.value.value)
.enter({
usage: { type: 'definition', range, fullRange: pair },
})
}
}
}
}

function bindLanguage(node: json.JsonObjectNode, ctx: core.BinderContext) {
const isEnglish = ctx.doc.uri.endsWith('/en_us.json')
for (const pair of node.children) {
if (pair.key) {
const desc = json.JsonStringNode.is(pair.value) ? pair.value.value : undefined
const range = core.Range.translate(pair.key.range, 1, -1)
ctx.symbols.query(ctx.doc, 'translation_key', pair.key.value)
.enter({
data: { desc: isEnglish ? desc : undefined },
usage: { type: 'definition', range, fullRange: pair },
})
}
}
}

const file: core.SyncBinder<json.JsonFileNode> = (node, ctx) => {
if (ctx.doc.uri.match(/\/lang\/[a-z_]+.json$/)) {
const child = node.children[0]
if (json.JsonObjectNode.is(child)) {
if (ctx.doc.uri.endsWith('/deprecated.json')) {
bindDeprecated(child, ctx)
} else {
bindLanguage(child, ctx)
}
}
}
}

export function register(meta: core.MetaRegistry) {
meta.registerBinder<json.JsonFileNode>('json:file', file)
}
2 changes: 2 additions & 0 deletions packages/java-edition/src/json/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
/* istanbul ignore file */

import type * as core from '@spyglassmc/core'
import * as binder from './binder/index.js'
import * as checker from './checker/index.js'
import * as completer from './completer/index.js'
import { registerMcdocAttributes } from './mcdocAttributes.js'

export const initialize = (ctx: core.ProjectInitializerContext) => {
registerMcdocAttributes(ctx.meta)

binder.register(ctx.meta)
checker.register(ctx.meta)
completer.register(ctx.meta)
}
26 changes: 25 additions & 1 deletion packages/java-edition/src/json/mcdocAttributes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as core from '@spyglassmc/core'
import * as mcdoc from '@spyglassmc/mcdoc'
import { dissectUri } from '../binder/index.js'
import type { TextureSlotKind, TextureSlotNode } from './node/index.js'
import { textureSlotParser } from './parser/index.js'
import { textureSlotParser, translationValueParser } from './parser/index.js'

const validator = mcdoc.runtime.attribute.validator

Expand All @@ -23,6 +23,13 @@ const textureSlotValidator = validator.alternatives<TextureSlotConfig>(
() => ({ kind: 'value' }),
)

const translationKeyValidator = validator.alternatives(
validator.tree({
definition: validator.boolean,
}),
() => ({ definition: false }),
)

export function registerMcdocAttributes(meta: core.MetaRegistry) {
mcdoc.runtime.registerAttribute(meta, 'criterion', criterionValidator, {
stringParser: (config, _, ctx) => {
Expand Down Expand Up @@ -62,4 +69,21 @@ export function registerMcdocAttributes(meta: core.MetaRegistry) {
} satisfies TextureSlotNode
},
})
mcdoc.runtime.registerAttribute(meta, 'translation_key', translationKeyValidator, {
stringParser: (config, _, ctx) => {
return core.symbol({
category: 'translation_key',
usageType: config.definition ? 'definition' : 'reference',
})
},
stringMocker: (config, _, ctx) => {
return core.SymbolNode.mock(ctx.offset, {
category: 'translation_key',
usageType: config.definition ? 'definition' : 'reference',
})
},
})
mcdoc.runtime.registerAttribute(meta, 'translation_value', () => undefined, {
stringParser: () => translationValueParser,
})
}
12 changes: 12 additions & 0 deletions packages/java-edition/src/json/node/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,15 @@ export namespace TextureSlotNode {
return (node as TextureSlotNode)?.type === 'java_edition:texture_slot'
}
}

export interface TranslationValueNode extends core.AstNode {
type: 'java_edition:translation_value'
children: core.LiteralNode[]
value: string
}

export namespace TranslationValueNode {
export function is(node: core.AstNode): node is TranslationValueNode {
return (node as TranslationValueNode)?.type === 'java_edition:translation_value'
}
}
53 changes: 50 additions & 3 deletions packages/java-edition/src/json/parser/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as core from '@spyglassmc/core'
import { localize } from '@spyglassmc/locales'
import type { TextureSlotKind, TextureSlotNode } from '../node/index.js'
import { localeQuote, localize } from '@spyglassmc/locales'
import type { TextureSlotKind, TextureSlotNode, TranslationValueNode } from '../node/index.js'

export function textureSlotParser(kind: TextureSlotKind): core.InfallibleParser<TextureSlotNode> {
return (src, ctx) => {
Expand All @@ -21,7 +21,7 @@ export function textureSlotParser(kind: TextureSlotKind): core.InfallibleParser<
ans.children.push(slot)
ans.slot = slot
} else if (kind === 'reference') {
ctx.err.report(localize('expected', '#'), ans)
ctx.err.report(localize('expected', localeQuote('#')), src)
} else {
const id = core.resourceLocation({ category: 'texture', usageType: 'reference' })(src, ctx)
ans.children.push(id)
Expand All @@ -31,3 +31,50 @@ export function textureSlotParser(kind: TextureSlotKind): core.InfallibleParser<
return ans
}
}

export const translationValueParser: core.InfallibleParser<TranslationValueNode> = (src, ctx) => {
const start = src.cursor
const ans: TranslationValueNode = {
type: 'java_edition:translation_value',
range: core.Range.create(start),
children: [],
value: '',
}
while (src.canRead()) {
src.skipUntilOrEnd('%')
const argStart = src.cursor
if (src.trySkip('%')) {
if (src.trySkip('%')) {
const token = src.sliceToCursor(argStart)
ans.children.push({
type: 'literal',
range: core.Range.create(argStart, src),
options: { pool: [token], colorTokenType: 'escape' },
value: token,
})
continue
}
let hasInteger = false
while (src.canRead() && core.Source.isDigit(src.peek())) {
src.skip()
hasInteger = true
}
if (hasInteger && !src.trySkip('$')) {
ctx.err.report(localize('expected', localeQuote('$')), src)
}
if (!src.trySkip('s')) {
ctx.err.report(localize('expected', localeQuote('s')), src)
}
const token = src.sliceToCursor(argStart)
ans.children.push({
type: 'literal',
range: core.Range.create(argStart, src),
options: { pool: [token] },
value: token,
})
}
}
ans.value = src.sliceToCursor(start)
ans.range = core.Range.create(start, src)
return ans
}
3 changes: 3 additions & 0 deletions packages/vscode-extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,9 @@
"error": [
"invalid.illegal"
],
"escape": [
"constant.character.escape"
],
"literal": [
"keyword.other"
],
Expand Down
Loading