Skip to main content

Localize Call Directory and Call Screening Labels Without Leaving English Caller ID Chrome

2026-11-13

Localize Call Directory and Call Screening Labels Without Leaving English Caller ID Chrome

A multilingual spam-blocking, caller-ID, or business-directory app can look finished in the host UI and still sound English where users actually trust the product. Settings → Phone → Call Blocking & Identification shows an English Call Directory extension name. An incoming PSTN call shows an English identification label such as a spam category or business role. Android CallScreeningService still paints English identifying chrome. Translators finished the host screens. Caller ID still drifted because the Call Directory .appex bundle, the label strings written during beginRequest(with:), and the screening-service UI own copy that never entered the ordinary host catalog.

Give Call Directory and call screening the same release discipline as permissions or checkout. Inventory Settings display names, identification labels, extension reload timing, and Android screening identification UI apart from host screens and apart from VoIP CallKit caller names, localize them on purpose, then prove Settings and a real identified call after Language and Region changes. If QA never opens Call Blocking & Identification, it will miss every failure mode in this article.

Why Call Directory surfaces drift from the app language

Call Directory is a CallKit app extension, not an ordinary screen in the containing app. Apple's CXCallDirectoryProvider is the principal object for a Call Directory app extension. During beginRequest(with:), the system passes a CXCallDirectoryExtensionContext so the extension can add blocking and identification entries. Identification uses addIdentificationEntry(withNextSequentialPhoneNumber:label:). Apple documents that when a phone number has an identification entry, incoming calls from that phone number will display its associated label to the user.

CXCallDirectoryManager manages enabled status and reloads. Its openSettings(completionHandler:) method opens the iOS Settings app and shows the Call Blocking & Identification settings, the place users enable your extension and read its display name. reloadExtension(withIdentifier:completionHandler:) asks the system to reload extension data after you change the directory.

The localization gap shows up in four places:

  1. Host screens live in the main String Catalog or Localizable.strings. The name under Call Blocking & Identification comes from the Call Directory extension target's CFBundleDisplayName and InfoPlist.strings.
  2. Identification labels are String values you pass into addIdentificationEntry. An English literal written at directory-build time stays English on the next PSTN ring until you reload with localized labels.
  3. Extension string lookup resolves from the Call Directory .appex bundle. Host catalogs do not automatically appear there.
  4. On Android, CallScreeningService can display a user-interface containing identifying information for a call. That UI is a separate localization surface from ordinary Activities.

Apple's App Extension Programming Guide states that the displayed name of an app extension comes from the extension target's CFBundleDisplayName, and it instructs you to localize the app extension's name when you ship a localized extension. Phrase's iOS localization overview and the live i18nAgent iOS localization guide cover catalogs and note that extensions need their own bundle resources. They do not connect Call Blocking & Identification naming, identification labels, and Android screening chrome into one release checklist. VoIP CallKit localizedCallerName paths covered elsewhere are a different problem: a translated self-managed call screen is not the same as shipping a localized Call Directory Settings name and PSTN identification label.

Localize the Call Blocking & Identification display name

Localize the Call Directory extension CFBundleDisplayName with InfoPlist.strings on the extension target: Apple's App Extension Programming Guide says to localize the extension name, and that name is what Settings shows under Call Blocking & Identification.

For each Call Directory extension target, record:

Field Why it matters
Extension CFBundleDisplayName Name shown under Call Blocking & Identification
Extension InfoPlist.strings per language Localized Settings name values
Target membership Strings must belong to the Call Directory .appex, not only the host
Identification label keys Strings passed to addIdentificationEntry
Reload trigger When language or directory data changes
Android screening UI strings Labels inside CallScreeningService identification UI

Put CFBundleDisplayName translations in the Call Directory extension's language-specific InfoPlist.strings, and confirm those files are in the built .appex. The containing app's localized display name does not propagate into Call Blocking & Identification. After translators deliver host screens, open Settings with CXCallDirectoryManager.sharedInstance.openSettings, switch device language, and verify the extension label before you call the release done.

Resolve identification labels in the callee-facing language

Resolve identification label strings in the callee-facing language when calling addIdentificationEntry: Apple documents that incoming calls display the associated label, so English literals stay English on PSTN caller ID.

Practitioner materials still show the failure mode in plain sight. A public Stack Overflow thread on how to use a Call Directory extension to identify an incoming call walks through addIdentificationEntry with an English literal label (@"Telemarketer"). That pattern ships a directory that can only ever paint English categories on the Phone UI until you rebuild entries from localized resources. Another Stack Overflow thread asks whether app extensions need their own Localizable.strings and documents that extension strings must be present for the extension target.

Build labels from the Call Directory extension bundle at request time:

