Skip to content
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
2 changes: 1 addition & 1 deletion babel.config.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export default {
module.exports = {
presets: ['@docusaurus/babel/preset'],
};
2 changes: 1 addition & 1 deletion docusaurus.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const config: Config = {
searchPagePath: false,
},
prism: {
additionalLanguages: ['icu-message-format'],
additionalLanguages: ['icu-message-format', 'php'],
},
zoom: {
selector: '.zoomable-image',
Expand Down
114 changes: 114 additions & 0 deletions js-sdk/integrations/backend-rendered/overview.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
---
id: overview
title: In-Context Editing for Backend-Rendered Apps
sidebar_label: Overview
description: "Add Tolgee in-context translation editing to PHP, Ruby on Rails, Django, and Node.js server-side rendered applications without changing your backend rendering logic."
keywords: [backend rendering, server-side rendering, SSR, PHP translation, Rails i18n, Django translation, in-context editing, localization]
---

:::info TL;DR
Enable in-context translation editing for **backend-rendered apps** (PHP, Rails, Django, etc.). Your backend wraps translations with invisible characters, Tolgee JS detects them, and translators can edit strings directly in the browser. **Tradeoff:** Changes require a page refresh to appear.
:::

## How it works

```
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Your Backend │ ──▶ │ Browser loads │ ──▶ │ Translator │
│ wraps strings │ │ Tolgee JS │ │ clicks to edit │
│ (invisible) │ │ detects keys │ │ │
└─────────────────┘ └──────────────────┘ └─────────────────┘
```

1. **Backend wraps**: Your server appends invisible Unicode characters encoding the translation key to each string
2. **Tolgee detects**: Tolgee JS scans the DOM, finds wrapped strings, and makes them editable
3. **Translator edits**: Alt/Option+click opens the editor, changes go to Tolgee platform

The CLI running in watch mode pulls changes back to your local files. Refresh the page to see updates.

## Is this right for you?

**Use this approach if:**
- Your app renders translations on the server (PHP, Rails, Django, Node.js SSR, etc.)
- You want in-context editing without migrating to a JS framework
- You're OK with refreshing the page to see translation changes

**Consider the standard SDK if:**
- You're using React, Vue, Svelte, or Angular
- You need instant live updates when translations change
- Your app is a single-page application (SPA)

## Key limitation

:::warning Page refresh required
When you edit a translation, the change saves to Tolgee immediately, but the text on your page won't update until you refresh. This is because your backend (not Tolgee JS) controls what's rendered.
:::

In standard SDK integrations, Tolgee controls the entire translation lifecycle and can re-render instantly. With backend-rendered apps, Tolgee JS only *observes* pre-rendered content—it can't change what your server sent.

## Quick comparison

| Aspect | Backend-rendered | Standard SDK (React, Vue, etc.) |
|--------|------------------|---------------------------------|
| Who renders translations | Your backend | Tolgee JS |
| Live updates after edit | No (refresh required) | Yes (instant) |
| Supported frameworks | Any backend | React, Vue, Svelte, Angular, etc. |
| Setup complexity | Implement wrapper function | Install SDK package |
| Production overhead | None (skip wrapping) | Minimal (translation loading) |

<details>
<summary>Technical details: How Tolgee JS detects wrapped translations</summary>

### The wrapping mechanism

To prepare a translation for detection, your backend wraps it with invisible characters. The wrapper encodes a JSON object containing the translation key and namespace (e.g., `{"k":"welcome","n":""}`) into binary. Each bit becomes an invisible Unicode character: `0` maps to Zero-Width Non-Joiner (`\u200C`) and `1` maps to Zero-Width Joiner (`\u200D`).

The output looks like:
```
<translated string><JSON-encoded key as invisible chars>
// e.g.
Hello\u{200C}\u{200D}\u{200C}\u{200D}...
```

### How the observer works

When your page loads, it contains HTML with visible translated text and invisible character sequences appended to each translation. Users see only the translated text because the Unicode characters are genuinely invisible—they don't create visual artifacts, spacing issues, or layout problems.

Tolgee JS starts monitoring the DOM using a MutationObserver as soon as it initializes. This observer scans every text node, looking for invisible character sequences. When found, it decodes the sequence back into the translation key and namespace.

Once Tolgee detects a translation key, it removes the invisible characters from the DOM and adds event listeners to the parent element. These listeners enable the in-context editing experience—holding Alt/Option and clicking opens the translation editor.

The mutation observer also handles dynamically added content. If your application adds new DOM content with wrapped translations (via AJAX or JavaScript), the observer detects them automatically.

### Frontend initialization

Enable detection by initializing Tolgee with the `ObserverPlugin` and `fullKeyEncode: true`:

```javascript
const { Tolgee, DevTools, ObserverPlugin } = window['@tolgee/web'];

Tolgee()
.use(DevTools())
.use(ObserverPlugin())
.init({
language: 'en',
apiKey: '<your api key>',
observerOptions: { fullKeyEncode: true }
})
.run();
```

### Production considerations

In production, omit the Tolgee script entirely. Since your backend doesn't wrap translations in production mode, there's no need for Tolgee JS. Use a server-side condition to exclude the `<script>` tag (see the [setup guide](./setup.mdx#step-2-initialize-tolgee-js-with-the-observer) for examples).

</details>

## Next steps

Ready to implement? The [setup guide](./setup.mdx) walks you through:
- Implementing the invisible wrapper in your backend
- Configuring Tolgee JS on the frontend
- Running the CLI in watch mode to sync changes

For a working reference, see the [PHP example repository](https://github.com/tolgee/tolgee-php-demo).
137 changes: 137 additions & 0 deletions js-sdk/integrations/backend-rendered/production-editing.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
---
id: production-editing
title: Setup Editing in Production for Backend-Rendered Apps
sidebar_label: Setup Editing in Production
description: "Enable Tolgee in-context editing for translators on production without exposing API keys to end users. Use the Tolgee Browser Plugin for secure, on-demand translation editing."
keywords: [production editing, browser plugin, Tolgee extension, secure translation, translator workflow]
---

:::info TL;DR
Enable in-context editing on production for your team without exposing API keys. Team members use the **Tolgee Browser Plugin** to inject credentials locally, while regular users see a normal site.
:::

:::warning Prerequisites
Complete the [Setup for Development](./setup.mdx) guide first. This guide builds on that setup by adding a secure way to enable editing on production.
:::

## How it works

```
┌──────────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ User visits with │ ──▶ │ Server wraps │ ──▶ │ Browser plugin │
│ ?tolgeeDevelopment │ │ translations │ │ injects API key │
└──────────────────────┘ └──────────────────┘ └─────────────────┘
```

1. Team member visits your site with a trigger (e.g., `?tolgeeDevelopment` query param)
2. Server enables wrapping and loads Tolgee JS—but **without** the API key
3. The browser plugin detects Tolgee and injects the API key from local settings
4. In-context editing works, but only for team members with the plugin configured

Regular users see a normal production site. No credentials exposed.

---

By default, you don't want to expose Tolgee dev tools or in-context editing capabilities to end users in production. The API key should never be bundled into client-side code, and the invisible character wrapping adds unnecessary overhead.

However, **translators and product managers often need to edit strings directly on production**—seeing translations in their real context is invaluable for quality.

## The Tolgee Browser Plugin solution

The [Tolgee Browser Plugin](/js-sdk/in-context#1-tolgee-chrome-plugin) solves this elegantly. It injects the API key and dev tools directly into Tolgee JS on the client side, enabling in-context editing without exposing credentials in your server code.

## Server-side implementation

Update your development mode check to include a query parameter trigger:

```php
<?php
// Development mode is enabled when:
// - API key is present in environment (local development)
// - ?tolgeeDevelopment query param is present (for browser plugin on production)
$apiKey = getenv('TOLGEE_API_KEY') ?: '';
$isDevelopment = !empty($apiKey) || isset($_GET['tolgeeDevelopment']);
?>
```

Then conditionally load Tolgee JS. Note that the API key may be empty - the browser plugin will provide it:

```php
<?php if ($isDevelopment): ?>
<script src="https://cdn.jsdelivr.net/npm/@tolgee/web/dist/tolgee-web.development.umd.min.js"></script>
<script>
const { Tolgee, DevTools, ObserverPlugin } = window['@tolgee/web'];

Tolgee()
.use(DevTools())
.use(ObserverPlugin())
.init({
language: '<?= htmlspecialchars($lang) ?>',
apiUrl: '<?= htmlspecialchars(getenv('TOLGEE_API_URL') ?: 'https://app.tolgee.io') ?>',
apiKey: '<?= htmlspecialchars($apiKey) ?>', // May be empty - plugin injects it
observerOptions: { fullKeyEncode: true }
})
.run();
</script>
<?php endif; ?>
```

## Setting up the browser plugin

Team members who need to edit translations on production:

1. Install the [Tolgee Chrome Extension](https://chromewebstore.google.com/detail/tolgee-tools/hacnbapajkkfohnonhbmegojnddagfnj) or [Firefox Add-on](https://addons.mozilla.org/en-US/firefox/addon/tolgee-tools/)
2. Open the plugin and configure:
- **API URL**: Your Tolgee instance (e.g., `https://app.tolgee.io`)
- **API Key**: A project API key with translation edit permissions
3. Visit your production site with `?tolgeeDevelopment` in the URL
4. The plugin injects the credentials, and in-context editing works

## Alternative trigger methods

The `?tolgeeDevelopment` query parameter is simple but visible in the URL. Depending on your security requirements, consider these alternatives:

### Session-based toggle

Store the preference in a session so the query param isn't needed on every page:

```php
// Enable via: /toggle-tolgee?enable=1
if (isset($_GET['enable'])) {
$_SESSION['tolgee_dev'] = $_GET['enable'] === '1';
}
$isDevelopment = $_SESSION['tolgee_dev'] ?? false;
```

### Staging environment detection

Auto-enable on staging subdomains:

```php
$host = $_SERVER['HTTP_HOST'] ?? '';
$isDevelopment = str_contains($host, 'staging.')
|| str_contains($host, 'dev.')
|| $host === 'localhost';
```

### Authenticated users only

Enable for logged-in users with a specific role:

```php
$isDevelopment = isLoggedIn() && currentUser()->hasRole('translator');
```

### IP allowlist

Enable only for office or VPN IP ranges:

```php
$allowedIPs = ['192.168.1.0/24', '10.0.0.0/8'];
$isDevelopment = ipInRange($_SERVER['REMOTE_ADDR'], $allowedIPs);
```

## Security considerations

- **The query parameter itself is not a security risk** - it only enables the invisible wrapping and loads Tolgee JS. Without an API key (from environment or plugin), no editing is possible.
- **The browser plugin stores the API key locally** - it's never sent to your server or exposed in the page source.
Loading