The main problem with your approach is that the translate function will replace all occurrences of the quotation mark with your smart-opening quote. Therefore, the subsequent substring-after function won't actually find anything because there won't be any more quotation marks to find.
What you really need in this case is a recursive template, together with a combination of both substring-before and substring-after. The named template could actually be combined with your existing template, with default parameters to be used in the initial match
<xsl:template match="para/text()" name="replace">
<xsl:param name="text" select="."/>
<xsl:param name="usequote" select="$openquote"/>
($openquote will be a variable containing the opening quote)
You would then check if the selected text contains a quotation mark
<xsl:when test="contains($text,$quot)">
If so, you would first output the text before the quotation mark, following by the new open quote
<xsl:value-of select="concat(substring-before($text, $quot), $usequote)"/>
The template would then be recursively called with the text after the quote, and with the close quote as a parameter, so the next quote find will be closed.
<xsl:call-template name="replace">
<xsl:with-param name="text" select="substring-after($text,$quot)"/>
<xsl:with-param name="usequote"
select="substring(concat($openquote, $closequote), 2 - ($usequote=$closequote), 1)"/>
</xsl:call-template>
Note: the selecting of the usequote will basically switch between open and close quotes. It takes advantage of the fact 'true' evaluates to 1 in a numeric expression, and 'false' to 0.
Try this XSLT. Note, I am using open and close brackets in this case, just to make the output clearer, but you can easily change the variables to your smart quotes as required. (You may have to specify an encoding for the output in this case though).
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text"/>
<xsl:variable name="quot">"</xsl:variable>
<xsl:variable name="openquote">[</xsl:variable>
<xsl:variable name="closequote">]</xsl:variable>
<xsl:template match="para/text()" name="replace">
<xsl:param name="text" select="."/>
<xsl:param name="usequote" select="$openquote"/>
<xsl:choose>
<xsl:when test="contains($text,$quot)">
<xsl:value-of select="concat(substring-before($text, $quot), $usequote)"/>
<xsl:call-template name="replace">
<xsl:with-param name="text" select="substring-after($text,$quot)"/>
<xsl:with-param name="usequote"
select="substring(concat($openquote, $closequote), 2 - ($usequote=$closequote), 1)"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When applied to your XML, the following is output
A number of the offences set out in the Companies Ordinance are expressed to apply to [officers] of the company. [Officer] includes directors, managers and the company secretary: Companies Ordinance, s.2(1).