StringLane

    Type to search titles, sections and body text across all 58 pages.

    How-To

    Find Keys Your Code No Longer Uses

    StringLane reads your source and shows where each key is called, plus a filter for the ones nothing calls. What the scan can see, the four ways a live key lands in that list, and why not scanned is different from nothing found.

    Last updated

    Every project accumulates them. A screen is redesigned, a feature is cut, a key is renamed and the old one stays behind: still translated into every language you support, still counted in your completion percentage, still sent to your AI provider on the next run.

    StringLane reads your source code after a project opens and answers two questions: where is this key called, and which keys are called nowhere.

    Where one key is used#

    The detail pane shows it under the key name.

    The homeWelcomeBack key. Under the description it reads Used in 3 places, followed by three monospace paths: lib/screens/home_screen.dart:10, :14 and :20

    Used in N places, then the file and line of each call site. Past the first ten, Show all opens the rest.

    When nothing calls it, the pane says so, and says what that does and does not mean:

    The brandSlogan key, whose description reads Marketing strap-line, Ledger is brand, must not translate. Under it: No call site found, then The scanner did not find this key in your code. It can still be live, if its name is built at runtime, or it is read somewhere the scanner cannot see. Check before deleting.

    That caveat sits beside the finding rather than in this page, because it is the finding people act on.

    Filtering the sidebar to unused keys#

    Open the filter popover in the key sidebar and turn on Show only unused.

    The sidebar filter popover: a Show only issues checkbox, a checked Show only unused checkbox, and a note reading Based on 2 source files. A key can be live and still appear in this list, if its name is built at runtime, or read somewhere the scanner cannot see. Check before deleting. Below is a Missing in locale dropdown set to Any locale

    The line under the toggle is the important one. It says how many source files the answer is based on, so you can tell an answer from an absence of one.

    The key sidebar with an Unused chip and a clear link at the top, reading Showing 22 of 30 keys, and the key tree narrowed to the unused keys grouped by namespace

    An Unused chip stays visible while the filter is on, with the count beside it, so a filtered list is never mistaken for the whole project.

    Not scanned is not the same as nothing found#

    A project StringLane has not read yet says not scanned, never "used in 0 places".

    Those two look identical in a list and mean opposite things, and only one of them is a reason to delete something. If StringLane read no source files at all, or read them and recognised no keys in them, it tells you that and reports nothing as unused rather than reporting everything. It also refuses when every use it found in Swift is only a possible use (see below) and none of them matches a key in your project, as happens when an app calls its strings through a wrapper.

    The scan starts after your project opens, not before, so it never delays the editor.

    What the scan can see#

    Your codeCall shapes it matches
    DartAppLocalizations.of(context)!.key and S.of(context).key, including through a name you bound first (final l10n = AppLocalizations.of(context)!; then l10n.key); context.l10n.key under any getter name; S.current.key; easy_localization's 'key'.tr(), tr('key') and context.tr('key'); slang's t.screen.title and context.t.screen.title
    JavaScript, TypeScript, Vue, Sveltet('key') and its i18n.t / i18next.t / $t spellings, plus <Trans i18nKey="…">. In a namespaced i18next project, t('common:hello') matches as written, and an unqualified t('hello') matches in the default namespace: translation, or the defaultNamespace set in stringlane.yaml
    Swift, Objective-CExact uses: NSLocalizedString("key", …) and String(localized: "key"). Possible uses: every other single-line string literal in a Swift file, so Text("Save"), .navigationTitle("Settings"), a key passed to your own view such as SettingsRow(title: "Privacy"), a labelled argument, SwiftGen's L10n.some.path, and a string with up to four interpolations such as Text("\(count) items"), read as %lld items and its other value types. A possible use keeps a key off the unused list and never reports a key as undefined
    Kotlin, Java, Android XMLR.string.key, and @string/key in layouts and manifests

    Four ways a key can be live and still appear in the unused list:

    • its name is built at runtime: t('errors.' + code) is invisible to any scanner;
    • it is called through a wrapper StringLane does not recognise;
    • it is named only in a file type the scan does not read;
    • it is read directly out of the locale file by the platform, which is how iOS InfoPlist.strings and some Android resources work.

    And one way a dead key can stay off the list: in Swift, a key whose text also appears as an ordinary string in your code, such as a log message or a dictionary key, counts as a possible use, so it is not reported as unused even when no screen shows it.

    Some Flutter spellings are not followed yet, so a key used only this way appears in the unused list:

    • an alias of an alias, such as final t = l10n; then t.save, or the accessor reached through another object, such as widget.l10n.save;
    • S.current bound to a name first, such as final s = S.current; then s.title;
    • slang: a top-level key with no nesting (t.hello), or a key looked up by string (t['a.b']);
    • easy_localization: LocaleKeys for a nested key, because the generator turns dots into underscores and that cannot be reversed;
    • any call through a renamed import, such as show tr as Tr.

    Silencing a key that is built at runtime#

    For a key you have checked and know is live, the panel has a decision rather than a dismissal. Open Key Metadata for it and tick Called dynamically, under Code usage. The key stops being reported as unused, and the detail pane says Not reported as unused in place of the usual note, so a later session does not re-litigate a question you have already answered.

    It is a record in your repository, not a local preference: x-dynamic-key in the @key block on ARB, and in .stringlane/metadata.yaml on the other four formats. That means it is committed, it applies to everyone on the team, and stringlane scan and stringlane check honour it too, because both read the same metadata. Unticking it drops the field rather than recording a false.

    For a whole family of runtime-built keys, a pattern is the better tool: see ignore patterns in the CLI, which stringlane.yaml carries for both surfaces.

    Before you delete anything#

    1. Search for the key yourself, including any runtime spelling that differs from the one shown.
    2. Never delete keys in the same change as anything else. A deletion that turns out to be wrong needs to be revertible on its own.

    The same answer is available outside the app: stringlane scan from a terminal, and the read_key_usage tool for a coding agent. See Unused keys in the CLI.

    Frequently asked questions

    How do I find unused translation keys in StringLane?
    Open the filter popover in the key sidebar and turn on Show only unused. The list narrows to keys no call site in your source code names, and an Unused chip stays visible so you always know the list is filtered. Treat it as a starting point for a search rather than a delete queue.
    Is a key StringLane reports as unused safe to delete?
    Not on its own. Not referenced means no call site was found, not that the key is dead. A key whose name is built at runtime, called through a wrapper StringLane does not recognise, named only in a file type it does not read, or read straight out of the locale file by the platform will appear in that list and still be live. Search for it before deleting, and never delete keys in the same change as anything else.
    The StringLane app showing the key home.welcomeBack, used in three places in Swift files, with four locale cards: English, German and Japanese in place and French flagged PARAM.

    See every locale side by side

    The desktop app flags what is missing or broken on the cell, and saves to your files.

    Download for macOS

    14-day trial, no email