Skip to content

Conversation

swissspidy
Copy link
Member

@swissspidy swissspidy commented Jun 15, 2025

What?

Closes #52985

Replaces the sprintf-js library with the leaner @tannin/sprintf library which has much stronger types. We already use the main tannin library for i18n anyway, so this is a great companion.

Why?

How?

Testing Instructions

Tests should all pass, TypeScript check should pass too.

Testing Instructions for Keyboard

Screenshots or screencast

Before After

@USERSATOSHI
Copy link
Contributor

USERSATOSHI commented Jun 16, 2025

Continuing on what we discussed what I meant was that TranslatableText is return type for all translate function and we use the Extract and sprintf definitions in the sprintf.ts file

aka the whole i18n pkg needs to be written in TypeScript where each translate fn gets there type updated to use TranslatableText in return type and we update sprintf to allow working with it.

so for example the __ type can be updated to

export type __ = < T extends string >(
	text: T,
	domain?: string
) => TranslatableText< T >;

also after trying somewhat more We can remove ExtractT

and only do

/**
 * Returns a formatted string. If an error occurs in applying the format, the
 * original format string is returned.
 *
 * @template {string} T
 * @param {T | TranslatableText<T>} format The format of the string to generate.
 * @param {SprintfArgs<T>}          args   Arguments to apply to the format.
 *
 * @see https://www.npmjs.com/package/sprintf-js
 *
 * @return {string} The formatted string.
 */
export function sprintf< T extends string >(
	format: T | TranslatableText< T >,
	...args: SprintfArgs< T >
): string;
export function sprintf< T extends string >(
	format: T | TranslatableText< T >,
	args: SprintfArgs< T >
): string;
export function sprintf< T extends string >(
	format: T | TranslatableText< T >,
	...args: SprintfArgs< T >
): string {
	try {
		return sprintfjs( format as T, ...( args as SprintfArgs< T > ) );
	} catch ( error ) {
		if ( error instanceof Error ) {
			logErrorOnce( 'sprintf error: \n\n' + error.toString() );
		}
		return format;
	}
}

and it works for acceptable cases ( except the one with wrong type in arg )

cc: @swissspidy

