Restrict content access by user language
I managed to solve this over the weekend, thought I'd share my solution if someone has the same problem.

TL;DR. 

In my setup I use the contrib module Content Access. Basic configuration; In the System user preferences I deny access content to anonymous users, and in the Access Control settings for each content-type (admin/content/node-type/[type]/access) I disable View access for authenticated user. This way Content Access module will control exactly which role or which user can view/update/delete any content, even down on an individual node level.

I then wrote my own module that implemented the ACL api. It was a bit tricky to learn the node access system, and even more so with the complete lack of ACL module documentation/tutorials, but when I figured it out the module works really well.

The module utilizes Translation Access (i18n_access module) for deciding the user language access permissions. Translation Access provides access control for Edit/Delete, but not for Viewing content. If that would have been the case, this module would not have been necessary...

So first I added a custom submit handler to the default permissions rebuild form 

/**
 * Rebuild all permission when using the standard form
 */
function mymodule_form_node_configure_rebuild_confirm_alter(&amp;$form, &amp;$form_state) {
    array_unshift($form['#submit'], 'mymodule_rebuild_permissions_submit_handler');
}

/**
 * Route submit function through custom handler to get rid of unwanted $form arguments
 */
function mymodule_rebuild_permissions_submit_handler() {
    mymodule_rebuild_permissions();
    drupal_set_message(t('Language permissions have been rebuilt.'));
}


Now this is where the fun begins, I created a "rebuild permissions" function that iterates all nodes restricted by language (in my case, all nodes). If a uid is provided, permissions will be rebuilt for that user only.

/**
 * Rebuilds permissions for all nodes and users, or only for a specified user
 */
function mymodule_rebuild_permissions($uid = null) {
    //Select the nodes to apply language restriction on
    $nodes = db_query("SELECT nid, language FROM {node}");
    while($node = db_fetch_object($nodes)) {
        $acl_id = mymodule_get_node_acl($node);
        if(!$uid) {
            mymodule_update_user_access_all($acl_id, $node);
        } else {
            mymodule_update_user_access($acl_id, $node, $uid);
        }
    }
}


As seen above we need a function to retrieve/create the ACL id of the node

/**
 * Gets the node ACL id
 */
function mymodule_get_node_acl($node) {
    $module = 'mymodule';
    $acl_name = $module.'_'.$node-&gt;nid;
    //Get the ACL for this node
    $acl_id = acl_get_id_by_name($module, $acl_name);
    //If it doesn't exist, create new ACL
    if(!$acl_id){
        $acl_id = acl_create_new_acl($module, $acl_name);
        // View = 1, Update = 0, Delete = 0, Priority = 0
        acl_node_add_acl($node-&gt;nid, $acl_id, 1, 0, 0, 0);
    }
    return $acl_id;
}


As well as for updating user permissions

/**
 * Update language permissions for all users
 */
function mymodule_update_user_access_all($acl_id, $node) {
    $users = db_query("SELECT uid FROM {users} WHERE uid &gt; 1");
    while($user = db_fetch_object($users)) {
        mymodule_update_user_access($acl_id, $node, $user-&gt;uid);
    }
}

/**
 * Updates language permissions for a specific user
 */
function mymodule_update_user_access($acl_id, $node, $uid) {
    //Remove from list
    acl_remove_user($acl_id, $uid);
    //Add again if user has permissions
    if(mymodule_node_access($node, $uid)) {
        acl_add_user($acl_id, $uid);
    }
}


Check if the user has role and language access

function mymodule_node_access($node, $uid) {
    //Get the user roles
    $user_roles = user_load($uid)-&gt;roles;
    //Get the content access roles
    $node_roles = content_access_get_per_node_settings($node);

    //Check if user has permission to view node
    $node_access = false;
    if(isset($node_roles['view'])) {
        foreach($node_roles['view'] as $role) {
            if(isset($user_roles[$role])) {
                $node_access = true;
            }
        }
    }
    //Check i18n selected languages
    $perm = db_result(db_query("SELECT perm FROM {i18n_access} WHERE uid = %d", $uid));
    $user_languages = $perm ? explode(', ', $perm) : array();
    $lang_access = in_array($node-&gt;language, $user_languages) || $node-&gt;language == '';
    //Return true if we have both node and language access
    return $node_access &amp;&amp; $lang_access;
}


Obviously we have to update access control when inserting/updating nodes and users

/**
 * Implementation of hook_nodeapi()
 */
function mymodule_nodeapi(&amp;$node, $op) {

    if($op == 'insert' || $op == 'update') {
        $acl_id = mymodule_get_node_acl($node);
        mymodule_update_user_access_all($acl_id, $node);
        if($op == 'insert') {
            node_access_rebuild();
        }
    }
}

/**
 * Implementation of hook_user()
 */
function mymodule_user($op, &amp;$edit, &amp;$account) {
    if($op == 'after_update') {
        mymodule_rebuild_permissions($account-&gt;uid);
    }
}


Finally these functions had to be included to make the ACL api work

/**
 * Implementation of ACL hook hook_enabled()
 */
function mymodule_enabled() {
    return !mymodule_disabling();
}

/**
 * Implementation of hook_disable().
 */
function mymodule_disable() {
    mymodule_disabling(TRUE);
}

/**
 * Remembers if we have disabled access.
 */
function mymodule_disabling($set = NULL) {
    static $disabling = FALSE;

    if (isset($set)) {
        $disabling = $set;
    }
    return $disabling;
}


...and that's it!

Don't forget to rebuild permissions before anything (/admin/content/node-settings)