GitHub + Google Sheets: catch missing iOS strings
You merge a PR, everything looks fine, and then the build breaks because one locale is missing a single iOS .strings key. Now you’re digging through files, trying to figure out which screen introduced it, and someone pings you asking, “Can translators get a list?”
This kind of mess hits iOS engineers first, but localization managers feel it too, and agency teams shipping white-label apps run into it constantly. With this GitHub Sheets automation, you get a running “missing keys” report plus an optional PR that inserts placeholders so your app still compiles.
Below is what the workflow does, what it saves you, and how you can adapt it for multiple languages or stricter rules.
How This Automation Works
The full n8n workflow, from trigger to final output:
n8n Workflow Template: GitHub + Google Sheets: catch missing iOS strings
flowchart LR
subgraph sg0["Flow 1"]
direction LR
n0["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/webhook.dark.svg' width='40' height='40' /></div><br/>Webhook"]
n1@{ icon: "mdi:swap-vertical", form: "rounded", label: "Config", pos: "b", h: 48 }
n2["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/code.svg' width='40' height='40' /></div><br/>Process File Tree"]
n3["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/httprequest.dark.svg' width='40' height='40' /></div><br/>Get Source File"]
n4["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/code.svg' width='40' height='40' /></div><br/>Find Missing Keys"]
n5@{ icon: "mdi:database", form: "rounded", label: "Google Sheets", pos: "b", h: 48 }
n6["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/httprequest.dark.svg' width='40' height='40' /></div><br/>Get GitHub Tree"]
n7["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/merge.svg' width='40' height='40' /></div><br/>Merge"]
n8@{ icon: "mdi:swap-vertical", form: "rounded", label: "Edit Fields", pos: "b", h: 48 }
n9["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/httprequest.dark.svg' width='40' height='40' /></div><br/>HTTP Request"]
n10["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/code.svg' width='40' height='40' /></div><br/>Code"]
n10 --> n9
n7 --> n4
n1 --> n6
n0 --> n1
n8 --> n10
n9 --> n7
n6 --> n2
n3 --> n8
n4 --> n5
n2 --> n3
end
%% Styling
classDef trigger fill:#e8f5e9,stroke:#388e3c,stroke-width:2px
classDef ai fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
classDef aiModel fill:#e8eaf6,stroke:#3f51b5,stroke-width:2px
classDef decision fill:#fff8e1,stroke:#f9a825,stroke-width:2px
classDef database fill:#fce4ec,stroke:#c2185b,stroke-width:2px
classDef api fill:#fff3e0,stroke:#e65100,stroke-width:2px
classDef code fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
classDef disabled stroke-dasharray: 5 5,opacity: 0.5
class n5 database
class n0,n3,n6,n9 api
class n2,n4,n10 code
classDef customIcon fill:none,stroke:none
class n0,n2,n3,n4,n6,n7,n9,n10 customIcon
The Problem: Missing .strings keys show up too late
Localization gaps rarely fail loudly at the right time. One dev adds a new button label in Base.lproj (or en.lproj), the PR gets merged, and only later does someone notice that fr.lproj (or any other locale) is missing the key. Then you get the worst combo: context switching plus guesswork. You’re comparing files by hand, trying not to miss a duplicate key or a weird formatting edge case, and translators still don’t have a single source of truth. Meanwhile, release pressure doesn’t pause.
The friction compounds.
- Missing keys get discovered during QA or right before a build, when fixing them is the most disruptive.
- Manual diffing across
Base.lproj,en.lproj, and each locale file is slow, and it’s easy to overlook one key. - Translators end up working from screenshots, Slack messages, or stale spreadsheets instead of one current list.
- Builds can fail (or strings ship blank), which means releases slip for a problem that should be routine.
The Solution: GitHub scanning + a live Google Sheet (plus optional PR)
This workflow listens to GitHub activity (pushes or pull requests) and automatically checks your iOS localization files for missing .strings keys. It scans your repo for source files in Base.lproj or en.lproj, then finds the matching target-language files under *.lproj. After that, it compares keys and builds a clean “what’s missing” list per locale. Those missing entries get appended into a Google Sheet tab (for example, a fr tab), which becomes a shared queue for translation work. If you want, it can also open a GitHub PR that inserts placeholder values like __TODO_TRANSLATE__, so the project stays buildable while translations catch up.
The workflow starts with a GitHub webhook and pulls the repo tree via HTTP requests. It fetches the relevant .strings files, identifies missing keys, and writes a simple report into Google Sheets. If ENABLE_PR is turned on (and you’re not in DRY_RUN), it also prepares the changes needed for a placeholder PR.
What You Get: Automation vs. Results
| What This Workflow Automates | Results You’ll Get |
|---|---|
|
|
Example: What This Looks Like
Say your app supports 6 locales and you add 12 new strings for a new screen. Manually, a quick “did we miss anything?” check usually means opening at least 6 files, searching for the new keys, and double-checking formatting, which is easily 10 minutes per locale (about an hour), plus the inevitable follow-up message to translators. With this workflow, you merge as usual and the webhook kicks it off; it scans, logs missing keys to Sheets, and (if enabled) prepares a placeholder PR. Your “time spent” becomes a couple minutes to glance at the sheet, not an hour of diffing.
What You’ll Need
- n8n instance (try n8n Cloud free)
- Self-hosting option if you prefer (Hostinger works well)
- GitHub to trigger runs and open PRs.
- Google Sheets to store the missing-key report.
- GitHub token (create it in GitHub developer settings).
Skill level: Intermediate. You’ll copy config values, connect credentials, and adjust file globs to match your repo.
Don’t want to set this up yourself? Talk to an automation expert (free 15-minute consultation).
How It Works
A GitHub webhook fires on push or pull request. You point GitHub to your n8n webhook URL and choose the events you care about, so the scan happens automatically when code changes.
The workflow maps your repo and finds localization files. Using your configured glob patterns (like **/Base.lproj/*.strings and **/*.lproj/*.strings), it pulls the repo tree and builds a list of candidate files to compare.
Keys are compared and missing entries are extracted. It fetches the source .strings content, reads keys, then detects what doesn’t exist in each target locale. If you have ignore prefixes configured, those get skipped so internal/debug strings don’t clutter the report.
Results go to Google Sheets (and optionally back to GitHub). Missing keys are appended into a locale-specific sheet tab, which becomes the translator worklist. If PR creation is enabled, the workflow also prepares placeholder values and opens a GitHub PR on a branch like chore/l10n-gap-YYYYMMDD.
You can easily modify the locale list and glob patterns to scan only certain modules, or to loop through 5+ languages in one run. See the full implementation guide below for customization options.
Step-by-Step Implementation Guide
Step 1: Configure the Webhook Trigger
Set up the inbound endpoint that starts the workflow when your system posts a request.
- Add and open Inbound Webhook Trigger.
- Set HTTP Method to
POST. - Set Path to
new-pathss. - Copy the production URL from the node after saving to use in your webhook sender.
Step 2: Connect GitHub and Define Repository Parameters
Configure repo parameters and connect GitHub to retrieve the file tree and source files.
- Open Parameter Setup and set values: GITHUB_OWNER to
github_user_name, GITHUB_REPO ton8n-iOS-Github-repo, BASE_BRANCH tomain, SOURCE_LANG toen, TARGET_LANG tofr, and PLACEHOLDER_VALUE to__TODO_TRANSLATE__. - Open Retrieve Repo Tree and confirm the URL expression is set to
={{"https://api.github.com/repos/" + $json.GITHUB_OWNER + "/" + $json.GITHUB_REPO + "/git/trees/" + $json.BASE_BRANCH + "?recursive=1"}}. - Credential Required: Connect your githubApi credentials in Retrieve Repo Tree.
- Open Fetch Source File and verify the URL is
{{"https://api.github.com/repos/github-user-name/n8n-iOS-Github-repo/contents/" + $json.source.path + "?ref=main"}}. - Credential Required: Connect your githubApi credentials in Fetch Source File.
Step 3: Set Up File Discovery and URL Construction
Parse the repo tree, pick the localization files, and build URLs for retrieval.
- Open Analyze File Tree and keep the provided JavaScript Code intact to extract
.stringsfiles and map source/target pairs usingTARGET_LANGfrom Parameter Setup. - Open Adjust Fields and confirm the assignments include en.lproj set to
https://api.github.com/repos/github-user-name/n8n-iOS-Github-repo/contents/en.lproj/Localizable.stringsand fr.lproj set tohttps://api.github.com/repos/github-user-name/n8n-iOS-Github-repo/contents/fr.lproj/Localizable.strings. - Open Build URL List and verify the code returns the two URLs with
langandurlvalues. - Open Remote API Call and set URL to
={{ $json["url"] }}so each generated URL is fetched.
Remote API Call outputs to both Combine Streams and Combine Streams in parallel, then the merged output flows to Identify Missing Keys.
Step 4: Detect Missing Keys and Append to Google Sheets
Merge source and target localization content, then write missing keys into a Google Sheet for translation tracking.
- Open Combine Streams and ensure Mode is
combinewith Combine By set tocombineByPosition. - Open Identify Missing Keys and keep the JavaScript Code intact to compare source/target keys and output missing entries with
__TODO_TRANSLATE__. - Open Append to Sheet and set Operation to
append. - Set Document to the Google Sheet ID
[YOUR_ID]and Sheet Name tolocalize. - Confirm the column mappings use expressions like
={{ $json["Key"] }},={{ $json["File"] }},={{ $json["Source Path"] }},={{ $json["Target Path"] }},={{ $json["Source Value"] }}, and={{ $json["Placeholder"] }}. - Credential Required: Connect your googleSheetsOAuth2Api credentials in Append to Sheet.
⚠️ Common Pitfall: If the Google Sheet doesn’t have matching column headers, the append will fail or write empty values. Ensure the headers exactly match the field names used in Append to Sheet.
Step 5: Test and Activate Your Workflow
Validate the execution end-to-end and enable the workflow for production.
- Click Execute Workflow and send a test POST request to the Inbound Webhook Trigger URL.
- Confirm that Retrieve Repo Tree and Fetch Source File return GitHub data and that Identify Missing Keys outputs missing keys.
- Check your Google Sheet to verify new rows appended by Append to Sheet.
- Toggle the workflow Active to enable continuous webhook processing.
Common Gotchas
- GitHub credentials can expire or need specific permissions. If things break, check your token scopes (repo access) and the n8n credential connection test first.
- If you’re using Wait nodes or external rendering, processing times vary. Bump up the wait duration if downstream nodes fail on empty responses.
- Google Sheets updates fail silently when
SHEET_IDis wrong or the sheet isn’t shared with the connected Google account. Check the spreadsheet sharing settings and confirm the tab names match your locale codes.
Frequently Asked Questions
About 30 minutes once your GitHub token and Google access are ready.
No. You’ll mostly paste configuration values and connect accounts in n8n.
Yes. n8n has a free self-hosted option and a free trial on n8n Cloud. Cloud plans start at $20/month for higher volume. You’ll also need to factor in Google Sheets usage (usually free) and whatever hosting costs you choose if you self-host.
Two options: n8n Cloud (managed, easiest setup) or self-hosting on a VPS. For self-hosting, Hostinger VPS is affordable and handles n8n well. Self-hosting gives you unlimited executions but requires basic server management.
Yes. You can expand your locale list (for example via a TARGET_LANGS_CSV value like fr,de,es) and loop the “append to sheet” action so each language gets its own tab. Many teams also tweak IOS_SOURCE_GLOB and IOS_TARGET_GLOB to scan only specific modules, and add IGNORE_KEY_PREFIXES_CSV so debug strings don’t flood the report.
Usually it’s an expired token or the token doesn’t have repo scope. Regenerate the GitHub token (or update your GitHub App permissions) and reselect the credential in n8n so the HTTP requests can read the repo tree and create PRs. If failures only happen on busy days, rate limiting can be the culprit; spacing requests and reducing your glob scope helps. Also check that the webhook event you picked (push vs pull request) matches what you’re testing.
A typical iOS repo with a few thousand keys is fine, and the real limit is how many webhook events you run each month (on Cloud) or how much CPU your server has (self-hosted).
Often, yes, because this workflow relies on file-tree scanning, branching logic, and custom comparisons that get awkward (and pricey) in simpler automation tools. n8n is comfortable doing multiple HTTP calls, merging streams, and running “if PR creation is enabled” logic without turning into a fragile chain. You also get the self-hosting option, which is handy when webhooks fire a lot during active development. On the other hand, if all you want is “send a message when a PR opens,” Zapier or Make can be faster to set up. If you want a sanity check, Talk to an automation expert.
Once this is running, missing iOS strings stop being a last-minute scramble and become a tidy, shared list that updates itself. Honestly, that’s the whole win.
Need Help Setting This Up?
Our automation experts can build and customize this workflow for your specific needs. Free 15-minute consultation—no commitment required.