Skip to content

Conversation

@akirk
Copy link
Member

@akirk akirk commented Jan 23, 2026

Motivation for the change, related issues

Adds an "Install Apps" section to the Personal Playground menu overlay. Users can browse available apps from the WordPress blueprints repository and install them with a single click.

Also includes automatic language detection that configures WordPress based on the user's browser language.

Finally, this also adds the ability to add custom apps by just pasting a blueprint while on the apps screen. This is to make it easier to develop new apps and conveniently reinstall them via a click.

Note: When WordPress/blueprints#166 is merged we need to update the my-wordpress branch in the URLs.

Screenshot

Screenshot 2026-01-25 at 12 02 21

Implementation details

App catalog (menu-overlay/index.tsx):

  • Fetches the app index from https://raw.githubusercontent.com/WordPress/blueprints/my-wordpress/apps.json
  • Displays apps in a styled list with title and description
  • Each app links to a blueprint URL that will install the app

i18n support (i18n.ts):

  • Maps browser language codes (BCP 47) to WordPress locales
  • Creates a setSiteLanguage blueprint step prepended to the personal blueprint

Testing Instructions (or ideally a Blueprint)

  1. Start the dev server: npm run dev
  2. Click the menu button and verify the "Install Apps" section loads
  3. Click an app to verify it navigates to the blueprint URL

Additionally: paste a blueprint while on the apps page to test the custom apps.

@akirk akirk changed the title [website] Personal Playground: Add app catalog [personal-wp] Add app catalog Jan 23, 2026
@akirk akirk force-pushed the persistent/multi-tab-coordination branch from 7cdbb30 to c1b2abd Compare January 23, 2026 12:07
@akirk akirk force-pushed the persistent/i18n-app-catalog branch from 055eea1 to 6aff978 Compare January 23, 2026 12:07
@akirk akirk force-pushed the persistent/multi-tab-coordination branch from c1b2abd to c94e2d5 Compare January 23, 2026 13:17
@akirk akirk force-pushed the persistent/i18n-app-catalog branch from 6aff978 to f651ba1 Compare January 23, 2026 13:17
* This map provides explicit mappings for cases where the conversion
* isn't straightforward (e.g., "de" -> "de_DE", not "de").
*/
const BROWSER_TO_WP_LOCALE: Record<string, string> = {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Interesting! What's the source of that? If we're confident every single item of this list checks out, let's put it somewhere in core (could also be after this PR). We always struggle with the right locale strings.

Copy link
Member Author

Choose a reason for hiding this comment

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

Actually https://github.com/GlotPress/GlotPress/blob/develop/locales/locales.php. I created a script to get the data from there and convert it to a json.

@$relay_http_code_and_initial_headers_if_not_already_sent();
}
// Close cURL session
curl_close($ch);
Copy link
Collaborator

Choose a reason for hiding this comment

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

What's that about?

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's the cherry-pick from #3164 (comment) that I mentioned in the PR description. This is just to make it work for testing locally with a PHP 8.5, I can remove it and we can discuss more over there.

return cachedResponse;

// The Cache API only supports GET requests
const canCache = request.method === 'GET';
Copy link
Collaborator

Choose a reason for hiding this comment

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

Ditto. Was this method ever called for non-GET requests? If yes, that sounds like an issue in itself. I'd rather throw an error here in this code path than silently accept a wrong input.

Copy link
Member Author

Choose a reason for hiding this comment

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

Same as above, cherry picked from #3164, we can discuss there and I'll remove it from this PR

Copy link
Collaborator

@adamziel adamziel left a comment

Choose a reason for hiding this comment

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

Small but important nitpicks

@akirk akirk force-pushed the persistent/multi-tab-coordination branch from 978b7de to ee9ad88 Compare January 23, 2026 18:23
@akirk akirk force-pushed the persistent/i18n-app-catalog branch from e696f40 to 3318673 Compare January 23, 2026 18:24
@akirk akirk requested a review from adamziel January 23, 2026 18:25
@akirk akirk force-pushed the persistent/i18n-app-catalog branch from 3318673 to c9943c8 Compare January 25, 2026 10:54
@akirk akirk force-pushed the persistent/multi-tab-coordination branch from ee9ad88 to 48a1290 Compare January 26, 2026 09:00
@akirk akirk force-pushed the persistent/i18n-app-catalog branch from 7c6e8a0 to 5e694fc Compare January 26, 2026 09:00
@akirk akirk force-pushed the persistent/multi-tab-coordination branch from 48a1290 to 55a7f29 Compare January 26, 2026 09:27
@akirk akirk force-pushed the persistent/i18n-app-catalog branch 3 times, most recently from 36800a5 to b17472b Compare January 26, 2026 12:52
* 4. Outputs JSON to src/lib/personalwp/locale-map.json
*/

import * as fs from 'fs';
Copy link
Collaborator

Choose a reason for hiding this comment

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

Not blocking: This script seems really fragile. Could GlotPress just load that data from a json file and then we'd reuse that json file here?

Copy link
Member Author

Choose a reason for hiding this comment

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

Let's check with @amieiro: Are there long term plans like that for GlotPress?

if (url.startsWith('data:application/json;base64,')) {
try {
const base64 = url.replace('data:application/json;base64,', '');
const json = decodeURIComponent(escape(atob(base64)));
Copy link
Collaborator

@adamziel adamziel Jan 26, 2026

Choose a reason for hiding this comment

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

atob has no UTF-8 support:

php > echo base64_encode('łącznik');
xYLEhWN6bmlr
console.log(atob('xYLEhWN6bmlr'))
VM720:1 ��cznik

Also, btoa would throw on the opposite operation.

Let's use the decodeBase64ToString and encodeStringAsBase64 helpers for working with base64.

return url;
}

function looksLikeBlueprint(text: string): boolean {
Copy link
Collaborator

Choose a reason for hiding this comment

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

not terrible but again, I think it's duplicating an existing helper from the website package. We can move forward here but I'm just marking it for some refactoring later on to extract shared dependencies.

const urlPath = new URL(trimmed).pathname;
const filename = urlPath.split('/').pop() || '';
if (filename) {
title = filename
Copy link
Collaborator

Choose a reason for hiding this comment

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

We can probably tweak this quite a bit to account for different extensions, word separators, and totally messy paths that don't contain any useful information. Not a blocker at all.

* @param browserLang - Browser language code (e.g., "en-US", "de", "pt-BR")
* @returns WordPress locale (e.g., "en_US", "de_DE", "pt_BR") or null if no mapping
*/
export async function browserLanguageToWpLocale(
Copy link
Collaborator

@adamziel adamziel Jan 26, 2026

Choose a reason for hiding this comment

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

I'm nervous about this. Can we have some test coverage?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, I'll add more tests.

Copy link
Collaborator

@adamziel adamziel left a comment

Choose a reason for hiding this comment

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

I didn't test this, but I also don't see any risks to playground.wordpress.net and I don't see any blockers in the code.

@akirk akirk force-pushed the persistent/multi-tab-coordination branch from 55a7f29 to 45accb9 Compare January 27, 2026 12:07
Base automatically changed from persistent/multi-tab-coordination to trunk January 27, 2026 12:24
@akirk akirk force-pushed the persistent/i18n-app-catalog branch from b17472b to 81095b9 Compare January 27, 2026 12:25
@akirk akirk merged commit 98e7688 into trunk Jan 27, 2026
35 checks passed
@akirk akirk deleted the persistent/i18n-app-catalog branch January 27, 2026 14:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants