At Forerunner, we build tools that help governments communicate flood risk and resilience to their constituents. We know that effective communication depends on reaching people in the languages they use every day. Earlier this year, Forerunner made it a priority to expand our public sites to support non-English speakers — ensuring that more communities can access critical flood information when they need it most.
Our goal was simple: make flood safety information accessible to everyone, regardless of the language they speak. For some communities, it is a legal requirement. For us, it is a design principle.
The first step in supporting internationalization was understanding what we could realistically commit to, given our timelines and other product priorities. Internationalization can not be taken lightly—when communication falls short, critical information might never reach its intended audience. Worse, it could be misunderstood or misrepresented, especially in the jargon-heavy world of floodplain management.
Forerunner knew some of our community partners were required by law to support Spanish content. Since Spanish is the most commonly spoken language after English in many of the communities we serve, it was a natural starting point. We also had to acknowledge that whatever we built needed to scale: our system had to support Spanish now and make it easy to add more languages in the future.
Forerunner’s first core requirement was clear—support Spanish today, and more languages tomorrow.
The next few requirements that we drafted came from how we support and store copy within our application and the public sites we generate for our accounts. Within our public sites, Forerunner supported three different types of copy:
Once we categorized these three types of copy, we had to figure out how to translate them without ballooning the scope for our small team. After some ideation and confirming what would be feasible with our product team, we landed on a multi-faceted approach that depended on the type of copy we wanted to translate.
For all static copy that we serve across our many sites, we store the English version and its translation in our database along with a meaningful translation key. In our web pages, wherever we would want to render the phrase “Flood Zone Determination Letter”, we instead use the flood-zone-letter key as a placeholder. Then, we replace all instances of that key with the language-appropriate phrase.
While our key-to-translated-phrase solution was simple, relatively scalable for static copy, and a best practice, we needed a way to override translations for content that was account-specific or dynamic, depending on account configurations.
In the screenshot above, you can see the Flood info table that we insert into every public property page. This table and other such tables on this page contain alternating color rows called account property attributes. Each attribute represents important information about a property that is collected and organized based on customer needs. Our highest priority for this data is that it is accurate, specific, and can be configured quickly for our customers. For example: if a customer wants to add a new attribute that links to an emergency service website or change the label of “Evacuation Zone” to something more specific, such as “Preferred Evacuation Zone”, our system needs to be flexible enough to support this functionality while also serving translated versions of the same labels and data.
Since these labels and their data are configured on an account-by-account basis, this makes it difficult to apply our key-based translation solution to this feature. The key-based approach does not scale fast enough to cover how variable the content could be and how fast we would need it. So how do we translate dynamic content?
Well, we considered two ways to solve this problem: we either allow our users to provide the translations themselves when entering account-specific content, or we translate the content with every request for content from our servers.
As mentioned before, Forerunner’s primary goal is to make sure that the governing bodies that react to emergencies and disasters can do so quickly. A wildfire is not going to wait for a community leader to secure the proper translation for the emergency bulletin or public notice they may want to publish on their public site. Realistically, there may be an official who can quickly translate some copy from English to Spanish, but what if there’s a public request for resources in Creole?
Given our customers’ resource constraints, we realized per-request translations were the best way to meet their needs, especially during emergencies. We took our time evaluating various translation services and APIs that would allow us to provide an English phrase and get the translated phrase in a single request. We considered accuracy, speed, and ease-of-use, and we landed on leveraging AI to translate content for us, with the option for overriding translations for extra customization.
Our plan to handle dynamically translated content became the following: receive a request from clients for translated content, fetch that content as it’s currently stored, translate it with AI, cache the translation for faster delivery in the future, and serve the translated content to the client. The next step was to figure out the best way to tell our system when the requested content needed to be in a different language.
One option we considered was to expand our GraphQL fields to accept arguments in the request. This means that instead of just asking for an account property attribute’s label (e.g. “Evacuation Zone”, “In Floodway”, etc), we would also provide the language as a parameter for the query. This would have required that we update all of the places across all of our clients where we make GraphQL requests that needed to be translated to Spanish. This was untenable given our timelines. We needed a way to mark fields as potentially translatable that required little to no updates client-side. After going back to the drawing board, we realized that a better solution was to leverage GraphQL directives.
When drafting a GraphQL schema, you can append a “directive” to the field’s type, and then it is up to the GraphQL server implementers to figure out what that directive means. For example, many GraphQL server libraries support a @deprecated directive whose sole purpose is to mark which fields within a schema should not be used. There is no real business logic or behavior tied to the @deprecated directive other than to signal to our clients that the field will no longer be supported soon. So if we wanted to signal to our GraphQL server that the label field should be translatable, we needed to update the definition of that label to include some sort of @Translatable directive, and then implement the logic that would know what to do once it encountered a @Translatable field.
We decided that a custom @Translatable directive was the way to go and we would apply this directive to each GraphQL field that could potentially be translated. Our GraphQL server handles field directives by fulfilling the request, then applying any directive logic to the field’s content before sending the response to the client. When processing a field’s content, the directive’s handler also receives metadata about the request, such as HTTP headers. It’s in these HTTP headers that our client app lets the server know what language we want our dynamic content to be returned in.
The end result is that we have fully translated dynamic content. As an additional optimization, anything we translate is cached for faster requests and responses in the future.
By the end of this project, Forerunner’s engineering team built a translation system that works in two modes:
With these tools in place, we shipped Spanish support across all of our public sites. Our goal was to make flood risk information accessible to many non-English speakers in the communities that use Forerunner. We also laid the groundwork for future language expansion. Adding a new language now is as straightforward as adding the relevant static translations and updating input fields and menu buttons.
There is still more to do. We want to improve translation quality for specialized terminology, make it easier for partners to override AI translations with their own, and explore support for right-to-left languages. But this release marks a significant milestone.
Disaster events do not wait for anyone, and neither should the information that helps keep communities safe. If you are curious about our approach, have ideas for improving multilingual support in GovTech, or want to help us build more tools like this, get in touch with our engineering team!
Receive a monthly update from us with news, product updates, and resources from our team.