Skip to content

Commit 746dcca

Browse files
authored
Enable localization (#2123)
1 parent 733dc6a commit 746dcca

File tree

153 files changed

+7605
-1204
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

153 files changed

+7605
-1204
lines changed

internal/ast/diagnostic.go

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,20 @@ import (
77

88
"github.com/microsoft/typescript-go/internal/core"
99
"github.com/microsoft/typescript-go/internal/diagnostics"
10+
"github.com/microsoft/typescript-go/internal/locale"
1011
)
1112

1213
// Diagnostic
1314

1415
type Diagnostic struct {
15-
file *SourceFile
16-
loc core.TextRange
17-
code int32
18-
category diagnostics.Category
19-
message string
16+
file *SourceFile
17+
loc core.TextRange
18+
code int32
19+
category diagnostics.Category
20+
// Original message; may be nil.
21+
message *diagnostics.Message
22+
messageKey diagnostics.Key
23+
messageArgs []string
2024
messageChain []*Diagnostic
2125
relatedInformation []*Diagnostic
2226
reportsUnnecessary bool
@@ -31,7 +35,8 @@ func (d *Diagnostic) Len() int { return d.loc.Len() }
3135
func (d *Diagnostic) Loc() core.TextRange { return d.loc }
3236
func (d *Diagnostic) Code() int32 { return d.code }
3337
func (d *Diagnostic) Category() diagnostics.Category { return d.category }
34-
func (d *Diagnostic) Message() string { return d.message }
38+
func (d *Diagnostic) MessageKey() diagnostics.Key { return d.messageKey }
39+
func (d *Diagnostic) MessageArgs() []string { return d.messageArgs }
3540
func (d *Diagnostic) MessageChain() []*Diagnostic { return d.messageChain }
3641
func (d *Diagnostic) RelatedInformation() []*Diagnostic { return d.relatedInformation }
3742
func (d *Diagnostic) ReportsUnnecessary() bool { return d.reportsUnnecessary }
@@ -72,12 +77,22 @@ func (d *Diagnostic) Clone() *Diagnostic {
7277
return &result
7378
}
7479

75-
func NewDiagnosticWith(
80+
func (d *Diagnostic) Localize(locale locale.Locale) string {
81+
return diagnostics.Localize(locale, d.message, d.messageKey, d.messageArgs...)
82+
}
83+
84+
// For debugging only.
85+
func (d *Diagnostic) String() string {
86+
return diagnostics.Localize(locale.Default, d.message, d.messageKey, d.messageArgs...)
87+
}
88+
89+
func NewDiagnosticFromSerialized(
7690
file *SourceFile,
7791
loc core.TextRange,
7892
code int32,
7993
category diagnostics.Category,
80-
message string,
94+
messageKey diagnostics.Key,
95+
messageArgs []string,
8196
messageChain []*Diagnostic,
8297
relatedInformation []*Diagnostic,
8398
reportsUnnecessary bool,
@@ -89,7 +104,8 @@ func NewDiagnosticWith(
89104
loc: loc,
90105
code: code,
91106
category: category,
92-
message: message,
107+
messageKey: messageKey,
108+
messageArgs: messageArgs,
93109
messageChain: messageChain,
94110
relatedInformation: relatedInformation,
95111
reportsUnnecessary: reportsUnnecessary,
@@ -104,7 +120,9 @@ func NewDiagnostic(file *SourceFile, loc core.TextRange, message *diagnostics.Me
104120
loc: loc,
105121
code: message.Code(),
106122
category: message.Category(),
107-
message: message.Format(args...),
123+
message: message,
124+
messageKey: message.Key(),
125+
messageArgs: diagnostics.StringifyArgs(args),
108126
reportsUnnecessary: message.ReportsUnnecessary(),
109127
reportsDeprecated: message.ReportsDeprecated(),
110128
}
@@ -185,13 +203,13 @@ func EqualDiagnosticsNoRelatedInfo(d1, d2 *Diagnostic) bool {
185203
return getDiagnosticPath(d1) == getDiagnosticPath(d2) &&
186204
d1.Loc() == d2.Loc() &&
187205
d1.Code() == d2.Code() &&
188-
d1.Message() == d2.Message() &&
206+
slices.Equal(d1.MessageArgs(), d2.MessageArgs()) &&
189207
slices.EqualFunc(d1.MessageChain(), d2.MessageChain(), equalMessageChain)
190208
}
191209

192210
func equalMessageChain(c1, c2 *Diagnostic) bool {
193211
return c1.Code() == c2.Code() &&
194-
c1.Message() == c2.Message() &&
212+
slices.Equal(c1.MessageArgs(), c2.MessageArgs()) &&
195213
slices.EqualFunc(c1.MessageChain(), c2.MessageChain(), equalMessageChain)
196214
}
197215

@@ -211,7 +229,7 @@ func compareMessageChainSize(c1, c2 []*Diagnostic) int {
211229

212230
func compareMessageChainContent(c1, c2 []*Diagnostic) int {
213231
for i := range c1 {
214-
c := strings.Compare(c1[i].Message(), c2[i].Message())
232+
c := slices.Compare(c1[i].MessageArgs(), c2[i].MessageArgs())
215233
if c != 0 {
216234
return c
217235
}
@@ -256,7 +274,7 @@ func CompareDiagnostics(d1, d2 *Diagnostic) int {
256274
if c != 0 {
257275
return c
258276
}
259-
c = strings.Compare(d1.Message(), d2.Message())
277+
c = slices.Compare(d1.MessageArgs(), d2.MessageArgs())
260278
if c != 0 {
261279
return c
262280
}

internal/bundled/generate.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414

1515
"github.com/microsoft/typescript-go/internal/ast"
1616
"github.com/microsoft/typescript-go/internal/core"
17+
"github.com/microsoft/typescript-go/internal/locale"
1718
"github.com/microsoft/typescript-go/internal/parser"
1819
"github.com/microsoft/typescript-go/internal/repo"
1920
"github.com/microsoft/typescript-go/internal/tspath"
@@ -150,7 +151,7 @@ func readLibs() []lib {
150151

151152
if len(diags) > 0 {
152153
for _, diag := range diags {
153-
log.Printf("%s", diag.Message())
154+
log.Printf("%s", diag.Localize(locale.Default))
154155
}
155156
log.Fatalf("failed to parse libs.json")
156157
}

internal/checker/jsx.go

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ func (c *Checker) elaborateJsxComponents(node *ast.Node, source *Type, target *T
297297
if !ast.IsJsxSpreadAttribute(prop) && !isHyphenatedJsxName(prop.Name().Text()) {
298298
nameType := c.getStringLiteralType(prop.Name().Text())
299299
if nameType != nil && nameType.flags&TypeFlagsNever == 0 {
300-
reportedError = c.elaborateElement(source, target, relation, prop.Name(), prop.Initializer(), nameType, nil, diagnosticOutput) || reportedError
300+
reportedError = c.elaborateElement(source, target, relation, prop.Name(), prop.Initializer(), nameType, nil, nil, diagnosticOutput) || reportedError
301301
}
302302
}
303303
}
@@ -326,13 +326,14 @@ func (c *Checker) elaborateJsxComponents(node *ast.Node, source *Type, target *T
326326
nonArrayLikeTargetParts = c.filterType(childrenTargetType, func(t *Type) bool { return !c.isArrayOrTupleLikeType(t) })
327327
}
328328
var invalidTextDiagnostic *diagnostics.Message
329-
getInvalidTextualChildDiagnostic := func() *diagnostics.Message {
329+
var invalidTextDiagnosticArgs []any
330+
getInvalidTextualChildDiagnostic := func() (*diagnostics.Message, []any) {
330331
if invalidTextDiagnostic == nil {
331332
tagNameText := scanner.GetTextOfNode(node.Parent.TagName())
332-
diagnostic := diagnostics.X_0_components_don_t_accept_text_as_child_elements_Text_in_JSX_has_the_type_string_but_the_expected_type_of_1_is_2
333-
invalidTextDiagnostic = diagnostics.FormatMessage(diagnostic, tagNameText, childrenPropName, c.TypeToString(childrenTargetType))
333+
invalidTextDiagnostic = diagnostics.X_0_components_don_t_accept_text_as_child_elements_Text_in_JSX_has_the_type_string_but_the_expected_type_of_1_is_2
334+
invalidTextDiagnosticArgs = []any{tagNameText, childrenPropName, c.TypeToString(childrenTargetType)}
334335
}
335-
return invalidTextDiagnostic
336+
return invalidTextDiagnostic, invalidTextDiagnosticArgs
336337
}
337338
if moreThanOneRealChildren {
338339
if arrayLikeTargetParts != c.neverType {
@@ -350,7 +351,7 @@ func (c *Checker) elaborateJsxComponents(node *ast.Node, source *Type, target *T
350351
child := validChildren[0]
351352
e := c.getElaborationElementForJsxChild(child, childrenNameType, getInvalidTextualChildDiagnostic)
352353
if e.errorNode != nil {
353-
reportedError = c.elaborateElement(source, target, relation, e.errorNode, e.innerExpression, e.nameType, e.errorMessage, diagnosticOutput) || reportedError
354+
reportedError = c.elaborateElement(source, target, relation, e.errorNode, e.innerExpression, e.nameType, nil, e.createDiagnostic, diagnosticOutput) || reportedError
354355
}
355356
} else if !c.isTypeRelatedTo(c.getIndexedAccessType(source, childrenNameType), childrenTargetType, relation) {
356357
// arity mismatch
@@ -364,13 +365,13 @@ func (c *Checker) elaborateJsxComponents(node *ast.Node, source *Type, target *T
364365
}
365366

366367
type JsxElaborationElement struct {
367-
errorNode *ast.Node
368-
innerExpression *ast.Node
369-
nameType *Type
370-
errorMessage *diagnostics.Message
368+
errorNode *ast.Node
369+
innerExpression *ast.Node
370+
nameType *Type
371+
createDiagnostic func(prop *ast.Node) *ast.Diagnostic // Optional: creates a custom diagnostic for this element
371372
}
372373

373-
func (c *Checker) generateJsxChildren(node *ast.Node, getInvalidTextDiagnostic func() *diagnostics.Message) iter.Seq[JsxElaborationElement] {
374+
func (c *Checker) generateJsxChildren(node *ast.Node, getInvalidTextDiagnostic func() (*diagnostics.Message, []any)) iter.Seq[JsxElaborationElement] {
374375
return func(yield func(JsxElaborationElement) bool) {
375376
memberOffset := 0
376377
for i, child := range node.Children().Nodes {
@@ -387,7 +388,7 @@ func (c *Checker) generateJsxChildren(node *ast.Node, getInvalidTextDiagnostic f
387388
}
388389
}
389390

390-
func (c *Checker) getElaborationElementForJsxChild(child *ast.Node, nameType *Type, getInvalidTextDiagnostic func() *diagnostics.Message) JsxElaborationElement {
391+
func (c *Checker) getElaborationElementForJsxChild(child *ast.Node, nameType *Type, getInvalidTextDiagnostic func() (*diagnostics.Message, []any)) JsxElaborationElement {
391392
switch child.Kind {
392393
case ast.KindJsxExpression:
393394
// child is of the type of the expression
@@ -398,7 +399,15 @@ func (c *Checker) getElaborationElementForJsxChild(child *ast.Node, nameType *Ty
398399
return JsxElaborationElement{}
399400
}
400401
// child is a string
401-
return JsxElaborationElement{errorNode: child, innerExpression: nil, nameType: nameType, errorMessage: getInvalidTextDiagnostic()}
402+
return JsxElaborationElement{
403+
errorNode: child,
404+
innerExpression: nil,
405+
nameType: nameType,
406+
createDiagnostic: func(prop *ast.Node) *ast.Diagnostic {
407+
errorMessage, errorArgs := getInvalidTextDiagnostic()
408+
return NewDiagnosticForNode(prop, errorMessage, errorArgs...)
409+
},
410+
}
402411
case ast.KindJsxElement, ast.KindJsxSelfClosingElement, ast.KindJsxFragment:
403412
// child is of type JSX.Element
404413
return JsxElaborationElement{errorNode: child, innerExpression: child, nameType: nameType}
@@ -448,18 +457,21 @@ func (c *Checker) elaborateIterableOrArrayLikeTargetElementwise(iterator iter.Se
448457
if next != nil {
449458
specificSource = c.checkExpressionForMutableLocationWithContextualType(next, sourcePropType)
450459
}
451-
if c.exactOptionalPropertyTypes && c.isExactOptionalPropertyMismatch(specificSource, targetPropType) {
460+
if e.createDiagnostic != nil {
461+
// Use the custom diagnostic factory if provided (e.g., for JSX text children with dynamic error messages)
462+
c.reportDiagnostic(e.createDiagnostic(prop), diagnosticOutput)
463+
} else if c.exactOptionalPropertyTypes && c.isExactOptionalPropertyMismatch(specificSource, targetPropType) {
452464
diag := createDiagnosticForNode(prop, diagnostics.Type_0_is_not_assignable_to_type_1_with_exactOptionalPropertyTypes_Colon_true_Consider_adding_undefined_to_the_type_of_the_target, c.TypeToString(specificSource), c.TypeToString(targetPropType))
453465
c.reportDiagnostic(diag, diagnosticOutput)
454466
} else {
455467
targetIsOptional := propName != ast.InternalSymbolNameMissing && core.OrElse(c.getPropertyOfType(tupleOrArrayLikeTargetParts, propName), c.unknownSymbol).Flags&ast.SymbolFlagsOptional != 0
456468
sourceIsOptional := propName != ast.InternalSymbolNameMissing && core.OrElse(c.getPropertyOfType(source, propName), c.unknownSymbol).Flags&ast.SymbolFlagsOptional != 0
457469
targetPropType = c.removeMissingType(targetPropType, targetIsOptional)
458470
sourcePropType = c.removeMissingType(sourcePropType, targetIsOptional && sourceIsOptional)
459-
result := c.checkTypeRelatedToEx(specificSource, targetPropType, relation, prop, e.errorMessage, diagnosticOutput)
471+
result := c.checkTypeRelatedToEx(specificSource, targetPropType, relation, prop, nil, diagnosticOutput)
460472
if result && specificSource != sourcePropType {
461473
// If for whatever reason the expression type doesn't yield an error, make sure we still issue an error on the sourcePropType
462-
c.checkTypeRelatedToEx(sourcePropType, targetPropType, relation, prop, e.errorMessage, diagnosticOutput)
474+
c.checkTypeRelatedToEx(sourcePropType, targetPropType, relation, prop, nil, diagnosticOutput)
463475
}
464476
}
465477
}

internal/checker/relater.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -503,10 +503,10 @@ func (c *Checker) elaborateObjectLiteral(node *ast.Node, source *Type, target *T
503503
}
504504
switch prop.Kind {
505505
case ast.KindSetAccessor, ast.KindGetAccessor, ast.KindMethodDeclaration, ast.KindShorthandPropertyAssignment:
506-
reportedError = c.elaborateElement(source, target, relation, prop.Name(), nil, nameType, nil, diagnosticOutput) || reportedError
506+
reportedError = c.elaborateElement(source, target, relation, prop.Name(), nil, nameType, nil, nil, diagnosticOutput) || reportedError
507507
case ast.KindPropertyAssignment:
508508
message := core.IfElse(ast.IsComputedNonLiteralName(prop.Name()), diagnostics.Type_of_computed_property_s_value_is_0_which_is_not_assignable_to_type_1, nil)
509-
reportedError = c.elaborateElement(source, target, relation, prop.Name(), prop.Initializer(), nameType, message, diagnosticOutput) || reportedError
509+
reportedError = c.elaborateElement(source, target, relation, prop.Name(), prop.Initializer(), nameType, message, nil, diagnosticOutput) || reportedError
510510
}
511511
}
512512
return reportedError
@@ -531,12 +531,12 @@ func (c *Checker) elaborateArrayLiteral(node *ast.Node, source *Type, target *Ty
531531
}
532532
nameType := c.getNumberLiteralType(jsnum.Number(i))
533533
checkNode := c.getEffectiveCheckNode(element)
534-
reportedError = c.elaborateElement(source, target, relation, checkNode, checkNode, nameType, nil, diagnosticOutput) || reportedError
534+
reportedError = c.elaborateElement(source, target, relation, checkNode, checkNode, nameType, nil, nil, diagnosticOutput) || reportedError
535535
}
536536
return reportedError
537537
}
538538

539-
func (c *Checker) elaborateElement(source *Type, target *Type, relation *Relation, prop *ast.Node, next *ast.Node, nameType *Type, errorMessage *diagnostics.Message, diagnosticOutput *[]*ast.Diagnostic) bool {
539+
func (c *Checker) elaborateElement(source *Type, target *Type, relation *Relation, prop *ast.Node, next *ast.Node, nameType *Type, errorMessage *diagnostics.Message, diagnosticFactory func(prop *ast.Node) *ast.Diagnostic, diagnosticOutput *[]*ast.Diagnostic) bool {
540540
targetPropType := c.getBestMatchIndexedAccessTypeOrUndefined(source, target, nameType)
541541
if targetPropType == nil || targetPropType.flags&TypeFlagsIndexedAccess != 0 {
542542
// Don't elaborate on indexes on generic variables
@@ -557,7 +557,10 @@ func (c *Checker) elaborateElement(source *Type, target *Type, relation *Relatio
557557
if next != nil {
558558
specificSource = c.checkExpressionForMutableLocationWithContextualType(next, sourcePropType)
559559
}
560-
if c.exactOptionalPropertyTypes && c.isExactOptionalPropertyMismatch(specificSource, targetPropType) {
560+
if diagnosticFactory != nil {
561+
// Use the custom diagnostic factory if provided (e.g., for JSX text children with dynamic error messages)
562+
diags = append(diags, diagnosticFactory(prop))
563+
} else if c.exactOptionalPropertyTypes && c.isExactOptionalPropertyMismatch(specificSource, targetPropType) {
561564
diags = append(diags, createDiagnosticForNode(prop, diagnostics.Type_0_is_not_assignable_to_type_1_with_exactOptionalPropertyTypes_Colon_true_Consider_adding_undefined_to_the_type_of_the_target, c.TypeToString(specificSource), c.TypeToString(targetPropType)))
562565
} else {
563566
propName := c.getPropertyNameFromIndex(nameType, nil /*accessNode*/)

internal/compiler/fileloader.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import (
1818
type libResolution struct {
1919
libraryName string
2020
resolution *module.ResolvedModule
21-
trace []string
21+
trace []module.DiagAndArgs
2222
}
2323

2424
type LibFile struct {
@@ -231,7 +231,7 @@ func processAllProgramFiles(
231231
module.ModeAwareCacheKey{Name: value.libraryName, Mode: core.ModuleKindCommonJS}: value.resolution,
232232
}
233233
for _, trace := range value.trace {
234-
opts.Host.Trace(trace)
234+
opts.Host.Trace(trace.Message, trace.Args...)
235235
}
236236
}
237237

@@ -286,7 +286,7 @@ func (p *fileLoader) addAutomaticTypeDirectiveTasks() {
286286
func (p *fileLoader) resolveAutomaticTypeDirectives(containingFileName string) (
287287
toParse []resolvedRef,
288288
typeResolutionsInFile module.ModeAwareCache[*module.ResolvedTypeReferenceDirective],
289-
typeResolutionsTrace []string,
289+
typeResolutionsTrace []module.DiagAndArgs,
290290
) {
291291
automaticTypeDirectiveNames := module.GetAutomaticTypeDirectiveNames(p.opts.Config.CompilerOptions(), p.opts.Host)
292292
if len(automaticTypeDirectiveNames) != 0 {
@@ -449,7 +449,7 @@ func (p *fileLoader) resolveTypeReferenceDirectives(t *parseTask) {
449449
meta := t.metadata
450450

451451
typeResolutionsInFile := make(module.ModeAwareCache[*module.ResolvedTypeReferenceDirective], len(file.TypeReferenceDirectives))
452-
var typeResolutionsTrace []string
452+
var typeResolutionsTrace []module.DiagAndArgs
453453
for index, ref := range file.TypeReferenceDirectives {
454454
redirect, fileName := p.projectReferenceFileMapper.getRedirectForResolution(file)
455455
resolutionMode := getModeForTypeReferenceDirectiveInFile(ref, file, meta, module.GetCompilerOptionsWithRedirect(p.opts.Config.CompilerOptions(), redirect))
@@ -527,7 +527,7 @@ func (p *fileLoader) resolveImportsAndModuleAugmentations(t *parseTask) {
527527

528528
if len(moduleNames) != 0 {
529529
resolutionsInFile := make(module.ModeAwareCache[*module.ResolvedModule], len(moduleNames))
530-
var resolutionsTrace []string
530+
var resolutionsTrace []module.DiagAndArgs
531531

532532
for index, entry := range moduleNames {
533533
moduleName := entry.Text()

internal/compiler/filesparser.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ type parseTask struct {
2525

2626
metadata ast.SourceFileMetaData
2727
resolutionsInFile module.ModeAwareCache[*module.ResolvedModule]
28-
resolutionsTrace []string
28+
resolutionsTrace []module.DiagAndArgs
2929
typeResolutionsInFile module.ModeAwareCache[*module.ResolvedTypeReferenceDirective]
30-
typeResolutionsTrace []string
30+
typeResolutionsTrace []module.DiagAndArgs
3131
resolutionDiagnostics []*ast.Diagnostic
3232
importHelpersImportSpecifier *ast.Node
3333
jsxRuntimeImportSpecifier *jsxRuntimeImportSpecifier
@@ -255,10 +255,10 @@ func (w *filesParser) collectWorker(loader *fileLoader, tasks []*parseTask, iter
255255
continue
256256
}
257257
for _, trace := range task.typeResolutionsTrace {
258-
loader.opts.Host.Trace(trace)
258+
loader.opts.Host.Trace(trace.Message, trace.Args...)
259259
}
260260
for _, trace := range task.resolutionsTrace {
261-
loader.opts.Host.Trace(trace)
261+
loader.opts.Host.Trace(trace.Message, trace.Args...)
262262
}
263263
if subTasks := task.subTasks; len(subTasks) > 0 {
264264
w.collectWorker(loader, subTasks, iterate, seen)

0 commit comments

Comments
 (0)