Skip to content

Conversation

@JanCizmar
Copy link
Contributor

@JanCizmar JanCizmar commented Feb 10, 2026

Problem

When taking screenshots with Playwright, there is no way to programmatically discover which translation keys are visible on screen and where they are positioned. This makes it impossible to automatically associate screenshots with translation keys in the Tolgee platform — users have to manually tag each screenshot with the relevant keys.

Solution

Exposes a window.__tolgee object (automatically set when InContextTools/DevTools plugin is active) with methods to query visible keys and their positions:

  • getVisibleKeys() — returns all keys visible in the current viewport with their bounding rects
  • highlight(keyName?, ns?) — highlights matching DOM elements (returns an unhighlight handle)
  • isRunning() — checks if the tolgee observer is active

Usage from Playwright

const keys = await page.evaluate(() => window.__tolgee?.getVisibleKeys());
// → [{ keyName: "hello", keyNamespace: "", position: { x, y, width, height } }, ...]

Test plan

  • Unit tests: 12 tests covering API setup, viewport filtering, highlight handle, and cleanup on stop
  • Manual: verified on dev app via Playwright MCP — getVisibleKeys() returns correct keys with positions, viewport filtering works (9 keys full viewport → 6 keys with 400px viewport)

Summary by CodeRabbit

  • Documentation

    • Added project conventions guide covering branch naming, pre-commit checks, and commit/PR standards.
  • New Features

    • Introduced Window API exposing lightweight browser integration with capabilities to retrieve visible translation keys, highlight specific keys, and check running status.
  • Tests

    • Added comprehensive test coverage for Window API functionality across multiple scenarios.

Adds a window.__tolgee object when InContextTools plugin is active,
enabling programmatic access to visible translation keys and their
positions from Playwright tests or the browser console.

This allows automated screenshot tools to discover which keys are
visible on screen and where they are, making it possible to
associate screenshots with translation keys in the Tolgee platform.

API:
- getVisibleKeys() - returns keys with bounding rects (viewport-filtered)
- highlight(keyName?, ns?) - highlights matching DOM elements
- isRunning() - checks if the tolgee observer is active
@JanCizmar JanCizmar requested a review from Anty0 February 10, 2026 10:48
@coderabbitai
Copy link

coderabbitai bot commented Feb 10, 2026

Walkthrough

This PR introduces a Window API integration for Tolgee that exposes key translation utilities (getVisibleKeys, highlight, isRunning) on the global window.__tolgee object. The API is dynamically managed through Tolgee's lifecycle, with setup/teardown triggered by running state changes. Comprehensive tests validate the functionality and a documentation file establishes project conventions.

Changes

Cohort / File(s) Summary
Documentation
CLAUDE.md
Establishes project conventions for branch naming (jancizmar/), pre-commit checks (Prettier, unit tests), and commit/PR format (Conventional Commits).
Window API Implementation
packages/web/src/package/WindowApi.ts
Introduces TolgeeWindowApi interface exposing getVisibleKeys(), highlight(), and isRunning() methods. Implements setupWindowApi() function that attaches the API to window.__tolgee in browser environments with SSR safety and returns a cleanup function.
Window API Integration
packages/web/src/package/InContextTools.ts
Wires the Window API lifecycle by importing setupWindowApi and subscribing to Tolgee's 'running' event. Dynamically initializes, tears down, and re-initializes the Window API in response to running state changes.
Window API Tests
packages/web/src/package/__test__/windowApi.test.ts
Comprehensive test suite validating window.__tolgee population, API method availability, isRunning() behavior, getVisibleKeys() viewport filtering logic, highlight() functionality, and teardown paths via setupWindowApi() and tolgee.stop().

Sequence Diagram