Comment on lines -30 to -35
} catch ( error ) {
if ( error instanceof Error ) {
logErrorOnce( 'sprintf error: \n\n' + error.toString() );
}
return format;
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tannin/sprintf just silently returns the original string without throwing any errors.

@swissspidy
Copy link
Member Author

aka the whole i18n pkg needs to be written in TypeScript where each translate fn gets there type updated to use TranslatableText in return type and we update sprintf to allow working with it.

I think I made it work with just JSDoc alone. Though it would be nice to rewrite in TS eventually.

Copy link

github-actions bot commented Jun 16, 2025

Size Change: -1.33 kB (-0.07%)

Total Size: 1.87 MB

Filename Size Change
build/components/index.min.js 230 kB +7 B (0%)
build/edit-site/index.min.js 227 kB +7 B (0%)
build/editor/index.min.js 119 kB +9 B (+0.01%)
build/i18n/index.min.js 2.23 kB -1.35 kB (-37.72%) 🎉
ℹ️ View Unchanged
Filename Size
build-module/a11y/index.min.js 482 B
build-module/block-library/file/view.min.js 447 B
build-module/block-library/form/view.min.js 533 B
build-module/block-library/image/view.min.js 1.78 kB
build-module/block-library/navigation/view.min.js 1.19 kB
build-module/block-library/query/view.min.js 767 B
build-module/block-library/search/view.min.js 639 B
build-module/interactivity-router/full-page.min.js 565 B
build-module/interactivity-router/index.min.js 11.4 kB
build-module/interactivity/debug.min.js 17.5 kB
build-module/interactivity/index.min.js 13.9 kB
build/a11y/index.min.js 952 B
build/annotations/index.min.js 2.26 kB
build/api-fetch/index.min.js 2.4 kB
build/autop/index.min.js 2.12 kB
build/blob/index.min.js 579 B
build/block-directory/index.min.js 7.18 kB
build/block-directory/style-rtl.css 1.03 kB
build/block-directory/style.css 1.03 kB
build/block-editor/content-rtl.css 4.43 kB
build/block-editor/content.css 4.42 kB
build/block-editor/default-editor-styles-rtl.css 392 B
build/block-editor/default-editor-styles.css 392 B
build/block-editor/index.min.js 264 kB
build/block-editor/style-rtl.css 15.9 kB
build/block-editor/style.css 15.9 kB
build/block-library/blocks/archives/editor-rtl.css 84 B
build/block-library/blocks/archives/editor.css 83 B
build/block-library/blocks/archives/style-rtl.css 90 B
build/block-library/blocks/archives/style.css 90 B
build/block-library/blocks/audio/editor-rtl.css 149 B
build/block-library/blocks/audio/editor.css 151 B
build/block-library/blocks/audio/style-rtl.css 132 B
build/block-library/blocks/audio/style.css 132 B
build/block-library/blocks/audio/theme-rtl.css 134 B
build/block-library/blocks/audio/theme.css 134 B
build/block-library/blocks/avatar/editor-rtl.css 115 B
build/block-library/blocks/avatar/editor.css 115 B
build/block-library/blocks/avatar/style-rtl.css 104 B
build/block-library/blocks/avatar/style.css 104 B
build/block-library/blocks/button/editor-rtl.css 265 B
build/block-library/blocks/button/editor.css 265 B
build/block-library/blocks/button/style-rtl.css 554 B
build/block-library/blocks/button/style.css 554 B
build/block-library/blocks/buttons/editor-rtl.css 291 B
build/block-library/blocks/buttons/editor.css 291 B
build/block-library/blocks/buttons/style-rtl.css 349 B
build/block-library/blocks/buttons/style.css 349 B
build/block-library/blocks/calendar/style-rtl.css 239 B
build/block-library/blocks/calendar/style.css 239 B
build/block-library/blocks/categories/editor-rtl.css 132 B
build/block-library/blocks/categories/editor.css 131 B
build/block-library/blocks/categories/style-rtl.css 152 B
build/block-library/blocks/categories/style.css 152 B
build/block-library/blocks/code/editor-rtl.css 53 B
build/block-library/blocks/code/editor.css 53 B
build/block-library/blocks/code/style-rtl.css 139 B
build/block-library/blocks/code/style.css 139 B
build/block-library/blocks/code/theme-rtl.css 122 B
build/block-library/blocks/code/theme.css 122 B
build/block-library/blocks/columns/editor-rtl.css 108 B
build/block-library/blocks/columns/editor.css 108 B
build/block-library/blocks/columns/style-rtl.css 420 B
build/block-library/blocks/columns/style.css 420 B
build/block-library/blocks/comment-author-avatar/editor-rtl.css 124 B
build/block-library/blocks/comment-author-avatar/editor.css 124 B
build/block-library/blocks/comment-author-name/style-rtl.css 72 B
build/block-library/blocks/comment-author-name/style.css 72 B
build/block-library/blocks/comment-content/style-rtl.css 120 B
build/block-library/blocks/comment-content/style.css 120 B
build/block-library/blocks/comment-date/style-rtl.css 65 B
build/block-library/blocks/comment-date/style.css 65 B
build/block-library/blocks/comment-edit-link/style-rtl.css 70 B
build/block-library/blocks/comment-edit-link/style.css 70 B
build/block-library/blocks/comment-reply-link/style-rtl.css 71 B
build/block-library/blocks/comment-reply-link/style.css 71 B
build/block-library/blocks/comment-template/style-rtl.css 191 B
build/block-library/blocks/comment-template/style.css 191 B
build/block-library/blocks/comments-pagination-numbers/editor-rtl.css 122 B
build/block-library/blocks/comments-pagination-numbers/editor.css 121 B
build/block-library/blocks/comments-pagination/editor-rtl.css 168 B
build/block-library/blocks/comments-pagination/editor.css 168 B
build/block-library/blocks/comments-pagination/style-rtl.css 201 B
build/block-library/blocks/comments-pagination/style.css 201 B
build/block-library/blocks/comments-title/editor-rtl.css 75 B
build/block-library/blocks/comments-title/editor.css 75 B
build/block-library/blocks/comments/editor-rtl.css 842 B
build/block-library/blocks/comments/editor.css 842 B
build/block-library/blocks/comments/style-rtl.css 637 B
build/block-library/blocks/comments/style.css 637 B
build/block-library/blocks/cover/editor-rtl.css 631 B
build/block-library/blocks/cover/editor.css 631 B
build/block-library/blocks/cover/style-rtl.css 1.7 kB
build/block-library/blocks/cover/style.css 1.69 kB
build/block-library/blocks/details/editor-rtl.css 65 B
build/block-library/blocks/details/editor.css 65 B
build/block-library/blocks/details/style-rtl.css 86 B
build/block-library/blocks/details/style.css 86 B
build/block-library/blocks/embed/editor-rtl.css 331 B
build/block-library/blocks/embed/editor.css 331 B
build/block-library/blocks/embed/style-rtl.css 419 B
build/block-library/blocks/embed/style.css 419 B
build/block-library/blocks/embed/theme-rtl.css 133 B
build/block-library/blocks/embed/theme.css 133 B
build/block-library/blocks/file/editor-rtl.css 326 B
build/block-library/blocks/file/editor.css 326 B
build/block-library/blocks/file/style-rtl.css 278 B
build/block-library/blocks/file/style.css 279 B
build/block-library/blocks/footnotes/style-rtl.css 198 B
build/block-library/blocks/footnotes/style.css 197 B
build/block-library/blocks/form-input/editor-rtl.css 229 B
build/block-library/blocks/form-input/editor.css 229 B
build/block-library/blocks/form-input/style-rtl.css 349 B
build/block-library/blocks/form-input/style.css 349 B
build/block-library/blocks/form-submission-notification/editor-rtl.css 344 B
build/block-library/blocks/form-submission-notification/editor.css 341 B
build/block-library/blocks/form-submit-button/style-rtl.css 69 B
build/block-library/blocks/form-submit-button/style.css 69 B
build/block-library/blocks/freeform/editor-rtl.css 2.59 kB
build/block-library/blocks/freeform/editor.css 2.59 kB
build/block-library/blocks/gallery/editor-rtl.css 615 B
build/block-library/blocks/gallery/editor.css 616 B
build/block-library/blocks/gallery/style-rtl.css 1.84 kB
build/block-library/blocks/gallery/style.css 1.84 kB
build/block-library/blocks/gallery/theme-rtl.css 108 B
build/block-library/blocks/gallery/theme.css 108 B
build/block-library/blocks/group/editor-rtl.css 334 B
build/block-library/blocks/group/editor.css 334 B
build/block-library/blocks/group/style-rtl.css 103 B
build/block-library/blocks/group/style.css 103 B
build/block-library/blocks/group/theme-rtl.css 79 B
build/block-library/blocks/group/theme.css 79 B
build/block-library/blocks/heading/style-rtl.css 188 B
build/block-library/blocks/heading/style.css 188 B
build/block-library/blocks/html/editor-rtl.css 353 B
build/block-library/blocks/html/editor.css 354 B
build/block-library/blocks/image/editor-rtl.css 763 B
build/block-library/blocks/image/editor.css 763 B
build/block-library/blocks/image/style-rtl.css 1.6 kB
build/block-library/blocks/image/style.css 1.59 kB
build/block-library/blocks/image/theme-rtl.css 137 B
build/block-library/blocks/image/theme.css 137 B
build/block-library/blocks/latest-comments/style-rtl.css 355 B
build/block-library/blocks/latest-comments/style.css 354 B
build/block-library/blocks/latest-posts/editor-rtl.css 139 B
build/block-library/blocks/latest-posts/editor.css 138 B
build/block-library/blocks/latest-posts/style-rtl.css 520 B
build/block-library/blocks/latest-posts/style.css 520 B
build/block-library/blocks/list/style-rtl.css 107 B
build/block-library/blocks/list/style.css 107 B
build/block-library/blocks/loginout/style-rtl.css 61 B
build/block-library/blocks/loginout/style.css 61 B
build/block-library/blocks/media-text/editor-rtl.css 321 B
build/block-library/blocks/media-text/editor.css 320 B
build/block-library/blocks/media-text/style-rtl.css 543 B
build/block-library/blocks/media-text/style.css 542 B
build/block-library/blocks/more/editor-rtl.css 427 B
build/block-library/blocks/more/editor.css 427 B
build/block-library/blocks/navigation-link/editor-rtl.css 566 B
build/block-library/blocks/navigation-link/editor.css 568 B
build/block-library/blocks/navigation-link/style-rtl.css 192 B
build/block-library/blocks/navigation-link/style.css 191 B
build/block-library/blocks/navigation-submenu/editor-rtl.css 295 B
build/block-library/blocks/navigation-submenu/editor.css 294 B
build/block-library/blocks/navigation/editor-rtl.css 2.23 kB
build/block-library/blocks/navigation/editor.css 2.24 kB
build/block-library/blocks/navigation/style-rtl.css 2.26 kB
build/block-library/blocks/navigation/style.css 2.25 kB
build/block-library/blocks/nextpage/editor-rtl.css 392 B
build/block-library/blocks/nextpage/editor.css 392 B
build/block-library/blocks/page-list/editor-rtl.css 378 B
build/block-library/blocks/page-list/editor.css 378 B
build/block-library/blocks/page-list/style-rtl.css 192 B
build/block-library/blocks/page-list/style.css 192 B
build/block-library/blocks/paragraph/editor-rtl.css 251 B
build/block-library/blocks/paragraph/editor.css 251 B
build/block-library/blocks/paragraph/style-rtl.css 341 B
build/block-library/blocks/paragraph/style.css 340 B
build/block-library/blocks/post-author-biography/style-rtl.css 74 B
build/block-library/blocks/post-author-biography/style.css 74 B
build/block-library/blocks/post-author-name/style-rtl.css 69 B
build/block-library/blocks/post-author-name/style.css 69 B
build/block-library/blocks/post-author/style-rtl.css 188 B
build/block-library/blocks/post-author/style.css 189 B
build/block-library/blocks/post-comments-count/style-rtl.css 72 B
build/block-library/blocks/post-comments-count/style.css 72 B
build/block-library/blocks/post-comments-form/editor-rtl.css 96 B
build/block-library/blocks/post-comments-form/editor.css 96 B
build/block-library/blocks/post-comments-form/style-rtl.css 527 B
build/block-library/blocks/post-comments-form/style.css 528 B
build/block-library/blocks/post-comments-link/style-rtl.css 71 B
build/block-library/blocks/post-comments-link/style.css 71 B
build/block-library/blocks/post-content/style-rtl.css 61 B
build/block-library/blocks/post-content/style.css 61 B
build/block-library/blocks/post-date/style-rtl.css 62 B
build/block-library/blocks/post-date/style.css 62 B
build/block-library/blocks/post-excerpt/editor-rtl.css 71 B
build/block-library/blocks/post-excerpt/editor.css 71 B
build/block-library/blocks/post-excerpt/style-rtl.css 155 B
build/block-library/blocks/post-excerpt/style.css 155 B
build/block-library/blocks/post-featured-image/editor-rtl.css 722 B
build/block-library/blocks/post-featured-image/editor.css 720 B
build/block-library/blocks/post-featured-image/style-rtl.css 347 B
build/block-library/blocks/post-featured-image/style.css 347 B
build/block-library/blocks/post-navigation-link/style-rtl.css 215 B
build/block-library/blocks/post-navigation-link/style.css 214 B
build/block-library/blocks/post-template/style-rtl.css 414 B
build/block-library/blocks/post-template/style.css 414 B
build/block-library/blocks/post-terms/style-rtl.css 96 B
build/block-library/blocks/post-terms/style.css 96 B
build/block-library/blocks/post-time-to-read/style-rtl.css 70 B
build/block-library/blocks/post-time-to-read/style.css 70 B
build/block-library/blocks/post-title/style-rtl.css 162 B
build/block-library/blocks/post-title/style.css 162 B
build/block-library/blocks/preformatted/style-rtl.css 125 B
build/block-library/blocks/preformatted/style.css 125 B
build/block-library/blocks/pullquote/editor-rtl.css 133 B
build/block-library/blocks/pullquote/editor.css 133 B
build/block-library/blocks/pullquote/style-rtl.css 365 B
build/block-library/blocks/pullquote/style.css 365 B
build/block-library/blocks/pullquote/theme-rtl.css 176 B
build/block-library/blocks/pullquote/theme.css 176 B
build/block-library/blocks/query-pagination-numbers/editor-rtl.css 121 B
build/block-library/blocks/query-pagination-numbers/editor.css 118 B
build/block-library/blocks/query-pagination/editor-rtl.css 154 B
build/block-library/blocks/query-pagination/editor.css 154 B
build/block-library/blocks/query-pagination/style-rtl.css 237 B
build/block-library/blocks/query-pagination/style.css 237 B
build/block-library/blocks/query-title/style-rtl.css 64 B
build/block-library/blocks/query-title/style.css 64 B
build/block-library/blocks/query-total/style-rtl.css 64 B
build/block-library/blocks/query-total/style.css 64 B
build/block-library/blocks/query/editor-rtl.css 404 B
build/block-library/blocks/query/editor.css 404 B
build/block-library/blocks/quote/style-rtl.css 238 B
build/block-library/blocks/quote/style.css 238 B
build/block-library/blocks/quote/theme-rtl.css 233 B
build/block-library/blocks/quote/theme.css 236 B
build/block-library/blocks/read-more/style-rtl.css 131 B
build/block-library/blocks/read-more/style.css 131 B
build/block-library/blocks/rss/editor-rtl.css 126 B
build/block-library/blocks/rss/editor.css 126 B
build/block-library/blocks/rss/style-rtl.css 284 B
build/block-library/blocks/rss/style.css 283 B
build/block-library/blocks/search/editor-rtl.css 199 B
build/block-library/blocks/search/editor.css 199 B
build/block-library/blocks/search/style-rtl.css 674 B
build/block-library/blocks/search/style.css 671 B
build/block-library/blocks/search/theme-rtl.css 113 B
build/block-library/blocks/search/theme.css 113 B
build/block-library/blocks/separator/editor-rtl.css 100 B
build/block-library/blocks/separator/editor.css 100 B
build/block-library/blocks/separator/style-rtl.css 248 B
build/block-library/blocks/separator/style.css 248 B
build/block-library/blocks/separator/theme-rtl.css 195 B
build/block-library/blocks/separator/theme.css 195 B
build/block-library/blocks/shortcode/editor-rtl.css 286 B
build/block-library/blocks/shortcode/editor.css 286 B
build/block-library/blocks/site-logo/editor-rtl.css 773 B
build/block-library/blocks/site-logo/editor.css 770 B
build/block-library/blocks/site-logo/style-rtl.css 218 B
build/block-library/blocks/site-logo/style.css 218 B
build/block-library/blocks/site-tagline/editor-rtl.css 87 B
build/block-library/blocks/site-tagline/editor.css 87 B
build/block-library/blocks/site-tagline/style-rtl.css 65 B
build/block-library/blocks/site-tagline/style.css 65 B
build/block-library/blocks/site-title/editor-rtl.css 85 B
build/block-library/blocks/site-title/editor.css 85 B
build/block-library/blocks/site-title/style-rtl.css 143 B
build/block-library/blocks/site-title/style.css 143 B
build/block-library/blocks/social-link/editor-rtl.css 314 B
build/block-library/blocks/social-link/editor.css 314 B
build/block-library/blocks/social-links/editor-rtl.css 339 B
build/block-library/blocks/social-links/editor.css 338 B
build/block-library/blocks/social-links/style-rtl.css 1.51 kB
build/block-library/blocks/social-links/style.css 1.51 kB
build/block-library/blocks/spacer/editor-rtl.css 346 B
build/block-library/blocks/spacer/editor.css 346 B
build/block-library/blocks/spacer/style-rtl.css 48 B
build/block-library/blocks/spacer/style.css 48 B
build/block-library/blocks/table-of-contents/style-rtl.css 83 B
build/block-library/blocks/table-of-contents/style.css 83 B
build/block-library/blocks/table/editor-rtl.css 394 B
build/block-library/blocks/table/editor.css 394 B
build/block-library/blocks/table/style-rtl.css 640 B
build/block-library/blocks/table/style.css 639 B
build/block-library/blocks/table/theme-rtl.css 152 B
build/block-library/blocks/table/theme.css 152 B
build/block-library/blocks/tag-cloud/editor-rtl.css 92 B
build/block-library/blocks/tag-cloud/editor.css 92 B
build/block-library/blocks/tag-cloud/style-rtl.css 248 B
build/block-library/blocks/tag-cloud/style.css 248 B
build/block-library/blocks/template-part/editor-rtl.css 368 B
build/block-library/blocks/template-part/editor.css 368 B
build/block-library/blocks/template-part/theme-rtl.css 113 B
build/block-library/blocks/template-part/theme.css 113 B
build/block-library/blocks/term-description/style-rtl.css 126 B
build/block-library/blocks/term-description/style.css 126 B
build/block-library/blocks/text-columns/editor-rtl.css 95 B
build/block-library/blocks/text-columns/editor.css 95 B
build/block-library/blocks/text-columns/style-rtl.css 165 B
build/block-library/blocks/text-columns/style.css 165 B
build/block-library/blocks/verse/style-rtl.css 98 B
build/block-library/blocks/verse/style.css 98 B
build/block-library/blocks/video/editor-rtl.css 441 B
build/block-library/blocks/video/editor.css 442 B
build/block-library/blocks/video/style-rtl.css 192 B
build/block-library/blocks/video/style.css 192 B
build/block-library/blocks/video/theme-rtl.css 134 B
build/block-library/blocks/video/theme.css 134 B
build/block-library/classic-rtl.css 179 B
build/block-library/classic.css 179 B
build/block-library/common-rtl.css 1.08 kB
build/block-library/common.css 1.08 kB
build/block-library/editor-elements-rtl.css 75 B
build/block-library/editor-elements.css 75 B
build/block-library/editor-rtl.css 11.2 kB
build/block-library/editor.css 11.2 kB
build/block-library/elements-rtl.css 54 B
build/block-library/elements.css 54 B
build/block-library/index.min.js 229 kB
build/block-library/reset-rtl.css 472 B
build/block-library/reset.css 472 B
build/block-library/style-rtl.css 15 kB
build/block-library/style.css 15 kB
build/block-library/theme-rtl.css 715 B
build/block-library/theme.css 719 B
build/block-serialization-default-parser/index.min.js 1.12 kB
build/block-serialization-spec-parser/index.min.js 2.87 kB
build/blocks/index.min.js 52.6 kB
build/commands/index.min.js 16.2 kB
build/commands/style-rtl.css 956 B
build/commands/style.css 952 B
build/components/style-rtl.css 12.5 kB
build/components/style.css 12.5 kB
build/compose/index.min.js 12.8 kB
build/core-commands/index.min.js 3.09 kB
build/core-data/index.min.js 74.3 kB
build/customize-widgets/index.min.js 11 kB
build/customize-widgets/style-rtl.css 1.43 kB
build/customize-widgets/style.css 1.43 kB
build/data-controls/index.min.js 641 B
build/data/index.min.js 8.67 kB
build/date/index.min.js 18 kB
build/deprecated/index.min.js 458 B
build/dom-ready/index.min.js 325 B
build/dom/index.min.js 4.68 kB
build/edit-post/classic-rtl.css 577 B
build/edit-post/classic.css 578 B
build/edit-post/index.min.js 13.5 kB
build/edit-post/style-rtl.css 2.77 kB
build/edit-post/style.css 2.77 kB
build/edit-site/posts-rtl.css 8.47 kB
build/edit-site/posts.css 8.48 kB
build/edit-site/style-rtl.css 14.6 kB
build/edit-site/style.css 14.6 kB
build/edit-widgets/index.min.js 17.7 kB
build/edit-widgets/style-rtl.css 4.05 kB
build/edit-widgets/style.css 4.06 kB
build/editor/style-rtl.css 9.06 kB
build/editor/style.css 9.06 kB
build/element/index.min.js 4.82 kB
build/escape-html/index.min.js 537 B
build/format-library/index.min.js 8.09 kB
build/format-library/style-rtl.css 472 B
build/format-library/style.css 472 B
build/hooks/index.min.js 1.65 kB
build/html-entities/index.min.js 467 B
build/is-shallow-equal/index.min.js 526 B
build/keyboard-shortcuts/index.min.js 1.31 kB
build/keycodes/index.min.js 1.46 kB
build/list-reusable-blocks/index.min.js 2.13 kB
build/list-reusable-blocks/style-rtl.css 847 B
build/list-reusable-blocks/style.css 848 B
build/media-utils/index.min.js 3.69 kB
build/notices/index.min.js 946 B
build/nux/index.min.js 1.62 kB
build/nux/style-rtl.css 767 B
build/nux/style.css 763 B
build/patterns/index.min.js 7.36 kB
build/patterns/style-rtl.css 687 B
build/patterns/style.css 685 B
build/plugins/index.min.js 1.86 kB
build/preferences-persistence/index.min.js 2.06 kB
build/preferences/index.min.js 2.9 kB
build/preferences/style-rtl.css 551 B
build/preferences/style.css 552 B
build/primitives/index.min.js 829 B
build/priority-queue/index.min.js 1.54 kB
build/private-apis/index.min.js 978 B
build/react-i18n/index.min.js 630 B
build/react-refresh-entry/index.min.js 9.47 kB
build/react-refresh-runtime/index.min.js 6.76 kB
build/redux-routine/index.min.js 2.7 kB
build/reusable-blocks/index.min.js 2.53 kB
build/reusable-blocks/style-rtl.css 255 B
build/reusable-blocks/style.css 255 B
build/rich-text/index.min.js 10.3 kB
build/router/index.min.js 5.44 kB
build/server-side-render/index.min.js 1.94 kB
build/shortcode/index.min.js 1.4 kB
build/style-engine/index.min.js 2.04 kB
build/token-list/index.min.js 581 B
build/url/index.min.js 3.96 kB
build/vendors/react-dom.min.js 41.7 kB
build/vendors/react-jsx-runtime.min.js 556 B
build/vendors/react.min.js 4.02 kB
build/viewport/index.min.js 965 B
build/vips/index.min.js 36.2 kB
build/warning/index.min.js 250 B
build/widgets/index.min.js 7.16 kB
build/widgets/style-rtl.css 1.16 kB
build/widgets/style.css 1.16 kB
build/wordcount/index.min.js 1.03 kB

compressed-size-action

@swissspidy swissspidy marked this pull request as ready for review June 16, 2025 12:32
@swissspidy swissspidy requested a review from ajitbohra as a code owner June 16, 2025 12:32
Copy link

github-actions bot commented Jun 16, 2025

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: swissspidy <swissspidy@git.wordpress.org>
Co-authored-by: USERSATOSHI <tusharbharti@git.wordpress.org>
Co-authored-by: sirreal <jonsurrell@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@swissspidy swissspidy added [Type] Enhancement A suggestion for improvement. [Package] i18n /packages/i18n labels Jun 16, 2025
*
* Retrieve translated string with gettext context.
*
* @see https://developer.wordpress.org/reference/functions/_x/
*/
/**
* @typedef {(single: string, plural: string, number: number, domain?: string) => string} _n
* @typedef {<T extends string>(single: T, plural: T, number: number, domain?: string) => import('./types').TranslatableText< T >} _n
Copy link
Contributor

@USERSATOSHI USERSATOSHI Jun 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think, this might fail when the placeholders are different for single and plural.

For example:

// translators: %1$sd and %2$d are greetings to be displayed in the message.
sprintf( _n( '%2$d hi %1$s', '%1$s hi %2$d', 1 ), 'hi', 'hi' );

It should normally fail but it doesn't as both plural and single are using same generic, it adds an OR for both which causes the 2nd args to be any/unknown

image

Possible solution that I was able to come up with is:

type BuildTuple<
	L extends number,
	T extends unknown[] = [],
> = T[ 'length' ] extends L ? T : BuildTuple< L, [ unknown, ...T ] >;

type IsGreaterThan1< N extends number > = BuildTuple< N > extends [
	unknown,
	unknown,
	// eslint-disable-next-line @typescript-eslint/no-unused-vars
	...infer _Rest,
]
	? true
	: false;

export type _n = < T extends string, P extends string, N extends number >(
	single: T,
	plural: P,
	number: N,
	domain?: string
) => TranslatableText< IsGreaterThan1< N > extends true ? P : T >;
image image

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting. I'll need to check it out a bit more closely.

At first glance: IsGreaterThan1 is a fallacy that doesn't work for every language. Plus the number is likely not known anyway (could be arr.length or so).

Copy link
Contributor

@USERSATOSHI USERSATOSHI Jun 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That does make sense, and yeah the number inside a generic function does cause TS to not know the value and defaults to single.
I will try to experiment a bit more to see what can be done so it is usable for dynamic cases.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would be amazing, thanks for your help!

Worst case, if the OR for both causes some errors, it would be up to the consumer to do any code fixes or ignore any errors. As long as that is documented, it would be fine by me.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I made it work.

type DistributeSprintfArgs< T extends string > = T extends any
	? SprintfArgs< T >
	: never;

and replacing SprintfArgs<T> with this new DistributeSprintfArgs<T> in sprintf,

export function sprintf< T extends string >(
	format: T | TranslatableText< T >,
	...args: DistributeSprintfArgs< T >
): string;
export function sprintf< T extends string >(
	format: T | TranslatableText< T >,
	args: DistributeSprintfArgs< T >
): string;
export function sprintf< T extends string >(
	format: T | TranslatableText< T >,
	...args: DistributeSprintfArgs< T >
): string { 
	//...
}

it will distribute unions and we get an OR of both SprintfArgs<T> and SprintfArgs<P> ( or only T if both single and plural are type of T ( tho I prefer them to be separated, looks better on intellisense ) ).

Then we won't be reallying on the number part of _n and will give us a decent typing to work with.

Screenshot

image

cc: @swissspidy

*
* Translates and retrieves the singular or plural form based on the supplied
* number.
*
* @see https://developer.wordpress.org/reference/functions/_n/
*/
/**
* @typedef {(single: string, plural: string, number: number, context: string, domain?: string) => string} _nx
* @typedef {<T extends string>(single: T, plural: T, number: number, context: string, domain?: string) => import('./types').TranslatableText< T >} _nx
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same case as above

@tyxla tyxla requested a review from aduth June 16, 2025 14:12
@sirreal
Copy link
Member

sirreal commented Jun 17, 2025

I may have missed something, but will someone please explain TranslateableText and why it must be used now?

My biggest concern here is that this type is now exposed to consumers as the return type of all the __ family of translation functions.

I see part of this comment but I'm not sure where this discussion can be followed:

Continuing on what we discussed what I meant was that TranslatableText is return type for all translate function and we use the Extract and sprintf definitions in the sprintf.ts file

@swissspidy
Copy link
Member Author

swissspidy commented Jun 17, 2025

I may have missed something, but will someone please explain TranslateableText and why it must be used now?

My biggest concern here is that this type is now exposed to consumers as the return type of all the __ family of translation functions.

I see part of this comment but I'm not sure where this discussion can be followed:

Continuing on what we discussed what I meant was that TranslatableText is return type for all translate function and we use the Extract and sprintf definitions in the sprintf.ts file

Sure, this was at aduth/tannin#15 (comment)

Basically, the new types work fine for sprintf( 'Hello %s', 'foo' ), but not when the text is wrapped in __(), e.g. sprintf( // translators: %d: heading level e.g: "1", "2", "3" __( 'Heading %d' ), level ). So this extra type is for better "visibility" for TypeScript, so it can infer the type from the string passed to __() (so it knows it needs to look at the first param of __()).

@swissspidy swissspidy merged commit e8c3982 into trunk Jul 3, 2025
60 of 61 checks passed
@swissspidy swissspidy deleted the fix/52985-sprintf branch July 3, 2025 11:27
@swissspidy swissspidy changed the title I18N Use @tannin/sprintf I18N: Use @tannin/sprintf Jul 3, 2025
@github-actions github-actions bot added this to the Gutenberg 21.2 milestone Jul 3, 2025
@aduth
Copy link
Member

aduth commented Jul 3, 2025

Glad to see this land 🎉 Already with the changes included in this pull request, the strong typing is already looking to prove itself quite valuable. Major props to all the work along the way from aduth/tannin#15, @swissspidy @erikyo @USERSATOSHI 🙌

@gziolo
Copy link
Member

gziolo commented Jul 7, 2025

Nice, thank you for taking over the prototype I started and bringing it to completion. That's going to be a nice win!

@luisherranz
Copy link
Member

Thanks Pascal ❤️

@nerrad
Copy link
Contributor

nerrad commented Jul 21, 2025

While I agree this was a good change with stricter types, this change does mean that undefined passed in as the format value results in a fatal error whereas sprintf-js handled/absorbed undefined values.

This has surfaced at least one fatal in the wild.

@USERSATOSHI
Copy link
Contributor

While I agree this was a good change with stricter types, this change does mean that undefined passed in as the format value results in a fatal error whereas sprintf-js handled/absorbed undefined values.

This has surfaced at woocommerce/woocommerce#59846.

Hmm, This could be fixed by either adding an optional chaining to tannin/sprintf or by adding a variable check in @wordpress/i18n's sprintf.

What do you think would be better solution?
cc: @nerrad @aduth

@aduth
Copy link
Member

aduth commented Jul 21, 2025

Hmm, This could be fixed by either adding an optional chaining to tannin/sprintf or by adding a variable check in @wordpress/i18n's sprintf.

If I understand, the issue is specifically with an undefined format string, not with placeholder values.

  • sprintf('Hello, %(name)s', { name: undefined }) ➡️ 'Hello, '
  • sprintf('Hello, %(name)s', undefined) ➡️ 'Hello, '
  • sprintf(undefined); ➡️ Uncaught TypeError: Cannot read properties of undefined (reading 'replace')

Likely not a very common case since presumably someone wanting to format a string has a string to work with, but possible in a scenario where passing through some variable to sprintf.

@nerrad
Copy link
Contributor

nerrad commented Jul 21, 2025

Likely not a very common case since presumably someone wanting to format a string has a string to work with, but possible in a scenario where passing through some variable to sprintf.

Yes, exactly.

@LiamSarsfield
Copy link

I also agree with having stricter types, though we just encountered an issue that sprintf-js's (https://www.npmjs.com/package/sprintf-js#computed-values) functionality handled, but @tannin/sprint didn't.
We addressed this here however: Automattic/jetpack#44418, just confirming though that this is an expected breaking change?

The fix: Changed from sprintf(template, functionRef) to sprintf(template, functionRef()) to ensure we pass the actual value instead of the function reference.

@talldan
Copy link
Contributor

talldan commented Aug 5, 2025

I think this change may have broken the React Native codebase. When I try running it now I see this error:

error: Error: While trying to resolve module @tannin/sprintf from file gutenberg/packages/i18n/src/sprintf.ts, the package gutenberg/node_modules/@tannin/sprintf/package.json was successfully found. However, this package itself specifies a main module field that could not be resolved (gutenberg/node_modules/@tannin/sprintf/index).

@USERSATOSHI
Copy link
Contributor

I think this change may have broken the React Native codebase. When I try running it now I see this error:

error: Error: While trying to resolve module @tannin/sprintf from file gutenberg/packages/i18n/src/sprintf.ts, the package gutenberg/node_modules/@tannin/sprintf/package.json was successfully found. However, this package itself specifies a main module field that could not be resolved (gutenberg/node_modules/@tannin/sprintf/index).

hi @talldan, wanted to confirm something, is the react-native environment configured to support typescript files? As I believe the migration of i18n to typescript converted src folder to typescript and now I am seeing that in package.json, react-native is pointed to src/index which I missed while checking package.json main and module when migrating ( sorry for that mistake on my side )

@aduth
Copy link
Member

aduth commented Aug 5, 2025

Another thought is that the package only defines exports and not main. exports has been supported in Node.js quite a ways back (v12.x), but I'm not familiar enough with React Native or our specific setup to know how well it's supported in that context. A quick Google search yields this blog post from 2023 about support being added. Maybe we can update? It's increasingly common that NPM packages will only define exports instead of main.

@ramonjd
Copy link
Member

ramonjd commented Aug 7, 2025

For more context here's the metro build error:

Log
Metro has encountered an error: While trying to resolve module `@tannin/sprintf` from file `/Users/me/projects/gutenberg/packages/i18n/src/sprintf.ts`, the package `/Users/me/projects/gutenberg/node_modules/@tannin/sprintf/package.json` was successfully found. However, this package itself specifies a `main` module field that could not be resolved (`/Users/me/projects/gutenberg/node_modules/@tannin/sprintf/index`. Indeed, none of these files exist:

  * /Users/me/projects/gutenberg/node_modules/@tannin/sprintf/index(.ios.js|.native.js|.js|.ios.cjs|.native.cjs|.cjs|.ios.json|.native.json|.json|.ios.scss|.native.scss|.scss|.ios.sass|.native.sass|.sass|.ios.ts|.native.ts|.ts|.ios.tsx|.native.tsx|.tsx)
  * /Users/me/projects/gutenberg/node_modules/@tannin/sprintf/index/index(.ios.js|.native.js|.js|.ios.cjs|.native.cjs|.cjs|.ios.json|.native.json|.json|.ios.scss|.native.scss|.scss|.ios.sass|.native.sass|.sass|.ios.ts|.native.ts|.ts|.ios.tsx|.native.tsx|.tsx): /Users/ramon/projects/gutenberg/node_modules/metro/src/node-haste/DependencyGraph.js (283:17)

  281 |         }
  282 |         if (error instanceof InvalidPackageError) {
> 283 |           throw new PackageResolutionError({
      |                 ^
  284 |             packageError: error,
  285 |             originModulePath: from,
  286 |             targetModuleName: to,

RCTFatal
__28-[RCTCxxBridge handleError:]_block_invoke
_dispatch_call_block_and_release
_dispatch_client_callout
_dispatch_main_queue_drain.cold.7
_dispatch_main_queue_drain
_dispatch_main_queue_callback_4CF
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
__CFRunLoopRun
CFRunLoopRunSpecific
GSEventRunModal
-[UIApplication _run]
UIApplicationMain
__debug_main_executable_dylib_entry_point
start_sim
0x0

@talldan
Copy link
Contributor

talldan commented Aug 8, 2025

hi @talldan, wanted to confirm something, is the react-native environment configured to support typescript files?

Sorry for the delay replying. I don't know, I've not personally worked on the native code much, I only noticed the error when testing some changes to react native files.

@USERSATOSHI
Copy link
Contributor

hi @talldan, wanted to confirm something, is the react-native environment configured to support typescript files?

Sorry for the delay replying. I don't know, I've not personally worked on, I only noticed the error when testing some changes to react native files.

Got it, I will test it on my side and see any solutions/fixes for this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Package] i18n /packages/i18n [Type] Enhancement A suggestion for improvement.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

I18N: Strongly typed sprintf
10 participants