---
source: https://studio.eloqnt.dev/docs/guides/static-analysis
docs_index: https://studio.eloqnt.dev/llms.txt
---

# Static analysis

eloqnt/cli can read and understand your source code, which unlocks usage-aware lint rules and call-site context for translations (`next-intl`-only).

To enable static analysis of source code, you can configure [`srcPath`](https://studio.eloqnt.dev/docs/configuration#srcPath):

```ts title=".eloqnt/config.ts"
export default {
  srcPath: './src',
  messages: {
    path: './messages',
    locales: 'infer',
    sourceLocale: 'en',
    format: 'json'
  }
};
```

## What this gives you

- The [`orphan-message`](https://studio.eloqnt.dev/docs/lint-rules/orphan-message) lint rule can detect messages that are no longer used in source code
- The [`undefined-key`](https://studio.eloqnt.dev/docs/lint-rules/undefined-key) lint rule finds keys your code calls that are not present in your messages
- [`eloqnt translate`](https://studio.eloqnt.dev/docs/cli/translate) and [`eloqnt review`](https://studio.eloqnt.dev/docs/cli/review) will get additional context on how a message is used in order to improve translation quality

## What it asks of your code

Reading source means resolving every `t()` call to a key without having to actually run the app.

**Two rules enable this:**

1. `t` receives a **literal string** as its message argument.
2. `t` is called in the **same function body** where it came from `useTranslations`, `getTranslations`, `useExtracted` or `getExtracted`.

**Supported:**

```tsx
import {useTranslations} from 'next-intl';

function Example({name}) {
  const t = useTranslations('Example');

  // ✅ A string literal
  t('greeting');

  // ✅ Dynamic values go in the second argument, not the key
  t('welcome', {name});

  function onClick() {
    // ✅ Event handlers are fine, `t` is still in scope
    t('clicked');
  }

  // ✅ JSX is also fine
  return <button onClick={onClick}>{t('submit')}</button>;
}
```

Destructuring from `Promise.all` is supported too:

```tsx
import {getTranslations} from 'next-intl/server';

async function Async() {
  // ✅ `t` is still statically analyzable
  const [t, post] = await Promise.all([getTranslations(), getPost()]);

  return <h1>{t('title')}</h1>;
}
```

**Not supported:**

```tsx
import {useTranslations} from 'next-intl';

function Example({status, items}) {
  const t = useTranslations();

  // ❌ Only known at runtime
  t(status);

  // ❌ An interpolated key is still a runtime value
  t(`Example.status.${status}`);
  t('Example.status.' + status);

  // ❌ Handing `t` to another function moves the call out of scope
  renderRow(t);
}
```

## Translating enum-like values

The common reason to reach for a dynamic key is a value with a small set of cases.

To avoid the dynamic key, you can use a shared hook or async function (in case of `getTranslations`) to hold the actual call to `t`:

```tsx
import {useTranslations} from 'next-intl';

type OrderStatus = 'pending' | 'shipped' | 'delivered';

function useOrderStatusLabel() {
  const t = useTranslations('OrderStatus');

  return function getOrderStatusLabel(status: OrderStatus) {
    switch (status) {
      case 'pending':
        return t('pending');
      case 'shipped':
        return t('shipped');
      case 'delivered':
        return t('delivered');
    }
  };
}
```

```tsx
function OrderStatus({status}: {status: OrderStatus}) {
  const getOrderStatusLabel = useOrderStatusLabel();
  return <span>{getOrderStatusLabel(status)}</span>;
}
```

---

For an index of every eloqnt/studio documentation page, see [https://studio.eloqnt.dev/llms.txt](https://studio.eloqnt.dev/llms.txt).
