The only “Apple Intelligence” I want is for my Mac to stop being so dense about which languages I want to translate emails into

Don’t get me wrong, I greatly appreciate the fact that the Mac has built-in translation features (which predate the marketing appellation “Apple Intelligence”). I just don’t get why it lacks so much context.

ICS Calendar Pro is a big (and growing, slowly) part of my business. And a big (and growing, faster than the business as a whole) chunk of it is in Europe. I’m a lame American, who really only speaks English. Sure, I studied French for three years in high school, and Russian (!) for two years in college. And I have enough of a general understanding of most common European languages that I can at least identify on sight what language something is written in, even if I have some trouble actually reading it — and I most definitely can’t respond in that language. (Well, I almost can in French, sometimes. But it definitely wouldn’t sound professional.)

Enter the Mac’s built-in system-wide translation feature. Just highlight a block of text, right-click on it, and the contextual menu offers translation as an option. It can’t translate to every known living language, of course, but I am mainly dealing with four languages other than English, in this order: German, Dutch, French and Italian.

I just don’t understand why the Mac is so stupid about what kinds of translations I want to do.

My Mac system language is set to U.S. English. So the Mac is usually pretty good, when I’ve selected a block of text that is not in English, at recognizing such, and choosing the correct “from” language in the dropdown.

The “to” language… not so much. It generally just remembers the last language I translated something into, and uses that. But shouldn’t it be smart enough to figure out that if the “from” language is not my system default, the language I want to translate it into is probably the system default?

When the “from” language is English, I get that it is harder for it to know what I want to translate into. There’s context that the system-wide translation tool is probably isolated from.

Ideally, the translation tools would have the context available, such as “this text is inside an email being sent to an address with the .de TLD.” In that context, it should be obvious that if I’m bothering to translate some text out of English, the most likely language I’d want to translate it into would be German.

Yeah, I have a lot of users in countries where it’s not so straightforward — e.g. Belgium and Switzerland — but in general, it seems like there’s a more logical starting point than, “OK, here’s a block of text written in English. Let’s try translating it from German into Spanish.” Sheesh.

ST:TNG Treadmill Review #42: Remember Me

Remember Me
Season 4 Episode 5
Original airdate: October 20, 1990

Netflix Synopsis

Dr. Crusher’s anxiety over losing loved ones is magnified when she becomes trapped in an alternate reality.

My Brief Review

I’m not sure I’d say this is my favorite episode of the show, but it’s definitely the one that’s stuck with me most vividly. I love these Twilight Zone-type episodes, and this one is a classic.

Wesley is conducting a warp field experiment in engineering, and shortly thereafter, Dr. Crusher starts to notice a strange phenomenon: people are vanishing from onboard the Enterprise, and no one but her seems to remember them ever existing.

Eventually, it comes to this… Beverly is alone on the bridge (and, in fact, alone on the ship), when she poses the question to the computer: “What is the nature of the universe?”

Computer: “The universe is a spheroid region 704 meters in diameter.”

Eventually the ship starts to break down due to what the computer calls a “design flaw”… namely, that parts of the ship’s design now fall outside the universe. But Beverly is smart, almost as smart as her son Wesley, who gets into serious Jedi mode with The Traveler, who comes back and works his metaphysical magic.

Of course everything works out fine in the end. But it’s quite a trip along the way.

Memorable Moment

When Dr. Crusher asks the computer to display a model of the universe and she recognizes the shape as Wesley’s warp field, that’s an image that’s burned into my brain. But I think the most memorable moment is when it’s down to just her and Picard left on board, and she’s sitting next to him. She has the computer giving a continual audible readout of Picard’s life functions. For a moment she glances away from him, the computer falls silent, and… he’s gone.

Crew Rando

Uh, I mean, come on. There’s like nobody in this episode. So let’s go with Dr. Dalen Quaice, Beverly’s old (and I mean old) friend, who’s the first to disappear. He’s played by Bill Erwin, who according to IMDb is “known for” the unforgettable roles “Man on Plane,” “Man in Airport,” and “70-Year-Old Man.”

Distane Rating: 6K

IMDb score: 7.9/10

Migrating primary keys in a CakePHP site’s database from GUIDs to integers