sequenceDiagram
    participant Client as Browser Client
    participant InContextTools
    participant WindowApi
    participant TolgeeInstance as Tolgee Instance
    participant DOM

    Client->>InContextTools: Initialize Tolgee
    InContextTools->>WindowApi: setupWindowApi(tolgee)
    WindowApi->>WindowApi: Check SSR environment
    WindowApi->>DOM: Attach window.__tolgee API
    WindowApi-->>InContextTools: Return cleanup function
    InContextTools->>TolgeeInstance: Subscribe to 'running' event

    Note over Client,DOM: User interaction

    TolgeeInstance->>InContextTools: Emit 'running' event (state change)
    InContextTools->>WindowApi: Call teardownWindowApi()
    WindowApi->>DOM: Remove window.__tolgee

    alt Running state is true
        InContextTools->>WindowApi: setupWindowApi(tolgee)
        WindowApi->>DOM: Re-attach window.__tolgee API
        WindowApi-->>InContextTools: Return cleanup function
    end

    Note over Client,DOM: API Usage

    Client->>DOM: Call window.__tolgee.getVisibleKeys()
    DOM->>TolgeeInstance: Filter positions by viewport
    DOM-->>Client: Return visible key positions
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A Window API blooms so bright,
Tolgee's keys now dance in sight,
Visible translations, highlighted with care,
Setup and teardown float through the air,
SSR-safe and tested well,
Window.__tolgee has quite the tale to tell! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely describes the main change: exposing a new window.__tolgee API for use in Playwright and console environments.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch jancizmar/window-tolgee-api

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@packages/web/src/package/__test__/windowApi.test.ts`:
- Around line 43-73: The test title says getVisibleKeys() but the body asserts
tolgee.findPositions(); update the test so the behavior matches the name by
calling the window API (window.__tolgee.getVisibleKeys()) after rendering and
awaiting the attribute, then assert the returned array has length > 0 and that
its first element matches { keyName: 'hello', keyNamespace: '' } and has a
defined position; alternatively, if you prefer to keep the existing assertions
on tolgee.findPositions(), simply rename the test description string to reflect
findPositions() instead of getVisibleKeys().

In `@packages/web/src/package/WindowApi.ts`:
- Around line 16-39: The teardown returned by setupWindowApi unconditionally
deletes window.__tolgee which can remove another Tolgee instance’s API; capture
the reference assigned (e.g., const api = { ... } assigned to window.__tolgee)
and in the returned cleanup function only delete window.__tolgee if
window.__tolgee === api, otherwise leave it intact; update setupWindowApi to
create the api object, assign it to window.__tolgee, and perform the guarded
delete in the returned function, referencing the setupWindowApi, window.__tolgee
and the local api variable names to locate the change.

Comment on lines +43 to +73
it('getVisibleKeys() returns key positions for rendered translations', async () => {
tolgee = TolgeeCore()
.use(InContextTools())
.init({
language: 'en',
staticData: { en: { hello: 'world' } },
observerType,
});
await tolgee.run();

document.body.innerHTML = `
<span data-testid="translation">${tolgee.t({ key: 'hello' })}</span>
`;

await waitFor(() => {
expect(
document
.querySelector('[data-testid="translation"]')
?.getAttribute(TOLGEE_ATTRIBUTE_NAME)
).not.toBeFalsy();
});

// findPositions finds the key in the DOM (jsdom returns zero-size rects)
const allPositions = tolgee.findPositions();
expect(allPositions.length).toBeGreaterThan(0);
expect(allPositions[0]).toMatchObject({
keyName: 'hello',
keyNamespace: '',
});
expect(allPositions[0].position).toBeDefined();
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Test name doesn’t match assertions.
Line 43’s test never calls window.__tolgee.getVisibleKeys(); it validates tolgee.findPositions() instead. Either rename the test or add a call to the window API so the title matches the behavior.

✏️ Rename option
-it('getVisibleKeys() returns key positions for rendered translations', async () => {
+it('findPositions() returns key positions for rendered translations', async () => {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it('getVisibleKeys() returns key positions for rendered translations', async () => {
tolgee = TolgeeCore()
.use(InContextTools())
.init({
language: 'en',
staticData: { en: { hello: 'world' } },
observerType,
});
await tolgee.run();
document.body.innerHTML = `
<span data-testid="translation">${tolgee.t({ key: 'hello' })}</span>
`;
await waitFor(() => {
expect(
document
.querySelector('[data-testid="translation"]')
?.getAttribute(TOLGEE_ATTRIBUTE_NAME)
).not.toBeFalsy();
});
// findPositions finds the key in the DOM (jsdom returns zero-size rects)
const allPositions = tolgee.findPositions();
expect(allPositions.length).toBeGreaterThan(0);
expect(allPositions[0]).toMatchObject({
keyName: 'hello',
keyNamespace: '',
});
expect(allPositions[0].position).toBeDefined();
});
it('findPositions() returns key positions for rendered translations', async () => {
tolgee = TolgeeCore()
.use(InContextTools())
.init({
language: 'en',
staticData: { en: { hello: 'world' } },
observerType,
});
await tolgee.run();
document.body.innerHTML = `
<span data-testid="translation">${tolgee.t({ key: 'hello' })}</span>
`;
await waitFor(() => {
expect(
document
.querySelector('[data-testid="translation"]')
?.getAttribute(TOLGEE_ATTRIBUTE_NAME)
).not.toBeFalsy();
});
// findPositions finds the key in the DOM (jsdom returns zero-size rects)
const allPositions = tolgee.findPositions();
expect(allPositions.length).toBeGreaterThan(0);
expect(allPositions[0]).toMatchObject({
keyName: 'hello',
keyNamespace: '',
});
expect(allPositions[0].position).toBeDefined();
});
🧰 Tools
🪛 ast-grep (0.40.5)

[warning] 52-54: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: document.body.innerHTML = <span data-testid="translation">${tolgee.t({ key: 'hello' })}</span>
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html

(dom-content-modification)


[warning] 52-54: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: document.body.innerHTML = <span data-testid="translation">${tolgee.t({ key: 'hello' })}</span>
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html

(unsafe-html-content-assignment)

🤖 Prompt for AI Agents
In `@packages/web/src/package/__test__/windowApi.test.ts` around lines 43 - 73,
The test title says getVisibleKeys() but the body asserts
tolgee.findPositions(); update the test so the behavior matches the name by
calling the window API (window.__tolgee.getVisibleKeys()) after rendering and
awaiting the attribute, then assert the returned array has length > 0 and that
its first element matches { keyName: 'hello', keyNamespace: '' } and has a
defined position; alternatively, if you prefer to keep the existing assertions
on tolgee.findPositions(), simply rename the test description string to reflect
findPositions() instead of getVisibleKeys().

Comment on lines +16 to +39
export function setupWindowApi(tolgee: TolgeeInstance): () => void {
if (isSSR()) {
return () => {};
}

window.__tolgee = {
getVisibleKeys() {
const vw = window.innerWidth;
const vh = window.innerHeight;
return tolgee.findPositions().filter(({ position: p }) => {
return p.x + p.width > 0 && p.y + p.height > 0 && p.x < vw && p.y < vh;
});
},
highlight(keyName?: string, ns?: string) {
return tolgee.highlight(keyName, ns);
},
isRunning() {
return tolgee.isRunning();
},
};

return () => {
delete window.__tolgee;
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Guard cleanup to avoid deleting another instance’s API.
If multiple Tolgee instances exist, teardown currently deletes window.__tolgee even if another instance overwrote it. Consider deleting only when the stored reference matches.

🛠️ Proposed fix
-  window.__tolgee = {
+  const api = {
     getVisibleKeys() {
       const vw = window.innerWidth;
       const vh = window.innerHeight;
       return tolgee.findPositions().filter(({ position: p }) => {
         return p.x + p.width > 0 && p.y + p.height > 0 && p.x < vw && p.y < vh;
       });
     },
     highlight(keyName?: string, ns?: string) {
       return tolgee.highlight(keyName, ns);
     },
     isRunning() {
       return tolgee.isRunning();
     },
   };
+  window.__tolgee = api;

   return () => {
-    delete window.__tolgee;
+    if (window.__tolgee === api) {
+      delete window.__tolgee;
+    }
   };
🤖 Prompt for AI Agents
In `@packages/web/src/package/WindowApi.ts` around lines 16 - 39, The teardown
returned by setupWindowApi unconditionally deletes window.__tolgee which can
remove another Tolgee instance’s API; capture the reference assigned (e.g.,
const api = { ... } assigned to window.__tolgee) and in the returned cleanup
function only delete window.__tolgee if window.__tolgee === api, otherwise leave
it intact; update setupWindowApi to create the api object, assign it to
window.__tolgee, and perform the guarded delete in the returned function,
referencing the setupWindowApi, window.__tolgee and the local api variable names
to locate the change.

return tolgee.highlight(keyName, ns);
},
isRunning() {
return tolgee.isRunning();
Copy link
Collaborator

Choose a reason for hiding this comment

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

Just a nit: with the current setup, it looks like this will always return true or won't exist.

@JanCizmar JanCizmar merged commit 0e4bf46 into main Feb 10, 2026
52 of 57 checks passed
@JanCizmar JanCizmar deleted the jancizmar/window-tolgee-api branch February 10, 2026 11:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants