{"id":32146687,"url":"https://github.com/asana/locheck","last_synced_at":"2026-02-21T03:02:19.707Z","repository":{"id":40656519,"uuid":"397674492","full_name":"Asana/locheck","owner":"Asana","description":"Validate iOS, Android, and Mac localizations. Find errors in .strings, .stringsdict, and strings.xml files.","archived":false,"fork":false,"pushed_at":"2025-09-23T13:54:01.000Z","size":171,"stargazers_count":109,"open_issues_count":12,"forks_count":14,"subscribers_count":4,"default_branch":"main","last_synced_at":"2026-02-20T20:48:32.494Z","etag":null,"topics":["android","ios","swift","xcode"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Asana.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2021-08-18T16:54:54.000Z","updated_at":"2026-01-19T16:02:52.000Z","dependencies_parsed_at":"2024-12-04T01:22:07.812Z","dependency_job_id":"d2f3ad91-d7b9-421e-9e8b-46c6552e401c","html_url":"https://github.com/Asana/locheck","commit_stats":{"total_commits":64,"total_committers":6,"mean_commits":"10.666666666666666","dds":0.453125,"last_synced_commit":"805b21954be3924a4f04addb44b375249e13bc41"},"previous_names":[],"tags_count":13,"template":false,"template_full_name":null,"purl":"pkg:github/Asana/locheck","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Asana%2Flocheck","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Asana%2Flocheck/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Asana%2Flocheck/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Asana%2Flocheck/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Asana","download_url":"https://codeload.github.com/Asana/locheck/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Asana%2Flocheck/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29672258,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-21T00:11:43.526Z","status":"online","status_checked_at":"2026-02-21T02:00:07.432Z","response_time":107,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","ios","swift","xcode"],"created_at":"2025-10-21T08:21:48.505Z","updated_at":"2026-02-21T03:02:19.696Z","avatar_url":"https://github.com/Asana.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Locheck\n\n[![Swift 5.7](https://img.shields.io/badge/swift-5.4-red.svg?style=flat)](https://developer.apple.com/swift)\n[![License](https://img.shields.io/badge/license-MIT-lightgrey.svg)](https://opensource.org/licenses/MIT)\n[![Build](https://github.com/stevelandeyasana/locheck/actions/workflows/tests.yml/badge.svg)](https://github.com/stevelandeyasana/locheck/actions/workflows/tests.yml)\n\nAn Xcode and Android localization file validator. Make sure your `.strings`, `.stringsdict`, `.xcstrings`, and `strings.xml` files do not have any errors!\n\n## What does it do?\n\nLocheck can perform many kinds of checks on localization files. The simplest one is making sure all strings appear in both the base language and translations, but it can also make sure all your format specifiers are consistent, even in `.stringsdict` files.\n\nConsider this string:\n\n```swift\n\"Send %d donuts to %@\" = \"%@ to donuts %d send\";\n```\n\n```xml\n\u003c!-- values/strings.xml --\u003e\n\u003cstring name=\"send_donuts\"\u003eSend %d donuts to %s\u003c/string\u003e\n\u003c!-- values-translation/strings.xml --\u003e\n\u003cstring name=\"send_donuts\"\u003e%s to donuts %d send\u003c/string\u003e\n```\n\nThe translation reads naturally on its own, but this would crash your app when iOS or Android tries to format a number as a string and a string as a number. Instead, the translation should look like this:\n\n```swift\n\"Send %d donuts to %@\" = \"%2$@ to donuts %1$d send\";\n```\n\n```xml\n\u003c!-- values-translation/strings.xml --\u003e\n\u003cstring name=\"send_donuts\"\u003e%2$s to donuts %1$d send\u003c/string\u003e\n```\n\nLocheck will make sure you get it right.\n\nIn the example above, the key happens to be equal to the base translation. But you might have special cases where you manually define your string in `Localizable.strings`, so the key's format string doesn't match the value:\n\n```swift\n// in en.lproj/Localizable.strings:\n\"send-donuts\" = \"Send %d donuts to %@\";\n\n// in backwards.lproj/Localizable.strings:\n\"send-donuts\" = \"%@ to donuts %d send\";\n```\n\nIn these cases, Locheck will use the base translation's _value_ (not its key) as the authoritative string, and would catch the error in the example above.\n\n## Installation\n\n### Manual\n\n```sh\ngit clone git@github.com:Asana/locheck.git\ncd locheck\nmake install\n```\n\n### Using [Mint](https://github.com/yonaskolb/Mint)\n\n```sh\nmint install Asana/locheck\nmint run locheck [...]\n\n# or link it to /usr/local/bin\nmint install Asana/locheck --link\nlocheck [...]\n```\n\nLocheck is not yet popular enough to be in `homebrew/core` and we haven't created a tap yet.\n\n## Usage\n\nThere are a few ways to invoke `locheck` depending on how much magic you want. In all cases, Locheck will write to stderr for Xcode integration, and stdout for a human-readable summary. Pull requests for additional output formats will probably be accepted.\n\n### `discoverlproj`\n\nThe simplest way to use Locheck with Xcode is to use `discoverlproj` and point to a directory containing all your `.lproj` files:\n\n```sh\nlocheck discoverlproj \"MyApp/Supporting Files\" --base en # use English as the base language\n```\n\nIf you use a language besides English as your base, you'll need to pass it as an argument as shown in the example. Locheck does not try to read your xcodeproj file to figure it out.\n\n### `discovervalues`\n\nThe simplest way to use Locheck on Android is to use `discovervalues` and point to a directory containing all your `values[-*]` directories, i.e. your `res/` directory.\n\n```sh\nlocheck discovervalues ./app/src/main/res\n```\n\n### `stringcatalog`\n\nThe easiest way to validate String Catalog files (.xcstrings) is to use the `stringcatalog` command:\n\n```sh\nlocheck stringcatalog MyApp/Localizable.xcstrings\n```\n\nThis will validate the string catalog for:\n- Missing format arguments in translations\n- Invalid format specifiers (e.g., using `%@` instead of `%d`)\n- Inconsistent plural forms across languages\n- Translations marked as \"needs work\"\n- Missing required plural forms\n\n### Other ways\n\nRun `locheck --help` to see a list of all commands. The rest of the commands just let you directly compare individual files of different types.\n\n## Example output\n\n```\n\u003e locheck discovervalues $ANDROID/app/src/main/res --ignore key_missing_from_translation --ignore key_missing_from_base\nDiscovering values[-*]/strings.xml files in /.../app/src/main/res\nSource of truth: /.../app/src/main/res/values/strings.xml\nTranslations to check: 12\n/.../app/src/main/res/values-de/strings.xml:242: error: Translation of 'could_not_mark_as_milestone' includes arguments that don't exist in the source: task_name (string_has_extra_arguments)\n/.../app/src/main/res/values-de/strings.xml:899: warning: 'organization_required_mfa_help_text' does not include argument(s): authy_url, duo_mobile_url, microsoft_authenticator_url (phrase_has_missing_arguments)\n/.../app/src/main/res/values-ko/strings.xml:1403: error: Translation of 'what_are_a_few_tasks_you_have_to_do_for_project_name' includes arguments that don't exist in the source: projectName (string_has_extra_arguments)\n/.../app/src/main/res/values-ko/strings.xml:1426: warning: 'created_video_phrase_template' does not include argument(s): author_name (phrase_has_missing_arguments)\n[...]\n\nSummary:\n/.../app/src/main/res/values-ko/strings.xml\n    could_not_mark_as_milestone:\n        ERROR: Translation of 'could_not_mark_as_milestone' includes arguments that don't exist in the source: task_name\n    created_video_phrase_template:\n        WARNING: 'created_video_phrase_template' does not include argument(s): author_name\n        ERROR: Translation of 'created_video_phrase_template' includes arguments that don't exist in the source: userName1\n    organization_required_mfa_help_text:\n        WARNING: 'organization_required_mfa_help_text' does not include argument(s): authy_url, duo_mobile_url, microsoft_authenticator_url\n    what_are_a_few_tasks_you_have_to_do_for_project_name:\n        WARNING: 'what_are_a_few_tasks_you_have_to_do_for_project_name' does not include argument(s): project_name\n        ERROR: Translation of 'what_are_a_few_tasks_you_have_to_do_for_project_name' includes arguments that don't exist in the source: projectName\n[...]\n20 warnings, 29 errors\nIgnored key_missing_from_translation, key_missing_from_base\nErrors found\n```\n\n## Contributing\n\nGitHub issues and pull requests are very welcome! Please format your code with `swiftformat Sources Tests` before opening your PR, otherwise tests will fail and we cannot merge your branch. We also run SwiftLint to help ensure best practices.\n\nThe simplest way to install SwiftFormat and SwiftLint is to use [Mint](https://github.com/yonaskolb/Mint): \n\n```sh\nbrew install mint\nmint bootstrap --link`\n```\n\nYou can then run both tools locally:\n\n```sh\nswiftformat Sources Tests\nswiftlint lint --quiet\n```\n\n## Further reading\n\n- [Localizing Strings that Contain Plurals](https://developer.apple.com/documentation/xcode/localizing-strings-that-contain-plurals)\n- [Stringsdict File Format](https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPInternational/StringsdictFileFormat/StringsdictFileFormat.html)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fasana%2Flocheck","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fasana%2Flocheck","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fasana%2Flocheck/lists"}