If you are a CakePHP developer, and find yourself in a situation where your database is using GUIDs but you want to switch to using integer-based primary keys instead, this post will describe how I dealt with that exact situation. (Actually, the post will do that even if you’re not a CakePHP developer; it just probably won’t be of any interest to you.)

Some unnecessary background details you can skip if you’re in a hurry

I may be the only CakePHP developer silly enough ever to have used GUIDs (those crazy pseudo-unique 36-character hexadecimal strings) instead of plain auto-increment integers as primary keys. Maybe not. In any case, it only took me one CakePHP project to realize the folly of the approach and I quickly abandoned GUIDs on my second CakePHP site.

Now the time has come, nearly four years later, to finally update that first CakePHP site to the latest version of cms34. There will surely be numerous challenges involved in the upgrade, given the extensive evolution the system has undergone in the intervening years. But without a doubt the biggest, or at least the first, of those challenges is getting the data converted from using GUIDs to integer-based primary keys.

The reason I used GUIDs in the first place was that I have a few multi-purpose cross-reference tables in the structure, and I wanted to just be able to refer to a single ID field as a foreign key, without having to keep track of both the table/model and an integer ID. Kind of silly, in retrospect, especially since I quickly realized how important it was to be able to easily identify which table the foreign key pointed to. At the time I was singularly focused on trying to keep the database as stripped-down basic as possible, to a self-defeating point.

But my early CakePHP learning curve is a bit beside the point here. The goal of this post is to document the process I am undertaking to migrate the site, for the benefit of anyone else who finds themselves in a similar situation. (Hopefully, for you, the idiot who used GUIDs was not yourself.)

Step 1: Preliminary set-up

First, a qualifier: the way I am doing this will result in a lot of gaps in the sequence of integers used in the individual tables after the migration. If the actual values of your integer primary keys matters to you, you may not want to take this approach. I can’t imagine a reason why the values would really matter though.

The idea in this step is, in essence, to convert each GUID in the system into a unique integer that can be used later in the update process. There’s no reason why the integers have to be unique across tables any longer; it’s just that having a definitive reference, in a single table, of how each GUID maps to an integer will make some of the later steps in the process easier. If you are compelled not to skip numbers in individual tables, you could add an extra field called table_id, and increment table-specific IDs in that field. But that would require extra code in the next step, and it just seems like an extra complication without any real benefits.

Do not do any of this on a live site. Set up a duplicate copy of your site somewhere else, and do all of this conversion work there. Also discourage users from updating the existing site while you’re doing the migration, if possible. This conversion can be set up as a repeatable process, however, so you could always do some test runs, and then once you have everything working reliably, get a fresh dump of the live database again, minimizing downtime.

In your copy database, create all of the tables for your old database and load in the data. Also set up empty tables for the new database (which hopefully doesn’t have overlapping table names; if it does, add a prefix to the table names for the old database). Once you have both the old and new tables in your database, and the old tables loaded with data, you’re ready to proceed.

Step 2: Build and populate a GUID map table

Assuming you’re using MySQL, this will take care of it:

CREATE TABLE IF NOT EXISTS `guid_map` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`table` varchar(255) NOT NULL,
`guid` char(36) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1;

If you have adequate permissions to create the table from within a PHP script, you may as well start building one, because you’ll be adding to it next anyway:

<?php
// Connect to database (replace values as appropriate)
$db_name = 'database'; // Set to your database name
$dbconn = mysql_connect('localhost','username','password',true);
$db = mysql_select_db($db_name);

