# Translation

Activity and comment text can be translated on demand using the same automated translation provider as [Stream Chat](/chat/docs/node/translation/). Translations are stored in an `i18n` object on the activity or comment and can be projected when reading feeds, activities, and comments.

<admonition type="info">

Feeds v3 does **not** automatically translate content when you create an activity or comment. Automatic translation on write is planned for a later release. Until then, call the translate endpoints explicitly — either on demand from your app, or from your backend when you receive an activity/comment create webhook — and read stored translations with the `language` query parameter.

</admonition>

## Translate an activity

Translates an activity's `text` field to the requested language. Requires the same permission as reading the activity (`ReadActivities`).

`POST /api/v2/feeds/activities/{id}/translate`

<Tabs>

```js label="JavaScript"
const response = await client.translateActivity({
  id: "activity_123",
  language: "fr",
});

console.log(response.activity.i18n?.fr_text);
// "Bonjour, j'aimerais en savoir plus sur votre produit."
```

```python label="Python"
resp = client.feeds.translate_activity("activity_123", "fr")
print(resp.data.activity.i18n["fr_text"])
```

```ruby label="Ruby"
request = GetStream::Generated::Models::TranslateActivityRequest.new(language: "fr")
response = client.feeds.translate_activity("activity_123", request)
response.activity.i18n.fr_text
```

```php label="PHP"
$response = $feedsClient->translateActivity(
    'activity_123',
    new GeneratedModels\TranslateActivityRequest(language: 'fr'),
);
echo $response->getData()->activity->i18n['fr_text'];
```

```go label="Go"
resp, err := client.Feeds().TranslateActivity(ctx, "activity_123", &getstream.TranslateActivityRequest{
    Language: "fr",
})
resp.Data.Activity.I18n["fr_text"]
```

```csharp label="C#"
var response = await _feedsV3Client.TranslateActivityAsync(
    "activity_123",
    new TranslateActivityRequest { Language = "fr" },
);
Console.WriteLine(response.Data.Activity.I18n["fr_text"]);
```

```java label="Java"
TranslateActivityRequest request = TranslateActivityRequest.builder()
    .language("fr")
    .build();
TranslateActivityResponse response = feeds
    .translateActivity("activity_123", request)
    .execute()
    .getData();
response.getActivity().getI18n().get("fr_text");
```

</Tabs>

