Denne integrasjonsveiledningen er kun tilgjengelig på engelsk.
BeMyWords with Android
Integration guide for Android apps using the standard res/values/strings.xml format.
Shape of the integration:
- At build time, fetch translations from BeMyWords and convert to
strings.xmlunderres/values-<lang>/. - Use
R.string.<name>orgetString(R.string.<name>)as normal. - Optionally push your default
strings.xmlto BeMyWords as the English master.
Prerequisites
- An Android Studio project with at least
app/src/main/res/values/strings.xml. - A BeMyWords workspace, project, namespace, API key.
- Node.js available in your build environment (the example uses Node; any scripting language works).
1. Environment variables
Locally in .env (gitignored) at project root; in CI, via your CI's secret store:
BEMYWORDS_BASE_URL=https://www.bemywords.org
BEMYWORDS_PROJECT_ID=<your-project-uuid>
BEMYWORDS_API_TOKEN=<your-api-token>
BEMYWORDS_NAMESPACE=android
2. Key naming convention
Android strings.xml requires names that match [a-z][a-z0-9_]*. BeMyWords allows dots and dashes — you need a convention.
Recommendation: use dots in BeMyWords (home.hero.title) and convert to underscores for Android (home_hero_title). Set and enforce this convention across your team before you start.
3. Fetch + convert script
scripts/fetch-translations.mjs:
import { writeFileSync, mkdirSync } from "node:fs";
import { join, dirname } from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url));
const RES_DIR = join(__dirname, "..", "app", "src", "main", "res");
const BASE_URL = process.env.BEMYWORDS_BASE_URL || "https://www.bemywords.org";
const PROJECT_ID = process.env.BEMYWORDS_PROJECT_ID;
const API_TOKEN = process.env.BEMYWORDS_API_TOKEN;
const NAMESPACE = process.env.BEMYWORDS_NAMESPACE || "android";
// BeMyWords lang → Android values-<qualifier>
// Default (English) goes in values/, not values-en/
const LANGUAGES = {
en: "values",
nb: "values-nb",
"pt-BR": "values-pt-rBR",
};
function toAndroidName(key) {
// Dots + dashes → underscores. Normalize to [a-z][a-z0-9_]*
const name = key.replace(/[.\-]/g, "_").toLowerCase();
if (!/^[a-z][a-z0-9_]*$/.test(name)) {
console.warn(` skipping invalid name for Android: ${key}`);
return null;
}
return name;
}
function escapeXmlText(s) {
return s
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, "\\\"")
.replace(/'/g, "\\'")
.replace(/\n/g, "\\n");
}
function toStringsXml(dict) {
const lines = ['<?xml version="1.0" encoding="utf-8"?>'];
lines.push('<!-- Generated by fetch-translations.mjs from BeMyWords. Do not edit. -->');
lines.push("<resources>");
for (const [key, value] of Object.entries(dict).sort()) {
const name = toAndroidName(key);
if (!name) continue;
lines.push(` <string name="${name}">${escapeXmlText(value)}</string>`);
}
lines.push("</resources>");
return lines.join("\n") + "\n";
}
async function fetchLanguage(bwLang) {
const url = `${BASE_URL}/api/${PROJECT_ID}/latest/${bwLang}/${NAMESPACE}`;
const res = await fetch(url, {
headers: { Authorization: `Token token=${API_TOKEN}` },
});
if (!res.ok) throw new Error(`${bwLang}: HTTP ${res.status}`);
return res.json();
}
async function main() {
if (!PROJECT_ID || !API_TOKEN) {
console.warn("BEMYWORDS_PROJECT_ID or BEMYWORDS_API_TOKEN not set.");
return;
}
for (const [bwLang, valuesDir] of Object.entries(LANGUAGES)) {
const data = await fetchLanguage(bwLang);
const outDir = join(RES_DIR, valuesDir);
mkdirSync(outDir, { recursive: true });
const outPath = join(outDir, "strings.xml");
writeFileSync(outPath, toStringsXml(data), "utf-8");
console.log(` [${bwLang}] ${Object.keys(data).length} keys → ${outPath}`);
}
}
main().catch((e) => {
console.error(e);
process.exit(1);
});
4. Hook into the Gradle build
Edit app/build.gradle (or build.gradle.kts):
tasks.register("fetchTranslations", Exec) {
workingDir = rootDir
commandLine "node", "scripts/fetch-translations.mjs"
}
preBuild.dependsOn("fetchTranslations")
Kotlin DSL:
tasks.register<Exec>("fetchTranslations") {
workingDir = rootDir
commandLine("node", "scripts/fetch-translations.mjs")
}
tasks.named("preBuild") { dependsOn("fetchTranslations") }
Alternatively, run the script manually or from CI before ./gradlew build.
5. Use in code
val title = getString(R.string.home_hero_title)
val greeting = getString(R.string.greeting, userName)
Android positional format specifiers (%1$s, %2$d) pass through BeMyWords verbatim. Enable Android-aware placeholder validation in your BeMyWords project settings.
6. (Optional) Push English to BeMyWords
Parse app/src/main/res/values/strings.xml and PUT to BeMyWords:
import { readFileSync } from "node:fs";
const path = join(RES_DIR, "values", "strings.xml");
const xml = readFileSync(path, "utf-8");
const re = /<string\s+name="([^"]+)">([\s\S]*?)<\/string>/g;
const translations = {};
let match;
while ((match = re.exec(xml)) !== null) {
const androidName = match[1];
const rawValue = match[2];
const value = rawValue
.replace(/\\'/g, "'")
.replace(/\\"/g, '"')
.replace(/\\n/g, "\n")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/&/g, "&");
// Reverse the underscore convention if you want dots in BeMyWords:
// const key = androidName.replace(/_/g, "."); // only if your naming round-trips
const key = androidName;
translations[key] = value;
}
const url = `${BASE_URL}/api/overwrite/${PROJECT_ID}/latest/en/${NAMESPACE}`;
await fetch(url, { method: "PUT", headers: { Authorization: `Token token=${API_TOKEN}`, "Content-Type": "application/json" }, body: JSON.stringify({ translations }) });
Note the irreversibility concern: if you're converting BeMyWords dots → Android underscores on fetch, you can't trivially reverse on push unless your naming is strictly foo_bar_baz with no collisions. Either:
- Keep underscore names in BeMyWords (simplest — no conversion either direction).
- Keep dot names in BeMyWords AND maintain a mapping file in the script.
Pluralization
Android uses <plurals> for pluralization:
<plurals name="apples">
<item quantity="one">%d apple</item>
<item quantity="other">%d apples</item>
</plurals>
Store plural variants in BeMyWords as dot-keyed siblings (apples.one, apples.other) and write a second generator pass that emits <plurals> blocks. This is not covered by the basic script above.
API reference
Troubleshooting
AAPT: error: Invalid file name: your values-<qualifier> folder doesn't match Android conventions. Norwegian is values-nb, Brazilian Portuguese is values-pt-rBR (region prefixed with r). Reference.
error: resource string/<name> not found: the BeMyWords key got filtered out by toAndroidName (starts with digit, contains uppercase, etc.). Rename the key in BeMyWords to fit Android conventions.
HTML entities double-escaped: if you have HTML in your translations, make sure escapeXmlText only runs once. Android's CDATA support may be easier for strings that are unavoidably full of HTML.
Hva koster BeMyWords?
Gratis å starte: 5 000 ord lagret, 1 nettsted, ingen kort. Betalte nivåer starter på 199 kr i måneden, eller 1 990 kr for ett år. De teller ordene du lagrer, originalen pluss hver oversettelse av den, og aldri personer, sidevisninger eller et tak på hvor mange språk du kjører. Ta med din egen AI-nøkkel og det er 199 kr i måneden fast uten ordgrense.
Hvilket nivå det er avhenger av hvor mange ord nettstedet ditt inneholder. Lim inn adressen din så leser vi den og forteller deg.
Hvert nivå, og hva det inneholder Beregn størrelsen på nettstedet dittInstaller det på prosjektet du nettopp leste om.
Gratisversjonen er nok til å oversette en landingsside og vurdere resultatet selv. Ingen kort.
Start gratis