// Build guid_map table
mysql_query("CREATE TABLE IF NOT EXISTS `guid_map` (
    `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
    `table` varchar(255) NOT NULL,
    `guid` char(36) NOT NULL,
    PRIMARY KEY (`id`)
    ) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1"
);

Next, you’ll want to build a PHP script that will crank through all of your old tables, grab all of the GUIDs, and write them into this new table. Here’s a snippet of PHP to get the job done. You’ll need to decide how you want to run it; it assumes a database connection is already established, so you could just drop it in as an action in one of your controllers or you could build a stand-alone PHP script and add the necessary database connection code at the top.

Note: This works with direct SQL queries, not CakePHP’s model methods. That’s just how I did it. Also, when I build PHP utilities like this, I run them in a browser and like to dump status information as basic HTML. If you prefer to run this at the command line, or don’t care about a verbose dump of the details, you could modify/remove any lines here that contain HTML.

// Clear current GUID map data (makes this process repeatable)
mysql_query('TRUNCATE TABLE guid_map');
if (mysql_error()) { echo '<p>' . mysql_error() . '</p>'; exit; }
echo '<p>GUID map cleared.</p>';

// Get tables
$old_prefix = 'old_'; // Set to prefix for old table names
$rs = mysql_query('SHOW TABLES');
$tables = array();
if (!empty($rs)) {
    while ($row = mysql_fetch_array($rs)) {
        if (strpos($row[0],$old_prefix) === 0 && $row[0] != 'guid_map') {
            $tables[] = $row[0];
        }
    }
}

// Loop through tables, retrieving all GUIDs
foreach ((array)$tables as $table) {
    $sql = 'SELECT id FROM `' . $table . '`';
    $rs = mysql_query($sql);
    
    // Loop through rows, adding to GUID map
    $error_count = 0;
    $mapped_count = 0;
    if (mysql_num_rows($rs) > 0) {
        echo '<hr /><h3>Processing ' . mysql_num_rows($rs) . ' rows in table: ' . $table . '</h3>';
        while ($row = mysql_fetch_assoc($rs)) {
            $sql = 'INSERT INTO `guid_map` (`table`, `guid`) VALUES (\'' . mysql_real_escape_string($table) . '\', \'' . mysql_real_escape_string($row['id']) . '\');';
            mysql_query($sql);
            if (mysql_error()) { echo '<p>' . mysql_error() . '</p>'; }
            else { $mapped_count++; }
        }
        echo '<p>Mapped ' . $mapped_count . ' rows with ' . $error_count . ' error(s).</p>';
    }
    else {
        echo '<p>No data rows found in table: ' . $table . '</p>';
    }
}

Once you’ve run this script, your guid_map table will now contain a row for every row of every table in your database. And you now have an integer ID you can use for each of them. And what’s extra cool about it is that it’s repeatable. Run it as many times as you want. Get a new dump of data from the old site and run it again. Fun!

The next step is where things get a lot more complicated, and a lot more customized to your specific data structures. I’ll be using one of my actual data tables as an example, but bear in mind that your system is unlikely to match this schema exactly. This is just a demonstration to work from in building your own script.

Teaser: My least-readable blog post may be coming soon

Aside

I have a topic for an impending blog post that is likely to be my most arcane, geeky, unreadable post ever. Even if you know what I’m talking about. It will be the blog post equivalent of Metal Machine Music. Even I won’t be able to read it after it is written.

The topic: migrating the primary keys in a CakePHP site’s MySQL database from GUIDs to integers. I can’t wait.

A follow-up on Apache not starting on my web server

About 6 weeks ago, I wrote about a problem I was having with Apache not starting with SSLEngine on. I ended the post somewhat ominously with the following:

I’m a little concerned that Apache is going to require manual input of these pass phrases again whenever it restarts (e.g. if the server reboots). I hope not, but for now I am at least able to move forward knowing it works at all.

This morning, a little before 6 AM, that happened. I was awakened by notifications (with their attendant beeps and nightstand vibrations) on my iPhone that my web server was down. Great. Half-awake, I fired up my hosting provider’s handy iPhone app, tapped the “Hard Reboot” button, and tried to go back to sleep. Except, the notifications kept coming. Eventually I was awake enough to realize that the server was coming back up, but Apache wasn’t. Time to get up and deal with this problem from a real computer.

SSHed in, I tried manually starting Apache, and got this:

(98)Address already in use: make_sock: could not bind to address 0.0.0.0:80
no listening sockets available, shutting down
Unable to open logs

What the crap? After spending a half hour visually scanning log and configuration files, to no avail, I decided I needed to try to find out what was running on port 80. This page was helpful in that regard. I ran the command lsof +M -i4 and found that, whaddayknow, Apache was running. Apparently. But I couldn’t shut it down, and I couldn’t restart it. There were no signs of any compromise of the system’s security, so I just chalked this up to some minor problem deeply buried somewhere in a configuration file that I have yet to track down (but which is probably my fault). At any rate, lsof gave me what I really wanted: the process ID that was listening on port 80. Time for the dreaded kill -9 command.

After that, I tried starting Apache again, and it worked… and, as I suspected, it did ask for the pass phrases again. But now, all is well. (Except for the nagging feeling of not knowing what caused this to happen in the first place. Stay tuned…)