The response includes the full activity with the updated `i18n` map. An `activity.updated` event is emitted (see [Events](#events) below).

## Translate a comment

Translates a comment's `text` field to the requested language.

`POST /api/v2/feeds/comments/{id}/translate`

<Tabs>

```js label="JavaScript"
const response = await client.translateComment({
  id: "comment_456",
  language: "es",
});

console.log(response.comment.i18n?.es_text);
```

```python label="Python"
resp = client.feeds.translate_comment("comment_456", "es")
print(resp.data.comment.i18n["es_text"])
```

```ruby label="Ruby"
request = GetStream::Generated::Models::TranslateCommentRequest.new(language: "es")
response = client.feeds.translate_comment("comment_456", request)
response.comment.i18n.es_text
```

```php label="PHP"
$response = $feedsClient->translateComment(
    'comment_456',
    new GeneratedModels\TranslateCommentRequest(language: 'es'),
);
echo $response->getData()->comment->i18n['es_text'];
```

```go label="Go"
resp, err := client.Feeds().TranslateComment(ctx, "comment_456", &getstream.TranslateCommentRequest{
    Language: "es",
})
resp.Data.Comment.I18n["es_text"]
```

```csharp label="C#"
var response = await _feedsV3Client.TranslateCommentAsync(
    "comment_456",
    new TranslateCommentRequest { Language = "es" },
);
Console.WriteLine(response.Data.Comment.I18n["es_text"]);
```

```java label="Java"
TranslateCommentRequest request = TranslateCommentRequest.builder()
    .language("es")
    .build();
TranslateCommentResponse response = feeds
    .translateComment("comment_456", request)
    .execute()
    .getData();
response.getComment().getI18n().get("es_text");
```

</Tabs>

## Reading translated content

After a translation is stored, you can project it when reading feeds, activities, and comments using two optional query parameters:

| Parameter        | Type    | Description                                                                                                                 |
| ---------------- | ------- | --------------------------------------------------------------------------------------------------------------------------- |
| `language`       | string  | ISO 639-1 language code. Returns a slim `i18n` map with the original language and the requested translation when available. |
| `translate_text` | boolean | When `true`, substitutes the top-level `text` field with the requested translation when one exists. Default is `false`.     |

These parameters are supported on:

- Feed reads (`getOrCreateFeed`)
- `getActivity`, `queryActivities`
- Comment reads (`getComments`, `getComment`, `getCommentReplies`, `queryComments`)
- `queryPinnedActivities`, `queryBookmarks`

### Default projection (`translate_text=false`)

The `text` field stays in the original language. The response includes a projected `i18n` object with the original language key and the requested translation when stored.

<Tabs>

```js label="JavaScript"
const feed = client.feed("user", "john");
const response = await feed.getOrCreate({
  language: "es",
});

// text is still the original English
console.log(response.activities[0].text);
// "Hello world"

// projected i18n includes Spanish when translated
console.log(response.activities[0].i18n?.es_text);
// "Hola mundo"
```

```php label="PHP"
// Request body first; optional language / translate_text follow (PHP SDK v9+)
$response = $feedsClient->feed('user', 'john')->getOrCreateFeed(
    new GeneratedModels\GetOrCreateFeedRequest(userID: 'john'),
    language: 'es',
);

// text is still the original English
echo $response->getData()->activities[0]->text;
// "Hello world"

// projected i18n includes Spanish when translated
echo $response->getData()->activities[0]->i18n['es_text'];
// "Hola mundo"
```

</Tabs>

### Substitute `text` (`translate_text=true`)

When a translation exists for the requested language, the `text` field is replaced with the translated version. The original is always recoverable from `i18n`.

<Tabs>

```js label="JavaScript"
const feed = client.feed("user", "john");
const response = await feed.getOrCreate({
  language: "es",
  translate_text: true,
});

console.log(response.activities[0].text);
// "Hola mundo"

console.log(response.activities[0].i18n?.language);
// "en"
```

```php label="PHP"
$response = $feedsClient->feed('user', 'john')->getOrCreateFeed(
    new GeneratedModels\GetOrCreateFeedRequest(userID: 'john'),
    language: 'es',
    translateText: true,
);

echo $response->getData()->activities[0]->text;
// "Hola mundo"

echo $response->getData()->activities[0]->i18n['language'];
// "en"
```

</Tabs>

<admonition type="info">

If no translation exists for the requested language, `text` is returned unchanged regardless of the `translate_text` flag.

</admonition>

## The `i18n` object

When content is translated, an `i18n` object is added to the activity or comment. It uses the same format as [Chat message translations](/chat/docs/node/translation/#i18n-data):

- `language` — ISO 639-1 code of the original text
- `{language}_text` — text in that language (e.g. `en_text`, `fr_text`, `es_text`)

<Tabs>

```json label="JSON"
{
  "language": "en",
  "en_text": "Hello world",
  "fr_text": "Bonjour le monde",
  "es_text": "Hola mundo"
}
```

</Tabs>

The explicit translate endpoints return the **full** `i18n` map. Read endpoints with `language` return a **projected** subset: the original language metadata plus the requested translation when available.

## Set user language

Setting a user's `language` improves source-language detection when translating. The source language priority is:

1. Stored `i18n.language` (set on first translation)
2. The activity or comment author's `user.language`
3. Automatic detection by the translation provider

<Tabs>

```js label="JavaScript"
await client.updateUsers({
  users: {
    user_123: { id: "user_123", language: "en", name: "Jane" },
  },
});
```

```python label="Python"
from getstream.models import UserRequest

client.upsert_users(UserRequest(id="user_123", language="en", name="Jane"))
```

```php label="PHP"
$client->updateUsers(new GeneratedModels\UpdateUsersRequest(
    users: [
        'user_123' => [
            'id' => 'user_123',
            'name' => 'Jane',
            'language' => 'en',
        ],
    ],
));
```

</Tabs>

See [User management](/activity-feeds/docs/node/user-management/) for full user create/update examples. The `language` field is shared across Chat, Feeds, and Video products.

## Events

Translating an activity or comment emits an `activity.updated` or `comment.updated` event.

To pre-translate content when it is created, listen for activity/comment create webhooks (or WebSocket events) and call the translate endpoints from your backend for the languages you need. Create events do not include translations — you add them with an explicit translate call.

<admonition type="warning">

WebSocket and webhook event payloads **do not include `i18n`**. Use the HTTP translate response, or re-fetch the object with `language` projection, to read stored translations after an event.

</admonition>

## Limits and behavior

- **Scope** — Only the `text` field is translated. `custom` fields, attachments, and other metadata are not included.
- **Idempotency** — Translating into a language that is already stored is a no-op (no additional billing for that language).
- **Text edits** — Updating an activity or comment's text clears all stored translations for that object.
- **Mentions** — `@mentioned` display names in text are protected during translation (same behavior as Chat).
- **Search** — Translated text is not indexed for activity search.
- **Size limits** — Up to 10 languages per object. Maximum `i18n` JSON size: 32 KB for activities, 16 KB for comments (separate from user text size limits).
- **Permissions** — Translate endpoints require the same read permission as the underlying object (`ReadActivities`).

## Billing

Each **new** destination language translated for an activity or comment counts toward your app's translation usage (`translations_daily`). Re-requesting a language that is already stored does not increment usage.

## Available languages

Feeds uses the same supported language codes as Chat. See the [Chat translation languages table](/chat/docs/node/translation/#available-languages).


---

This page was last updated at 2026-07-17T15:25:03.609Z.

For the most recent version of this documentation, visit [https://getstream.io/activity-feeds/docs/node/translation/](https://getstream.io/activity-feeds/docs/node/translation/).