override func beginRequest(with context: CXCallDirectoryExtensionContext) {
    context.delegate = self
    let label = String(
        localized: "caller_id.category.telemarketer",
        bundle: Bundle(for: type(of: self))
    )
    // Phone numbers must be added in ascending order.
    for number in identificationNumbers.sorted() {
        context.addIdentificationEntry(
            withNextSequentialPhoneNumber: number,
            label: label
        )
    }
    context.completeRequest()
}

Keep category strings, business-role strings, and unknown-caller fallbacks in the extension catalog (or in an App Group store keyed by language if labels are server-authored). Resolve them in the language you intend the callee to read before you call addIdentificationEntry. Do not write English literals into the directory and hope the Phone UI will translate them later: the label string you supply is what Apple says the incoming call displays.

When labels come from a server taxonomy, store per-locale display strings or map stable category IDs to localizable keys on device. Reload the extension after a language change or after a taxonomy update so the Phone UI does not keep yesterday's English directory.

Reload after language change and prove Settings plus a real call

Reload the Call Directory extension after language or directory-data changes and verify Settings plus a real incoming identification label: CXCallDirectoryManager.reloadExtension refreshes entries, and openSettings jumps to Call Blocking & Identification for QA.

Identification entries persist until the system reloads the extension. Changing the host app language, updating an on-device directory database, or shipping new label translations does not automatically rewrite the Phone UI. After those events, call:

CXCallDirectoryManager.sharedInstance.reloadExtension(
    withIdentifier: "com.example.app.CallDirectory"
) { error in
    // Log and surface reload failures; a stale directory keeps old labels.
}

Then run a release checklist that a host-only screenshot pass cannot satisfy:

  1. Switch the device (or per-app) language to a non-English locale that you ship.
  2. Open Call Blocking & Identification via openSettings and confirm the extension display name.
  3. Confirm the extension remains enabled after the language change.
  4. Trigger reloadExtension after language change and after directory data refresh.
  5. Place a test call from a number in the identification list and confirm the localized label on the incoming call UI.
  6. Repeat for English fallback after removing a translation to prove missing keys do not silently show raw key names in production caller ID.

Split ownership clearly: engineering owns extension target membership and reload hooks; localization owns Settings names and identification taxonomy strings; QA owns the Settings-plus-real-call gate.

Localize Android CallScreeningService identification chrome

Android's call-identification path is not Call Directory, but it creates the same product failure when ignored. AOSP CallScreeningService states that call-identification services can display a user-interface containing identifying information for a call, and that Telecom binds to the user-chosen screening app for incoming and outgoing calls. That identifying UI (category chips, spam explanations, business names, action labels) must resolve from the screening component's localized resources in the user's effective app language.

Treat the screening service process like an extension bundle:

  • Keep identification strings in the module that implements CallScreeningService, not only in the main Activity resources.
  • Resolve display labels before you show identifying UI or attach them to call metadata your UI reads.
  • Re-test after per-app language changes on Android 13+ because the screening UI can keep a previous locale configuration if you cache English strings at process start.
  • Pair this checklist with the i18nAgent Android localization guide for resource qualifiers, but do not stop at Activity screens.

Do not confuse this surface with VoIP Connection.setCallerDisplayName paths used for self-managed calling. Screening identification chrome is the spam/caller-ID product surface; Telecom connection display names for in-app VoIP are a separate inventory already covered by CallKit/Telecom caller-label work.

Keep Call Directory work off the VoIP caller-name checklist

Teams that already localize CXCallUpdate.localizedCallerName sometimes assume Call Directory is done. It is not. VoIP caller names apply when your app reports a CallKit call. Call Directory identification labels apply when the system Phone UI matches a PSTN number against your extension's directory. Settings display names apply when the user manages Call Blocking & Identification. You need all three inventories if you ship both VoIP calling and spam/caller-ID features, but this article's release gate is the Call Directory and screening pair.

A practical split:

Surface API owner Failure if skipped
Call Blocking & Identification name Extension CFBundleDisplayName English toggle label in Settings
PSTN identification label addIdentificationEntry label English category on incoming PSTN call
Directory freshness reloadExtension Stale English labels after language change
Android screening UI CallScreeningService resources English identifying chrome
VoIP call screen name CallKit / Telecom caller APIs Out of scope here; track separately

Release checklist

Before you mark caller-ID localization done:

  1. Extension InfoPlist.strings exists for every shipped language and is a member of the Call Directory target.
  2. Identification labels resolve from localized resources or a locale-keyed taxonomy at beginRequest time: no English literals in production directory builds.
  3. Language change and directory update both trigger reloadExtension, with failure logging.
  4. QA opens Call Blocking & Identification and places a real identified test call in at least one non-English locale.
  5. Android CallScreeningService identifying UI strings are localized and retested after per-app language changes.
  6. Host UI translation completion is never accepted as proof that Settings names or PSTN labels are localized.

Ship the host screens and the caller-ID edges together. Users decide whether to trust your spam or business labels in Settings and on the incoming call UI, not on the in-app settings screen your translators finished first.