Initial Contribution
diff --git a/tools/localize/Perforce.cpp b/tools/localize/Perforce.cpp
new file mode 100644
index 0000000..3184dfc
--- /dev/null
+++ b/tools/localize/Perforce.cpp
@@ -0,0 +1,228 @@
+#include "Perforce.h"
+#include "log.h"
+#include <sstream>
+#include <sys/types.h>
+#include <unistd.h>
+#include <sys/wait.h>
+
+using namespace std;
+
+extern char** environ;
+
+int
+Perforce::RunCommand(const string& cmd, string* result, bool printOnFailure)
+{
+    int err;
+    int outPipe[2];
+    int errPipe[2];
+    pid_t pid;
+
+    log_printf("Perforce::RunCommand: %s\n", cmd.c_str());
+
+    err = pipe(outPipe);
+    err |= pipe(errPipe);
+    if (err == -1) {
+        printf("couldn't create pipe. exiting.\n");
+        exit(1);
+        return -1;
+    }
+
+    pid = fork();
+    if (pid == -1) {
+        printf("couldn't fork. eixiting\n");
+        exit(1);
+        return -1;
+    }
+    else if (pid == 0) {
+        char const* args[] = {
+            "/bin/sh",
+            "-c",
+            cmd.c_str(),
+            NULL
+        };
+        close(outPipe[0]);
+        close(errPipe[0]);
+        dup2(outPipe[1], 1);
+        dup2(errPipe[1], 2);
+        execve(args[0], (char* const*)args, environ);
+        // done
+    }
+
+    close(outPipe[1]);
+    close(errPipe[1]);
+
+    result->clear();
+
+    char buf[1024];
+
+    // stdout
+    while (true) {
+        size_t amt = read(outPipe[0], buf, sizeof(buf));
+        result->append(buf, amt);
+        if (amt <= 0) {
+            break;
+        }
+    }
+
+    // stderr -- the messages are short so it ought to just fit in the buffer
+    string error;
+    while (true) {
+        size_t amt = read(errPipe[0], buf, sizeof(buf));
+        error.append(buf, amt);
+        if (amt <= 0) {
+            break;
+        }
+    }
+
+    close(outPipe[0]);
+    close(errPipe[0]);
+
+    waitpid(pid, &err, 0);
+    if (WIFEXITED(err)) {
+        err = WEXITSTATUS(err);
+    } else {
+        err = -1;
+    }
+    if (err != 0 && printOnFailure) {
+        write(2, error.c_str(), error.length());
+    }
+    return err;
+}
+
+int
+Perforce::GetResourceFileNames(const string& version, const string& base,
+                                const vector<string>& apps, vector<string>* results,
+                                bool printOnFailure)
+{
+    int err;
+    string text;
+    stringstream cmd;
+
+    cmd << "p4 files";
+
+    const size_t I = apps.size();
+    for (size_t i=0; i<I; i++) {
+        cmd << " \"" << base << '/' << apps[i] << "/res/values/strings.xml@" << version << '"';
+    }
+
+    err = RunCommand(cmd.str(), &text, printOnFailure);
+
+    const char* str = text.c_str();
+    while (*str) {
+        const char* lineend = strchr(str, '\n');
+        if (lineend == str) {
+            str++;
+            continue;
+        }
+        if (lineend-str > 1023) {
+            fprintf(stderr, "line too long!\n");
+            return 1;
+        }
+
+        string s(str, lineend-str);
+
+        char filename[1024];
+        char edit[1024];
+        int count = sscanf(str, "%[^#]#%*d - %s change %*d %*[^\n]\n", filename, edit);
+
+        if (count == 2 && 0 != strcmp("delete", edit)) {
+            results->push_back(string(filename));
+        }
+
+        str = lineend + 1;
+    }
+
+    return err;
+}
+
+int
+Perforce::GetFile(const string& file, const string& version, string* result,
+        bool printOnFailure)
+{
+    stringstream cmd;
+    cmd << "p4 print -q \"" << file << '@' << version << '"';
+    return RunCommand(cmd.str(), result, printOnFailure);
+}
+
+string
+Perforce::GetCurrentChange(bool printOnFailure)
+{
+    int err;
+    string text;
+
+    err = RunCommand("p4 changes -m 1 \\#have", &text, printOnFailure);
+    if (err != 0) {
+        return "";
+    }
+
+    long long n;
+    int count = sscanf(text.c_str(), "Change %lld on", &n);
+    if (count != 1) {
+        return "";
+    }
+
+    char result[100];
+    sprintf(result, "%lld", n);
+
+    return string(result);
+}
+
+static int
+do_files(const string& op, const vector<string>& files, bool printOnFailure)
+{
+    string text;
+    stringstream cmd;
+
+    cmd << "p4 " << op;
+
+    const size_t I = files.size();
+    for (size_t i=0; i<I; i++) {
+        cmd << " \"" << files[i] << "\"";
+    }
+
+    return Perforce::RunCommand(cmd.str(), &text, printOnFailure);
+}
+
+int
+Perforce::EditFiles(const vector<string>& files, bool printOnFailure)
+{
+    return do_files("edit", files, printOnFailure);
+}
+
+int
+Perforce::AddFiles(const vector<string>& files, bool printOnFailure)
+{
+    return do_files("add", files, printOnFailure);
+}
+
+int
+Perforce::DeleteFiles(const vector<string>& files, bool printOnFailure)
+{
+    return do_files("delete", files, printOnFailure);
+}
+
+string
+Perforce::Where(const string& depotPath, bool printOnFailure)
+{
+    int err;
+    string text;
+    string cmd = "p4 where ";
+    cmd += depotPath;
+
+    err = RunCommand(cmd, &text, printOnFailure);
+    if (err != 0) {
+        return "";
+    }
+
+    size_t index = text.find(' ');
+    if (index == text.npos) {
+        return "";
+    }
+    index = text.find(' ', index+1)+1;
+    if (index == text.npos) {
+        return "";
+    }
+
+    return text.substr(index, text.length()-index-1);
+}
+